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)
1085 lines
43 KiB
C++
1085 lines
43 KiB
C++
// Copyright 2018-2022 Global Phasing Ltd.
|
|
|
|
#include <gemmi/topo.hpp>
|
|
#include <cmath> // for sqrt, round
|
|
#include <map> // for multimap
|
|
#include <unordered_map> // for unordered_multimap
|
|
#include <gemmi/polyheur.hpp> // for get_or_check_polymer_type, ...
|
|
#include <gemmi/riding_h.hpp> // for place_hydrogens_on_all_atoms, ...
|
|
#include <gemmi/modify.hpp> // for remove_hydrogens
|
|
|
|
namespace gemmi {
|
|
|
|
std::unique_ptr<ChemComp> make_chemcomp_with_restraints(const Residue& res) {
|
|
std::unique_ptr<ChemComp> cc(new ChemComp());
|
|
cc->name = res.name;
|
|
cc->group = ChemComp::Group::Null;
|
|
cc->has_coordinates = true;
|
|
// add atoms
|
|
cc->atoms.reserve(res.atoms.size());
|
|
for (const Atom& a : res.atoms) {
|
|
if (a.altloc != '\0' && cc->has_atom(a.name))
|
|
continue;
|
|
Element el = a.element;
|
|
if (el == El::X)
|
|
el = El::N;
|
|
if (el == El::D)
|
|
el = El::H;
|
|
const std::string& chem_type = el.uname();
|
|
cc->atoms.push_back(ChemComp::Atom{a.name, "", el, float(a.charge), chem_type, "", a.pos});
|
|
}
|
|
// prepare pairs of atoms
|
|
struct Pair {
|
|
size_t n1, n2;
|
|
double dist;
|
|
};
|
|
std::vector<Pair> pairs;
|
|
// first heavy atoms only
|
|
for (size_t i = 0; i != res.atoms.size(); ++i) {
|
|
const Atom& at1 = res.atoms[i];
|
|
if (at1.is_hydrogen())
|
|
continue;
|
|
float r1 = at1.element.covalent_r();
|
|
for (size_t j = i+1; j != res.atoms.size(); ++j) {
|
|
const Atom& at2 = res.atoms[j];
|
|
if (at2.is_hydrogen() || !at2.same_conformer(at1))
|
|
continue;
|
|
double d2 = at1.pos.dist_sq(at2.pos);
|
|
float r2 = at2.element.covalent_r();
|
|
double dmax = std::max(2.0, 1.3 * std::max(r1, r2));
|
|
if (d2 < sq(dmax))
|
|
pairs.push_back(Pair{i, j, std::sqrt(d2)});
|
|
}
|
|
}
|
|
// now each hydrogen with the nearest heavy atom
|
|
for (size_t i = 0; i != res.atoms.size(); ++i) {
|
|
const Atom& at1 = res.atoms[i];
|
|
if (at1.is_hydrogen()) {
|
|
size_t nearest = (size_t)-1;
|
|
double min_d2 = sq(2.5);
|
|
for (size_t j = 0; j != res.atoms.size(); ++j) {
|
|
const Atom& at2 = res.atoms[j];
|
|
if (!at2.is_hydrogen() && at2.same_conformer(at1)) {
|
|
double d2 = at1.pos.dist_sq(at2.pos);
|
|
if (d2 < min_d2) {
|
|
min_d2 = d2;
|
|
nearest = j;
|
|
}
|
|
}
|
|
}
|
|
if (nearest != (size_t)-1) {
|
|
pairs.push_back(Pair{nearest, i, std::sqrt(min_d2)});
|
|
}
|
|
}
|
|
}
|
|
|
|
// add bonds
|
|
// note: duplicated bonds (from different conformations) are not a problem
|
|
for (const Pair& p : pairs) {
|
|
Restraints::Bond bond;
|
|
bond.id1 = Restraints::AtomId{1, res.atoms[p.n1].name};
|
|
bond.id2 = Restraints::AtomId{1, res.atoms[p.n2].name};
|
|
bond.type = BondType::Unspec;
|
|
bond.aromatic = false;
|
|
double rounded_dist = 0.001 * std::round(1000 * p.dist);
|
|
bond.value = bond.value_nucleus = rounded_dist;
|
|
bond.esd = bond.esd_nucleus = 0.02;
|
|
cc->rt.bonds.push_back(bond);
|
|
}
|
|
// add angles
|
|
struct Triple {
|
|
size_t n1, n2, n3;
|
|
};
|
|
std::vector<Triple> triples;
|
|
for (size_t i = 0; i != pairs.size(); ++i)
|
|
for (size_t j = i+1; j != pairs.size(); ++j) {
|
|
if (pairs[i].n1 == pairs[j].n1)
|
|
triples.push_back(Triple{pairs[i].n2, pairs[i].n1, pairs[j].n2});
|
|
else if (pairs[i].n1 == pairs[j].n2)
|
|
triples.push_back(Triple{pairs[i].n2, pairs[i].n1, pairs[j].n1});
|
|
else if (pairs[i].n2 == pairs[j].n1)
|
|
triples.push_back(Triple{pairs[i].n1, pairs[i].n2, pairs[j].n2});
|
|
else if (pairs[i].n2 == pairs[j].n2)
|
|
triples.push_back(Triple{pairs[i].n1, pairs[i].n2, pairs[j].n1});
|
|
}
|
|
for (const Triple& triple : triples) {
|
|
Restraints::Angle angle;
|
|
angle.id1 = Restraints::AtomId{1, res.atoms[triple.n1].name};
|
|
angle.id2 = Restraints::AtomId{1, res.atoms[triple.n2].name};
|
|
angle.id3 = Restraints::AtomId{1, res.atoms[triple.n3].name};
|
|
double angle_rad = calculate_angle(res.atoms[triple.n1].pos,
|
|
res.atoms[triple.n2].pos,
|
|
res.atoms[triple.n3].pos);
|
|
angle.value = 0.01 * std::round(100 * deg(angle_rad));
|
|
angle.esd = 3.0;
|
|
cc->rt.angles.push_back(angle);
|
|
}
|
|
return cc;
|
|
}
|
|
|
|
Topo::ChainInfo::ChainInfo(ResidueSpan& subchain, const Chain& chain, const Entity* ent)
|
|
: chain_ref(chain) {
|
|
subchain_name = subchain.at(0).subchain;
|
|
res_infos.reserve(subchain.size());
|
|
if (ent) {
|
|
entity_id = ent->name;
|
|
polymer = ent->entity_type == EntityType::Polymer;
|
|
polymer_type = get_or_check_polymer_type(ent, subchain);
|
|
} else {
|
|
polymer = false;
|
|
polymer_type = PolymerType::Unknown;
|
|
}
|
|
for (Residue& res : subchain)
|
|
res_infos.emplace_back(&res);
|
|
}
|
|
|
|
// Add a ChemLink that restraints only bond length.
|
|
static
|
|
const std::string& add_auto_chemlink(MonLib& monlib,
|
|
const std::string& resname1, const std::string& aname1,
|
|
const std::string& resname2, const std::string& aname2,
|
|
double ideal_dist, double esd) {
|
|
ChemLink cl;
|
|
cl.side1.comp = resname1;
|
|
cl.side2.comp = resname2;
|
|
cl.id = resname1 + resname2;
|
|
cl.name = "auto-" + cl.id;
|
|
cl.rt.bonds.push_back({Restraints::AtomId{1, aname1},
|
|
Restraints::AtomId{2, aname2},
|
|
BondType::Unspec, false,
|
|
ideal_dist, esd,
|
|
ideal_dist, esd});
|
|
// ensure unique link id
|
|
size_t orig_len = cl.id.size();
|
|
for (int n = 0; monlib.get_link(cl.id) != nullptr; ++n)
|
|
cl.id.replace(orig_len, cl.id.size(), std::to_string(n));
|
|
|
|
auto it = monlib.links.emplace(cl.id, cl);
|
|
return it.first->first;
|
|
}
|
|
|
|
static const ChemLink* setup_link_if_matches(Topo::Link& link, const MonLib& monlib,
|
|
const std::string& atom1,
|
|
const std::string& atom2) {
|
|
bool invert;
|
|
const ChemLink* match = nullptr;
|
|
std::tie(match, invert, link.aliasing1, link.aliasing2) =
|
|
monlib.match_link(*link.res1, atom1, link.alt1, *link.res2, atom2, link.alt2);
|
|
if (match) {
|
|
link.link_id = match->id;
|
|
if (invert) {
|
|
std::swap(link.res1, link.res2);
|
|
std::swap(link.alt1, link.alt2);
|
|
std::swap(link.atom1_name_id, link.atom2_name_id);
|
|
std::swap(link.aliasing1, link.aliasing2);
|
|
}
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static int atom_name_id(const std::string& s) {
|
|
int id = 0;
|
|
if (s.size() > 0) // there is '\0' at the end
|
|
id = (s[0] << 24 | s[1] << 16);
|
|
if (s.size() > 2)
|
|
id |= (s[2] << 8 | s[3]);
|
|
return id | 0x20202020;
|
|
}
|
|
|
|
static void add_polymer_links(PolymerType polymer_type,
|
|
const Topo::ResInfo& ri1,
|
|
Topo::ResInfo& ri2,
|
|
MonLib* monlib) {
|
|
Topo::Link link;
|
|
link.res1 = ri1.res;
|
|
link.res2 = ri2.res;
|
|
assert(&ri1 - &ri2 == link.res_distance());
|
|
bool groups_ok = (ri1.orig_chemcomp != nullptr && ri2.orig_chemcomp != nullptr);
|
|
|
|
if (is_polypeptide(polymer_type)) {
|
|
std::string c = "C";
|
|
std::string ca1 = "CA";
|
|
std::string n = "N";
|
|
std::string ca2 = "CA";
|
|
if (ri1.orig_chemcomp && !ChemComp::is_peptide_group(ri1.orig_chemcomp->group)) {
|
|
for (const ChemComp::Aliasing& aliasing : ri1.orig_chemcomp->aliases)
|
|
if (ChemComp::is_peptide_group(aliasing.group)) {
|
|
link.aliasing1 = &aliasing;
|
|
if (const std::string* ptr = aliasing.name_from_alias(c))
|
|
c = *ptr;
|
|
if (const std::string* ptr = aliasing.name_from_alias(ca1))
|
|
ca1 = *ptr;
|
|
break;
|
|
}
|
|
if (!link.aliasing1)
|
|
groups_ok = false;
|
|
}
|
|
ChemComp::Group n_terminus_group = ri2.orig_chemcomp ? ri2.orig_chemcomp->group
|
|
: ChemComp::Group::Null;
|
|
if (ri2.orig_chemcomp && !ChemComp::is_peptide_group(ri2.orig_chemcomp->group)) {
|
|
for (const ChemComp::Aliasing& aliasing : ri2.orig_chemcomp->aliases)
|
|
if (ChemComp::is_peptide_group(aliasing.group)) {
|
|
link.aliasing2 = &aliasing;
|
|
n_terminus_group = aliasing.group;
|
|
if (const std::string* ptr = aliasing.name_from_alias(n))
|
|
n = *ptr;
|
|
if (const std::string* ptr = aliasing.name_from_alias(ca2))
|
|
ca2 = *ptr;
|
|
break;
|
|
}
|
|
if (!link.aliasing2)
|
|
groups_ok = false;
|
|
}
|
|
for (const Atom& a1 : ri1.res->atoms)
|
|
if (a1.name == c && a1.element == El::C) {
|
|
for (const Atom& a2 : ri2.res->atoms)
|
|
if (a2.name == n && a2.element == El::N &&
|
|
(a2.altloc == a1.altloc || a2.altloc == '\0' || a1.altloc == '\0') &&
|
|
in_peptide_bond_distance(&a1, &a2)) {
|
|
// One C-N pair of atoms can create here only one link.
|
|
// Ignoring artificial configuration of no-altloc C and N atoms,
|
|
// and CA atoms in 2+ conformations making both CIS and TRANS links.
|
|
link.alt1 = a1.altloc;
|
|
link.alt2 = a2.altloc;
|
|
link.atom1_name_id = atom_name_id(a1.name);
|
|
link.atom2_name_id = atom_name_id(a2.name);
|
|
if (groups_ok) {
|
|
// Deciding CIS/TRANS based on omega angle.
|
|
char alt = a1.altloc_or(a2.altloc_or('*'));
|
|
const Atom* ca1_atom = ri1.res->find_atom(ca1, alt, El::C);
|
|
const Atom* ca2_atom = ri2.res->find_atom(ca2, alt, El::C);
|
|
link.is_cis = is_peptide_bond_cis(ca1_atom, &a1, &a2, ca2_atom, 60.);
|
|
if (n_terminus_group == ChemComp::Group::PPeptide)
|
|
link.link_id = link.is_cis ? "PCIS" : "PTRANS";
|
|
else if (n_terminus_group == ChemComp::Group::MPeptide)
|
|
link.link_id = link.is_cis ? "NMCIS" : "NMTRANS";
|
|
else
|
|
link.link_id = link.is_cis ? "CIS" : "TRANS";
|
|
} else if (monlib) {
|
|
if (!setup_link_if_matches(link, *monlib, c, n))
|
|
link.link_id = add_auto_chemlink(*monlib,
|
|
ri1.res->name, c,
|
|
ri2.res->name, n,
|
|
1.34, 0.04);
|
|
}
|
|
ri2.prev.push_back(link);
|
|
}
|
|
}
|
|
|
|
} else if (is_polynucleotide(polymer_type)) {
|
|
std::string o3p = "O3'";
|
|
std::string p = "P";
|
|
if (ri1.orig_chemcomp && !ChemComp::is_nucleotide_group(ri1.orig_chemcomp->group)) {
|
|
for (const ChemComp::Aliasing& aliasing : ri1.orig_chemcomp->aliases)
|
|
if (ChemComp::is_nucleotide_group(aliasing.group)) {
|
|
link.aliasing1 = &aliasing;
|
|
if (const std::string* o3p_ptr = aliasing.name_from_alias(o3p))
|
|
o3p = *o3p_ptr;
|
|
break;
|
|
}
|
|
if (!link.aliasing1)
|
|
groups_ok = false;
|
|
}
|
|
if (ri2.orig_chemcomp && !ChemComp::is_nucleotide_group(ri2.orig_chemcomp->group)) {
|
|
for (const ChemComp::Aliasing& aliasing : ri2.orig_chemcomp->aliases)
|
|
if (ChemComp::is_nucleotide_group(aliasing.group)) {
|
|
link.aliasing2 = &aliasing;
|
|
if (const std::string* p_ptr = aliasing.name_from_alias(p))
|
|
p = *p_ptr;
|
|
break;
|
|
}
|
|
if (!link.aliasing2)
|
|
groups_ok = false;
|
|
}
|
|
for (const Atom& a1 : ri1.res->atoms)
|
|
if (a1.name == o3p && a1.element == El::O) {
|
|
for (const Atom& a2 : ri2.res->atoms)
|
|
if (a2.name == p && a2.element == El::P &&
|
|
(a2.altloc == a1.altloc || a2.altloc == '\0' || a1.altloc == '\0') &&
|
|
in_nucleotide_bond_distance(&a1, &a2)) {
|
|
link.alt1 = a1.altloc;
|
|
link.alt2 = a2.altloc;
|
|
link.atom1_name_id = atom_name_id(a1.name);
|
|
link.atom2_name_id = atom_name_id(a2.name);
|
|
if (groups_ok) {
|
|
link.link_id = "p";
|
|
} else if (monlib) {
|
|
if (!setup_link_if_matches(link, *monlib, o3p, p))
|
|
link.link_id = add_auto_chemlink(*monlib,
|
|
ri1.res->name, o3p,
|
|
ri2.res->name, p,
|
|
1.606, 0.02);
|
|
}
|
|
ri2.prev.push_back(link);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ri2.prev.empty()) {
|
|
link.link_id = "gap";
|
|
ri2.prev.push_back(link);
|
|
}
|
|
}
|
|
|
|
double Topo::ideal_chiral_abs_volume(const Chirality &ch) const {
|
|
const Restraints::Bond* bond_c1 = take_bond(ch.atoms[0], ch.atoms[1]);
|
|
const Restraints::Bond* bond_c2 = take_bond(ch.atoms[0], ch.atoms[2]);
|
|
const Restraints::Bond* bond_c3 = take_bond(ch.atoms[0], ch.atoms[3]);
|
|
const Restraints::Angle* angle_1c2 = take_angle(ch.atoms[1], ch.atoms[0], ch.atoms[2]);
|
|
const Restraints::Angle* angle_2c3 = take_angle(ch.atoms[2], ch.atoms[0], ch.atoms[3]);
|
|
const Restraints::Angle* angle_3c1 = take_angle(ch.atoms[3], ch.atoms[0], ch.atoms[1]);
|
|
if (bond_c1 && bond_c2 && bond_c3 && angle_1c2 && angle_2c3 && angle_3c1)
|
|
return chiral_abs_volume(bond_c1->value, bond_c2->value, bond_c3->value,
|
|
angle_1c2->value, angle_2c3->value, angle_3c1->value);
|
|
return std::numeric_limits<double>::quiet_NaN();
|
|
}
|
|
|
|
std::vector<Topo::Rule> Topo::apply_restraints(const Restraints& rt,
|
|
Residue& res, Residue* res2, Asu asu,
|
|
char altloc1, char altloc2, bool require_alt) {
|
|
std::string altlocs;
|
|
if (altloc1 == '\0' && altloc2 == '\0') {
|
|
add_distinct_altlocs(res, altlocs);
|
|
if (res2)
|
|
add_distinct_altlocs(*res2, altlocs);
|
|
}
|
|
if (altlocs.empty())
|
|
altlocs += altloc1 != 0 ? altloc1 : altloc2;
|
|
|
|
std::vector<Rule> rules;
|
|
for (const Restraints::Bond& bond : rt.bonds)
|
|
for (char alt : altlocs)
|
|
if (Atom* at1 = bond.id1.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at2 = bond.id2.get_from(res, res2, alt, altloc2)) {
|
|
bool with_alt = at1->altloc || at2->altloc;
|
|
if (with_alt || !require_alt) {
|
|
rules.push_back({RKind::Bond, bonds.size()});
|
|
bonds.push_back({&bond, {{at1, at2}}, asu});
|
|
}
|
|
if (!with_alt)
|
|
break;
|
|
}
|
|
|
|
if (only_bonds)
|
|
return rules;
|
|
|
|
for (const Restraints::Angle& angle : rt.angles)
|
|
for (char alt : altlocs)
|
|
if (Atom* at1 = angle.id1.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at2 = angle.id2.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at3 = angle.id3.get_from(res, res2, alt, altloc2)) {
|
|
bool with_alt = at1->altloc || at2->altloc || at3->altloc;
|
|
if (with_alt || !require_alt) {
|
|
rules.push_back({RKind::Angle, angles.size()});
|
|
angles.push_back({&angle, {{at1, at2, at3}}});
|
|
}
|
|
if (!with_alt)
|
|
break;
|
|
}
|
|
for (const Restraints::Torsion& tor : rt.torsions)
|
|
for (char alt : altlocs)
|
|
if (Atom* at1 = tor.id1.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at2 = tor.id2.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at3 = tor.id3.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at4 = tor.id4.get_from(res, res2, alt, altloc2)) {
|
|
bool with_alt = at1->altloc || at2->altloc || at3->altloc || at4->altloc;
|
|
if (with_alt || !require_alt) {
|
|
rules.push_back({RKind::Torsion, torsions.size()});
|
|
torsions.push_back({&tor, {{at1, at2, at3, at4}}});
|
|
}
|
|
if (!with_alt)
|
|
break;
|
|
}
|
|
for (const Restraints::Chirality& chir : rt.chirs)
|
|
for (char alt : altlocs)
|
|
if (Atom* at1 = chir.id_ctr.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at2 = chir.id1.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at3 = chir.id2.get_from(res, res2, alt, altloc2))
|
|
if (Atom* at4 = chir.id3.get_from(res, res2, alt, altloc2)) {
|
|
bool with_alt = at1->altloc || at2->altloc || at3->altloc || at4->altloc;
|
|
if (with_alt || !require_alt) {
|
|
rules.push_back({RKind::Chirality, chirs.size()});
|
|
chirs.push_back({&chir, {{at1, at2, at3, at4}}});
|
|
}
|
|
if (!with_alt)
|
|
break;
|
|
}
|
|
for (const Restraints::Plane& plane : rt.planes)
|
|
for (char alt : altlocs) {
|
|
std::vector<Atom*> atoms;
|
|
bool with_alt = false;
|
|
for (const Restraints::AtomId& id : plane.ids)
|
|
if (Atom* atom = id.get_from(res, res2, alt, altloc2)) {
|
|
with_alt = with_alt || atom->altloc;
|
|
atoms.push_back(atom);
|
|
}
|
|
if (atoms.size() >= 4 && (with_alt || !require_alt)) {
|
|
rules.push_back({RKind::Plane, planes.size()});
|
|
planes.push_back({&plane, atoms});
|
|
}
|
|
if (!with_alt)
|
|
break;
|
|
}
|
|
return rules;
|
|
}
|
|
|
|
void Topo::apply_restraints_from_link(Link& link, const MonLib& monlib) {
|
|
if (link.link_id.empty() || link.link_id == "gap")
|
|
return;
|
|
const ChemLink* chem_link = monlib.get_link(link.link_id);
|
|
if (!chem_link) {
|
|
logger.err("ignoring link '", link.link_id, "' as it is not in the monomer library");
|
|
return;
|
|
}
|
|
const Restraints* rt = &chem_link->rt;
|
|
if (link.alt1 && link.alt2 && link.alt1 != link.alt2)
|
|
logger.err("LINK between different conformers: ", link.alt1, " (in ",
|
|
link.res1->name, ") and ", link.alt2, " (in ", link.res2->name, ").");
|
|
// aliases are a new feature - introduced in 2022
|
|
if (link.aliasing1 || link.aliasing2) {
|
|
std::unique_ptr<Restraints> rt_copy(new Restraints(*rt));
|
|
if (link.aliasing1)
|
|
for (const auto& p : link.aliasing1->related)
|
|
rt_copy->rename_atom(Restraints::AtomId{1, p.second}, p.first);
|
|
if (link.aliasing2)
|
|
for (const auto& p : link.aliasing2->related)
|
|
rt_copy->rename_atom(Restraints::AtomId{2, p.second}, p.first);
|
|
rt = rt_copy.get();
|
|
rt_storage.push_back(std::move(rt_copy));
|
|
}
|
|
link.link_rules = apply_restraints(*rt, *link.res1, link.res2, link.asu,
|
|
link.alt1, link.alt2, false);
|
|
}
|
|
|
|
// see comments above the declaration
|
|
void Topo::initialize_refmac_topology(Structure& st, Model& model0,
|
|
MonLib& monlib, bool ignore_unknown_links) {
|
|
// initialize chain_infos
|
|
for (Chain& chain : model0.chains)
|
|
for (ResidueSpan& sub : chain.subchains()) {
|
|
// set Residue::group_idx which is used in Restraints::AtomId::get_from()
|
|
for (size_t i = 0; i != sub.size(); ++i) {
|
|
sub[i].group_idx = 0;
|
|
if (i != 0 && sub[i-1].seqid == sub[i].seqid)
|
|
sub[i].group_idx = sub[i-1].group_idx + 1;
|
|
}
|
|
const Entity* ent = st.get_entity_of(sub);
|
|
chain_infos.emplace_back(sub, chain, ent);
|
|
}
|
|
|
|
// add modifications from MODRES records (Refmac's extension)
|
|
for (const ModRes& modres : st.mod_residues)
|
|
if (!modres.mod_id.empty()) {
|
|
for (Topo::ChainInfo& chain_info : chain_infos)
|
|
if (chain_info.chain_ref.name == modres.chain_name) {
|
|
for (Topo::ResInfo& res_info : chain_info.res_infos)
|
|
if (*res_info.res == modres.res_id)
|
|
res_info.add_mod(modres.mod_id, nullptr, '\0');
|
|
}
|
|
}
|
|
|
|
// setup pointers to monomers and links in the polymer
|
|
for (ChainInfo& ci : chain_infos) {
|
|
for (ResInfo& ri : ci.res_infos) {
|
|
auto it = monlib.monomers.find(ri.res->name);
|
|
if (it != monlib.monomers.end())
|
|
ri.orig_chemcomp = &it->second;
|
|
// orig_chemcomp is left null for missing monomers
|
|
}
|
|
// setup polymer links
|
|
if (ci.polymer && !ci.res_infos.empty()) {
|
|
// handling of microheterogeneities makes it more complicated
|
|
// and it should be even more complex to handle partial bonding
|
|
auto prev_begin = ci.res_infos.begin();
|
|
auto prev_end = ci.group_end(prev_begin);
|
|
while (prev_end != ci.res_infos.end()) {
|
|
auto group_begin = prev_end;
|
|
auto group_end = ci.group_end(group_begin);
|
|
for (auto ri = group_begin; ri != group_end; ++ri)
|
|
for (auto prev_ri = prev_begin; prev_ri != prev_end; ++prev_ri) {
|
|
MonLib* monlib_ptr = ignore_unknown_links ? nullptr : &monlib;
|
|
add_polymer_links(ci.polymer_type, *prev_ri, *ri, monlib_ptr);
|
|
}
|
|
prev_begin = group_begin;
|
|
prev_end = group_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add extra links
|
|
for (Connection& conn : st.connections)
|
|
if (conn.type != Connection::Hydrog) // ignoring hydrogen bonds
|
|
setup_connection(conn, model0, monlib, ignore_unknown_links);
|
|
|
|
// Add modifications from standard links. We do it here b/c polymer links
|
|
// could be disabled (link_id.clear()) in setup_connection().
|
|
for (ChainInfo& ci : chain_infos)
|
|
for (ResInfo& ri : ci.res_infos)
|
|
for (Link& prev : ri.prev)
|
|
if (const ChemLink* chem_link = monlib.get_link(prev.link_id)) {
|
|
ResInfo* ri_prev = &ri + prev.res_distance();
|
|
ri_prev->add_mod(chem_link->side1.mod, prev.aliasing1, prev.alt1);
|
|
ri.add_mod(chem_link->side2.mod, prev.aliasing2, prev.alt2);
|
|
}
|
|
|
|
// Apply modifications to monomer restraints
|
|
for (ChainInfo& chain_info : chain_infos)
|
|
for (ResInfo& ri : chain_info.res_infos) {
|
|
if (ri.orig_chemcomp) {
|
|
// The final ChemComp restraints that we'll use are made from
|
|
// original (_chem_comp) restraints with modifications (_chem_mod)
|
|
// applied. There is a corner case in which different conformations
|
|
// of the residue have different modifications applied.
|
|
bool has_mod_altlocs = false;
|
|
for (const Mod& mod : ri.mods)
|
|
if (mod.altloc != '\0')
|
|
has_mod_altlocs = true;
|
|
std::string altlocs;
|
|
if (has_mod_altlocs)
|
|
add_distinct_altlocs(*ri.res, altlocs); // cf. apply_restraints
|
|
if (altlocs.empty())
|
|
altlocs += '\0';
|
|
for (char altloc : altlocs) {
|
|
// key for caching in Topo::cc_cache: ChemComp::name + modifications
|
|
std::string key = ri.orig_chemcomp->name;
|
|
for (const Mod& mod : ri.mods)
|
|
if (!mod.altloc || altloc == mod.altloc) {
|
|
key += char(1 + static_cast<int>(mod.alias));
|
|
key += mod.id;
|
|
}
|
|
auto it = cc_cache.find(key);
|
|
if (it != cc_cache.end()) {
|
|
ri.chemcomps.push_back({altloc, it->second.get()});
|
|
} else { // it's not in cache yet - we need to add it
|
|
std::unique_ptr<ChemComp> cc_copy(new ChemComp(*ri.orig_chemcomp));
|
|
// apply modifications
|
|
for (const Mod& mod : ri.mods) {
|
|
if (!mod.altloc || altloc == mod.altloc) {
|
|
if (const ChemMod* chem_mod = monlib.get_mod(mod.id)) {
|
|
try {
|
|
chem_mod->apply_to(*cc_copy, mod.alias);
|
|
} catch(std::runtime_error& e) {
|
|
logger.err("failed to apply modification ", chem_mod->id,
|
|
" to ", ri.res->name, ": ", e.what());
|
|
}
|
|
} else {
|
|
logger.err("modification not found: ", mod.id);
|
|
}
|
|
}
|
|
}
|
|
ri.chemcomps.push_back({altloc, cc_copy.get()});
|
|
cc_cache.emplace(key, std::move(cc_copy));
|
|
}
|
|
}
|
|
// Usually the same modifications are applied to all conformers.
|
|
// In such case reduce chemcomps to a single value.
|
|
if (ri.chemcomps.size() > 0 &&
|
|
std::all_of(ri.chemcomps.begin() + 1, ri.chemcomps.end(),
|
|
[&](const FinalChemComp& f) { return f.cc == ri.chemcomps[0].cc; })) {
|
|
ri.chemcomps.resize(1);
|
|
ri.chemcomps[0].altloc = '\0';
|
|
}
|
|
} else { // orig_chemcomp not set - make ChemComp with ad-hoc restraints
|
|
// no cache - ad-hoc restraints are separate for each residue
|
|
cc_storage.emplace_back(make_chemcomp_with_restraints(*ri.res));
|
|
ri.chemcomps.push_back({'\0', cc_storage.back().get()});
|
|
}
|
|
}
|
|
}
|
|
|
|
void Topo::apply_all_restraints(const MonLib& monlib) {
|
|
bonds.clear();
|
|
angles.clear();
|
|
torsions.clear();
|
|
chirs.clear();
|
|
planes.clear();
|
|
rt_storage.clear();
|
|
for (ChainInfo& chain_info : chain_infos)
|
|
for (ResInfo& ri : chain_info.res_infos) {
|
|
// link restraints
|
|
for (Link& link : ri.prev)
|
|
apply_restraints_from_link(link, monlib);
|
|
// monomer restraints
|
|
auto it = ri.chemcomps.cbegin();
|
|
ri.monomer_rules = apply_restraints(it->cc->rt, *ri.res, nullptr, Asu::Same,
|
|
it->altloc, '\0', /*require_alt=*/false);
|
|
while (++it != ri.chemcomps.end()) {
|
|
auto rules = apply_restraints(it->cc->rt, *ri.res, nullptr, Asu::Same,
|
|
it->altloc, '\0', /*require_alt=*/true);
|
|
// calling reserve avoids bogus GCC warning -Wstringop-overflow
|
|
ri.monomer_rules.reserve(ri.monomer_rules.size() + rules.size());
|
|
vector_move_extend(ri.monomer_rules, std::move(rules));
|
|
}
|
|
}
|
|
for (Link& link : extras)
|
|
apply_restraints_from_link(link, monlib);
|
|
}
|
|
|
|
void Topo::create_indices() {
|
|
bond_index.clear();
|
|
for (Bond& bond : bonds) {
|
|
bond_index.emplace(bond.atoms[0], &bond);
|
|
if (bond.atoms[1] != bond.atoms[0])
|
|
bond_index.emplace(bond.atoms[1], &bond);
|
|
}
|
|
angle_index.clear();
|
|
for (Angle& ang : angles)
|
|
angle_index.emplace(ang.atoms[1], &ang);
|
|
torsion_index.clear();
|
|
for (Torsion& tor : torsions) {
|
|
torsion_index.emplace(tor.atoms[1], &tor);
|
|
if (tor.atoms[1] != tor.atoms[2])
|
|
torsion_index.emplace(tor.atoms[2], &tor);
|
|
}
|
|
plane_index.clear();
|
|
for (Plane& plane : planes)
|
|
for (Atom* atom : plane.atoms)
|
|
plane_index.emplace(atom, &plane);
|
|
}
|
|
|
|
Topo::Link* Topo::find_polymer_link(const AtomAddress& aa1, const AtomAddress& aa2) {
|
|
auto are_bonded = [&](const Link& link, const AtomAddress& a1, const AtomAddress& a2) {
|
|
return a1.res_id.matches_noseg(*link.res1) &&
|
|
a2.res_id.matches_noseg(*link.res2) &&
|
|
a1.altloc == link.alt1 &&
|
|
a2.altloc == link.alt2 &&
|
|
(a1.atom_name.empty() || atom_name_id(a1.atom_name) == link.atom1_name_id) &&
|
|
(a2.atom_name.empty() || atom_name_id(a2.atom_name) == link.atom2_name_id);
|
|
};
|
|
if (aa1.chain_name != aa2.chain_name)
|
|
return nullptr;
|
|
for (ChainInfo& ci : chain_infos)
|
|
if (aa1.chain_name == ci.chain_ref.name) {
|
|
for (ResInfo& ri : ci.res_infos)
|
|
for (Link& link : ri.prev) {
|
|
assert(link.res1 && link.res2);
|
|
if (are_bonded(link, aa1, aa2) || are_bonded(link, aa2, aa1))
|
|
return &link;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Tries to construct Topo::Link and append it to extras.
|
|
// Side-effects: it may modify conn.link_id and add ChemLink to monlib.links.
|
|
void Topo::setup_connection(Connection& conn, Model& model0, MonLib& monlib,
|
|
bool ignore_unknown_links) {
|
|
if (conn.link_id == "gap") {
|
|
if (Link* polymer_link = find_polymer_link(conn.partner1, conn.partner2))
|
|
polymer_link->link_id.clear(); // disable polymer link
|
|
return;
|
|
}
|
|
|
|
Link extra;
|
|
CRA cra1 = model0.find_cra(conn.partner1, true);
|
|
CRA cra2 = model0.find_cra(conn.partner2, true);
|
|
if (!cra1.atom || !cra2.atom)
|
|
return;
|
|
extra.res1 = cra1.residue;
|
|
extra.res2 = cra2.residue;
|
|
extra.alt1 = conn.partner1.altloc;
|
|
extra.alt2 = conn.partner2.altloc;
|
|
extra.asu = conn.asu;
|
|
|
|
const ChemLink* match = nullptr;
|
|
|
|
// If we have link_id find ChemLink by name (and check if it matches).
|
|
if (!conn.link_id.empty()) {
|
|
match = monlib.get_link(conn.link_id);
|
|
if (!match) {
|
|
logger.err("link not found in monomer library: ", conn.link_id);
|
|
return;
|
|
}
|
|
if (match->rt.bonds.empty() ||
|
|
!monlib.link_side_matches_residue(match->side1, extra.res1->name, &extra.aliasing1) ||
|
|
!monlib.link_side_matches_residue(match->side2, extra.res2->name, &extra.aliasing2) ||
|
|
!atom_match_with_alias(match->rt.bonds[0].id1.atom,
|
|
conn.partner1.atom_name, extra.aliasing1) ||
|
|
!atom_match_with_alias(match->rt.bonds[0].id2.atom,
|
|
conn.partner2.atom_name, extra.aliasing2)) {
|
|
logger.err("link from the monomer library does not match: ", conn.link_id);
|
|
return;
|
|
}
|
|
} else {
|
|
// we don't have link_id - use the best matching link (if any)
|
|
match = setup_link_if_matches(extra, monlib,
|
|
conn.partner1.atom_name, conn.partner2.atom_name);
|
|
if (match)
|
|
conn.link_id = match->id;
|
|
}
|
|
|
|
// If a polymer link is also given in LINK/struct_conn,
|
|
// use only one of them. If LINK has explicit name (ccp4_link_id),
|
|
// or if it matches residue-specific link from monomer library, use it;
|
|
// otherwise, LINK is repetition of TRANS/CIS, so ignore LINK.
|
|
if (Link* polymer_link = find_polymer_link(conn.partner1, conn.partner2)) {
|
|
if (!conn.link_id.empty() || // conn has explicit link name
|
|
// polymer link was not really identified
|
|
cif::is_null(polymer_link->link_id) || polymer_link->link_id == "gap" ||
|
|
// conn is residue-specific
|
|
(match && !(match->side1.comp.empty() && match->side2.comp.empty())))
|
|
polymer_link->link_id.clear(); // disable polymer link, use conn
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (match) {
|
|
extra.link_id = match->id;
|
|
// add modifications from the link
|
|
find_resinfo(extra.res1)->add_mod(match->side1.mod, extra.aliasing1, extra.alt1);
|
|
find_resinfo(extra.res2)->add_mod(match->side2.mod, extra.aliasing2, extra.alt2);
|
|
} else {
|
|
if (ignore_unknown_links)
|
|
return;
|
|
// create a new ChemLink and add it to the monomer library
|
|
double ideal_dist = monlib.find_ideal_distance(cra1, cra2);
|
|
extra.link_id = add_auto_chemlink(monlib,
|
|
extra.res1->name, conn.partner1.atom_name,
|
|
extra.res2->name, conn.partner2.atom_name,
|
|
ideal_dist, 0.02);
|
|
}
|
|
extras.push_back(extra);
|
|
}
|
|
|
|
void Topo::set_cispeps_in_structure(Structure& st) {
|
|
st.cispeps.clear();
|
|
if (chain_infos.empty())
|
|
return;
|
|
// model is not stored in Topo, let's determine it from chain_infos[0]
|
|
int model_num = 0;
|
|
for (const Model& model : st.models)
|
|
if (!model.chains.empty() &&
|
|
&model.chains[0] == &chain_infos[0].chain_ref)
|
|
model_num = model.num;
|
|
for (ChainInfo& chain_info : chain_infos)
|
|
for (ResInfo& res_info : chain_info.res_infos)
|
|
for (Link& link : res_info.prev)
|
|
if (link.is_cis) {
|
|
CisPep cp;
|
|
cp.model_num = model_num;
|
|
cp.partner_c = AtomAddress(chain_info.chain_ref.name, *link.res1, "", link.alt1);
|
|
cp.partner_n = AtomAddress(chain_info.chain_ref.name, *link.res2, "", link.alt2);
|
|
cp.only_altloc = link.alt1 ? link.alt1 : link.alt2;
|
|
for (const Rule& rule : link.link_rules)
|
|
if (rule.rkind == RKind::Torsion) {
|
|
const Torsion& tor = torsions[rule.index];
|
|
if (tor.restr->label == "omega") {
|
|
cp.reported_angle = tor.calculate();
|
|
break;
|
|
}
|
|
}
|
|
st.cispeps.push_back(cp);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
void remove_hydrogens_from_atom(Topo::ResInfo* ri,
|
|
const std::string& atom_name, char alt) {
|
|
if (!ri)
|
|
return;
|
|
std::vector<Atom>& atoms = ri->res->atoms;
|
|
const Restraints& rt = ri->get_final_chemcomp(alt).rt;
|
|
for (auto it = atoms.end(); it-- != atoms.begin(); ) {
|
|
if (it->is_hydrogen() && is_same_conformer(it->altloc, alt)) {
|
|
const Restraints::AtomId* heavy = rt.first_bonded_atom(it->name);
|
|
if (heavy && heavy->atom == atom_name)
|
|
atoms.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_cis_in_link(Topo::Link& link, bool is_cis) {
|
|
if (is_cis) {
|
|
if (ends_with(link.link_id, "TRANS"))
|
|
link.link_id.replace(link.link_id.size() - 5, 5, "CIS");
|
|
} else {
|
|
if (ends_with(link.link_id, "CIS"))
|
|
link.link_id.replace(link.link_id.size() - 3, 3, "TRANS");
|
|
}
|
|
link.is_cis = is_cis;
|
|
}
|
|
|
|
void force_cispeps(Topo& topo, bool single_model, const Model& model,
|
|
const std::vector<CisPep>& cispeps) {
|
|
std::multimap<const Residue*, const CisPep*> cispep_index;
|
|
for (const CisPep& cp : cispeps) {
|
|
if (single_model || model.num == cp.model_num)
|
|
if (const Residue* res_n = model.find_cra(cp.partner_n).residue)
|
|
cispep_index.emplace(res_n, &cp);
|
|
}
|
|
for (Topo::ChainInfo& chain_info : topo.chain_infos)
|
|
for (Topo::ResInfo& res_info : chain_info.res_infos) {
|
|
auto range = cispep_index.equal_range(res_info.res);
|
|
for (Topo::Link& link : res_info.prev) {
|
|
bool is_cis = false;
|
|
for (auto i = range.first; i != range.second; ++i) {
|
|
const CisPep& cp = *i->second;
|
|
if (cp.partner_c.res_id.matches_noseg(*link.res1) &&
|
|
(cp.only_altloc == '\0' || cp.only_altloc == link.alt1
|
|
|| cp.only_altloc == link.alt2))
|
|
is_cis = true;
|
|
}
|
|
if (is_cis != link.is_cis) {
|
|
set_cis_in_link(link, is_cis);
|
|
const std::string& ch_name = chain_info.chain_ref.name;
|
|
topo.logger.note("link between ", atom_str(ch_name, *link.res1, "", link.alt1),
|
|
" and ", atom_str(ch_name, *link.res2, "", link.alt2),
|
|
" forced to ", link.link_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Neigh {
|
|
char alt;
|
|
float occ;
|
|
};
|
|
using NeighMap = std::unordered_multimap<int, Neigh>;
|
|
|
|
// Assumes no hydrogens in the residue.
|
|
// Position and serial number are not assigned for new atoms.
|
|
void add_hydrogens_without_positions(Topo::ResInfo& ri, const NeighMap& neighbor_altlocs) {
|
|
std::vector<Atom>& atoms = ri.res->atoms;
|
|
// Add H atom for each conformation (altloc) of the parent atom and its
|
|
// first neighbors.
|
|
for (size_t i = 0, size = atoms.size(); i != size; ++i) {
|
|
if (atoms[i].calc_flag == CalcFlag::NoHydrogen)
|
|
continue;
|
|
char parent_alt = atoms[i].altloc;
|
|
float parent_occ = atoms[i].occ;
|
|
std::map<char, float> altlocs; // altloc + occupancy
|
|
if (parent_alt == '\0') {
|
|
// We could have neighbor 1 with altlocs A,B and neighbor 2 with C,D.
|
|
// Or A,B and A,B,C. Or, neighbor 1 could have different occupancy
|
|
// of altloc A than neighbor 2. We don't want to check for all possible
|
|
// weird cases. Only ensure that the added H has total occupancy <= 1.
|
|
float occ_sum = 0;
|
|
auto range = neighbor_altlocs.equal_range(atoms[i].serial);
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
const Neigh& neigh = it->second;
|
|
if (altlocs.count(neigh.alt) == 0 && neigh.occ < 1.0f && occ_sum + neigh.occ <= 1.001f) {
|
|
occ_sum += neigh.occ;
|
|
altlocs.emplace(neigh.alt, neigh.occ);
|
|
}
|
|
}
|
|
if (occ_sum - parent_occ > 0.001f)
|
|
for (auto& it : altlocs)
|
|
it.second *= parent_occ;
|
|
}
|
|
if (altlocs.empty())
|
|
altlocs.emplace(parent_alt, parent_occ);
|
|
const ChemComp& cc = ri.get_final_chemcomp(parent_alt);
|
|
for (const Restraints::Bond& bond : cc.rt.bonds) {
|
|
// atoms may get re-allocated, so we can't set parent earlier
|
|
const Atom& parent = atoms[i];
|
|
assert(!parent.is_hydrogen());
|
|
const Restraints::AtomId* atom_id = bond.other(parent.name);
|
|
if (!atom_id)
|
|
continue;
|
|
auto it = cc.find_atom(atom_id->atom);
|
|
if (it == cc.atoms.end())
|
|
fail("inconsistent _chem_comp " + cc.name);
|
|
if (it->is_hydrogen()) {
|
|
gemmi::Atom atom;
|
|
atom.name = it->id;
|
|
atom.element = it->el;
|
|
// calc_flag will be changed to Calculated when the position is set
|
|
atom.calc_flag = CalcFlag::Dummy;
|
|
atom.b_iso = parent.b_iso;
|
|
for (auto alt_occ : altlocs) {
|
|
atom.altloc = alt_occ.first;
|
|
atom.occ = alt_occ.second;
|
|
atoms.push_back(atom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NeighMap prepare_neighbor_altlocs(Topo& topo, const MonLib& monlib) {
|
|
// disable warnings here, so they are not printed twice
|
|
topo.logger.suspend();
|
|
// Prepare bonds. Fills topo.bonds, monomer_rules/link_rules and rt_storage,
|
|
// but they are all reset when apply_all_restraints() is called again.
|
|
topo.only_bonds = true;
|
|
topo.apply_all_restraints(monlib);
|
|
topo.only_bonds = false;
|
|
topo.logger.resume(); // re-enable warnings
|
|
NeighMap neighbors;
|
|
for (const Topo::Bond& bond : topo.bonds) {
|
|
const Atom* a1 = bond.atoms[0];
|
|
const Atom* a2 = bond.atoms[1];
|
|
if (a2->altloc)
|
|
neighbors.emplace(a1->serial, Neigh{a2->altloc, a2->occ});
|
|
if (a1->altloc && a1 != a2)
|
|
neighbors.emplace(a2->serial, Neigh{a1->altloc, a1->occ});
|
|
}
|
|
return neighbors;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
std::unique_ptr<Topo>
|
|
prepare_topology(Structure& st, MonLib& monlib, size_t model_index,
|
|
HydrogenChange h_change, bool reorder,
|
|
const Logger& logger, bool ignore_unknown_links, bool use_cispeps) {
|
|
std::unique_ptr<Topo> topo(new Topo);
|
|
topo->logger = logger;
|
|
if (model_index >= st.models.size())
|
|
fail("no such model index: " + std::to_string(model_index));
|
|
Model& model = st.models[model_index];
|
|
topo->initialize_refmac_topology(st, model, monlib, ignore_unknown_links);
|
|
|
|
if (use_cispeps)
|
|
force_cispeps(*topo, st.models.size() == 1, model, st.cispeps);
|
|
|
|
// remove hydrogens, or change deuterium to fraction, or nothing
|
|
// and then check atom names
|
|
for (Topo::ChainInfo& chain_info : topo->chain_infos)
|
|
for (Topo::ResInfo& ri : chain_info.res_infos) {
|
|
Residue& res = *ri.res;
|
|
if (h_change != HydrogenChange::NoChange && h_change != HydrogenChange::Shift
|
|
// don't re-add H's if we don't have chemical component description
|
|
&& (ri.orig_chemcomp != nullptr || h_change == HydrogenChange::Remove)) {
|
|
// remove/add hydrogens
|
|
remove_hydrogens(res);
|
|
} else {
|
|
// Special handling of Deuterium - mostly for Refmac.
|
|
// Note: if the model has deuterium, it gets modified.
|
|
if (replace_deuterium_with_fraction(res)) {
|
|
// deuterium names usually differ from the names in dictionary
|
|
for (Atom& atom : res.atoms)
|
|
if (atom.name[0] == 'D' && atom.fraction != 0) {
|
|
const ChemComp& cc = ri.get_final_chemcomp(atom.altloc);
|
|
if (cc.find_atom(atom.name) == cc.atoms.end())
|
|
atom.name[0] = 'H';
|
|
}
|
|
st.has_d_fraction = true;
|
|
}
|
|
}
|
|
// check atom names
|
|
for (Atom& atom : res.atoms) {
|
|
const ChemComp& cc = ri.get_final_chemcomp(atom.altloc);
|
|
if (!cc.has_atom(atom.name)) {
|
|
std::string msg = "definition not found for "
|
|
+ atom_str(chain_info.chain_ref, *ri.res, atom);
|
|
if (ri.orig_chemcomp && ri.orig_chemcomp->has_atom(atom.name)) {
|
|
msg += " (linkage should remove this atom)";
|
|
} else {
|
|
auto it = cc.find_atom_by_old_name(atom.name);
|
|
if (it != cc.atoms.end())
|
|
cat_to(msg, " (replace ", atom.name, " with ", it->id, ')');
|
|
}
|
|
topo->logger.err(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add hydrogens
|
|
if (h_change == HydrogenChange::ReAdd ||
|
|
h_change == HydrogenChange::ReAddButWater ||
|
|
h_change == HydrogenChange::ReAddKnown) {
|
|
NeighMap neighbor_altlocs = prepare_neighbor_altlocs(*topo, monlib);
|
|
for (Topo::ChainInfo& chain_info : topo->chain_infos)
|
|
for (Topo::ResInfo& ri : chain_info.res_infos) {
|
|
Residue& res = *ri.res;
|
|
if (ri.orig_chemcomp != nullptr &&
|
|
(h_change == HydrogenChange::ReAdd || !res.is_water())) {
|
|
add_hydrogens_without_positions(ri, neighbor_altlocs);
|
|
|
|
// a special handling of HIS for compatibility with Refmac
|
|
if (res.name == "HIS") {
|
|
for (gemmi::Atom& atom : ri.res->atoms)
|
|
if (atom.name == "HD1" || atom.name == "HE2")
|
|
atom.occ = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort atoms in residues
|
|
for (Topo::ChainInfo& chain_info : topo->chain_infos)
|
|
for (Topo::ResInfo& ri : chain_info.res_infos)
|
|
if (reorder && ri.orig_chemcomp) {
|
|
Residue& res = *ri.res;
|
|
const ChemComp& cc = *ri.orig_chemcomp;
|
|
for (Atom& atom : res.atoms) {
|
|
auto it = cc.find_atom(atom.name);
|
|
// If atom.name is not found (b/c it was added in a modification),
|
|
// the atom will be put after original atoms.
|
|
atom.serial = int(it - cc.atoms.begin()); // temporary, for sorting only
|
|
}
|
|
std::sort(res.atoms.begin(), res.atoms.end(), [](const Atom& a, const Atom& b) {
|
|
return a.serial != b.serial ? a.serial < b.serial
|
|
: a.altloc < b.altloc;
|
|
});
|
|
// check for missing altloc
|
|
for (auto atom = res.atoms.begin(); atom + 1 < res.atoms.end(); ++atom)
|
|
if (atom->name == (atom + 1)->name && atom->altloc == '\0')
|
|
topo->logger.err("missing altloc in ", atom_str(chain_info.chain_ref, *ri.res, *atom));
|
|
}
|
|
|
|
// for atoms with ad-hoc links, for now we don't want hydrogens
|
|
if (!ignore_unknown_links && h_change != HydrogenChange::NoChange) {
|
|
auto remove_h_from_auto_links = [&](const Topo::Link& link) {
|
|
const ChemLink* cl = monlib.get_link(link.link_id);
|
|
if (cl && starts_with(cl->name, "auto-")) {
|
|
const Restraints::Bond& bond = cl->rt.bonds.at(0);
|
|
remove_hydrogens_from_atom(topo->find_resinfo(link.res1), bond.id1.atom, link.alt1);
|
|
remove_hydrogens_from_atom(topo->find_resinfo(link.res2), bond.id2.atom, link.alt2);
|
|
}
|
|
};
|
|
for (const Topo::ChainInfo& chain_info : topo->chain_infos)
|
|
for (const Topo::ResInfo& res_info : chain_info.res_infos)
|
|
for (const Topo::Link& link : res_info.prev)
|
|
remove_h_from_auto_links(link);
|
|
for (const Topo::Link& link : topo->extras)
|
|
remove_h_from_auto_links(link);
|
|
}
|
|
|
|
// fill Topo::bonds, angles, ... and ResInfo::monomer_rules, Links::link_rules
|
|
topo->apply_all_restraints(monlib);
|
|
// fill bond_index, angle_index, etc
|
|
topo->create_indices();
|
|
|
|
// the hydrogens added previously have positions not set
|
|
if (h_change != HydrogenChange::NoChange)
|
|
place_hydrogens_on_all_atoms(*topo);
|
|
|
|
if (h_change == HydrogenChange::ReAddKnown) {
|
|
// To leave only known hydrogens, we remove Hs with zero occupancy.
|
|
// As a side-effect, it removes any H atoms on zero-occupancy parents.
|
|
for (Chain& chain : model.chains)
|
|
for (Residue& res : chain.residues)
|
|
vector_remove_if(res.atoms, [](Atom& a) { return a.is_hydrogen() && a.occ == 0; });
|
|
|
|
// disable warnings here, so they are not printed twice
|
|
topo->logger.suspend();
|
|
// re-set restraints and indices
|
|
topo->apply_all_restraints(monlib);
|
|
topo->create_indices();
|
|
topo->logger.resume();
|
|
}
|
|
|
|
assign_serial_numbers(model);
|
|
return topo;
|
|
}
|
|
|
|
std::vector<AtomAddress> find_missing_atoms(const Topo& topo, bool including_hydrogen) {
|
|
std::vector<AtomAddress> ret;
|
|
for (const Topo::ChainInfo& chain_info : topo.chain_infos)
|
|
for (const Topo::ResInfo& ri : chain_info.res_infos) {
|
|
// check only the first conformation
|
|
const Topo::FinalChemComp& fcc = ri.chemcomps.at(0);
|
|
char altloc = fcc.altloc != '\0' ? fcc.altloc : '*';
|
|
for (const ChemComp::Atom& at : fcc.cc->atoms)
|
|
if ((including_hydrogen || !at.is_hydrogen()) &&
|
|
ri.res->find_atom(at.id, altloc) == nullptr)
|
|
ret.emplace_back(chain_info.chain_ref.name, *ri.res, at.id, fcc.altloc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
}
|