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>
204 lines
7.8 KiB
C++
204 lines
7.8 KiB
C++
// Copyright 2017-2018 Global Phasing Ltd.
|
|
//
|
|
// Heuristic methods for working with chains and polymers.
|
|
// Also includes a few well-defined functions, such as removal of waters.
|
|
|
|
#ifndef GEMMI_POLYHEUR_HPP_
|
|
#define GEMMI_POLYHEUR_HPP_
|
|
|
|
#include <vector>
|
|
#include "model.hpp"
|
|
#include "util.hpp" // for vector_remove_if
|
|
|
|
namespace gemmi {
|
|
|
|
// A simplistic classification. It may change in the future.
|
|
// It returns PolymerType which corresponds to _entity_poly.type,
|
|
// but here we use only PeptideL, Rna, Dna, DnaRnaHybrid and Unknown.
|
|
GEMMI_DLL PolymerType check_polymer_type(const ConstResidueSpan& span,
|
|
bool ignore_entity_type=false);
|
|
|
|
inline PolymerType get_or_check_polymer_type(const Entity* ent,
|
|
const ConstResidueSpan& polymer) {
|
|
if (ent && ent->polymer_type != PolymerType::Unknown)
|
|
return ent->polymer_type;
|
|
return check_polymer_type(polymer);
|
|
}
|
|
|
|
struct AtomNameElement { std::string atom_name; El el; };
|
|
|
|
inline std::vector<AtomNameElement> get_mainchain_atoms(PolymerType ptype) {
|
|
if (is_polynucleotide(ptype))
|
|
return {{"P", El::P}, {"O5'", El::O}, {"C5'", El::C},
|
|
{"C4'", El::C}, {"O4'", El::O}, {"C3'", El::C}, {"O3'", El::O},
|
|
{"C2'", El::C}, {"O2'", El::O}, {"C1'", El::C}};
|
|
return {{"N", El::N}, {"CA", El::C}, {"C", El::C}, {"O", El::O}};
|
|
}
|
|
|
|
/// distance-based check for peptide bond
|
|
inline bool in_peptide_bond_distance(const Atom* a1, const Atom* a2) {
|
|
return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(1.341 * 1.5);
|
|
}
|
|
inline bool have_peptide_bond(const Residue& r1, const Residue& r2) {
|
|
return in_peptide_bond_distance(r1.get_c(), r2.get_n());
|
|
}
|
|
|
|
/// distance-based check for phosphodiester bond between nucleotide
|
|
inline bool in_nucleotide_bond_distance(const Atom* a1, const Atom* a2) {
|
|
return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(1.6 * 1.5);
|
|
}
|
|
inline bool have_nucleotide_bond(const Residue& r1, const Residue& r2) {
|
|
return in_nucleotide_bond_distance(r1.get_o3prim(), r2.get_p());
|
|
}
|
|
|
|
/// check C-N or O3'-P distance
|
|
inline bool are_connected(const Residue& r1, const Residue& r2, PolymerType ptype) {
|
|
if (is_polypeptide(ptype))
|
|
return have_peptide_bond(r1, r2);
|
|
if (is_polynucleotide(ptype))
|
|
return have_nucleotide_bond(r1, r2);
|
|
return false;
|
|
}
|
|
|
|
/// are_connected2() is less exact, but requires only CA (or P) atoms.
|
|
inline bool are_connected2(const Residue& r1, const Residue& r2, PolymerType ptype) {
|
|
auto this_or_first = [](const Atom* a, const Residue& r, El el) -> const Atom* {
|
|
if (a || r.atoms.empty())
|
|
return a;
|
|
if (const Atom* b = r.find_by_element(el))
|
|
return b;
|
|
return &r.atoms.front();
|
|
};
|
|
if (is_polypeptide(ptype)) {
|
|
const Atom* a1 = this_or_first(r1.get_ca(), r1, El::C);
|
|
const Atom* a2 = this_or_first(r2.get_ca(), r2, El::C);
|
|
return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(5.0);
|
|
}
|
|
if (is_polynucleotide(ptype)) {
|
|
const Atom* a1 = this_or_first(r1.get_p(), r1, El::P);
|
|
const Atom* a2 = this_or_first(r2.get_p(), r2, El::P);
|
|
return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(7.5);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// are_connected3() = are_connected() + fallback to are_connected2()
|
|
inline bool are_connected3(const Residue& r1, const Residue& r2, PolymerType ptype) {
|
|
if (is_polypeptide(ptype)) {
|
|
if (const Atom* a1 = r1.get_c())
|
|
if (const Atom* a2 = r2.get_n())
|
|
return a1->pos.dist_sq(a2->pos) < sq(1.341 * 1.5);
|
|
if (const Atom* a1 = r1.get_ca())
|
|
if (const Atom* a2 = r2.get_ca())
|
|
return a1->pos.dist_sq(a2->pos) < sq(5.0);
|
|
} else if (is_polynucleotide(ptype)) {
|
|
if (const Atom* a1 = r1.get_o3prim())
|
|
if (const Atom* a2 = r2.get_p())
|
|
return a1->pos.dist_sq(a2->pos) < sq(1.6 * 1.5);
|
|
if (const Atom* a1 = r1.get_p())
|
|
if (const Atom* a2 = r2.get_p())
|
|
return a1->pos.dist_sq(a2->pos) < sq(7.5);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GEMMI_DLL std::string make_one_letter_sequence(const ConstResidueSpan& polymer);
|
|
|
|
/// Assigns entity_type=Polymer|NonPolymer|Water for each Residue (only
|
|
/// for residues with entity_type==Unknown, unless overwrite=true).
|
|
/// Determining where the polymer ends and ligands start is sometimes
|
|
/// arbitrary -- there can be a non-standard residue at the end that can
|
|
/// be regarded as as either the last residue or a linked ligand.
|
|
GEMMI_DLL void add_entity_types(Chain& chain, bool overwrite);
|
|
GEMMI_DLL void add_entity_types(Structure& st, bool overwrite);
|
|
|
|
/// Assigns entity_type=Unknown for all residues.
|
|
GEMMI_DLL void remove_entity_types(Structure& st);
|
|
|
|
/// Assigns Residue::entity_id based on Residue::subchain and Entity::subchains.
|
|
GEMMI_DLL void add_entity_ids(Structure& st, bool overwrite);
|
|
|
|
/// The subchain field in the residue is where we store_atom_site.label_asym_id
|
|
/// from mmCIF files. As of 2018 wwPDB software splits author's chains
|
|
/// (auth_asym_id) into label_asym_id units:
|
|
/// * linear polymer,
|
|
/// * non-polymers (each residue has different separate label_asym_id),
|
|
/// * and waters.
|
|
/// Refmac/makecif is doing similar thing but using different naming and
|
|
/// somewhat different rules (it was written in 1990's before PDBx/mmCIF).
|
|
///
|
|
/// Here we use naming and rules different from both wwPDB and makecif.
|
|
/// Note: call add_entity_types() first.
|
|
GEMMI_DLL void assign_subchain_names(Chain& chain, int& nonpolymer_counter);
|
|
|
|
GEMMI_DLL void assign_subchains(Structure& st, bool force, bool fail_if_unknown=true);
|
|
|
|
GEMMI_DLL void ensure_entities(Structure& st);
|
|
|
|
GEMMI_DLL void deduplicate_entities(Structure& st);
|
|
|
|
inline void setup_entities(Structure& st) {
|
|
add_entity_types(st, /*overwrite=*/false);
|
|
assign_subchains(st, /*force=*/false);
|
|
ensure_entities(st);
|
|
deduplicate_entities(st);
|
|
}
|
|
|
|
/// Determine ATOM/HETATM record type, based on Residue::entity_type
|
|
GEMMI_DLL char recommended_het_flag(const Residue& res);
|
|
/// R = recommended_het_flag(), other valid values are A, H and '\0'
|
|
template<class T> void assign_het_flags(T& obj, char flag='R') {
|
|
for (auto& child : obj.children())
|
|
assign_het_flags(child, flag);
|
|
}
|
|
template<> inline void assign_het_flags(Residue& res, char flag) {
|
|
flag &= ~0x20; // uppercase letters, ' ' -> \0
|
|
if (flag != 'R' && flag != '\0' && flag != 'A' && flag != 'H')
|
|
fail("assign_het_flags(): the only allowed values are A, H, ' ' and R");
|
|
res.het_flag = flag == 'R' ? recommended_het_flag(res) : flag;
|
|
}
|
|
|
|
// Remove waters. It may leave empty chains.
|
|
template<class T> void remove_waters(T& obj) {
|
|
for (auto& child : obj.children())
|
|
remove_waters(child);
|
|
}
|
|
template<> inline void remove_waters(Chain& ch) {
|
|
vector_remove_if(ch.residues,
|
|
[](const Residue& res) { return res.is_water(); });
|
|
}
|
|
|
|
// Remove ligands and waters. It may leave empty chains.
|
|
template<class T> void remove_ligands_and_waters(T& obj) {
|
|
for (auto& child : obj.children())
|
|
remove_ligands_and_waters(child);
|
|
}
|
|
template<> inline void remove_ligands_and_waters(Chain& ch) {
|
|
vector_remove_if(ch.residues, [&](const Residue& res) {
|
|
if (res.entity_type == EntityType::Unknown)
|
|
fail("remove_ligands_and_waters(): missing entity_type in chain ", ch.name);
|
|
return res.entity_type != EntityType::Polymer;
|
|
});
|
|
}
|
|
|
|
// Trim to alanine. Returns true if trimmed, false if it's (likely) not AA.
|
|
GEMMI_DLL bool trim_to_alanine(Residue& res);
|
|
|
|
inline void trim_to_alanine(Chain& chain) {
|
|
for (Residue& res : chain.residues)
|
|
trim_to_alanine(res);
|
|
}
|
|
|
|
// Functions for switching between long (>3 chars) residue names (CCD codes)
|
|
// and shortened ones that are compatible with the PDB format.
|
|
GEMMI_DLL void shorten_ccd_codes(Structure& st);
|
|
GEMMI_DLL void restore_full_ccd_codes(Structure& st);
|
|
|
|
/// Modifies Entity::full_sequence. Uses only the first chain for each Entity.
|
|
GEMMI_DLL void add_microhetero_to_sequences(Structure& st, bool overwrite=false);
|
|
|
|
GEMMI_DLL void add_tls_group_ids(Structure& st);
|
|
|
|
} // namespace gemmi
|
|
#endif
|