Merge ncs-fix into rc181: NCS copies also within 2.5 A C-alpha rms after superposition
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVeDQqN9qRD57vnShzTC37
This commit is contained in:
commit
fee8ffacd9
5 files changed
+55
-20
No files matched your search
@@ -631,7 +631,8 @@ what the data support rather than a rule by resolution: E. A. Merritt, "To B or
|
|||||||
resolution?" (2012), Acta Cryst. D68, 468-477 [doi:10.1107/S0907444911028320](https://doi.org/10.1107/S0907444911028320).
|
resolution?" (2012), Acta Cryst. D68, 468-477 [doi:10.1107/S0907444911028320](https://doi.org/10.1107/S0907444911028320).
|
||||||
|
|
||||||
**NCS restraints** — copies of a molecule in the model are found by sequence alignment with REFMAC5's criterion
|
**NCS restraints** — copies of a molecule in the model are found by sequence alignment with REFMAC5's criterion
|
||||||
(more than 15 residues aligned, 80% identity - here of the longer chain; Murshudov et al. 2011, above) and restrained locally, on pairs of
|
(more than 15 residues aligned, 80% identity - here of the longer chain - and 2.5 Å rms after superposition;
|
||||||
|
Murshudov et al. 2011, above) and restrained locally, on pairs of
|
||||||
corresponding interatomic distances under the Geman-McClure robust loss, as REFMAC5's local NCS restraints are, with
|
corresponding interatomic distances under the Geman-McClure robust loss, as REFMAC5's local NCS restraints are, with
|
||||||
the distance range of R. A. Nicholls, F. Long and G. N. Murshudov, "Low-resolution refinement tools in REFMAC5"
|
the distance range of R. A. Nicholls, F. Long and G. N. Murshudov, "Low-resolution refinement tools in REFMAC5"
|
||||||
(2012), Acta Cryst. D68, 404-417 [doi:10.1107/S090744491105606X](https://doi.org/10.1107/S090744491105606X), and
|
(2012), Acta Cryst. D68, 404-417 [doi:10.1107/S090744491105606X](https://doi.org/10.1107/S090744491105606X), and
|
||||||
|
|||||||
@@ -236,11 +236,13 @@ the ceiling of §15.1.
|
|||||||
|
|
||||||
**NCS restraints.** Where the model holds several copies of one molecule, they are found from the model alone: two
|
**NCS restraints.** Where the model holds several copies of one molecule, they are found from the model alone: two
|
||||||
polymer chains of one kind (protein, or nucleic acid) are copies where their sequences align with more than 15
|
polymer chains of one kind (protein, or nucleic acid) are copies where their sequences align with more than 15
|
||||||
residues identical and identical in 80% of the longer chain or more - REFMAC5's criterion, taken on the longer chain. In the residues aligned to a residue of the same name, the
|
residues identical and identical in 80% of the longer chain or more, and their C-alpha (P) atoms lie within 2.5 Å
|
||||||
atoms of the same name correspond - not a hydrogen, not an alternative conformation, both refinable. Each copy is
|
rms of each other after superposition - REFMAC5's criterion, the identity taken on the longer chain. The same
|
||||||
paired with the next of its molecule (and the last with the first, where there are three or more), and each pair is
|
sequence in a conformation further away than that, such as a quasi-equivalent subunit of a virus capsid, is another
|
||||||
restrained **locally**, on its interatomic distances rather than on a superposition: every distance within a chain
|
molecule. In the residues aligned to a residue of the same name, the atoms of the same name correspond - not a
|
||||||
shorter than 4.2 Å in either copy, except those a bond or an angle of the dictionary already fixes, against the same
|
hydrogen, not an alternative conformation, both refinable. Each copy is paired with the next of its molecule (and the
|
||||||
|
last with the first, where there are three or more), and each pair is restrained **locally**, on its interatomic
|
||||||
|
distances rather than on a superposition: every distance within a chain shorter than 4.2 Å in either copy, except those a bond or an angle of the dictionary already fixes, against the same
|
||||||
distance in the other copy, with the robust loss of Geman and McClure,
|
distance in the other copy, with the robust loss of Geman and McClure,
|
||||||
|
|
||||||
$$
|
$$
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -26,6 +27,10 @@ constexpr float NCS_RELEASE = 0.5f; // A
|
|||||||
// Following Murshudov et al. (2011) Acta Cryst. D67, 355-367
|
// Following Murshudov et al. (2011) Acta Cryst. D67, 355-367
|
||||||
constexpr int NCS_MIN_ALIGNED = 15;
|
constexpr int NCS_MIN_ALIGNED = 15;
|
||||||
constexpr double NCS_MIN_IDENTITY = 80.0; // %
|
constexpr double NCS_MIN_IDENTITY = 80.0; // %
|
||||||
|
// and their C-alpha (P) atoms no further apart than this after superposition: the same sequence further away (a
|
||||||
|
// quasi-equivalent subunit of a virus capsid) is another molecule.
|
||||||
|
// Following Murshudov et al. (2011) Acta Cryst. D67, 355-367
|
||||||
|
constexpr double NCS_MAX_RMSD = 2.5; // A
|
||||||
|
|
||||||
// One polymer chain: its residues and, per residue, its atoms' indices by name.
|
// One polymer chain: its residues and, per residue, its atoms' indices by name.
|
||||||
struct NcsChain {
|
struct NcsChain {
|
||||||
@@ -65,15 +70,30 @@ void Correspond(const NcsChain &a, const NcsChain &b, std::vector<std::array<int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AreCopies(const NcsChain &a, const NcsChain &b) {
|
// The C-alpha (P) rmsd of two chains after superposition where their sequences make them copies of one molecule,
|
||||||
|
// infinity where they do not.
|
||||||
|
double CopyRmsd(const NcsChain &a, const NcsChain &b, const std::vector<gemmi::Position> &pos) {
|
||||||
|
const double none = std::numeric_limits<double>::infinity();
|
||||||
if (a.type != b.type || a.sequence.size() <= NCS_MIN_ALIGNED || b.sequence.size() <= NCS_MIN_ALIGNED)
|
if (a.type != b.type || a.sequence.size() <= NCS_MIN_ALIGNED || b.sequence.size() <= NCS_MIN_ALIGNED)
|
||||||
return false;
|
return none;
|
||||||
const gemmi::AlignmentResult al = gemmi::align_string_sequences(a.sequence, b.sequence, {},
|
const gemmi::AlignmentResult al = gemmi::align_string_sequences(a.sequence, b.sequence, {},
|
||||||
gemmi::AlignmentScoring::simple());
|
gemmi::AlignmentScoring::simple());
|
||||||
// identity over the longer chain: over the shorter one, a short RNA aligned with gaps into a long one finds most of
|
// identity over the longer chain: over the shorter one, a short RNA aligned with gaps into a long one finds most of
|
||||||
// its four letters matched
|
// its four letters matched
|
||||||
const size_t longer = std::max(a.sequence.size(), b.sequence.size());
|
const size_t longer = std::max(a.sequence.size(), b.sequence.size());
|
||||||
return al.match_count > NCS_MIN_ALIGNED && 100.0 * al.match_count >= NCS_MIN_IDENTITY * double(longer);
|
if (al.match_count <= NCS_MIN_ALIGNED || 100.0 * al.match_count < NCS_MIN_IDENTITY * double(longer))
|
||||||
|
return none;
|
||||||
|
std::vector<std::array<int, 2>> atoms, anchors;
|
||||||
|
std::vector<int> residue_of;
|
||||||
|
Correspond(a, b, atoms, residue_of, anchors);
|
||||||
|
if (anchors.size() < 3)
|
||||||
|
return none;
|
||||||
|
std::vector<gemmi::Position> pa, pb;
|
||||||
|
for (const std::array<int, 2> &p : anchors) {
|
||||||
|
pa.push_back(pos[p[0]]);
|
||||||
|
pb.push_back(pos[p[1]]);
|
||||||
|
}
|
||||||
|
return gemmi::superpose_positions(pa.data(), pb.data(), pa.size(), nullptr).rmsd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pairs (i, j), i < j, of `atoms` closer than `reach` at `pos`, by a grid of cells of that size.
|
// The pairs (i, j), i < j, of `atoms` closer than `reach` at `pos`, by a grid of cells of that size.
|
||||||
@@ -146,7 +166,7 @@ NcsCopies FindNcsCopies(const gemmi::Model &model, const RefinementTopology &top
|
|||||||
for (size_t k = 0; k < chains.size(); k++) {
|
for (size_t k = 0; k < chains.size(); k++) {
|
||||||
bool placed = false;
|
bool placed = false;
|
||||||
for (std::vector<int> &g : groups)
|
for (std::vector<int> &g : groups)
|
||||||
if (AreCopies(chains[g[0]], chains[k])) {
|
if (CopyRmsd(chains[g[0]], chains[k], pos) <= NCS_MAX_RMSD) {
|
||||||
g.push_back(static_cast<int>(k));
|
g.push_back(static_cast<int>(k));
|
||||||
placed = true;
|
placed = true;
|
||||||
break;
|
break;
|
||||||
@@ -154,7 +174,6 @@ NcsCopies FindNcsCopies(const gemmi::Model &model, const RefinementTopology &top
|
|||||||
if (!placed)
|
if (!placed)
|
||||||
groups.push_back({static_cast<int>(k)});
|
groups.push_back({static_cast<int>(k)});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The distances the dictionary fixes: bonded atoms and those bonded to a common atom.
|
// The distances the dictionary fixes: bonded atoms and those bonded to a common atom.
|
||||||
std::set<std::pair<int, int>> fixed;
|
std::set<std::pair<int, int>> fixed;
|
||||||
for (const RestraintRecord &r : topo.geometry.records)
|
for (const RestraintRecord &r : topo.geometry.records)
|
||||||
|
|||||||
@@ -30,13 +30,15 @@ struct NcsCopies {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The copies of `model` (whose entities are set up, as BuildRefinementTopology leaves them): polymer chains whose
|
// The copies of `model` (whose entities are set up, as BuildRefinementTopology leaves them): polymer chains whose
|
||||||
// sequences align with more than 15 residues identical, and identical in 80% of the longer chain's residues, are
|
// sequences align with more than 15 residues identical, identical in 80% of the longer chain's residues, and whose
|
||||||
// copies of one molecule (REFMAC's criterion, taken on the longer chain). Their residues of the same name aligned to
|
// C-alpha (P) atoms are within 2.5 A rms of each other after superposition are copies of one molecule (REFMAC's
|
||||||
// each other correspond, and in them the atoms of the same name - neither a hydrogen nor an alternative conformation,
|
// criterion, the identity taken on the longer chain); the same sequence in a conformation further away - a
|
||||||
// all refinable (topo.refinable). Each copy is paired with the next of its molecule (the last with the first, where
|
// quasi-equivalent subunit of a virus capsid - is another molecule. Their residues of the same name aligned to each
|
||||||
// there are three or more), and each pair restrained on the distances within a chain shorter than NCS_DISTANCE_MAX in
|
// other correspond, and in them the atoms of the same name - neither a hydrogen nor an alternative conformation, all
|
||||||
// either copy, those the dictionary's bonds and angles fix excepted. Atoms are numbered in model order, as
|
// refinable (topo.refinable). Each copy is paired with the next of its molecule (the last with the first, where there
|
||||||
// ModelPositions() lists them.
|
// are three or more), and each pair restrained on the distances within a chain shorter than NCS_DISTANCE_MAX in either
|
||||||
|
// copy, those the dictionary's bonds and angles fix excepted. Atoms are numbered in model order, as ModelPositions()
|
||||||
|
// lists them.
|
||||||
NcsCopies FindNcsCopies(const gemmi::Model &model, const RefinementTopology &topo);
|
NcsCopies FindNcsCopies(const gemmi::Model &model, const RefinementTopology &topo);
|
||||||
|
|
||||||
// The restraints at `sigma` (A).
|
// The restraints at `sigma` (A).
|
||||||
|
|||||||
@@ -997,8 +997,8 @@ TEST_CASE("Refinement_ion_form_factors", "[Refinement]") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two copies of one RNA chain in the asymmetric unit, the second turned and moved, and a fragment of it: the two found
|
// Two copies of one RNA chain in the asymmetric unit, the second turned and moved, a fragment of it and the chain in
|
||||||
// as copies of one molecule, the fragment not, held
|
// another conformation: the two found as copies of one molecule, the fragment and the other conformation not, held
|
||||||
// to each other by local distance restraints whose gradient is right on the CPU and the GPU, and let go where the
|
// to each other by local distance restraints whose gradient is right on the CPU and the GPU, and let go where the
|
||||||
// copies really differ. A single copy has none.
|
// copies really differ. A single copy has none.
|
||||||
TEST_CASE("RefinementNcs_two_copies", "[Refinement]") {
|
TEST_CASE("RefinementNcs_two_copies", "[Refinement]") {
|
||||||
@@ -1029,6 +1029,17 @@ TEST_CASE("RefinementNcs_two_copies", "[Refinement]") {
|
|||||||
for (gemmi::Atom &at : r.atoms)
|
for (gemmi::Atom &at : r.atoms)
|
||||||
at.pos += gemmi::Position(-30, 40, 20);
|
at.pos += gemmi::Position(-30, 40, 20);
|
||||||
st.models[0].chains.push_back(fragment);
|
st.models[0].chains.push_back(fragment);
|
||||||
|
// and the whole molecule in another conformation, each residue moved 3 A its own way: the same sequence, not a copy
|
||||||
|
gemmi::Chain other = st.models[0].chains.at(0);
|
||||||
|
other.name = "D";
|
||||||
|
std::mt19937 shift_rng(11);
|
||||||
|
std::uniform_real_distribution<double> shift(-1.0, 1.0);
|
||||||
|
for (gemmi::Residue &r : other.residues) {
|
||||||
|
const gemmi::Vec3 v = gemmi::Vec3(shift(shift_rng), shift(shift_rng), shift(shift_rng)).normalized() * 3.0;
|
||||||
|
for (gemmi::Atom &at : r.atoms)
|
||||||
|
at.pos += gemmi::Position(v) + gemmi::Position(30, -40, 25);
|
||||||
|
}
|
||||||
|
st.models[0].chains.push_back(other);
|
||||||
st.connections.clear(); // the LINK records name chain A only
|
st.connections.clear(); // the LINK records name chain A only
|
||||||
const RefinementTopology topo = BuildRefinementTopology(st);
|
const RefinementTopology topo = BuildRefinementTopology(st);
|
||||||
const NcsCopies ncs = FindNcsCopies(st.models[0], topo);
|
const NcsCopies ncs = FindNcsCopies(st.models[0], topo);
|
||||||
|
|||||||
Reference in new issue
Block a user