Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 11m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m54s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 11m33s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m19s
Build Packages / build:rpm (rocky8) (push) Successful in 12m23s
Build Packages / build:rpm (rocky9) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m55s
Build Packages / DIALS test (push) Successful in 13m42s
Build Packages / XDS test (durin plugin) (push) Successful in 9m26s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m41s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m12s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 52s
Build Packages / Create release (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 5m29s
Build Packages / build:viewer-tgz:cuda (push) Successful in 6m12s
Build Packages / build:windows:cuda (push) Successful in 18m36s
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use. * rugnux: Add `--model model.pdb` - score the merged data against an atomic model and compute initial maps. It reports R-work/R-free (scaling the model to the observed amplitudes with an overall scale, an anisotropic B and a flat bulk solvent - the standard few-parameter model, so a batch of maps stays directly comparable) and writes 2Fo-Fc / Fo-Fc electron-density maps (CCP4) plus a map-coefficient MTZ. The structure itself is not refined; the model is only re-fractionalised into the data cell. * rugnux: The merged reflection output now carries French-Wilson amplitudes (|F| and its sigma) next to the intensities - MTZ `F`/`SIGF`, mmCIF `_refln.F_meas_au`, and the text HKL - computed with the correct centric/acentric Wilson prior and epsilon multiplicity, so a downstream program (e.g. phenix.refine) can refine against amplitudes. The intensity columns are unchanged. * rugnux: R-free test-set flags are now assigned deterministically and consistently across symmetry - a Bijvoet pair I(+)/I(-) is never split between the work and free sets, and the assignment is a reproducible per-hkl hash that depends only on the reflection index, so every dataset of one crystal form gets the same ~5% free set (what a multi-dataset campaign such as PanDDA needs). On small data the fraction is floored so the test set stays large enough for a stable R-free (~500 reflections, capped at 10%); it stays flat at 5% on ordinary data. When a reference MTZ carries a `FreeR_flag` column its test set is imported instead, letting a whole campaign inherit one shared free set. * rugnux: A reference MTZ (`--reference-mtz`) can now fix the space group and cell for rotation data too (previously rejected), without being used to scale - the rotation merge stays self-consistent. When the crystal has an indexing (merohedral) ambiguity - a lattice symmetry higher than its Laue symmetry, e.g. P3/P4/P6/C2 - the reference also resolves it: each candidate reindexing (identity plus the twin-law cosets of the metric symmetry) is scored by its intensity correlation against the reference and the data are re-merged in the best-correlating one. This is a metric-preserving relabelling of hkl (the cell is unchanged) and a no-op for a holohedral crystal such as lysozyme. * rugnux: `--model` validation now aligns the data to the model before scoring - the observed reflections are reindexed into the model's enantiomorph when the two differ only by hand (indistinguishable from merged intensities). A merohedral indexing ambiguity is resolved against the reference MTZ when one is given (so a whole campaign shares one indexing convention); only with a model and no reference does validation fall back to fitting each candidate reindexing and keeping the lowest R-free. * rugnux: De-novo symmetry - recover a genuine high-symmetry group whose data are imperfectly scaled. Such a merge's within-orbit chi² lands just past the self-consistency bound (each real symmetry step adds a little systematic scatter), right where a merohedral twin also lands, so the chi² ratio alone cannot separate them. The candidate is now rescued when the extra intensity-proportional systematic error it invokes stays small relative to the confirmed subgroup - a genuine symmetry step gains multiplicity without inflating the merge error model's b, whereas a twin forces non-equivalent reflections together and b balloons. Fixes cubic insulin (I23 instead of I222) with no change to any other crystal in the test battery, including the twins that must stay in their lower symmetry. * Docs: Document the French-Wilson amplitude estimation, R-free flagging, reference-based space-group/ambiguity resolution, and model-based validation/maps in CPU_DATA_ANALYSIS.md. * Frontend: The status-bar pill now shows a progress bar during detector calibration (previously only during measurement), and the calibration state and its button are labelled "Calibration"/"CALIBRATE" (the internal `Pedestal` state name is unchanged for back-compatibility).Reviewed-on: #69 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
1108 lines
37 KiB
C++
1108 lines
37 KiB
C++
// Copyright 2017 Global Phasing Ltd.
|
|
//
|
|
// Data structures to store macromolecular structure models.
|
|
|
|
#ifndef GEMMI_MODEL_HPP_
|
|
#define GEMMI_MODEL_HPP_
|
|
|
|
#include <algorithm> // for find_if, count_if, lower_bound
|
|
#include <array>
|
|
#include <bitset>
|
|
#include <iterator> // for back_inserter
|
|
#include <map> // for map
|
|
#include <stdexcept> // for out_of_range
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "elem.hpp"
|
|
#include "fail.hpp" // for fail
|
|
#include "unitcell.hpp"
|
|
#include "symmetry.hpp"
|
|
#include "metadata.hpp"
|
|
#include "iterator.hpp"
|
|
#include "span.hpp" // for Span, MutableVectorSpan
|
|
#include "seqid.hpp"
|
|
#include "util.hpp" // for join_str, vector_move_extend, in_vector
|
|
|
|
namespace gemmi {
|
|
|
|
namespace impl {
|
|
|
|
template<typename T>
|
|
auto get_id(const T& m) -> decltype(m.name) { return m.name; }
|
|
template<typename T>
|
|
auto get_id(const T& m) -> decltype(m.num) { return m.num; }
|
|
|
|
|
|
template<typename Vec, typename S>
|
|
auto find_iter_(Vec& vec, const S& name) {
|
|
return std::find_if(vec.begin(), vec.end(), [&name](const auto& m) { return get_id(m) == name; });
|
|
}
|
|
|
|
template<typename T, typename S>
|
|
T* find_or_null(std::vector<T>& vec, const S& name) {
|
|
auto it = find_iter_(vec, name);
|
|
return it != vec.end() ? &*it : nullptr;
|
|
}
|
|
|
|
template<typename T, typename S>
|
|
T& find_or_add(std::vector<T>& vec, const S& name) {
|
|
if (T* ret = find_or_null(vec, name))
|
|
return *ret;
|
|
vec.emplace_back(name);
|
|
return vec.back();
|
|
}
|
|
|
|
template<typename Span, typename S>
|
|
typename Span::iterator find_iter(Span& span, const S& name) {
|
|
using T = typename Span::value_type;
|
|
auto it = find_iter_(span, name);
|
|
if (it == span.end())
|
|
throw std::invalid_argument(cat(
|
|
T::what(), ' ', name, " not found (only [",
|
|
join_str(span.begin(), span.end(), ' ', [](const T& item) { return cat(get_id(item)); }),
|
|
"])"));
|
|
return it;
|
|
}
|
|
|
|
template<typename Group>
|
|
typename Group::element_type& get_by_altloc(Group& group, char alt) {
|
|
for (auto& atom : group)
|
|
if (atom.altloc == alt)
|
|
return atom;
|
|
fail("No such altloc");
|
|
}
|
|
|
|
template<typename T, typename M> std::vector<T> model_subchains(M* model) {
|
|
std::vector<T> v;
|
|
for (auto& chain : model->chains)
|
|
vector_move_extend(v, chain.subchains());
|
|
return v;
|
|
}
|
|
} // namespace impl
|
|
|
|
|
|
/// File format of a macromolecular model. When passed to read_structure():
|
|
/// Unknown = guess format from the extension,
|
|
/// Detect = guess format from the content.
|
|
enum class CoorFormat : unsigned char {
|
|
Unknown, Detect, Pdb, Mmcif, Mmjson, ChemComp
|
|
};
|
|
|
|
/// corresponds to _atom_site.calc_flag in mmCIF
|
|
enum class CalcFlag : signed char {
|
|
// NoHydrogen is the same as NotSet; it's used internally to mark atoms
|
|
// which should not have riding hydrogens.
|
|
// NB: add_cif_atoms() relies on this order.
|
|
NotSet=0, NoHydrogen, Determined, Calculated, Dummy
|
|
};
|
|
|
|
/// options affecting how pdb file is read
|
|
struct PdbReadOptions {
|
|
int max_line_length = 0;
|
|
bool check_non_ascii = false;
|
|
bool ignore_ter = false; // ignores TER records completely
|
|
bool split_chain_on_ter = false;
|
|
bool skip_remarks = false;
|
|
};
|
|
// end of PdbReadOptions for mol.rst
|
|
|
|
// remove empty residues from chain, empty chains from model, etc
|
|
template<class T> void remove_empty_children(T& obj) {
|
|
using Item = typename T::child_type;
|
|
vector_remove_if(obj.children(), [](const Item& x) { return x.children().empty(); });
|
|
}
|
|
|
|
inline bool is_same_conformer(char altloc1, char altloc2) {
|
|
return altloc1 == '\0' || altloc2 == '\0' || altloc1 == altloc2;
|
|
}
|
|
|
|
/// Represents atom site in macromolecular structure (~100 bytes).
|
|
struct Atom {
|
|
static const char* what() { return "Atom"; }
|
|
std::string name;
|
|
char altloc = '\0'; // 0 if not set
|
|
signed char charge = 0; // [-8, +8]
|
|
Element element = El::X;
|
|
CalcFlag calc_flag = CalcFlag::NotSet; // mmCIF _atom_site.calc_flag
|
|
char flag = '\0'; // a custom flag
|
|
short tls_group_id = -1;
|
|
int serial = 0;
|
|
float fraction = 0.f; // custom value, one use is Refmac's ccp4_deuterium_fraction
|
|
Position pos;
|
|
float occ = 1.0f;
|
|
// ADP - in MX it's usual to give isotropic ADP as B and anisotropic as U
|
|
float b_iso = 20.0f; // arbitrary default value
|
|
SMat33<float> aniso = {0, 0, 0, 0, 0, 0};
|
|
|
|
char altloc_or(char null_char) const { return altloc ? altloc : null_char; }
|
|
bool same_conformer(const Atom& other) const {
|
|
return is_same_conformer(altloc, other.altloc);
|
|
}
|
|
bool altloc_matches(char request) const {
|
|
return request == '*' || altloc == '\0' || altloc == request;
|
|
}
|
|
// group_key() is used in UniqIter and similar tools
|
|
const std::string& group_key() const { return name; }
|
|
bool has_altloc() const { return altloc != '\0'; }
|
|
double b_eq() const { return u_to_b() / 3. * aniso.trace(); }
|
|
bool is_hydrogen() const { return gemmi::is_hydrogen(element); }
|
|
|
|
// Name as a string left-padded like in the PDB format:
|
|
// the first two characters make the element name.
|
|
std::string padded_name() const {
|
|
std::string s;
|
|
const char* el = element.uname();
|
|
if (el[1] == '\0' &&
|
|
(el[0] == alpha_up(name[0]) || (is_hydrogen() && alpha_up(name[0]) == 'H')) &&
|
|
name.size() < 4)
|
|
s += ' ';
|
|
s += name;
|
|
return s;
|
|
}
|
|
|
|
// a method present in Atom, Residue, ... Structure - used in templates
|
|
Atom empty_copy() const { return Atom(*this); }
|
|
};
|
|
|
|
template<typename AtomType>
|
|
struct AtomGroup_ : ItemGroup<AtomType> {
|
|
using ItemGroup<AtomType>::ItemGroup;
|
|
std::string name() const { return !this->empty() ? this->front().name : ""; }
|
|
AtomType& by_altloc(char alt) {
|
|
for (int i = 0; i != this->extent(); ++i) {
|
|
AtomType* a = &this->front() + i;
|
|
if (a->altloc == alt && (a->name == this->front().name))
|
|
return *a;
|
|
}
|
|
fail("No such altloc");
|
|
}
|
|
};
|
|
|
|
using AtomGroup = AtomGroup_<Atom>;
|
|
using ConstAtomGroup = AtomGroup_<const Atom>;
|
|
|
|
struct Residue : public ResidueId {
|
|
using OptionalNum = SeqId::OptionalNum;
|
|
static const char* what() { return "Residue"; }
|
|
|
|
std::string subchain; // mmCIF _atom_site.label_asym_id
|
|
std::string entity_id; // mmCIF _atom_site.label_entity_id
|
|
OptionalNum label_seq; // mmCIF _atom_site.label_seq_id
|
|
EntityType entity_type = EntityType::Unknown;
|
|
char het_flag = '\0'; // 'A' = ATOM, 'H' = HETATM, 0 = unspecified
|
|
char flag = '\0'; // custom flag
|
|
SiftsUnpResidue sifts_unp; // UniProt reference from SIFTS
|
|
short group_idx = 0; // ignore - internal variable
|
|
std::vector<Atom> atoms;
|
|
|
|
Residue() = default;
|
|
explicit Residue(const ResidueId& rid) noexcept : ResidueId(rid) {}
|
|
|
|
// copy all but atoms (children) - for use in templates
|
|
Residue empty_copy() const {
|
|
Residue res((ResidueId&)*this);
|
|
res.subchain = subchain;
|
|
res.entity_id = entity_id;
|
|
res.label_seq = label_seq;
|
|
res.entity_type = entity_type;
|
|
res.het_flag = het_flag;
|
|
res.flag = flag;
|
|
res.sifts_unp = sifts_unp;
|
|
return res;
|
|
}
|
|
using child_type = Atom;
|
|
std::vector<Atom>& children() { return atoms; }
|
|
const std::vector<Atom>& children() const { return atoms; }
|
|
|
|
const Atom* find_by_element(El el) const {
|
|
for (const Atom& a : atoms)
|
|
if (a.element == el)
|
|
return &a;
|
|
return nullptr;
|
|
}
|
|
|
|
/// El::X means anything;
|
|
/// in strict_altloc mode, '*' = any altloc, otherwise it's \0
|
|
Atom* find_atom(const std::string& atom_name, char altloc, El el=El::X,
|
|
bool strict_altloc=true) {
|
|
if (!strict_altloc && altloc == '\0')
|
|
altloc = '*';
|
|
for (Atom& a : atoms)
|
|
if (a.name == atom_name && a.altloc_matches(altloc) && (el == El::X || a.element == el))
|
|
return &a;
|
|
return nullptr;
|
|
}
|
|
const Atom* find_atom(const std::string& atom_name, char altloc, El el=El::X,
|
|
bool strict_altloc=true) const {
|
|
return const_cast<Residue*>(this)->find_atom(atom_name, altloc, el, strict_altloc);
|
|
}
|
|
|
|
std::vector<Atom>::iterator find_atom_iter(const std::string& atom_name,
|
|
char altloc, El el=El::X) {
|
|
if (Atom* a = find_atom(atom_name, altloc, el))
|
|
return atoms.begin() + (a - atoms.data());
|
|
fail("Atom not found.");
|
|
}
|
|
|
|
AtomGroup get(const std::string& atom_name) {
|
|
for (Atom& atom : atoms)
|
|
if (atom.name == atom_name)
|
|
return AtomGroup(&atom, atoms.data() + atoms.size());
|
|
fail("No such atom: " + atom_name);
|
|
}
|
|
|
|
Atom& sole_atom(const std::string& atom_name) {
|
|
AtomGroup aa = get(atom_name);
|
|
if (aa.size() != 1)
|
|
fail("Multiple alternative atoms " + atom_name);
|
|
return aa.front();
|
|
}
|
|
|
|
// short-cuts to access peptide backbone atoms
|
|
const Atom* get_ca() const { return find_atom("CA", '*', El::C); }
|
|
const Atom* get_c() const { return find_atom("C", '*', El::C); }
|
|
const Atom* get_n() const { return find_atom("N", '*', El::N); }
|
|
const Atom* get_o() const { return find_atom("O", '*', El::O); }
|
|
// short-cuts to access nucleic acid atoms
|
|
const Atom* get_p() const { return find_atom("P", '*', El::P); }
|
|
const Atom* get_o3prim() const { return find_atom("O3'", '*', El::O); }
|
|
|
|
bool same_conformer(const Residue& other) const {
|
|
return atoms.empty() || other.atoms.empty() ||
|
|
atoms[0].same_conformer(other.atoms[0]) ||
|
|
other.find_atom(other.atoms[0].name, atoms[0].altloc) != nullptr;
|
|
}
|
|
|
|
/// Convenience function that duplicates functionality from resinfo.hpp.
|
|
/// Returns true for HOH and DOD (and old alternative names of HOH),
|
|
/// but not for OH and H3O/D3O.
|
|
bool is_water() const {
|
|
if (name.length() != 3)
|
|
return false;
|
|
int id = ialpha4_id(name.c_str());
|
|
return id == ialpha4_id("HOH") || id == ialpha4_id("DOD") ||
|
|
id == ialpha4_id("WAT") || id == ialpha4_id("H2O");
|
|
}
|
|
|
|
// Iterators that in case of multiple conformations (alt. locations)
|
|
// skip all but the first conformation.
|
|
UniqProxy<Atom> first_conformer() { return {atoms}; }
|
|
ConstUniqProxy<Atom> first_conformer() const { return {atoms}; }
|
|
};
|
|
|
|
inline void add_distinct_altlocs(const Residue& res, std::string& altlocs) {
|
|
for (const Atom& atom : res.atoms)
|
|
if (atom.altloc && altlocs.find(atom.altloc) == std::string::npos)
|
|
altlocs += atom.altloc;
|
|
}
|
|
|
|
struct ResidueGroup;
|
|
struct ConstResidueGroup;
|
|
|
|
struct ConstResidueSpan : Span<const Residue> {
|
|
using Parent = Span<const Residue>;
|
|
using Parent::Span;
|
|
ConstResidueSpan(Parent&& span) : Parent(std::move(span)) {}
|
|
|
|
int length() const {
|
|
int length = (int) size();
|
|
for (int n = length - 1; n > 0; --n)
|
|
if ((begin() + n)->group_key() == (begin() + n - 1)->group_key())
|
|
--length;
|
|
return length;
|
|
}
|
|
|
|
// sign=-1 for min, sign=1 for max
|
|
SeqId::OptionalNum extreme_num(bool label, int sign) const {
|
|
SeqId::OptionalNum result;
|
|
for (const Residue& r : *this) {
|
|
if (auto num = label ? r.label_seq : r.seqid.num)
|
|
if (!result || sign * int(num) > sign * int(result))
|
|
result = num;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ConstUniqProxy<Residue, ConstResidueSpan> first_conformer() const {
|
|
return {*this};
|
|
}
|
|
|
|
const std::string& subchain_id() const {
|
|
if (this->empty())
|
|
throw std::out_of_range("subchain_id(): empty span");
|
|
if (this->size() > 1 && this->front().subchain != this->back().subchain)
|
|
fail("subchain id varies in a residue span: ", this->front().subchain,
|
|
" vs ", this->back().subchain);
|
|
return this->begin()->subchain;
|
|
}
|
|
|
|
ConstResidueGroup find_residue_group(SeqId id) const;
|
|
|
|
std::vector<std::string> extract_sequence() const {
|
|
std::vector<std::string> seq;
|
|
for (const Residue& res : first_conformer())
|
|
seq.push_back(res.name);
|
|
return seq;
|
|
}
|
|
|
|
// We assume residues are ordered. It works (approximately) also with
|
|
// missing numbers which can be present in DBREF.
|
|
SeqId label_seq_id_to_auth(SeqId::OptionalNum label_seq_id) const {
|
|
if (empty())
|
|
throw std::out_of_range("label_seq_id_to_auth(): empty span");
|
|
const auto* it = std::lower_bound(begin(), end(), label_seq_id,
|
|
[](const Residue& r, SeqId::OptionalNum v){ return r.label_seq < v; });
|
|
if (it == end())
|
|
--it;
|
|
else if (it->label_seq == label_seq_id)
|
|
return it->seqid;
|
|
else if (it != begin() &&
|
|
label_seq_id - (it-1)->label_seq < it->label_seq - label_seq_id)
|
|
--it;
|
|
return {it->seqid.num + (label_seq_id - it->label_seq), ' '};
|
|
}
|
|
// The residue numbers (auth) are sometimes not ordered.
|
|
// That is why we use this multi-step heuristic.
|
|
SeqId::OptionalNum auth_seq_id_to_label(SeqId auth_seq_id) const {
|
|
if (empty())
|
|
throw std::out_of_range("auth_seq_id_to_label(): empty span");
|
|
for (const Residue& r : *this)
|
|
if (r.seqid == auth_seq_id)
|
|
return r.label_seq;
|
|
const_iterator it;
|
|
if (auth_seq_id.num < front().seqid.num) {
|
|
it = begin();
|
|
} else if (back().seqid.num < auth_seq_id.num) {
|
|
it = end() - 1;
|
|
} else {
|
|
it = std::lower_bound(begin(), end(), auth_seq_id.num,
|
|
[](const Residue& r, SeqId::OptionalNum v){ return r.seqid.num < v; });
|
|
while (it != end() && it->seqid.num == auth_seq_id.num &&
|
|
it->seqid.icode != auth_seq_id.icode)
|
|
++it;
|
|
if (it == end())
|
|
--it;
|
|
}
|
|
return it->label_seq + (auth_seq_id.num - it->seqid.num);
|
|
}
|
|
};
|
|
|
|
// ResidueSpan represents consecutive residues within the same chain.
|
|
// It's used as return value of get_polymer(), get_ligands(), get_waters()
|
|
// and get_subchain().
|
|
struct ResidueSpan : MutableVectorSpan<Residue> {
|
|
using Parent = MutableVectorSpan<Residue>;
|
|
struct GroupingProxy;
|
|
ResidueSpan() = default;
|
|
ResidueSpan(Parent&& span) : Parent(std::move(span)) {}
|
|
ResidueSpan(vector_type& v, iterator begin, std::size_t n)
|
|
: Parent(v, begin, n) {}
|
|
int length() const { return const_().length(); }
|
|
SeqId::OptionalNum extreme_num(bool label, int sign) const {
|
|
return const_().extreme_num(label, sign);
|
|
}
|
|
UniqProxy<Residue, ResidueSpan> first_conformer() { return {*this}; }
|
|
ConstUniqProxy<Residue, ResidueSpan> first_conformer() const { return {*this}; }
|
|
GroupingProxy residue_groups();
|
|
const std::string& subchain_id() const { return const_().subchain_id(); }
|
|
ResidueGroup find_residue_group(SeqId id);
|
|
std::vector<std::string> extract_sequence() const { return const_().extract_sequence(); }
|
|
ConstResidueGroup find_residue_group(SeqId id) const;
|
|
SeqId label_seq_id_to_auth(SeqId::OptionalNum label_seq_id) const {
|
|
return const_().label_seq_id_to_auth(label_seq_id);
|
|
}
|
|
SeqId::OptionalNum auth_seq_id_to_label(SeqId auth_seq_id) const {
|
|
return const_().auth_seq_id_to_label(auth_seq_id);
|
|
}
|
|
private:
|
|
ConstResidueSpan const_() const { return ConstResidueSpan(begin(), size()); }
|
|
};
|
|
|
|
// ResidueGroup represents residues with the same sequence number and insertion
|
|
// code, but different residue names. I.e. microheterogeneity.
|
|
// Usually, there is only one residue in the group.
|
|
// The residues must be consecutive.
|
|
struct ResidueGroup : ResidueSpan {
|
|
ResidueGroup() = default;
|
|
ResidueGroup(ResidueSpan&& span) : ResidueSpan(std::move(span)) {}
|
|
Residue& by_resname(const std::string& name) {
|
|
return *impl::find_iter(*this, name);
|
|
}
|
|
void remove_residue(const std::string& name) {
|
|
erase(impl::find_iter(*this, name));
|
|
}
|
|
};
|
|
|
|
struct ConstResidueGroup : ConstResidueSpan {
|
|
ConstResidueGroup() = default;
|
|
ConstResidueGroup(ConstResidueSpan&& sp) : ConstResidueSpan(std::move(sp)) {}
|
|
const Residue& by_resname(const std::string& name) {
|
|
return *impl::find_iter(*this, name);
|
|
}
|
|
};
|
|
|
|
inline ResidueGroup ResidueSpan::find_residue_group(SeqId id) {
|
|
return ResidueSpan(subspan([&](const Residue& r) { return r.seqid == id; }));
|
|
}
|
|
inline ConstResidueGroup ResidueSpan::find_residue_group(SeqId id) const {
|
|
return const_().find_residue_group(id);
|
|
}
|
|
inline ConstResidueGroup ConstResidueSpan::find_residue_group(SeqId id) const {
|
|
return ConstResidueSpan(subspan([&](const Residue& r) { return r.seqid == id; }));
|
|
}
|
|
|
|
struct ResidueSpan::GroupingProxy {
|
|
ResidueSpan& span;
|
|
using iterator = GroupingIter<ResidueSpan, ResidueGroup>;
|
|
iterator begin() {
|
|
return ++iterator{ResidueGroup(span.sub(span.begin(), span.begin()))};
|
|
}
|
|
iterator end() {
|
|
return iterator{ResidueGroup(span.sub(span.end(), span.end()))};
|
|
}
|
|
};
|
|
|
|
inline ResidueSpan::GroupingProxy ResidueSpan::residue_groups() {
|
|
return {*this};
|
|
}
|
|
|
|
|
|
namespace impl {
|
|
template<typename T, typename Ch> std::vector<T> chain_subchains(Ch* ch) {
|
|
std::vector<T> v;
|
|
for (auto start = ch->residues.begin(); start != ch->residues.end(); ) {
|
|
auto end = start + 1;
|
|
while (end != ch->residues.end() && end->subchain == start->subchain)
|
|
++end;
|
|
v.push_back(ch->whole().sub(start, end));
|
|
start = end;
|
|
}
|
|
return v;
|
|
}
|
|
} // namespace impl
|
|
|
|
struct Chain {
|
|
static const char* what() { return "Chain"; }
|
|
std::string name;
|
|
std::vector<Residue> residues;
|
|
|
|
Chain() = default;
|
|
explicit Chain(const std::string& name_) noexcept : name(name_) {}
|
|
|
|
ResidueSpan whole() {
|
|
Residue* begin = residues.empty() ? nullptr : &residues[0];
|
|
return ResidueSpan(residues, begin, residues.size());
|
|
}
|
|
ConstResidueSpan whole() const {
|
|
const Residue* begin = residues.empty() ? nullptr : &residues[0];
|
|
return ConstResidueSpan(begin, residues.size());
|
|
}
|
|
|
|
template<typename F> ResidueSpan get_residue_span(F&& func) {
|
|
return whole().subspan(func);
|
|
}
|
|
template<typename F> ConstResidueSpan get_residue_span(F&& func) const {
|
|
return whole().subspan(func);
|
|
}
|
|
|
|
ResidueSpan get_polymer() {
|
|
auto begin = residues.begin();
|
|
while (begin != residues.end() && begin->entity_type != EntityType::Polymer)
|
|
++begin;
|
|
auto end = begin;
|
|
while (end != residues.end() && end->entity_type == EntityType::Polymer
|
|
&& end->subchain == begin->subchain)
|
|
++end;
|
|
return ResidueSpan(residues, &*begin, end - begin);
|
|
}
|
|
ConstResidueSpan get_polymer() const {
|
|
return const_cast<Chain*>(this)->get_polymer();
|
|
}
|
|
|
|
ResidueSpan get_ligands() {
|
|
return get_residue_span([](const Residue& r) {
|
|
return r.entity_type == EntityType::NonPolymer ||
|
|
r.entity_type == EntityType::Branched;
|
|
});
|
|
}
|
|
ConstResidueSpan get_ligands() const {
|
|
return const_cast<Chain*>(this)->get_ligands();
|
|
}
|
|
|
|
ResidueSpan get_waters() {
|
|
return get_residue_span([](const Residue& r) {
|
|
return r.entity_type == EntityType::Water;
|
|
});
|
|
}
|
|
ConstResidueSpan get_waters() const {
|
|
return const_cast<Chain*>(this)->get_waters();
|
|
}
|
|
|
|
ResidueSpan get_subchain(const std::string& s) {
|
|
return get_residue_span([&](const Residue& r) { return r.subchain == s; });
|
|
}
|
|
ConstResidueSpan get_subchain(const std::string& s) const {
|
|
return const_cast<Chain*>(this)->get_subchain(s);
|
|
}
|
|
|
|
std::vector<ResidueSpan> subchains() {
|
|
return impl::chain_subchains<ResidueSpan>(this);
|
|
}
|
|
std::vector<ConstResidueSpan> subchains() const {
|
|
return impl::chain_subchains<ConstResidueSpan>(this);
|
|
}
|
|
|
|
ResidueGroup find_residue_group(SeqId id) {
|
|
return whole().find_residue_group(id);
|
|
}
|
|
ConstResidueGroup find_residue_group(SeqId id) const {
|
|
return whole().find_residue_group(id);
|
|
}
|
|
|
|
Residue* find_residue(const ResidueId& rid);
|
|
const Residue* find_residue(const ResidueId& rid) const {
|
|
return const_cast<Chain*>(this)->find_residue(rid);
|
|
}
|
|
|
|
Residue* find_or_add_residue(const ResidueId& rid);
|
|
void append_residues(std::vector<Residue> new_resi, int min_sep=0);
|
|
|
|
// methods present in Structure, Model, ... - used in templates
|
|
Chain empty_copy() const { return Chain(name); }
|
|
using child_type = Residue;
|
|
std::vector<Residue>& children() { return residues; }
|
|
const std::vector<Residue>& children() const { return residues; }
|
|
|
|
// Returns false only for alternative conformation (microheterogeneity).
|
|
bool is_first_in_group(const Residue& res) const {
|
|
return &res == residues.data() || (&res - 1)->group_key() != res.group_key();
|
|
}
|
|
|
|
// Returns the previous residue or nullptr.
|
|
// Got complicated by handling of multi-conformations / microheterogeneity.
|
|
const Residue* previous_residue(const Residue& res) const {
|
|
const Residue* start = residues.data();
|
|
for (const Residue* p = &res; p-- != start; )
|
|
if (res.group_key() != p->group_key()) {
|
|
while (p != start && p->group_key() == (p-1)->group_key() &&
|
|
(res.atoms.at(0).altloc == '\0' || !res.same_conformer(*p)))
|
|
--p;
|
|
return p;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Returns the next residue or nullptr.
|
|
const Residue* next_residue(const Residue& res) const {
|
|
const Residue* end = residues.data() + residues.size();
|
|
for (const Residue* p = &res + 1; p != end; ++p)
|
|
if (res.group_key() != p->group_key()) {
|
|
while (p+1 != end && p->group_key() == (p+1)->group_key() &&
|
|
!res.same_conformer(*p))
|
|
++p;
|
|
return p;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Iterators that in case of multiple conformations (microheterogeneity)
|
|
// skip all but the first conformation.
|
|
UniqProxy<Residue> first_conformer() { return {residues}; }
|
|
ConstUniqProxy<Residue> first_conformer() const { return {residues}; }
|
|
};
|
|
|
|
inline std::string atom_str(const Chain& chain,
|
|
const ResidueId& res_id,
|
|
const Atom& atom,
|
|
bool as_cid=false) {
|
|
return atom_str(chain.name, res_id, atom.name, atom.altloc, as_cid);
|
|
}
|
|
|
|
struct const_CRA {
|
|
const Chain* chain;
|
|
const Residue* residue;
|
|
const Atom* atom;
|
|
};
|
|
|
|
struct CRA {
|
|
Chain* chain;
|
|
Residue* residue;
|
|
Atom* atom;
|
|
operator const_CRA() const { return const_CRA{chain, residue, atom}; }
|
|
};
|
|
|
|
inline std::string atom_str(const const_CRA& cra, bool as_cif=false) {
|
|
static const ResidueId null_residue_id = {};
|
|
return atom_str(cra.chain ? cra.chain->name : "null",
|
|
cra.residue ? *cra.residue : null_residue_id,
|
|
cra.atom ? cra.atom->name : "null",
|
|
cra.atom ? cra.atom->altloc : '\0',
|
|
as_cif);
|
|
}
|
|
|
|
inline bool atom_matches(const const_CRA& cra, const AtomAddress& addr, bool ignore_segment=false) {
|
|
return cra.chain && cra.chain->name == addr.chain_name &&
|
|
cra.residue && cra.residue->matches_noseg(addr.res_id) &&
|
|
(ignore_segment || cra.residue->segment == addr.res_id.segment) &&
|
|
cra.atom && cra.atom->name == addr.atom_name &&
|
|
cra.atom->altloc == addr.altloc;
|
|
}
|
|
|
|
inline AtomAddress make_address(const Chain& ch, const Residue& res, const Atom& at) {
|
|
return AtomAddress(ch.name, res, at.name, at.altloc);
|
|
}
|
|
|
|
|
|
template<typename CraT>
|
|
class CraIterPolicy {
|
|
public:
|
|
using value_type = CraT;
|
|
using reference = const CraT;
|
|
CraIterPolicy() : chains_end(nullptr), cra{nullptr, nullptr, nullptr} {}
|
|
CraIterPolicy(const Chain* end, CraT cra_) : chains_end(end), cra(cra_) {}
|
|
void increment() {
|
|
if (cra.atom == nullptr)
|
|
return;
|
|
if (++cra.atom == vector_end_ptr(cra.residue->atoms)) {
|
|
do {
|
|
if (++cra.residue == vector_end_ptr(cra.chain->residues)) {
|
|
do {
|
|
if (++cra.chain == chains_end) {
|
|
cra.atom = nullptr;
|
|
return;
|
|
}
|
|
} while (cra.chain->residues.empty());
|
|
cra.residue = &cra.chain->residues[0];
|
|
}
|
|
} while (cra.residue->atoms.empty());
|
|
cra.atom = &cra.residue->atoms[0];
|
|
}
|
|
}
|
|
void decrement() {
|
|
while (cra.atom == nullptr || cra.atom == cra.residue->atoms.data()) {
|
|
while (cra.residue == nullptr || cra.residue == cra.chain->residues.data()) {
|
|
// iterating backward beyond begin() will have undefined effects
|
|
while ((--cra.chain)->residues.empty()) {}
|
|
cra.residue = vector_end_ptr(cra.chain->residues);
|
|
}
|
|
--cra.residue;
|
|
cra.atom = vector_end_ptr(cra.residue->atoms);
|
|
}
|
|
--cra.atom;
|
|
}
|
|
bool equal(const CraIterPolicy& o) const { return cra.atom == o.cra.atom; }
|
|
CraT dereference() { return cra; } // make copy b/c increment() modifies cra
|
|
using const_policy = CraIterPolicy<const_CRA>;
|
|
operator const_policy() const { return const_policy(chains_end, cra); }
|
|
private:
|
|
const Chain* chains_end;
|
|
CraT cra;
|
|
};
|
|
|
|
template<typename CraT, typename ChainsRefT>
|
|
struct CraProxy_ {
|
|
ChainsRefT chains;
|
|
using iterator = BidirIterator<CraIterPolicy<CraT>>;
|
|
iterator begin() {
|
|
for (auto& chain : chains)
|
|
for (auto& residue : chain.residues)
|
|
for (auto& atom : residue.atoms)
|
|
return CraIterPolicy<CraT>{vector_end_ptr(chains), CraT{&chain, &residue, &atom}};
|
|
return {};
|
|
}
|
|
iterator end() {
|
|
auto* chains_end = vector_end_ptr(chains);
|
|
return CraIterPolicy<CraT>{chains_end, CraT{chains_end, nullptr, nullptr}};
|
|
}
|
|
};
|
|
|
|
using CraProxy = CraProxy_<CRA, std::vector<Chain>&>;
|
|
using ConstCraProxy = CraProxy_<const_CRA, const std::vector<Chain>&>;
|
|
|
|
struct Model {
|
|
static const char* what() { return "Model"; }
|
|
int num = 0;
|
|
std::vector<Chain> chains;
|
|
|
|
Model() = default;
|
|
explicit Model(int num_) noexcept : num(num_) {}
|
|
|
|
// Returns the first chain with given name, or nullptr.
|
|
Chain* find_chain(const std::string& chain_name) {
|
|
return impl::find_or_null(chains, chain_name);
|
|
}
|
|
const Chain* find_chain(const std::string& chain_name) const {
|
|
return const_cast<Model*>(this)->find_chain(chain_name);
|
|
}
|
|
|
|
// Returns the last chain with given name, or nullptr.
|
|
Chain* find_last_chain(const std::string& chain_name) {
|
|
auto it = std::find_if(chains.rbegin(), chains.rend(),
|
|
[&](const Chain& c) { return c.name == chain_name; });
|
|
return it != chains.rend() ? &*it : nullptr;
|
|
}
|
|
|
|
|
|
void remove_chain(const std::string& chain_name) {
|
|
vector_remove_if(chains,
|
|
[&](const Chain& c) { return c.name == chain_name; });
|
|
}
|
|
|
|
void merge_chain_parts(int min_sep=0) {
|
|
for (auto i = chains.begin(); i != chains.end(); ++i)
|
|
for (auto j = i + 1; j != chains.end(); ++j)
|
|
if (i->name == j->name) {
|
|
i->append_residues(j->residues, min_sep);
|
|
chains.erase(j--);
|
|
}
|
|
}
|
|
|
|
ResidueSpan get_subchain(const std::string& sub_name) {
|
|
for (Chain& chain : chains)
|
|
if (ResidueSpan sub = chain.get_subchain(sub_name))
|
|
return sub;
|
|
return ResidueSpan();
|
|
}
|
|
ConstResidueSpan get_subchain(const std::string& sub_name) const {
|
|
return const_cast<Model*>(this)->get_subchain(sub_name);
|
|
}
|
|
|
|
std::vector<ResidueSpan> subchains() {
|
|
return impl::model_subchains<ResidueSpan>(this);
|
|
}
|
|
std::vector<ConstResidueSpan> subchains() const {
|
|
return impl::model_subchains<ConstResidueSpan>(this);
|
|
}
|
|
|
|
std::map<std::string, std::string> subchain_to_chain() const {
|
|
std::map<std::string, std::string> mapping;
|
|
for (const Chain& chain : chains) {
|
|
std::string prev;
|
|
for (const Residue& res : chain.residues)
|
|
if (!res.subchain.empty() && res.subchain != prev) {
|
|
prev = res.subchain;
|
|
mapping[res.subchain] = chain.name;
|
|
}
|
|
}
|
|
return mapping;
|
|
}
|
|
|
|
Residue* find_residue(const std::string& chain_name, const ResidueId& rid) {
|
|
for (Chain& chain : chains)
|
|
if (chain.name == chain_name)
|
|
if (Residue* residue = chain.find_residue(rid))
|
|
return residue;
|
|
return nullptr;
|
|
}
|
|
const Residue* find_residue(const std::string& chain_name, const ResidueId& rid) const {
|
|
return const_cast<Model*>(this)->find_residue(chain_name, rid);
|
|
}
|
|
|
|
|
|
ResidueGroup find_residue_group(const std::string& chain_name, SeqId seqid) {
|
|
for (Chain& chain : chains)
|
|
if (chain.name == chain_name)
|
|
if (ResidueGroup res_group = chain.find_residue_group(seqid))
|
|
return res_group;
|
|
fail("No such chain or residue: " + chain_name + " " + seqid.str());
|
|
}
|
|
|
|
Residue& sole_residue(const std::string& chain_name, SeqId seqid) {
|
|
ResidueSpan rr = find_residue_group(chain_name, seqid);
|
|
if (rr.size() != 1)
|
|
fail("Multiple residues " + chain_name + " " + seqid.str());
|
|
return rr[0];
|
|
}
|
|
|
|
std::vector<std::string> get_all_residue_names() const {
|
|
std::vector<std::string> names;
|
|
for (const Chain& chain : chains)
|
|
for (const Residue& res : chain.residues)
|
|
if (!in_vector(res.name, names))
|
|
names.push_back(res.name);
|
|
return names;
|
|
}
|
|
|
|
CRA find_cra(const AtomAddress& address, bool ignore_segment=false) {
|
|
for (Chain& chain : chains)
|
|
if (chain.name == address.chain_name) {
|
|
for (Residue& res : chain.residues)
|
|
if (address.res_id.matches_noseg(res) &&
|
|
(ignore_segment || address.res_id.segment == res.segment)) {
|
|
Atom *at = nullptr;
|
|
if (!address.atom_name.empty())
|
|
at = res.find_atom(address.atom_name, address.altloc);
|
|
return {&chain, &res, at};
|
|
}
|
|
}
|
|
return {nullptr, nullptr, nullptr};
|
|
}
|
|
|
|
const_CRA find_cra(const AtomAddress& address, bool ignore_segment=false) const {
|
|
return const_cast<Model*>(this)->find_cra(address, ignore_segment);
|
|
}
|
|
|
|
CraProxy all() { return {chains}; }
|
|
ConstCraProxy all() const { return {chains}; }
|
|
|
|
Atom* find_atom(const AtomAddress& address) { return find_cra(address).atom; }
|
|
const Atom* find_atom(const AtomAddress& address) const { return find_cra(address).atom; }
|
|
|
|
std::array<int, 3> get_indices(const Chain* c, const Residue* r,
|
|
const Atom* a) const {
|
|
return {{ c ? static_cast<int>(c - chains.data()) : -1,
|
|
c && r ? static_cast<int>(r - c->residues.data()) : -1,
|
|
r && a ? static_cast<int>(a - r->atoms.data()) : -1 }};
|
|
}
|
|
|
|
std::bitset<(size_t)El::END> present_elements() const {
|
|
std::bitset<(size_t)El::END> table;
|
|
for (const Chain& chain : chains)
|
|
for (const Residue& res : chain.residues)
|
|
for (const Atom& a : res.atoms)
|
|
table.set(a.element.ordinal());
|
|
return table;
|
|
}
|
|
|
|
// methods present in Structure, Model, ... - used in templates
|
|
Model empty_copy() const { return Model(num); }
|
|
using child_type = Chain;
|
|
std::vector<Chain>& children() { return chains; }
|
|
const std::vector<Chain>& children() const { return chains; }
|
|
};
|
|
|
|
inline Entity* find_entity_of_subchain(const std::string& subchain_id,
|
|
std::vector<Entity>& entities) {
|
|
if (!subchain_id.empty())
|
|
for (Entity& ent : entities)
|
|
if (in_vector(subchain_id, ent.subchains))
|
|
return &ent;
|
|
return nullptr;
|
|
}
|
|
inline const Entity* find_entity_of_subchain(const std::string& subchain_id,
|
|
const std::vector<Entity>& entities) {
|
|
return find_entity_of_subchain(subchain_id, const_cast<std::vector<Entity>&>(entities));
|
|
}
|
|
|
|
struct Structure {
|
|
static const char* what() { return "Structure"; }
|
|
std::string name;
|
|
UnitCell cell;
|
|
std::string spacegroup_hm; // usually pdb symbol, cf. SpaceGroup::pdb_name()
|
|
std::vector<Model> models;
|
|
std::vector<NcsOp> ncs;
|
|
std::vector<Entity> entities;
|
|
std::vector<Connection> connections;
|
|
std::vector<CisPep> cispeps;
|
|
std::vector<ModRes> mod_residues;
|
|
std::vector<Helix> helices;
|
|
std::vector<Sheet> sheets;
|
|
std::vector<Assembly> assemblies;
|
|
std::map<int, std::vector<int>> conect_map;
|
|
Metadata meta;
|
|
|
|
CoorFormat input_format = CoorFormat::Unknown;
|
|
bool has_d_fraction = false; // uses Refmac's ccp4_deuterium_fraction
|
|
int non_ascii_line = 0; // first PDB line with non-ASCII bytes, or 0
|
|
/// in input PDB file: y = TER records were read, e = errors were detected
|
|
char ter_status = '\0';
|
|
|
|
/// Store ORIGXn / _database_PDB_matrix.origx*
|
|
bool has_origx = false;
|
|
Transform origx;
|
|
|
|
/// Minimal metadata with keys being mmcif tags: _entry.id, _cell.Z_PDB, ...
|
|
std::map<std::string, std::string> info;
|
|
/// Mapping of long (4+) CCD codes (residue names) to PDB-compatible ones
|
|
std::vector<std::pair<std::string,std::string>> shortened_ccd_codes;
|
|
/// original REMARK records stored if the file was read from the PDB format
|
|
std::vector<std::string> raw_remarks;
|
|
/// simplistic resolution value from/for REMARK 2
|
|
double resolution = 0;
|
|
|
|
const SpaceGroup* find_spacegroup() const {
|
|
if (!cell.is_crystal())
|
|
return nullptr;
|
|
return find_spacegroup_by_name(spacegroup_hm, cell.alpha, cell.gamma);
|
|
}
|
|
|
|
const std::string& get_info(const std::string& tag) const {
|
|
static const std::string empty;
|
|
auto it = info.find(tag);
|
|
return it != info.end() ? it->second : empty;
|
|
}
|
|
|
|
Model& first_model() {
|
|
if (models.empty())
|
|
fail("no structural models");
|
|
return models[0];
|
|
}
|
|
const Model& first_model() const {
|
|
return const_cast<Structure*>(this)->first_model();
|
|
}
|
|
|
|
Model* find_model(int model_num) {
|
|
return impl::find_or_null(models, model_num);
|
|
}
|
|
const Model* find_model(int model_num) const {
|
|
return const_cast<Structure*>(this)->find_model(model_num);
|
|
}
|
|
Model& find_or_add_model(int model_num) {
|
|
return impl::find_or_add(models, model_num);
|
|
}
|
|
|
|
void renumber_models() {
|
|
for (size_t i = 0; i != models.size(); ++i)
|
|
models[i].num = i + 1;
|
|
}
|
|
|
|
Entity* get_entity(const std::string& ent_id) {
|
|
return impl::find_or_null(entities, ent_id);
|
|
}
|
|
const Entity* get_entity(const std::string& ent_id) const {
|
|
return const_cast<Structure*>(this)->get_entity(ent_id);
|
|
}
|
|
|
|
Entity* get_entity_of(const ConstResidueSpan& sub) {
|
|
return sub ? find_entity_of_subchain(sub.subchain_id(), entities) : nullptr;
|
|
}
|
|
const Entity* get_entity_of(const ConstResidueSpan& sub) const {
|
|
return const_cast<Structure*>(this)->get_entity_of(sub);
|
|
}
|
|
|
|
Assembly* find_assembly(const std::string& assembly_id) {
|
|
return impl::find_or_null(assemblies, assembly_id);
|
|
}
|
|
|
|
Connection* find_connection_by_name(const std::string& conn_name) {
|
|
return impl::find_or_null(connections, conn_name);
|
|
}
|
|
const Connection* find_connection_by_name(const std::string& conn_name) const {
|
|
return const_cast<Structure*>(this)->find_connection_by_name(conn_name);
|
|
}
|
|
|
|
Connection* find_connection_by_cra(const const_CRA& cra1,
|
|
const const_CRA& cra2,
|
|
bool ignore_segment=false) {
|
|
for (Connection& c : connections)
|
|
if ((atom_matches(cra1, c.partner1, ignore_segment) &&
|
|
atom_matches(cra2, c.partner2, ignore_segment)) ||
|
|
(atom_matches(cra1, c.partner2, ignore_segment) &&
|
|
atom_matches(cra2, c.partner1, ignore_segment)))
|
|
return &c;
|
|
return nullptr;
|
|
}
|
|
|
|
Connection* find_connection(const AtomAddress& a1, const AtomAddress& a2) {
|
|
for (Connection& c : connections)
|
|
if ((a1 == c.partner1 && a2 == c.partner2) ||
|
|
(a1 == c.partner2 && a2 == c.partner1))
|
|
return &c;
|
|
return nullptr;
|
|
}
|
|
|
|
size_t ncs_given_count() const {
|
|
return std::count_if(ncs.begin(), ncs.end(), [](const NcsOp& o) { return o.given; });
|
|
}
|
|
double get_ncs_multiplier() const {
|
|
return (ncs.size() + 1.0) / (ncs_given_count() + 1.0); // +1 b/c identity not included
|
|
}
|
|
bool ncs_not_expanded() const {
|
|
return std::any_of(ncs.begin(), ncs.end(), [](const NcsOp& o) { return !o.given; });
|
|
}
|
|
|
|
void add_conect_one_way(int serial_a, int serial_b, int order) {
|
|
auto& vec = conect_map[serial_a];
|
|
for (int i = 0; i < order; ++i)
|
|
vec.insert(std::upper_bound(vec.begin(), vec.end(), serial_b), serial_b);
|
|
}
|
|
void add_conect(int serial1, int serial2, int order) {
|
|
add_conect_one_way(serial1, serial2, order);
|
|
add_conect_one_way(serial2, serial1, order);
|
|
}
|
|
|
|
void merge_chain_parts(int min_sep=0) {
|
|
for (Model& model : models)
|
|
model.merge_chain_parts(min_sep);
|
|
}
|
|
|
|
void remove_empty_chains() {
|
|
for (Model& model : models)
|
|
remove_empty_children(model);
|
|
}
|
|
|
|
// copy all but models (in general, empty_copy copies all but children)
|
|
Structure empty_copy() const {
|
|
Structure st;
|
|
st.name = name;
|
|
st.cell = cell;
|
|
st.spacegroup_hm = spacegroup_hm;
|
|
st.ncs = ncs;
|
|
st.entities = entities;
|
|
st.connections = connections;
|
|
st.cispeps = cispeps;
|
|
st.mod_residues = mod_residues;
|
|
st.helices = helices;
|
|
st.sheets = sheets;
|
|
st.assemblies = assemblies;
|
|
st.meta = meta;
|
|
st.input_format = input_format;
|
|
st.has_origx = has_origx;
|
|
st.origx = origx;
|
|
st.info = info;
|
|
st.raw_remarks = raw_remarks;
|
|
st.resolution = resolution;
|
|
return st;
|
|
}
|
|
|
|
using child_type = Model;
|
|
std::vector<Model>& children() { return models; }
|
|
const std::vector<Model>& children() const { return models; }
|
|
|
|
void setup_cell_images();
|
|
};
|
|
|
|
inline Residue* Chain::find_residue(const ResidueId& rid) {
|
|
auto it = std::find_if(residues.begin(), residues.end(),
|
|
[&](const Residue& r) { return r.matches(rid); });
|
|
return it != residues.end() ? &*it : nullptr;
|
|
}
|
|
|
|
inline Residue* Chain::find_or_add_residue(const ResidueId& rid) {
|
|
Residue* r = find_residue(rid);
|
|
if (r)
|
|
return r;
|
|
residues.emplace_back(rid);
|
|
return &residues.back();
|
|
}
|
|
|
|
inline void Chain::append_residues(std::vector<Residue> new_resi, int min_sep) {
|
|
if (new_resi.empty())
|
|
return;
|
|
if (min_sep > 0) {
|
|
ConstResidueSpan new_span(&new_resi[0], new_resi.size());
|
|
// adjust sequence numbers if necessary
|
|
auto diff = new_span.extreme_num(false, -1) - whole().extreme_num(false, 1);
|
|
if (diff && int(diff) < min_sep)
|
|
for (Residue& res : new_resi)
|
|
res.seqid.num += min_sep - int(diff);
|
|
// adjust label_seq_id if necessary
|
|
diff = new_span.extreme_num(true, -1) - whole().extreme_num(true, 1);
|
|
if (diff && int(diff) < min_sep)
|
|
for (Residue& res : new_resi)
|
|
res.label_seq += min_sep - int(diff);
|
|
}
|
|
std::move(new_resi.begin(), new_resi.end(), std::back_inserter(residues));
|
|
}
|
|
|
|
inline void Structure::setup_cell_images() {
|
|
const SpaceGroup* sg = find_spacegroup();
|
|
cell.set_cell_images_from_spacegroup(sg);
|
|
cell.add_ncs_images_to_cs_images(ncs);
|
|
}
|
|
|
|
} // namespace gemmi
|
|
|
|
#endif
|