/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/atom_type.hpp" #include "cif++/datablock.hpp" #include "cif++/point.hpp" #include #include #if __cpp_lib_format #include #endif /** @file model.hpp * * This file contains code to work with models of molecules. * * The classes available encapsulate the real world concepts of * atoms, residues, monomers, polymers and everything is then * bound together in a structure. * * This code is not finished yet, ideally it would be a high * level interface to manipulate macro molecular structures * and an attempt has been made to start work on this. But * there's still a lot that needs to be implemented. * * However, the code that is here is still useful in * manipulating the underlying mmCIF data model. * */ namespace cif::mm { class atom; class residue; class monomer; class polymer; class structure; // -------------------------------------------------------------------- /** * @brief The class atom encapsulates the data in _atom_site and * _atom_site_anisotrop * * The class atom is a kind of flyweight class. It can be copied * with low overhead. All data is stored in the underlying mmCIF * categories but some very often used items are cached in the * impl. * * It is also possible to have symmetry copies of atoms. They * share the same data in the cif::category but their location * differs by using a symmetry operator. */ class atom { private: /** @cond */ struct atom_impl : public std::enable_shared_from_this { atom_impl(const datablock &db, std::string_view id) : m_db(db) , m_cat(db["atom_site"]) , m_id(id) { auto r = row(); if (r) tie(m_location.m_x, m_location.m_y, m_location.m_z) = r.get("Cartn_x", "Cartn_y", "Cartn_z"); } // constructor for a symmetry copy of an atom atom_impl(const atom_impl &impl, const point &loc, const std::string &sym_op) : atom_impl(impl) { m_location = loc; m_symop = sym_op; } atom_impl(const atom_impl &i) = default; void prefetch(); int compare(const atom_impl &b) const; // bool getAnisoU(float anisou[6]) const; int get_charge() const; void moveTo(const point &p); // const compound *compound() const; std::string get_property(std::string_view name) const; int get_property_int(std::string_view name) const; float get_property_float(std::string_view name) const; void set_property(const std::string_view name, const std::string &value); row_handle row() { return m_cat[{ { "id", m_id } }]; } const row_handle row() const { return m_cat[{ { "id", m_id } }]; } row_handle row_aniso() { auto cat = m_db.get("atom_site_anisotrop"); return cat ? cat->operator[]({ { "id", m_id } }) : row_handle{}; } const row_handle row_aniso() const { auto cat = m_db.get("atom_site_anisotrop"); return cat ? cat->operator[]({ { "id", m_id } }) : row_handle{}; } const datablock &m_db; const category &m_cat; std::string m_id; point m_location; std::string m_symop = "1_555"; }; /** @endcond */ public: /** * @brief Construct a new, empty atom object */ atom() {} /** * @brief Construct a new atom object using @a impl as impl * * @param impl The implementation objectt */ atom(std::shared_ptr impl) : m_impl(impl) { } /** * @brief Copy construct a new atom object */ atom(const atom &rhs) : m_impl(rhs.m_impl) { } /** * @brief Construct a new atom object based on a cif::row * * @param db The datablock where the _atom_site category resides * @param row The row containing the data for this atom */ atom(const datablock &db, const row_handle &row) : atom(std::make_shared(db, row["id"].as())) { } /** * @brief A special constructor to create symmetry copies * * @param rhs The original atom to copy * @param symmmetry_location The symmetry location * @param symmetry_operation The symmetry operator used */ atom(const atom &rhs, const point &symmmetry_location, const std::string &symmetry_operation) : atom(std::make_shared(*rhs.m_impl, symmmetry_location, symmetry_operation)) { } /// \brief To quickly test if the atom has data explicit operator bool() const { return (bool)m_impl; } /// \brief Copy assignement operator atom &operator=(const atom &rhs) = default; /// \brief Return the item named @a name in the _atom_site category for this atom std::string get_property(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property(name); } /// \brief Return the item named @a name in the _atom_site category for this atom cast to an int int get_property_int(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property_int(name); } /// \brief Return the item named @a name in the _atom_site category for this atom cast to a float float get_property_float(std::string_view name) const { if (not m_impl) throw std::logic_error("Error trying to fetch a property from an uninitialized atom"); return m_impl->get_property_float(name); } /// \brief Set value for the item named @a name in the _atom_site category to @a value void set_property(const std::string_view name, const std::string &value) { if (not m_impl) throw std::logic_error("Error trying to modify an uninitialized atom"); m_impl->set_property(name, value); } /// \brief Set value for the item named @a name in the _atom_site category to @a value template , int> = 0> void set_property(const std::string_view name, const T &value) { set_property(name, std::to_string(value)); } /** Return the ID of the _atom_site record. * * @note Although I've never seen anything other than integers, * the standard says this should be a string and so we use that. */ const std::string &id() const { return impl().m_id; } /// \brief Return the type of the atom cif::atom_type get_type() const { return atom_type_traits(get_property("type_symbol")).type(); } /// \brief Return the cached location of this atom point get_location() const { return impl().m_location; } /// \brief Set the location of this atom, will set both the cached data as well as the data in the underlying _atom_site category void set_location(point p) { if (not m_impl) throw std::logic_error("Error trying to modify an uninitialized atom"); m_impl->moveTo(p); } /// \brief Translate the position of this atom by \a t void translate(point t) { set_location(get_location() + t); } /// \brief Rotate the position of this atom by \a q void rotate(quaternion q) { auto loc = get_location(); loc.rotate(q); set_location(loc); } /// \brief rotate the coordinates of this atom by \a q around point \a p void rotate(quaternion q, point p) { auto loc = get_location(); loc.rotate(q, p); set_location(loc); } /// \brief Translate and rotate the position of this atom by \a t and \a q void translate_and_rotate(point t, quaternion q) { auto loc = get_location(); loc += t; loc.rotate(q); set_location(loc); } /// \brief Translate, rotate and translate again the coordinates this atom by \a t1 , \a q and \a t2 void translate_rotate_and_translate(point t1, quaternion q, point t2) { auto loc = get_location(); loc += t1; loc.rotate(q); loc += t2; set_location(loc); } /// for direct access to underlying data, be careful! const row_handle get_row() const { return impl().row(); } /// for direct access to underlying data, be careful! const row_handle get_row_aniso() const { return impl().row_aniso(); } /// Return if the atom is actually a symmetry copy or the original one bool is_symmetry_copy() const { return impl().m_symop != "1_555"; } /// Return the symmetry operator used std::string symmetry() const { return impl().m_symop; } /// Return true if this atom is part of a water molecule bool is_water() const { auto comp_id = get_label_comp_id(); return comp_id == "HOH" or comp_id == "H2O" or comp_id == "WAT"; } /// Return the charge int get_charge() const { return impl().get_charge(); } /// Return the occupancy float get_occupancy() const { return get_property_float("occupancy"); } // specifications std::string get_label_asym_id() const { return get_property("label_asym_id"); } ///< Return the label_asym_id property int get_label_seq_id() const { return get_property_int("label_seq_id"); } ///< Return the label_seq_id property std::string get_label_atom_id() const { return get_property("label_atom_id"); } ///< Return the label_atom_id property std::string get_label_alt_id() const { return get_property("label_alt_id"); } ///< Return the label_alt_id property std::string get_label_comp_id() const { return get_property("label_comp_id"); } ///< Return the label_comp_id property std::string get_label_entity_id() const { return get_property("label_entity_id"); } ///< Return the label_entity_id property std::string get_auth_asym_id() const { return get_property("auth_asym_id"); } ///< Return the auth_asym_id property std::string get_auth_seq_id() const { return get_property("auth_seq_id"); } ///< Return the auth_seq_id property std::string get_auth_atom_id() const { return get_property("auth_atom_id"); } ///< Return the auth_atom_id property std::string get_auth_alt_id() const { return get_property("auth_alt_id"); } ///< Return the auth_alt_id property std::string get_auth_comp_id() const { return get_property("auth_comp_id"); } ///< Return the auth_comp_id property std::string get_pdb_ins_code() const { return get_property("pdbx_PDB_ins_code"); } ///< Return the pdb_ins_code property /// Return true if this atom is an alternate bool is_alternate() const { return not get_label_alt_id().empty(); } /// Convenience method to return a string that might be ID in PDB space std::string pdb_id() const { return get_label_comp_id() + '_' + get_auth_asym_id() + '_' + get_auth_seq_id() + get_pdb_ins_code(); } /// Compare two atoms bool operator==(const atom &rhs) const { if (m_impl == rhs.m_impl) return true; if (not(m_impl and rhs.m_impl)) return false; return &m_impl->m_db == &rhs.m_impl->m_db and m_impl->m_id == rhs.m_impl->m_id; } /// Compare two atoms bool operator!=(const atom &rhs) const { return not operator==(rhs); } /// Is this atom a backbone atom bool is_back_bone() const { auto atomID = get_label_atom_id(); return atomID == "N" or atomID == "O" or atomID == "C" or atomID == "CA"; } /// swap void swap(atom &b) { std::swap(m_impl, b.m_impl); } /// Compare this atom with @a b int compare(const atom &b) const { return impl().compare(*b.m_impl); } /// Should this atom sort before @a rhs bool operator<(const atom &rhs) const { return compare(rhs) < 0; } /// Write the atom to std::ostream @a os friend std::ostream &operator<<(std::ostream &os, const atom &atom); private: friend class structure; const atom_impl &impl() const { if (not m_impl) throw std::runtime_error("Uninitialized atom, not found?"); return *m_impl; } std::shared_ptr m_impl; }; /** swap */ inline void swap(atom &a, atom &b) { a.swap(b); } /** Calculate the distance between atoms @a and @a b in ångström */ inline float distance(const atom &a, const atom &b) { return distance(a.get_location(), b.get_location()); } /** Calculate the square of the distance between atoms @a and @a b in ångström * * @note Use this whenever possible instead of simply using distance since * this function does not have to calculate a square root which is expensive. */ inline float distance_squared(const atom &a, const atom &b) { return distance_squared(a.get_location(), b.get_location()); } // -------------------------------------------------------------------- /** * @brief The entity types that can be found in a mmCIF file * */ enum class EntityType { Polymer, ///< entity is a polymer NonPolymer, ///< entity is not a polymer Macrolide, ///< entity is a macrolide Water, ///< water in the solvent model Branched ///< entity is branched }; // -------------------------------------------------------------------- /** * @brief The class residue is a collection of atoms forming a molecule * * This class is used to store ligand e.g. Derived classes are monomer * and sugar. */ class residue { public: friend class structure; /** * @brief Construct a new residue object based on key items */ residue(structure &structure, const std::string &compoundID, const std::string &asymID, int seqID, const std::string &authAsymID, const std::string &authSeqID, const std::string &pdbInsCode) : m_structure(&structure) , m_compound_id(compoundID) , m_asym_id(asymID) , m_seq_id(seqID) , m_auth_asym_id(authAsymID) , m_auth_seq_id(authSeqID) , m_pdb_ins_code(pdbInsCode) { } /** Construct a new residue in structure with the atoms in @a atoms */ residue(structure &structure, const std::vector &atoms); /** @cond */ residue(const residue &rhs) = delete; residue &operator=(const residue &rhs) = delete; residue(residue &&rhs) = default; residue &operator=(residue &&rhs) = default; virtual ~residue() = default; /** @endcond */ /** Return the entity_id of this residue */ std::string get_entity_id() const; /** Return the entity type of this residue */ EntityType entity_type() const; const std::string &get_asym_id() const { return m_asym_id; } ///< Return the asym_id int get_seq_id() const { return m_seq_id; } ///< Return the seq_id const std::string get_auth_asym_id() const { return m_auth_asym_id; } ///< Return the auth_asym_id const std::string get_auth_seq_id() const { return m_auth_seq_id; } ///< Return the auth_seq_id std::string get_pdb_ins_code() const { return m_pdb_ins_code; } ///< Return the pdb_ins_code const std::string &get_compound_id() const { return m_compound_id; } ///< Return the compound_id void set_compound_id(const std::string &id) { m_compound_id = id; } ///< Set the compound_id to @a id /** Return the structure this residue belongs to */ structure *get_structure() const { return m_structure; } /** Return a list of the atoms for this residue */ std::vector &atoms() { return m_atoms; } /** Return a const list of the atoms for this residue */ const std::vector &atoms() const { return m_atoms; } /** Add atom @a atom to the atoms in this residue */ void add_atom(atom &atom); /// \brief Unique atoms returns only the atoms without alternates and the first of each alternate atom id. std::vector unique_atoms() const; /// \brief Return the atom with atom_id @a atomID atom get_atom_by_atom_id(const std::string &atomID) const; /// \brief Return the list of atoms having ID \a atomID /// /// This includes all alternate atoms with this ID /// whereas get_atom_by_atom_id only returns the first unique atom std::vector get_atoms_by_id(const std::string &atomID) const; /// \brief Is this residue a single entity? bool is_entity() const; /// \brief Is this residue a water molecule? bool is_water() const { return m_compound_id == "HOH"; } /// \brief Return true if this residue has alternate atoms bool has_alternate_atoms() const; /// \brief Return the list of unique alt ID's present in this residue std::set get_alternate_ids() const; /// \brief Return the list of unique atom ID's std::set get_atom_ids() const; /// \brief Return a tuple containing the center location and the radius for the atoms of this residue std::tuple center_and_radius() const; /// \brief Write the residue @a res to the std::ostream @a os friend std::ostream &operator<<(std::ostream &os, const residue &res); /// \brief Return true if this residue is equal to @a rhs bool operator==(const residue &rhs) const { return this == &rhs or (m_structure == rhs.m_structure and m_seq_id == rhs.m_seq_id and m_asym_id == rhs.m_asym_id and m_compound_id == rhs.m_compound_id and m_auth_seq_id == rhs.m_auth_seq_id); } protected: /** @cond */ residue() {} structure *m_structure = nullptr; std::string m_compound_id, m_asym_id; int m_seq_id = 0; std::string m_auth_asym_id, m_auth_seq_id, m_pdb_ins_code; std::vector m_atoms; /** @endcond */ }; // -------------------------------------------------------------------- /** * @brief a monomer models a single residue in a protein chain * */ class monomer : public residue { public: monomer(const monomer &rhs) = delete; monomer &operator=(const monomer &rhs) = delete; /// \brief Move constructor monomer(monomer &&rhs); /// \brief Move assignment operator monomer &operator=(monomer &&rhs); /// \brief constructor with actual values monomer(const polymer &polymer, size_t index, int seqID, const std::string &authSeqID, const std::string &pdbInsCode, const std::string &compoundID); bool is_first_in_chain() const; ///< Return if this residue is the first residue in the chain bool is_last_in_chain() const; ///< Return if this residue is the last residue in the chain // convenience bool has_alpha() const; ///< Return if a alpha value can be calculated (depends on location in chain) bool has_kappa() const; ///< Return if a kappa value can be calculated (depends on location in chain) // Assuming this is really an amino acid... float phi() const; ///< Return the phi value for this residue float psi() const; ///< Return the psi value for this residue float alpha() const; ///< Return the alpha value for this residue float kappa() const; ///< Return the kappa value for this residue float tco() const; ///< Return the tco value for this residue float omega() const; ///< Return the omega value for this residue // torsion angles size_t nr_of_chis() const; ///< Return how many torsion angles can be calculated float chi(size_t i) const; ///< Return torsion angle @a i bool is_cis() const; ///< Return true if this residue is in a cis conformation /// \brief Returns true if the four atoms C, CA, N and O are present bool is_complete() const; /// \brief Returns true if any of the backbone atoms has an alternate bool has_alternate_backbone_atoms() const; atom CAlpha() const { return get_atom_by_atom_id("CA"); } ///< Return the CAlpha atom atom C() const { return get_atom_by_atom_id("C"); } ///< Return the C atom atom N() const { return get_atom_by_atom_id("N"); } ///< Return the N atom atom O() const { return get_atom_by_atom_id("O"); } ///< Return the O atom atom H() const { return get_atom_by_atom_id("H"); } ///< Return the H atom /// \brief Return true if this monomer is bonded to monomer @a rhs bool is_bonded_to(const monomer &rhs) const { return this != &rhs and are_bonded(*this, rhs); } /** * @brief Return true if the distance between the CA atoms of the * two monomers @a a and @a b are within the expected range with * an error margin of @a errorMargin. * * The expected distance is 3.0 ångström for a cis conformation * and 3.8 ångström for trans. */ static bool are_bonded(const monomer &a, const monomer &b, float errorMargin = 0.5f); /// \brief Return true if the bond between @a a and @a b is cis static bool is_cis(const monomer &a, const monomer &b); /// \brief Return the omega angle between @a a and @a b static float omega(const monomer &a, const monomer &b); /// \brief Return the chiral volume, only for LEU and VAL float chiral_volume() const; /// \brief Compare this monomer with \a rhs bool operator==(const monomer &rhs) const { return m_polymer == rhs.m_polymer and m_index == rhs.m_index; } private: const polymer *m_polymer; size_t m_index; }; // -------------------------------------------------------------------- /** * @brief A polymer is simply a list of monomers * */ class polymer : public std::vector { public: /// \brief Constructor polymer(structure &s, const std::string &entityID, const std::string &asymID, const std::string &auth_asym_id); polymer(const polymer &) = delete; polymer &operator=(const polymer &) = delete; structure *get_structure() const { return m_structure; } ///< Return the structure std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id std::string get_auth_asym_id() const { return m_auth_asym_id; } ///< Return the PDB chain ID, actually std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id private: structure *m_structure; std::string m_entity_id; std::string m_asym_id; std::string m_auth_asym_id; }; // -------------------------------------------------------------------- // sugar and branch, to describe glycosylation sites class branch; /** * @brief A sugar is a residue that is part of a glycosylation site * */ class sugar : public residue { public: /// \brief constructor sugar(branch &branch, const std::string &compoundID, const std::string &asymID, int authSeqID); /** @cond */ sugar(sugar &&rhs); sugar &operator=(sugar &&rhs); /** @endcond */ /** * @brief Return the sugar number in the glycosylation tree * * To store the sugar number, the auth_seq_id item has been overloaded * in the specification. But since a sugar number should be, ehm, a number * and auth_seq_id is specified to contain a string, we do a check here * to see if it really is a number. * * @return The sugar number */ int num() const { int result; auto r = std::from_chars(m_auth_seq_id.data(), m_auth_seq_id.data() + m_auth_seq_id.length(), result); if ((bool)r.ec) throw std::runtime_error("The auth_seq_id should be a number for a sugar"); return result; } /// \brief Return the name of this sugar std::string name() const; /// \brief Return the atom the C1 is linked to atom get_link() const { return m_link; } /// \brief Set the link atom C1 is linked to to @a link void set_link(atom link) { m_link = link; } /// \brief Return the sugar number of the sugar linked to C1 size_t get_link_nr() const { size_t result = 0; if (m_link) result = m_link.get_property_int("auth_seq_id"); return result; } /// \brief Construct an atom based on the info in @a atom_info and add it to this sugar atom add_atom(row_initializer atom_info); private: branch *m_branch; atom m_link; }; /** * @brief A branch is a list of sugars * * A list is how it is stored, but a branch is like a branch in a tree, * with potentially lots of sub branches. Each sugar is linked to a sugar * up in the branch with its (almost always) C1 atom. * */ class branch : public std::vector { public: /// \brief constructor branch(structure &structure, const std::string &asym_id, const std::string &entity_id); branch(const branch &) = delete; branch &operator=(const branch &) = delete; /** @cond */ branch(branch &&) = default; branch &operator=(branch &&) = default; /** @endcond */ /// \brief Update the link atoms in all sugars in this branch void link_atoms(); /// \brief Return the name of the branch std::string name() const; /// \brief Return the weight of the branch based on the formulae of the sugars float weight() const; std::string get_asym_id() const { return m_asym_id; } ///< Return the asym_id std::string get_entity_id() const { return m_entity_id; } ///< Return the entity_id structure &get_structure() { return *m_structure; } ///< Return the structure structure &get_structure() const { return *m_structure; } ///< Return the structure /// \brief Return a reference to the sugar with number @a num sugar &get_sugar_by_num(int nr); /// \brief Return a const reference to the sugar with number @a num const sugar &get_sugar_by_num(int nr) const { return const_cast(this)->get_sugar_by_num(nr); } /// \brief Construct a new sugar with compound ID @a compound_id in this branch /// and return a reference to the newly created sugar. Use this to create a first /// sugar in a branch. sugar &construct_sugar(const std::string &compound_id); /// \brief Construct a new sugar with compound ID @a compound_id in this branch /// and return a reference to the newly created sugar. The newly created sugar /// will be connected to an already created sugar in the branch using the /// information in @a atom_id, @a linked_sugar_nr and @a linked_atom_id sugar &construct_sugar(const std::string &compound_id, const std::string &atom_id, int linked_sugar_nr, const std::string &linked_atom_id); private: friend sugar; std::string name(const sugar &s) const; structure *m_structure; std::string m_asym_id, m_entity_id; }; // -------------------------------------------------------------------- /// \brief A still very limited set of options for reading structures enum class StructureOpenOptions { SkipHydrogen = 1 << 0 ///< Do not include hydrogen atoms in the structure object }; /// \brief A way to combine two options. Not very useful as there is only one... constexpr inline bool operator&(StructureOpenOptions a, StructureOpenOptions b) { return static_cast(a) bitand static_cast(b); } // -------------------------------------------------------------------- /** * @brief A structure is the combination of polymers, ligand and sugar branches found * in the mmCIF file. This will always contain one model, the first model is taken * if not otherwise specified. * */ class structure { public: /// \brief Read the structure from cif::file @a p structure(file &p, size_t modelNr = 1, StructureOpenOptions options = {}); /// \brief Load the structure from already parsed mmCIF data in @a db structure(datablock &db, size_t modelNr = 1, StructureOpenOptions options = {}); /** @cond */ structure(structure &&s) = default; /** @endcond */ // structures cannot be copied. structure(const structure &) = delete; structure &operator=(const structure &) = delete; ~structure() = default; /// \brief Return the model number size_t get_model_nr() const { return m_model_nr; } /// \brief Return a list of all the atoms in this structure const std::vector &atoms() const { return m_atoms; } EntityType get_entity_type_for_entity_id(const std::string entityID) const; ///< Return the entity type for the entity with id @a entity_id EntityType get_entity_type_for_asym_id(const std::string asymID) const; ///< Return the entity type for the asym with id @a asym_id const std::list &polymers() const { return m_polymers; } ///< Return the list of polymers std::list &polymers() { return m_polymers; } ///< Return the list of polymers polymer &get_polymer_by_asym_id(const std::string &asymID); ///< Return the polymer having asym ID @a asymID const polymer &get_polymer_by_asym_id(const std::string &asymID) const ///< Return the polymer having asym ID @a asymID { return const_cast(this)->get_polymer_by_asym_id(asymID); } const std::list &branches() const { return m_branches; } ///< Return the list of all branches std::list &branches() { return m_branches; } ///< Return the list of all branches branch &get_branch_by_asym_id(const std::string &asymID); ///< Return the branch having asym ID @a asymID const branch &get_branch_by_asym_id(const std::string &asymID) const; ///< Return the branch having asym ID @a asymID const std::vector &non_polymers() const { return m_non_polymers; } ///< Return the list of non-polymers, actually the list of ligands bool has_atom_id(const std::string &id) const; ///< Return true if an atom with ID @a id exists in this structure atom get_atom_by_id(const std::string &id) const; ///< Return the atom with ID @a id /// \brief Return the atom identified by the label_ values specified atom get_atom_by_label(const std::string &atomID, const std::string &asymID, const std::string &compID, int seqID, const std::string &altID = ""); /// \brief Return the atom closest to point \a p atom get_atom_by_position(point p) const; /// \brief Return the atom closest to point \a p with atom type \a type in a residue of type \a res_type atom get_atom_by_position_and_type(point p, std::string_view type, std::string_view res_type) const; /// \brief Create a non-poly residue based on atoms already present in this structure. residue &create_residue(const std::vector &atoms); /// \brief Get a non-poly residue for an asym with id \a asymID residue &get_residue(const std::string &asymID) { return get_residue(asymID, 0, ""); } /// \brief Get a non-poly residue for an asym with id \a asymID const residue &get_residue(const std::string &asymID) const { return get_residue(asymID, 0, ""); } /// \brief Get a residue for an asym with id \a asymID seq id \a seqID and authSeqID \a authSeqID residue &get_residue(const std::string &asymID, int seqID, const std::string &authSeqID); /// \brief Get a the single residue for an asym with id \a asymID seq id \a seqID and authSeqID \a authSeqID const residue &get_residue(const std::string &asymID, int seqID, const std::string &authSeqID) const { return const_cast(this)->get_residue(asymID, seqID, authSeqID); } /// \brief Get a residue for an asym with id \a asymID, compound id \a compID, seq id \a seqID and authSeqID \a authSeqID residue &get_residue(const std::string &asymID, const std::string &compID, int seqID, const std::string &authSeqID); /// \brief Get a residue for an asym with id \a asymID, compound id \a compID, seq id \a seqID and authSeqID \a authSeqID const residue &get_residue(const std::string &asymID, const std::string &compID, int seqID, const std::string &authSeqID) const { return const_cast(this)->get_residue(asymID, compID, seqID, authSeqID); } /// \brief Get a the residue for atom \a atom residue &get_residue(const atom &atom) { return get_residue(atom.get_label_asym_id(), atom.get_label_comp_id(), atom.get_label_seq_id(), atom.get_auth_seq_id()); } /// \brief Get a the residue for atom \a atom const residue &get_residue(const atom &atom) const { return get_residue(atom.get_label_asym_id(), atom.get_label_comp_id(), atom.get_label_seq_id(), atom.get_auth_seq_id()); } // Actions. Originally a lot more actions were expected here /// \brief Remove atom @a a void remove_atom(atom &a) { remove_atom(a, true); } void swap_atoms(atom a1, atom a2); ///< swap the labels for these atoms void move_atom(atom a, point p); ///< move atom to a new location /** * @brief Change residue @a res to a new compound ID optionally * remapping atoms. * * A new chem_comp entry as well as an entity is created if needed and * if the list of @a remappedAtoms is not empty it is used to remap. * * The array in @a remappedAtoms contains tuples of strings, both * strings contain an atom_id. The first is the one in the current * residue and the second is the atom_id that should be used instead. * If the second string is empty, the atom is removed from the residue. * * @param res * @param newcompound * @param remappedAtoms */ void change_residue(residue &res, const std::string &newcompound, const std::vector> &remappedAtoms); /// \brief Remove a residue, can be monomer or nonpoly /// /// \param asym_id The asym ID /// \param seq_id The sequence ID /// \param auth_seq_id The auth sequence ID void remove_residue(const std::string &asym_id, int seq_id, const std::string &auth_seq_id); /// \brief Create a new non-polymer entity, returns new ID /// \param mon_id The mon_id for the new nonpoly, must be an existing and known compound from CCD /// \return The ID of the created entity std::string create_non_poly_entity(const std::string &mon_id); /// \brief Create a new NonPolymer struct_asym with atoms constructed from \a atoms, returns asym_id. /// This method assumes you are copying data from one cif file to another. /// /// \param entity_id The entity ID of the new nonpoly /// \param atoms The array of atom_site rows containing the data. /// \return The newly create asym ID std::string create_non_poly(const std::string &entity_id, const std::vector &atoms); /// \brief Create a new NonPolymer struct_asym with atoms constructed from info in \a atom_info, returns asym_id. /// This method creates new atom records filled with info from the info. /// /// \param entity_id The entity ID of the new nonpoly /// \param atoms The array of sets of item data containing the data for the atoms. /// \return The newly create asym ID std::string create_non_poly(const std::string &entity_id, std::vector atoms); /// \brief Create a new water with atom constructed from info in \a atom_info /// This method creates a new atom record filled with info from the info. /// /// \param atom The set of item data containing the data for the atoms. void create_water(row_initializer atom); /// \brief Create a new and empty (sugar) branch branch &create_branch(); // /// \brief Create a new (sugar) branch with one first NAG containing atoms constructed from \a atoms // branch &create_branch(std::vector atoms); // /// \brief Extend an existing (sugar) branch identified by \a asymID with one sugar containing atoms constructed from \a atom_info // /// // /// \param asym_id The asym id of the branch to extend // /// \param atom_info Array containing the info for the atoms to construct for the new sugar // /// \param link_sugar The sugar to link to, note: this is the sugar number (1 based) // /// \param link_atom The atom id of the atom linked in the sugar // branch &extend_branch(const std::string &asym_id, std::vector atom_info, // int link_sugar, const std::string &link_atom); /// \brief Remove \a branch void remove_branch(branch &branch); /// \brief Remove residue \a res /// /// \param res The residue to remove void remove_residue(residue &res); /// \brief Translate the coordinates of all atoms in the structure by \a t void translate(point t); /// \brief Rotate the coordinates of all atoms in the structure by \a q void rotate(quaternion t); /// \brief Translate and rotate the coordinates of all atoms in the structure by \a t and \a q void translate_and_rotate(point t, quaternion q); /// \brief Translate, rotate and translate again the coordinates of all atoms in the structure by \a t1 , \a q and \a t2 void translate_rotate_and_translate(point t1, quaternion q, point t2); /// \brief Remove all categories that have no rows left void cleanup_empty_categories(); /// \brief Direct access to underlying data category &get_category(std::string_view name) const { return m_db[name]; } /// \brief Direct access to underlying data datablock &get_datablock() const { return m_db; } /// \brief Check if all atoms are part of either a polymer, a branch or one of the non-polymer residues void validate_atoms() const; /// \brief emplace a newly created atom using @a args template atom &emplace_atom(Args &...args) { return emplace_atom(atom{ std::forward(args)... }); } /// \brief emplace the moved atom @a atom atom &emplace_atom(atom &&atom); private: friend polymer; friend residue; void load_atoms_for_model(StructureOpenOptions options); std::string insert_compound(const std::string &compoundID, bool is_entity); std::string create_entity_for_branch(branch &branch); void load_data(); void remove_atom(atom &a, bool removeFromResidue); void remove_sugar(sugar &sugar); datablock &m_db; size_t m_model_nr; std::vector m_atoms; std::vector m_atom_index; std::list m_polymers; std::list m_branches; std::vector m_non_polymers; }; } // namespace cif::mm