From bf68c3e0f9a6427027d8bff0c64ef99b5a05b6d2 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 14:07:00 +0200 Subject: [PATCH 01/15] docs: add Doxygen/Breathe infrastructure for C++ API reference Wires up Doxygen XML generation + Breathe Sphinx extension so that C++ API documentation from header comments renders in the existing Sphinx/readthedocs site at docs/api.rst. - docs/Doxyfile: Doxygen config (XML-only output, internal headers excluded) - docs/conf.py: runs Doxygen as subprocess, adds breathe extension - docs/api.rst: stub C++ API reference page (expanded by subsequent PRs) - docs/index.rst: replace external cxx-api link with internal api.rst - docs/requirements.txt: add breathe >= 4.35 - .readthedocs.yaml: add apt_packages: [doxygen] - .gitignore: exclude docs/_doxygen/ (generated) Builds on prior work in project-gemmi/gemmi#402 (Paul Emsley / pemsley). Co-authored-by: C. Vonrhein / CV-GPhL --- .gitignore | 3 +++ .readthedocs.yaml | 2 ++ docs/Doxyfile | 48 +++++++++++++++++++++++++++++++++++++++++++ docs/api.rst | 31 ++++++++++++++++++++++++++++ docs/conf.py | 16 ++++++++++++++- docs/index.rst | 2 +- docs/requirements.txt | 1 + 7 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 docs/Doxyfile create mode 100644 docs/api.rst diff --git a/.gitignore b/.gitignore index 0aa5b7ab6..6f22aa373 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,6 @@ fortran/*.a # test programs fortran/fsym fortran/ftest + +# Doxygen-generated output (consumed by Breathe/Sphinx, not committed) +docs/_doxygen/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e86ae0e3c..909080dca 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,6 +10,8 @@ build: os: ubuntu-24.04 tools: python: "3.12" + apt_packages: + - doxygen # Format htmlzip produces a single page. Instead, use zip as shown in: # https://github.com/readthedocs/readthedocs.org/issues/3242#issuecomment-1410321534 diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..d305f175a --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,48 @@ +PROJECT_NAME = "Gemmi" +PROJECT_BRIEF = "A library for macromolecular crystallography" +PROJECT_NUMBER = + +# Input +INPUT = ../include/gemmi +FILE_PATTERNS = *.hpp +STRIP_FROM_PATH = ../include +STRIP_FROM_INC_PATH = ../include +RECURSIVE = NO +# All public headers are flat in include/gemmi/; the only subdirectory is +# third_party/ which must not be documented. + +# Exclude internal/data-only headers +EXCLUDE_PATTERNS = ace_*.hpp \ + acedrg_tables.hpp \ + mc_tables.hpp \ + eig3.hpp \ + cc_adj.hpp \ + ccp4ener.hpp \ + mmcif_impl.hpp + +# Output: XML only (Breathe consumes this; Sphinx produces HTML) +GENERATE_XML = YES +GENERATE_HTML = NO +GENERATE_LATEX = NO +XML_OUTPUT = _doxygen/xml + +# Extraction +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_ANON_NSPACES = NO + +# Preprocessing (needed for GEMMI_DLL export macro and similar) +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +PREDEFINED = GEMMI_DLL= + +# Quiet build, warnings to file +QUIET = YES +WARN_IF_UNDOCUMENTED = NO +WARN_LOGFILE = _doxygen/doxygen-warnings.log + +# Source browsing off (Breathe handles cross-references) +SOURCE_BROWSER = NO +INLINE_SOURCES = NO diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 000000000..2386adfca --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,31 @@ +.. _api: + +C++ API Reference +################# + +This page documents the Gemmi C++ library API, generated from Doxygen comments +in ``include/gemmi/*.hpp``. It is updated with each pull request in the +API documentation series. + +For the Python API, see the `Python API reference `_. + +.. note:: + + Documentation coverage is being added incrementally. Headers not yet + listed here will appear in subsequent pull requests. + +Core Data Structures +-------------------- + +*(Full documentation added in PR 2.)* + +.. doxygenfile:: model.hpp + :project: gemmi + +Map and Grid Data +----------------- + +*(Stub — full documentation added in PR 6.)* + +.. doxygenfile:: grid.hpp + :project: gemmi diff --git a/docs/conf.py b/docs/conf.py index 2d8a8b95b..6bdf72386 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,11 +1,19 @@ # -*- coding: utf-8 -*- +import os +import subprocess + +# Run Doxygen before Sphinx processes doxygenfile:: directives. +# Must run from docs/ so Doxyfile and _doxygen/xml/ paths resolve correctly. +_docs_dir = os.path.dirname(os.path.abspath(__file__)) +subprocess.check_call(['doxygen', 'Doxyfile'], cwd=_docs_dir) + # -- General configuration ------------------------------------------------ # while we use Sphinx 8+, old version suffices to run doctests needs_sphinx = '5.3.0' -extensions = ['sphinx.ext.doctest', 'sphinx_inline_tabs'] +extensions = ['sphinx.ext.doctest', 'sphinx_inline_tabs', 'breathe'] templates_path = ['_templates'] @@ -125,3 +133,9 @@ def _compute_navigation_tree(context: Dict[str, Any]) -> str: import gemmi gemmi.set_leak_warnings(False) ''' + +# -- Breathe configuration (Doxygen XML → Sphinx) ------------------------- + +breathe_projects = {"gemmi": os.path.join(_docs_dir, "_doxygen", "xml")} +breathe_default_project = "gemmi" +breathe_default_members = ('members',) # show all public members in every doxygenfile:: directive diff --git a/docs/index.rst b/docs/index.rst index 4302a3789..c1f524ce6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -93,7 +93,7 @@ Contents ChangeLog Python API reference - C++ API reference + api Credits ======= diff --git a/docs/requirements.txt b/docs/requirements.txt index 223a532b1..804a29253 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ sphinx >= 8.1.3 furo >= 2024.8.6 sphinx-inline-tabs +breathe >= 4.35 From 1198417d9dd1f634b051271553f6d3197b22f7ad Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 19:55:10 +0200 Subject: [PATCH 02/15] =?UTF-8?q?fix(conf.py):=20guard=20doxygen=20call=20?= =?UTF-8?q?=E2=80=94=20skip=20gracefully=20when=20not=20installed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6bdf72386..55fab5e35 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,19 +1,25 @@ # -*- coding: utf-8 -*- import os +import shutil import subprocess -# Run Doxygen before Sphinx processes doxygenfile:: directives. -# Must run from docs/ so Doxyfile and _doxygen/xml/ paths resolve correctly. _docs_dir = os.path.dirname(os.path.abspath(__file__)) -subprocess.check_call(['doxygen', 'Doxyfile'], cwd=_docs_dir) +_doxygen_xml_dir = os.path.join(_docs_dir, "_doxygen", "xml") + +# Run Doxygen before Sphinx processes doxygenfile:: directives. +# Skipped gracefully when doxygen is not installed (e.g. CI doctest runner). +if shutil.which('doxygen'): + subprocess.check_call(['doxygen', 'Doxyfile'], cwd=_docs_dir) # -- General configuration ------------------------------------------------ # while we use Sphinx 8+, old version suffices to run doctests needs_sphinx = '5.3.0' -extensions = ['sphinx.ext.doctest', 'sphinx_inline_tabs', 'breathe'] +extensions = ['sphinx.ext.doctest', 'sphinx_inline_tabs'] +if os.path.isdir(_doxygen_xml_dir): + extensions.append('breathe') templates_path = ['_templates'] @@ -135,7 +141,9 @@ def _compute_navigation_tree(context: Dict[str, Any]) -> str: ''' # -- Breathe configuration (Doxygen XML → Sphinx) ------------------------- +# Only active when _doxygen/xml/ exists (i.e. doxygen was run). -breathe_projects = {"gemmi": os.path.join(_docs_dir, "_doxygen", "xml")} -breathe_default_project = "gemmi" -breathe_default_members = ('members',) # show all public members in every doxygenfile:: directive +if os.path.isdir(_doxygen_xml_dir): + breathe_projects = {"gemmi": _doxygen_xml_dir} + breathe_default_project = "gemmi" + breathe_default_members = ('members',) # show all public members in every doxygenfile:: directive From 1e4105153806e95a45bbb72a21c9d4d66d983113 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 20:02:20 +0200 Subject: [PATCH 03/15] ci: add docs-build job (Doxygen + Sphinx/Breathe HTML) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f399317c4..fdfc9c30c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,6 +217,22 @@ jobs: - name: run tests under valgrind run: PYTHONMALLOC=malloc valgrind python3 -m unittest discover -v -s tests/ + docs: + name: "Docs build (Doxygen + Sphinx/Breathe)" + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y doxygen + python3 -m pip install sphinx sphinx-inline-tabs breathe furo + - name: Build HTML docs + run: | + cd docs + sphinx-build -M html . _build -n -E + almalinux: runs-on: ubuntu-latest name: "AlmaLinux 8" From d49a086a273e68187b819d0394d99ff4b4ad4edf Mon Sep 17 00:00:00 2001 From: Clemens Vonrhein Date: Wed, 22 Apr 2026 23:27:59 +0200 Subject: [PATCH 04/15] docs(chemcomp.hpp): add full Doxygen API documentation Add comprehensive /// comments to all public enums, structs, nested types, and functions in chemcomp.hpp, including: - BondType and ChiralityType enums with value descriptions - Restraints struct with nested AtomId, Bond, Angle, Torsion, Chirality, Plane types - All Restraints methods for finding and managing bond, angle, torsion, chirality, and planarity restraints - ChemComp struct with Group enum, Atom and Aliasing nested types - ChemComp atom lookup and manipulation methods - Standalone utility functions for bond/chirality type conversion - make_chemcomp_from_block CIF parser All documentation follows Doxygen conventions with @brief, @param, @return, @tparam, @throws tags as appropriate. Co-Authored-By: Claude Opus 4.7 (1M context) --- include/gemmi/chemcomp.hpp | 385 ++++++++++++++++++++++++++++++------- 1 file changed, 313 insertions(+), 72 deletions(-) diff --git a/include/gemmi/chemcomp.hpp b/include/gemmi/chemcomp.hpp index e037ccc76..23f8b4806 100644 --- a/include/gemmi/chemcomp.hpp +++ b/include/gemmi/chemcomp.hpp @@ -22,63 +22,111 @@ namespace gemmi { struct Atom; struct Residue; +/// Bond type enum for restraints. enum class BondType { - Unspec, Single, Double, Triple, Aromatic, Deloc, Metal + Unspec, ///< Unspecified bond type + Single, ///< Single bond + Double, ///< Double bond + Triple, ///< Triple bond + Aromatic, ///< Aromatic bond + Deloc, ///< Delocalized bond + Metal ///< Metal coordination bond }; +/// @brief Check if bond type is aromatic or delocalized. +/// @param type Bond type to check. +/// @return True if type is Aromatic or Deloc. inline bool is_aromatic_or_deloc(BondType type) { return type == BondType::Aromatic || type == BondType::Deloc; } -enum class ChiralityType { Positive, Negative, Both }; +/// Chirality type enum for stereocenters. +enum class ChiralityType { + Positive, ///< Positive (S/R) chirality + Negative, ///< Negative chirality + Both ///< Either chirality accepted +}; +/// Geometric restraints for a chemical component. +/// Stores bond, angle, torsion, chirality, and planarity restraints. struct Restraints { + /// Atom identifier used in restraints. struct AtomId { - int comp; - std::string atom; + int comp; ///< Component index (1 or 2 for link restraints) + std::string atom; ///< Atom name + /// @brief Equality comparison. bool operator==(const AtomId& o) const { return comp == o.comp && atom == o.atom; } + /// @brief Inequality comparison. bool operator!=(const AtomId& o) const { return !operator==(o); } + /// @brief Equality comparison with atom name string. bool operator==(const std::string& name) const { return atom == name; } + /// @brief Inequality comparison with atom name string. bool operator!=(const std::string& name) const { return atom != name; } + /// @brief Lexicographic comparison. bool operator<(const AtomId& o) const { return comp == o.comp ? atom < o.atom : comp < o.comp; } - // altloc2 is needed only in rare case when we have a link between - // atoms with different altloc (example: 2e7z). + /// @brief Get the Atom from residues. + /// @param res1 First residue to search. + /// @param res2 Optional second residue for link restraints. + /// @param alt Alternate location character. + /// @param altloc2 Alternate location for second residue (rare case for links with different altloc). + /// @return Pointer to the Atom, or nullptr if not found. Atom* get_from(Residue& res1, Residue* res2, char alt, char altloc2) const; + /// @brief Const version of get_from(). const Atom* get_from(const Residue& res1, const Residue* res2, char alt, char alt2) const; }; + /// @brief Get canonical lexicographic string representation of two atom names. + /// @param name1 First atom name. + /// @param name2 Second atom name. + /// @return Hyphen-separated pair in lexicographic order. static std::string lexicographic_str(const std::string& name1, const std::string& name2) { return name1 < name2 ? cat(name1, '-', name2) : cat(name2, '-', name1); } - enum class DistanceOf { ElectronCloud, Nucleus }; + /// Reference frame for bond distance measurement. + enum class DistanceOf { + ElectronCloud, ///< Distance to electron cloud centre + Nucleus ///< Distance to nucleus + }; + /// Bond restraint between two atoms. struct Bond { + /// @brief Get restraint type name. static const char* what() { return "bond"; } - AtomId id1, id2; - BondType type; - bool aromatic; - double value; - double esd; - double value_nucleus; - double esd_nucleus; - std::string stereo_config = ""; - int ordinal = 0; + AtomId id1; ///< First atom + AtomId id2; ///< Second atom + BondType type; ///< Bond type + bool aromatic; ///< True if part of aromatic system + double value; ///< Ideal bond length (Å, electron cloud) + double esd; ///< Estimated standard deviation of value + double value_nucleus; ///< Ideal length to nucleus + double esd_nucleus; ///< ESD of nucleus length + std::string stereo_config = ""; ///< Stereo configuration character + int ordinal = 0; ///< Ordering index + /// @brief Get string representation (non-canonical). std::string str() const { return cat(id1.atom, '-', id2.atom); } + /// @brief Get canonical (lexicographic) string representation. std::string lexicographic_str() const { return Restraints::lexicographic_str(id1.atom, id2.atom); } + /// @brief Get ideal bond distance. + /// @param of Reference frame (electron cloud or nucleus). + /// @return Ideal distance in Å. double distance(DistanceOf of) const { return of == DistanceOf::ElectronCloud ? value : value_nucleus; } + /// @brief Find the other atom in the bond. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a First atom identifier. + /// @return Pointer to the other AtomId, or nullptr if a is not in this bond. template const AtomId* other(const T& a) const { if (id1 == a) return &id2; if (id2 == a) return &id1; @@ -86,74 +134,113 @@ struct Restraints { } }; + /// Angle restraint between three atoms. struct Angle { + /// @brief Get restraint type name. static const char* what() { return "angle"; } - AtomId id1, id2, id3; - double value; // degrees - double esd; + AtomId id1; ///< First atom + AtomId id2; ///< Central atom + AtomId id3; ///< Third atom + double value; ///< Ideal angle in degrees + double esd; ///< Estimated standard deviation in degrees + /// @brief Convert ideal angle to radians. + /// @return Ideal angle in radians. double radians() const { return rad(value); } + /// @brief Get string representation. std::string str() const { return cat(id1.atom, '-', id2.atom, '-', id3.atom); } }; + /// Torsion (dihedral) restraint between four atoms. struct Torsion { + /// @brief Get restraint type name. static const char* what() { return "torsion"; } - std::string label; - AtomId id1, id2, id3, id4; - double value = NAN; - double esd = 0.0; - int period = 0; + std::string label; ///< Torsion identifier string + AtomId id1; ///< First atom + AtomId id2; ///< Second atom (first bond partner) + AtomId id3; ///< Third atom (second bond partner) + AtomId id4; ///< Fourth atom + double value = NAN; ///< Ideal torsion angle in degrees + double esd = 0.0; ///< Estimated standard deviation in degrees + int period = 0; ///< Periodicity of the torsion + /// @brief Get string representation. std::string str() const { return cat(id1.atom, '-', id2.atom, '-', id3.atom, '-', id4.atom); } }; + /// Chirality (stereochemistry) restraint for a stereocenter. struct Chirality { + /// @brief Get restraint type name. static const char* what() { return "chirality"; } - AtomId id_ctr, id1, id2, id3; - ChiralityType sign; - + AtomId id_ctr; ///< Chiral centre atom + AtomId id1; ///< First substituent + AtomId id2; ///< Second substituent + AtomId id3; ///< Third substituent + ChiralityType sign; ///< Expected chirality type + + /// @brief Check if observed chiral volume contradicts expected chirality. + /// @param volume Computed chiral volume. + /// @return True if the sign of volume disagrees with expected chirality. bool is_wrong(double volume) const { return (sign == ChiralityType::Positive && volume < 0) || (sign == ChiralityType::Negative && volume > 0); } + /// @brief Get string representation. std::string str() const { return cat(id_ctr.atom, ',', id1.atom, ',', id2.atom, ',', id3.atom); } }; + /// Planarity restraint for a group of atoms. struct Plane { + /// @brief Get restraint type name. static const char* what() { return "plane"; } - std::string label; - std::vector ids; - double esd; + std::string label; ///< Plane identifier string + std::vector ids; ///< Atoms defining the plane + double esd; ///< Estimated standard deviation of planarity restraint + /// @brief Get string representation. std::string str() const { return join_str(ids, ',', [](const AtomId& a) { return a.atom; }); } }; - std::vector bonds; - std::vector angles; - std::vector torsions; - std::vector chirs; - std::vector planes; + std::vector bonds; ///< Bond restraints + std::vector angles; ///< Angle restraints + std::vector torsions; ///< Torsion restraints + std::vector chirs; ///< Chirality restraints + std::vector planes; ///< Planarity restraints + /// @brief Check if all restraint lists are empty. + /// @return True if there are no restraints of any type. bool empty() const { return bonds.empty() && angles.empty() && torsions.empty() && chirs.empty() && planes.empty(); } + /// @brief Find a Bond between two atoms. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a1 First atom. + /// @param a2 Second atom. + /// @return Iterator to the Bond, or bonds.end() if not found. + /// @note Bond order (a1, a2 vs a2, a1) is not significant. template std::vector::iterator find_bond(const T& a1, const T& a2) { return std::find_if(bonds.begin(), bonds.end(), [&](const Bond& b) { return (b.id1 == a1 && b.id2 == a2) || (b.id1 == a2 && b.id2 == a1); }); } + /// @brief Const version of find_bond(). template std::vector::const_iterator find_bond(const T& a1, const T& a2) const { return const_cast(this)->find_bond(a1, a2); } + /// @brief Get a Bond between two atoms (throw if not found). + /// @param a1 First atom. + /// @param a2 Second atom. + /// @return Reference to the Bond. + /// @throws Calls fail() if bond is not found. const Bond& get_bond(const AtomId& a1, const AtomId& a2) const { auto it = find_bond(a1, a2); if (it == bonds.end()) @@ -161,11 +248,20 @@ struct Restraints { return *it; } + /// @brief Check if two atoms are directly bonded. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a1 First atom. + /// @param a2 Second atom. + /// @return True if a bond exists between a1 and a2. template bool are_bonded(const T& a1, const T& a2) const { return find_bond(a1, a2) != bonds.end(); } + /// @brief Find the first atom bonded to the given atom. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a Atom to search for bonds from. + /// @return Pointer to the first bonded AtomId, or nullptr if none found. template const AtomId* first_bonded_atom(const T& a) const { for (const Bond& bond : bonds) @@ -174,7 +270,12 @@ struct Restraints { return nullptr; } - // BFS + /// @brief Find shortest bond path between two atoms (BFS algorithm). + /// @param a Start atom. + /// @param b End atom. + /// @param visited List of initially visited atoms (to exclude from search). + /// @param min_length Minimum path length required (default 1). + /// @return Vector of AtomIds forming the shortest path from b to a, or empty if not found. std::vector find_shortest_path(const AtomId& a, const AtomId& b, std::vector visited, int min_length=1) const { @@ -210,6 +311,13 @@ struct Restraints { return path; } + /// @brief Find an Angle restraint with given atoms. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a First atom (peripheral). + /// @param b Central atom. + /// @param c Third atom (peripheral). + /// @return Iterator to the Angle, or angles.end() if not found. + /// @note The order of peripheral atoms (a, c) is not significant. template std::vector::iterator find_angle(const T& a, const T& b, const T& c) { return std::find_if(angles.begin(), angles.end(), [&](const Angle& ang) { @@ -217,10 +325,17 @@ struct Restraints { (ang.id1 == c && ang.id3 == a)); }); } + /// @brief Const version of find_angle(). template std::vector::const_iterator find_angle(const T& a, const T& b, const T& c) const { return const_cast(this)->find_angle(a, b, c); } + /// @brief Get an Angle restraint with given atoms (throw if not found). + /// @param a First atom (peripheral). + /// @param b Central atom. + /// @param c Third atom (peripheral). + /// @return Reference to the Angle. + /// @throws Calls fail() if angle restraint is not found. const Angle& get_angle(const AtomId& a, const AtomId& b, const AtomId& c) const { auto it = const_cast(this)->find_angle(a, b, c); if (it == angles.end()) @@ -228,6 +343,14 @@ struct Restraints { return *it; } + /// @brief Find a Torsion restraint with given atoms. + /// @tparam T Atom identifier type (AtomId or string). + /// @param a First atom. + /// @param b Second atom (first bond partner). + /// @param c Third atom (second bond partner). + /// @param d Fourth atom. + /// @return Iterator to the Torsion, or torsions.end() if not found. + /// @note Forward and reverse orderings (a-b-c-d vs d-c-b-a) are considered equivalent. template std::vector::iterator find_torsion(const T& a, const T& b, const T& c, const T& d) { @@ -237,11 +360,20 @@ struct Restraints { (t.id1 == d && t.id2 == c && t.id3 == b && t.id4 == a); }); } + /// @brief Const version of find_torsion(). template std::vector::const_iterator find_torsion(const T& a, const T& b, const T& c, const T& d) const { return const_cast(this)->find_torsion(a, b, c, d); } + /// @brief Find a Chirality restraint for a given stereocenter. + /// @tparam T Atom identifier type (AtomId or string). + /// @param ctr Chiral centre atom. + /// @param a First substituent. + /// @param b Second substituent. + /// @param c Third substituent. + /// @return Iterator to the Chirality, or chirs.end() if not found. + /// @note The order of substituents (a, b, c) is not significant. template std::vector::iterator find_chir(const T& ctr, const T& a, const T& b, const T& c) { @@ -251,18 +383,29 @@ struct Restraints { (t.id1 == c && t.id2 == a && t.id3 == b)); }); } + /// @brief Const version of find_chir(). template std::vector::const_iterator find_chir(const T& ctr, const T& a, const T& b, const T& c) const { return const_cast(this)->find_chir(ctr, a, b, c); } + /// @brief Compute chiral volume from restraints. + /// @param ch Chirality restraint. + /// @return Absolute chiral volume computed from bond and angle restraints. + /// @throws May call fail() if required bond or angle restraints are missing. double chiral_abs_volume(const Restraints::Chirality& ch) const; + /// @brief Find a Plane by label. + /// @param label Plane identifier string. + /// @return Iterator to the Plane, or planes.end() if not found. std::vector::iterator get_plane(const std::string& label) { return std::find_if(planes.begin(), planes.end(), [&label](const Plane& p) { return p.label == label; }); } + /// @brief Get a Plane by label, creating it if absent. + /// @param label Plane identifier string. + /// @return Reference to the Plane (newly created with esd=0.0 if it didn't exist). Plane& get_or_add_plane(const std::string& label) { std::vector::iterator it = get_plane(label); if (it != planes.end()) @@ -271,6 +414,10 @@ struct Restraints { return planes.back(); } + /// @brief Rename an atom throughout all restraints. + /// @param atom_id The atom to rename (identified by comp and atom name). + /// @param new_name New atom name. + /// @note Updates all occurrences in bonds, angles, torsions, chiralities, and planes. void rename_atom(const AtomId& atom_id, const std::string& new_name) { auto rename_atom = [&](AtomId& id) { if (id == atom_id) @@ -303,11 +450,26 @@ struct Restraints { } }; +/// @brief Compute z-score (deviation in standard deviations) for angle restraints. +/// @tparam Restr Restraint type with value (degrees) and esd (degrees) members. +/// @param value_rad Observed angle in radians. +/// @param restr Restraint with ideal value and standard deviation. +/// @param full Full circle in degrees (default 360, use 180 for some torsions). +/// @return Z-score = |observed - ideal| / esd. template double angle_z(double value_rad, const Restr& restr, double full=360.) { return angle_abs_diff(deg(value_rad), restr.value, full) / restr.esd; } +/// @brief Compute absolute chiral volume from bond lengths and angles. +/// @param bond1 First bond length (Å). +/// @param bond2 Second bond length (Å). +/// @param bond3 Third bond length (Å). +/// @param angle1 First angle (degrees). +/// @param angle2 Second angle (degrees). +/// @param angle3 Third angle (degrees). +/// @return Absolute chiral volume. +/// @note Uses the formula: mult * sqrt(max(0, x + y)) where mult = bond1*bond2*bond3. inline double chiral_abs_volume(double bond1, double bond2, double bond3, double angle1, double angle2, double angle3) { double mult = bond1 * bond2 * bond3; @@ -330,42 +492,48 @@ inline double Restraints::chiral_abs_volume(const Restraints::Chirality& ch) con get_angle(ch.id3, ch.id_ctr, ch.id1).value); } +/// Chemical component (monomer) from a restraint library. +/// Represents a residue type from the Refmac monomer library or PDB CCD. struct ChemComp { - // Items used in _chem_comp.group and _chem_link.group_comp_N in CCP4. + /// Chemical component group classification (used in _chem_comp.group and _chem_link.group_comp_N). enum class Group { - Peptide, // "peptide" - PPeptide, // "P-peptide" - MPeptide, // "M-peptide" - Dna, // "DNA" - used in _chem_comp.group - Rna, // "RNA" - used in _chem_comp.group - DnaRna, // "DNA/RNA" - used in _chem_link.group_comp_N - Pyranose, // "pyranose" - Ketopyranose, // "ketopyranose" - Furanose, // "furanose" - NonPolymer, // "non-polymer" - Null + Peptide, ///< Peptide (L-amino acid) + PPeptide, ///< P-peptide (peptide with P configuration) + MPeptide, ///< M-peptide (cyclic peptide) + Dna, ///< DNA nucleotide + Rna, ///< RNA nucleotide + DnaRna, ///< DNA/RNA mixed nucleotide + Pyranose, ///< Pyranose sugar ring + Ketopyranose, ///< Ketopyranose sugar ring + Furanose, ///< Furanose sugar ring + NonPolymer, ///< Non-polymer ligand + Null ///< Unset or unknown group }; + /// Atom in a chemical component. struct Atom { - std::string id; - std::string old_id; // read from _chem_comp_atom.alt_atom_id - Element el = El::X; - // _chem_comp_atom.partial_charge can be non-integer, - // _chem_comp_atom.charge is always integer (but sometimes has format - // '0.000' which is not correct but we ignore it). - float charge = 0; - std::string chem_type; - std::string acedrg_type; // read from _chem_comp_atom.atom_type - Position xyz{NAN, NAN, NAN}; - + std::string id; ///< Atom name + std::string old_id; ///< Legacy atom name (read from _chem_comp_atom.alt_atom_id) + Element el = El::X; ///< Chemical element + float charge = 0; ///< Formal charge (can be non-integer for partial_charge) + std::string chem_type; ///< CCP4 chemical type string + std::string acedrg_type; ///< ACEdrg atom type (read from _chem_comp_atom.atom_type) + Position xyz{NAN, NAN, NAN}; ///< Idealized Cartesian coordinates (Å) + + /// @brief Check if this is a hydrogen atom. + /// @return True if element is hydrogen. bool is_hydrogen() const { return gemmi::is_hydrogen(el); } }; + /// Atom naming aliasing for a specific polymer group. struct Aliasing { - Group group; - // pairs of (name in chem_comp, usual name in this group) + Group group; ///< Polymer group this aliasing applies to + /// Pairs of (chem_comp name, standard name in this group) std::vector> related; + /// @brief Find chem_comp name from standard atom name. + /// @param atom_id Standard atom name (e.g., "CA" for peptide). + /// @return Pointer to the chem_comp atom name, or nullptr if not in aliasing. const std::string* name_from_alias(const std::string& atom_id) const { for (const auto& item : related) if (item.second == atom_id) @@ -374,14 +542,18 @@ struct ChemComp { } }; - std::string name; - std::string type_or_group; // _chem_comp.type or _chem_comp.group - Group group = Group::Null; - bool has_coordinates = false; - std::vector atoms; - std::vector aliases; - Restraints rt; - + std::string name; ///< Three-letter component code + std::string type_or_group; ///< Raw type/group string from CIF (_chem_comp.type or _chem_comp.group) + Group group = Group::Null; ///< Parsed Group enum + bool has_coordinates = false; ///< True if xyz coordinates are available + std::vector atoms; ///< Atoms in this component + std::vector aliases; ///< Atom name aliases for different polymer groups + Restraints rt; ///< Geometric restraints + + /// @brief Get atom name aliasing for a specific polymer group. + /// @param g Group to find aliasing for. + /// @return Reference to the Aliasing. + /// @throws Calls fail() if aliasing is not found for this group. const Aliasing& get_aliasing(Group g) const { for (const Aliasing& aliasing : aliases) if (aliasing.group == g) @@ -389,6 +561,9 @@ struct ChemComp { fail("aliasing not found"); } + /// @brief Parse group string to Group enum. + /// @param str Group identifier string (e.g., "peptide", "P-peptide", "DNA"). + /// @return Parsed Group enum value, or Group::Null if unrecognized. static Group read_group(const std::string& str) { if (str.size() >= 3) { const char* cstr = str.c_str(); @@ -411,6 +586,9 @@ struct ChemComp { return Group::Null; } + /// @brief Get string representation of a Group enum value. + /// @param g Group enum value. + /// @return String representation (e.g., "peptide", "P-peptide", "DNA", "."). static const char* group_str(Group g) { switch (g) { case Group::Peptide: return "peptide"; @@ -428,34 +606,53 @@ struct ChemComp { unreachable(); } + /// @brief Set group from string and update parsed Group enum. + /// @param s Group identifier string. void set_group(const std::string& s) { type_or_group = s; group = read_group(s); } + /// @brief Find an atom by name. + /// @param atom_id Atom name to search for. + /// @return Iterator to the Atom, or atoms.end() if not found. std::vector::iterator find_atom(const std::string& atom_id) { return std::find_if(atoms.begin(), atoms.end(), [&](const Atom& a) { return a.id == atom_id; }); } + /// @brief Const version of find_atom(). std::vector::const_iterator find_atom(const std::string& atom_id) const { return const_cast(this)->find_atom(atom_id); } + /// @brief Check if an atom with given name exists. + /// @param atom_id Atom name to search for. + /// @return True if atom exists. bool has_atom(const std::string& atom_id) const { return find_atom(atom_id) != atoms.end(); } + /// @brief Find an atom by legacy name. + /// @param old_id Legacy atom name (old_id field). + /// @return Iterator to the Atom, or atoms.end() if not found. std::vector::iterator find_atom_by_old_name(const std::string& old_id) { return std::find_if(atoms.begin(), atoms.end(), [&](const Atom& a) { return a.old_id == old_id; }); } + /// @brief Const version of find_atom_by_old_name(). std::vector::const_iterator find_atom_by_old_name(const std::string& old_id) const { return const_cast(this)->find_atom_by_old_name(old_id); } + /// @brief Check if any atom has non-trivial legacy names. + /// @return True if at least one atom has old_id set and different from id. bool has_old_names() const { return std::any_of(atoms.begin(), atoms.end(), [&](const Atom& a) { return !a.old_id.empty() && a.old_id != a.id; }); } + /// @brief Get index of an atom by name (throw if not found). + /// @param atom_id Atom name to search for. + /// @return Zero-based index in atoms vector. + /// @throws Calls fail() if atom is not found. int get_atom_index(const std::string& atom_id) const { auto it = find_atom(atom_id); if (it == atoms.end()) @@ -463,11 +660,16 @@ struct ChemComp { return int(it - atoms.begin()); } + /// @brief Find index of an atom by name. + /// @param atom_id Atom name to search for. + /// @return Zero-based index in atoms vector, or -1 if not found. int find_atom_index(const std::string& atom_id) const { auto it = find_atom(atom_id); return it != atoms.end() ? int(it - atoms.begin()) : -1; } + /// @brief Build a map of atom names to indices. + /// @return Map from atom id to vector index. std::map make_atom_index() const { std::map atom_index; for (size_t i = 0; i < atoms.size(); ++i) @@ -475,20 +677,30 @@ struct ChemComp { return atom_index; } + /// @brief Get an atom by name (throw if not found). + /// @param atom_id Atom name to search for. + /// @return Reference to the Atom. + /// @throws Calls get_atom_index() which may call fail(). const Atom& get_atom(const std::string& atom_id) const { return atoms[get_atom_index(atom_id)]; } - /// Check if the group (M-|P-)peptide + /// @brief Check if group is a peptide variant. + /// @param g Group enum value. + /// @return True if group is Peptide, PPeptide, or MPeptide. static bool is_peptide_group(Group g) { return g == Group::Peptide || g == Group::PPeptide || g == Group::MPeptide; } - /// Check if the group is DNA/RNA + /// @brief Check if group is a nucleic acid variant. + /// @param g Group enum value. + /// @return True if group is Dna, Rna, or DnaRna. static bool is_nucleotide_group(Group g) { return g == Group::Dna || g == Group::Rna || g == Group::DnaRna; } + /// @brief Remove restraints referring to absent atoms. + /// Called after atoms have been removed to keep restraints consistent. void remove_nonmatching_restraints() { vector_remove_if(rt.bonds, [&](const Restraints::Bond& x) { return !has_atom(x.id1.atom) || @@ -517,6 +729,8 @@ struct ChemComp { }); } + /// @brief Remove all hydrogen atoms and update restraints. + /// @return Reference to this ChemComp (for method chaining). ChemComp& remove_hydrogens() { vector_remove_if(atoms, [](const ChemComp::Atom& a) { return a.is_hydrogen(); @@ -526,6 +740,10 @@ struct ChemComp { } }; +/// @brief Parse string to BondType enum. +/// @param s String representation (e.g., "single", "double", "aromatic", "deloc", "metal"). +/// @return Parsed BondType, or Unspec for null or "coval". +/// @throws std::out_of_range for unexpected bond type strings. inline BondType bond_type_from_string(const std::string& s) { if (s.size() >= 3) switch (ialpha4_id(s.c_str())) { @@ -544,6 +762,9 @@ inline BondType bond_type_from_string(const std::string& s) { throw std::out_of_range("Unexpected bond type: " + s); } +/// @brief Convert BondType enum to string. +/// @param btype Bond type to convert. +/// @return String representation (".", "single", "double", "triple", "aromatic", "deloc", "metal"). inline const char* bond_type_to_string(BondType btype) { switch (btype) { case BondType::Unspec: return "."; @@ -557,6 +778,9 @@ inline const char* bond_type_to_string(BondType btype) { unreachable(); } +/// @brief Get bond order (multiplicity) for a BondType. +/// @param btype Bond type. +/// @return Bond order: 1.0 (single/metal), 1.5 (aromatic/deloc), 2.0 (double), 3.0 (triple), 0.0 (unspec). inline float order_of_bond_type(BondType btype) { switch (btype) { case BondType::Single: return 1.0f; @@ -570,7 +794,11 @@ inline float order_of_bond_type(BondType btype) { unreachable(); } -// it doesn't handle crossN types from the monomer library +/// @brief Parse string to ChiralityType enum. +/// @param s String representation: "p" or "P" for Positive, "n" or "N" for Negative, +/// "b" or "B" or "." for Both. +/// @return Parsed ChiralityType. +/// @throws std::out_of_range for unexpected chirality strings (e.g., crossN types). inline ChiralityType chirality_from_string(const std::string& s) { switch (s[0] | 0x20) { case 'p': return ChiralityType::Positive; @@ -581,6 +809,12 @@ inline ChiralityType chirality_from_string(const std::string& s) { } } +/// @brief Determine ChiralityType from stereo flag and computed chiral volume. +/// @param s Volume flag string: "s" or "S" (signed volume), "n" or "N" (no stereochemistry). +/// @param volume Computed chiral volume. +/// @return ChiralityType: Positive or Negative based on volume sign (if "s" flag), +/// or Both (if "n" flag). +/// @throws std::out_of_range for unexpected flag strings. inline ChiralityType chirality_from_flag_and_volume(const std::string& s, double volume) { switch (s[0] | 0x20) { @@ -591,6 +825,9 @@ inline ChiralityType chirality_from_flag_and_volume(const std::string& s, } } +/// @brief Convert ChiralityType enum to string. +/// @param chir_type Chirality type. +/// @return String representation ("positive", "negative", "both"). inline const char* chirality_to_string(ChiralityType chir_type) { switch (chir_type) { case ChiralityType::Positive: return "positive"; @@ -600,6 +837,10 @@ inline const char* chirality_to_string(ChiralityType chir_type) { unreachable(); } +/// @brief Parse a ChemComp from a CIF block. +/// Reads all _chem_comp* tables from the block (atoms, bonds, angles, torsions, chiralities, planes, aliases). +/// @param block_ CIF block containing chemical component definition. +/// @return Constructed ChemComp with all restraints and atom data. inline ChemComp make_chemcomp_from_block(const cif::Block& block_) { ChemComp cc; cc.name = block_.name.substr(starts_with(block_.name, "comp_") ? 5 : 0); From 0e5d304d30aa983c15e7f32cd701d858d0f49976 Mon Sep 17 00:00:00 2001 From: Clemens Vonrhein Date: Wed, 22 Apr 2026 23:28:11 +0200 Subject: [PATCH 05/15] docs(chemcomp_xyz.hpp): add full Doxygen API documentation Enhance /// comments for the two main functions: - generate_chemcomp_xyz_from_restraints: Detailed description of idealized coordinate generation from bond/angle/torsion restraints - refine_chemcomp_xyz: Levenberg-Marquardt refinement against restraints Add @brief, @param, @return tags with parameter descriptions and implementation notes about coordinate initialization and optimization. Co-Authored-By: Claude Opus 4.7 (1M context) --- include/gemmi/chemcomp_xyz.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/gemmi/chemcomp_xyz.hpp b/include/gemmi/chemcomp_xyz.hpp index 36a56c17b..ce706305e 100644 --- a/include/gemmi/chemcomp_xyz.hpp +++ b/include/gemmi/chemcomp_xyz.hpp @@ -9,12 +9,20 @@ namespace gemmi { -/// Generate a deterministic idealized conformer from bond/angle/torsion -/// restraints. Returns the number of atoms assigned finite coordinates. +/// @brief Generate idealized 3D coordinates for a chemical component. +/// Generates a deterministic idealized conformer by applying bond lengths, +/// angles, and torsion restraints in sequence. Modifies cc.atoms[*].xyz in-place. +/// @param cc ChemComp to generate coordinates for; atoms must be present. +/// @return Number of atoms assigned finite coordinates. +/// @note Atoms without restraints may remain uninitialized (NAN coordinates). GEMMI_DLL int generate_chemcomp_xyz_from_restraints(ChemComp& cc); -/// Refine monomer coordinates against bond and angle restraints -/// using Levenberg-Marquardt least squares. Returns final WSSR. +/// @brief Refine chemical component coordinates against restraints. +/// Refines atom coordinates against bond and angle restraints using +/// Levenberg-Marquardt least squares optimization. Modifies cc.atoms[*].xyz in-place. +/// @param cc ChemComp with initial coordinates to refine. +/// @return Final weighted sum of squared residuals (WSSR) of the fit. +/// @note Requires atoms to have initial finite coordinates (e.g., from generate_chemcomp_xyz_from_restraints). GEMMI_DLL double refine_chemcomp_xyz(ChemComp& cc); } // namespace gemmi From b926c7a823a010c9adc454f7f1c9d2104aad0767 Mon Sep 17 00:00:00 2001 From: Clemens Vonrhein Date: Wed, 22 Apr 2026 23:33:49 +0200 Subject: [PATCH 06/15] fix: correct two doc errors in chemcomp.hpp - find_shortest_path @return: path goes from a to b, not b to a - get_from const overload: rename alt2 -> altloc2 to match non-const signature Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/chemcomp.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/gemmi/chemcomp.hpp b/include/gemmi/chemcomp.hpp index 23f8b4806..5db2a4cdd 100644 --- a/include/gemmi/chemcomp.hpp +++ b/include/gemmi/chemcomp.hpp @@ -79,7 +79,7 @@ struct Restraints { Atom* get_from(Residue& res1, Residue* res2, char alt, char altloc2) const; /// @brief Const version of get_from(). const Atom* get_from(const Residue& res1, const Residue* res2, - char alt, char alt2) const; + char alt, char altloc2) const; }; /// @brief Get canonical lexicographic string representation of two atom names. @@ -275,7 +275,7 @@ struct Restraints { /// @param b End atom. /// @param visited List of initially visited atoms (to exclude from search). /// @param min_length Minimum path length required (default 1). - /// @return Vector of AtomIds forming the shortest path from b to a, or empty if not found. + /// @return Vector of AtomIds forming the shortest path from a to b, or empty if not found. std::vector find_shortest_path(const AtomId& a, const AtomId& b, std::vector visited, int min_length=1) const { From 5a8a9df5138f96495ce8d5230fe5fe7188c1fd81 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 23:38:17 +0200 Subject: [PATCH 07/15] docs: add Doxygen API documentation to ener_lib.hpp Add comprehensive Doxygen documentation for EnerLib struct and its nested components including RadiusType enum, Atom and Bond structs, along with free function operators. Follows Gemmi documentation standards. Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/ener_lib.hpp | 55 +++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/include/gemmi/ener_lib.hpp b/include/gemmi/ener_lib.hpp index af7dba515..f6d72ebde 100644 --- a/include/gemmi/ener_lib.hpp +++ b/include/gemmi/ener_lib.hpp @@ -14,24 +14,37 @@ namespace gemmi { +/// @brief Energy library from CCP4 ener_lib.cif. +/// Stores atomic properties and ideal bond parameters used for structure validation. struct GEMMI_DLL EnerLib { - enum class RadiusType {Vdw, Vdwh, Ion}; + /// @brief Atom radius types. + enum class RadiusType { + Vdw, ///< Van der Waals radius + Vdwh, ///< Van der Waals radius with hydrogen + Ion ///< Ionic radius + }; + + /// @brief Atomic properties indexed by atom type. struct Atom { - Element element; - char hb_type; - double vdw_radius; - double vdwh_radius; - double ion_radius; - int valency; - int sp; + Element element; ///< Chemical element + char hb_type; ///< Hydrogen bond type + double vdw_radius; ///< Van der Waals radius + double vdwh_radius; ///< Van der Waals radius (hydrogen atoms) + double ion_radius; ///< Ionic radius + int valency; ///< Valence + int sp; ///< sp hybridization state }; + + /// @brief Ideal bond parameters. struct Bond { - std::string atom_type_1; - std::string atom_type_2; - BondType type; - double length; - double value_esd; + std::string atom_type_1; ///< First atom type + std::string atom_type_2; ///< Second atom type + BondType type; ///< Bond type + double length; ///< Ideal bond length + double value_esd; ///< Standard deviation + /// @brief Comparison operator for sorting by atom types. + /// Sorts first by atom_type_1, then by atom_type_2. bool operator<(const Bond& o) const { if (atom_type_1 != o.atom_type_1) return atom_type_1 < o.atom_type_1; @@ -40,15 +53,27 @@ struct GEMMI_DLL EnerLib { }; EnerLib() {} + + /// @brief Read energy library data from a CIF document. + /// @param doc CIF document containing ener_lib data tables void read(const cif::Document& doc); - std::map atoms; // type->Atom - std::vector bonds; + + std::map atoms; ///< Atom properties indexed by type + std::vector bonds; ///< Ideal bond parameters }; +/// @brief Compare bond with atom type string (for binary search). +/// @param lhs Bond to compare +/// @param rhs Atom type string +/// @return true if bond's first atom type is less than the string inline bool operator<(const EnerLib::Bond& lhs, const std::string& rhs) { return lhs.atom_type_1 < rhs; } +/// @brief Compare atom type string with bond (for binary search). +/// @param lhs Atom type string +/// @param rhs Bond to compare +/// @return true if string is less than bond's first atom type inline bool operator<(const std::string& lhs, const EnerLib::Bond& rhs) { return lhs < rhs.atom_type_1; } From 090a515c31b8464a8a13473fdc9d0ddcd4b3270f Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 23:39:19 +0200 Subject: [PATCH 08/15] docs: add Doxygen API documentation to monlib.hpp Add comprehensive Doxygen documentation for MonLib, ChemLink, ChemMod structs and all their nested types, along with free functions. Document all data members, methods, and parameters. Mark deprecated read_monomer_lib free function with @deprecated tag. Follows Gemmi documentation standards. Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/monlib.hpp | 155 ++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 36 deletions(-) diff --git a/include/gemmi/monlib.hpp b/include/gemmi/monlib.hpp index 34e9bb45c..843224162 100644 --- a/include/gemmi/monlib.hpp +++ b/include/gemmi/monlib.hpp @@ -19,6 +19,11 @@ namespace gemmi { +/// @brief Check if an atom ID matches a canonical atom name, resolving aliases. +/// @param atom_id Atom identifier to check (may be aliased) +/// @param atom Canonical atom name +/// @param aliasing Optional aliasing rules to resolve atom_id +/// @return true if atom_id matches atom (directly or via aliasing) inline bool atom_match_with_alias(const std::string& atom_id, const std::string& atom, const ChemComp::Aliasing* aliasing) { if (aliasing) @@ -27,78 +32,121 @@ inline bool atom_match_with_alias(const std::string& atom_id, const std::string& return atom_id == atom; } +/// @brief Chemical link definition (bond, angle, dihedral between residues). struct GEMMI_DLL ChemLink { + /// @brief Specification of one side of a chemical link. struct Side { using Group = ChemComp::Group; - std::string comp; - std::string mod; - Group group = Group::Null; + std::string comp; ///< Specific chemical component name, or empty for group-based matching + std::string mod; ///< Chemical modification identifier + Group group = Group::Null; ///< Group type for general matching (peptide, nucleotide, etc.) + + /// @brief Check if this side matches a given chemical group. + /// @param res Group to test against + /// @return true if the side specification matches the group bool matches_group(Group res) const { if (group == Group::Null) return false; return res == group || (group == Group::Peptide && ChemComp::is_peptide_group(res)) || (group == Group::DnaRna && ChemComp::is_nucleotide_group(res)); } + + /// @brief Calculate specificity score for matching priority. + /// @return Higher scores indicate more specific matches (specific component > group-based) int specificity() const { if (!comp.empty()) return 3; return group == Group::PPeptide || group == Group::MPeptide ? 1 : 0; } }; - std::string id; - std::string name; - Side side1; - Side side2; - Restraints rt; - cif::Block block; // temporary, until we have ChemLink->Block function - - /// If multiple ChemLinks match a bond, the one with highest scores should be used. + + std::string id; ///< Link identifier + std::string name; ///< Link name + Side side1; ///< First residue specification + Side side2; ///< Second residue specification + Restraints rt; ///< Restraints (bonds, angles, dihedrals, etc.) + cif::Block block; ///< Temporary CIF block storage + + /// @brief Calculate matching score for this link between two residues. + /// If multiple ChemLinks match a bond, the one with highest score should be used. + /// @param res1 First residue + /// @param res2 Second residue (nullptr if not available) + /// @param alt First residue alternate location indicator + /// @param alt2 Second residue alternate location indicator + /// @param aliasing1 Aliasing rules for first residue + /// @param aliasing2 Aliasing rules for second residue + /// @return Numeric score indicating match quality (higher is better) int calculate_score(const Residue& res1, const Residue* res2, char alt, char alt2, const ChemComp::Aliasing* aliasing1, const ChemComp::Aliasing* aliasing2) const; }; +/// @brief Chemical modification (alteration to a chemical component). struct GEMMI_DLL ChemMod { + /// @brief Modification to a single atom. struct AtomMod { - int func; - std::string old_id; - std::string new_id; - Element el; - float charge; - std::string chem_type; + int func; ///< Modification function code + std::string old_id; ///< Original atom identifier + std::string new_id; ///< New atom identifier + Element el; ///< New element + float charge; ///< New formal charge + std::string chem_type; ///< New chemical type }; - std::string id; - std::string name; - std::string comp_id; - std::string group_id; - std::vector atom_mods; - Restraints rt; - cif::Block block; // temporary, until we have ChemMod->Block function + std::string id; ///< Modification identifier + std::string name; ///< Modification name + std::string comp_id; ///< Target chemical component + std::string group_id; ///< Group identifier + std::vector atom_mods; ///< Atom modifications to apply + Restraints rt; ///< Modified restraints + cif::Block block; ///< Temporary CIF block storage + /// @brief Apply this modification to a chemical component. + /// @param chemcomp Chemical component to modify (in-place) + /// @param alias_group Optional group alias to apply void apply_to(ChemComp& chemcomp, ChemComp::Group alias_group) const; }; +/// @brief Monomer library with chemical components, links, and modifications. +/// Stores the (Refmac) restraints dictionary including monomers, chemical links, +/// and modifications, along with atomic energy parameters. struct GEMMI_DLL MonLib { - std::string monomer_dir; - std::map monomers; - std::map links; - std::map modifications; - std::map cc_groups; - EnerLib ener_lib; + std::string monomer_dir; ///< Directory containing monomer CIF files + std::map monomers; ///< Chemical components indexed by name + std::map links; ///< Chemical links indexed by ID + std::map modifications; ///< Chemical modifications indexed by name + std::map cc_groups; ///< Component group assignments + EnerLib ener_lib; ///< Energy library with atomic properties + /// @brief Find a chemical link by identifier. + /// @param link_id Link identifier + /// @return Pointer to ChemLink, or nullptr if not found const ChemLink* get_link(const std::string& link_id) const { auto link = links.find(link_id); return link != links.end() ? &link->second : nullptr; } + + /// @brief Find a chemical modification by name. + /// @param name Modification name + /// @return Pointer to ChemMod, or nullptr if not found const ChemMod* get_mod(const std::string& name) const { auto modif = modifications.find(name); return modif != modifications.end() ? &modif->second : nullptr; } - // Returns the most specific link and a flag that is true - // if the order is comp2-comp1 in the link definition. + /// @brief Find the most specific chemical link between two residues and atoms. + /// Returns the most specific link and a flag indicating if the residue order + /// is inverted (comp2-comp1) in the link definition. + /// @param res1 First residue + /// @param atom1 Atom name in first residue + /// @param alt1 Alternate location indicator for first atom + /// @param res2 Second residue + /// @param atom2 Atom name in second residue + /// @param alt2 Alternate location indicator for second atom + /// @param min_bond_sq Minimum squared bond length to accept + /// @return Tuple of (link, inverted_flag, aliasing1, aliasing2); + /// link is nullptr if no match found std::tuple match_link(const Residue& res1, const std::string& atom1, char alt1, const Residue& res2, const std::string& atom2, char alt2, @@ -149,6 +197,8 @@ struct GEMMI_DLL MonLib { return std::make_tuple(best_link, inverted, aliasing1_final, aliasing2_final); } + /// @brief Add a chemical component from a CIF block if it contains atom definitions. + /// @param block CIF block containing chemical component data void add_monomer_if_present(const cif::Block& block) { if (block.has_tag("_chem_comp_atom.atom_id")) { ChemComp cc = make_chemcomp_from_block(block); @@ -162,6 +212,11 @@ struct GEMMI_DLL MonLib { } } + /// @brief Check if a link side specification matches a residue. + /// @param side Link side specification to test + /// @param res_name Residue name + /// @param aliasing Output parameter: aliasing rules if matched via alias, nullptr otherwise + /// @return true if side matches res_name (exactly or via group/alias) bool link_side_matches_residue(const ChemLink::Side& side, const std::string& res_name, ChemComp::Aliasing const** aliasing) const { @@ -182,34 +237,62 @@ struct GEMMI_DLL MonLib { return false; } - /// Returns path to the monomer cif file (the file may not exist). + /// @brief Returns path to the monomer CIF file (the file may not exist). + /// @param code Chemical component code + /// @return Full file path constructed from monomer_dir and code std::string path(const std::string& code) const { return monomer_dir + relative_monomer_path(code); } + /// @brief Get relative file path for a monomer within a standard directory structure. + /// @param code Chemical component code + /// @return Relative file path (e.g., "m/monomers/m_code.cif") static std::string relative_monomer_path(const std::string& code); + /// @brief Read monomer library data from a CIF document. + /// @param doc CIF document containing chemical components, links, and/or modifications void read_monomer_doc(const cif::Document& doc); + /// @brief Read monomer library data from a CIF file. + /// @param path_ File path to read void read_monomer_cif(const std::string& path_); + /// @brief Set the directory for monomer CIF files. + /// @param monomer_dir_ Directory path (trailing slash is optional and auto-added) void set_monomer_dir(const std::string& monomer_dir_) { monomer_dir = monomer_dir_; if (!monomer_dir.empty() && monomer_dir.back() != '/' && monomer_dir.back() != '\\') monomer_dir += '/'; } - /// Read mon_lib_list.cif, ener_lib.cif and required monomers. - /// Returns true if all requested monomers were added. + /// @brief Read mon_lib_list.cif, ener_lib.cif and required monomers. + /// @param monomer_dir_ Directory containing monomer library files + /// @param resnames List of chemical component names to load + /// @param logger Logger for diagnostic messages + /// @return true if all requested monomers were added bool read_monomer_lib(const std::string& monomer_dir_, const std::vector& resnames, const Logger& logger); + /// @brief Find ideal bond distance from library for two atoms. + /// @param cra1 First atom (chain, residue, atom reference) + /// @param cra2 Second atom (chain, residue, atom reference) + /// @return Ideal bond distance, or 0 if not found double find_ideal_distance(const const_CRA& cra1, const const_CRA& cra2) const; + + /// @brief Update old atom names in structure using alias information. + /// @param st Structure to update (modified in-place) + /// @param logger Logger for diagnostic messages void update_old_atom_names(Structure& st, const Logger& logger) const; }; -// to be deprecated +/// @brief Free function wrapper to read monomer library. +/// @deprecated Use MonLib::read_monomer_lib() method instead. +/// @param monomer_dir Directory containing monomer library files +/// @param resnames List of chemical component names to load +/// @param libin Optional path to additional library CIF file +/// @param ignore_missing If true, silently ignore missing components; if false, throw exception +/// @return Populated MonLib instance inline MonLib read_monomer_lib(const std::string& monomer_dir, const std::vector& resnames, const std::string& libin="", From c75a271852cce4960be61aa35231443ff34bf902 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Wed, 22 Apr 2026 23:47:04 +0200 Subject: [PATCH 09/15] docs: add Doxygen API documentation to topo.hpp Add comprehensive Doxygen triple-slash comments documenting: - HydrogenChange enum with all 6 enumerators - Topo struct and internal pointer constraints - Bond, Angle, Torsion, Chirality, and Plane restraint structs - RKind enum and Rule struct - Link and Mod structs with all members - FinalChemComp and ResInfo structs - ChainInfo struct with group_end() method - has_atom() template - All Topo data members and indices - Helper methods (find_resinfo, take_bond, take_angle, etc.) - Public methods with @note for pointer stability constraints - Free functions (prepare_topology, make_chemcomp_with_restraints, find_missing_atoms) Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/topo.hpp | 344 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 307 insertions(+), 37 deletions(-) diff --git a/include/gemmi/topo.hpp b/include/gemmi/topo.hpp index c9b0d8377..64a54712d 100644 --- a/include/gemmi/topo.hpp +++ b/include/gemmi/topo.hpp @@ -16,10 +16,20 @@ namespace gemmi { +/// @brief Specification for how to modify hydrogen atoms during topology preparation. enum class HydrogenChange { - NoChange, Shift, Remove, ReAdd, ReAddButWater, ReAddKnown + NoChange, ///< Leave hydrogen atoms as they are in the input model. + Shift, ///< Move hydrogen atoms to standard positions based on geometry. + Remove, ///< Remove all hydrogen atoms from the model. + ReAdd, ///< Remove all hydrogen atoms and then re-add them at standard positions. + ReAddButWater, ///< Remove and re-add hydrogen atoms, except in water molecules. + ReAddKnown ///< Re-add only hydrogen atoms that are known in the monomer library. }; +/// @brief Topology of restraints from a monomer library applied to a crystallographic model. +/// +/// Non-copyable due to internal atom pointers set up during apply_restraints() +/// that reference ResInfo chemical component restraint data. struct GEMMI_DLL Topo { // We have internal pointers in this class (pointers setup in // apply_restraints() that point to ResInfo::chemcomp.rt), @@ -28,42 +38,91 @@ struct GEMMI_DLL Topo { Topo(Topo const&) = delete; Topo& operator=(Topo const&) = delete; + /// @brief A bond restraint between two atoms. + /// + /// Holds a reference to the restraint specification and the two atoms, + /// and provides methods to calculate the bond distance and z-score. struct Bond { + /// @brief Pointer to the restraint specification from the monomer library. const Restraints::Bond* restr; + /// @brief The two atoms involved in the bond. std::array atoms; + /// @brief Asymmetric unit relationship between the atoms. Asu asu; + /// @brief Calculate the bond distance in Angstroms. + /// @return Distance between atoms, or NAN if atoms are in different asymmetric units. double calculate() const { return asu != Asu::Different ? atoms[0]->pos.dist(atoms[1]->pos) : NAN; } + /// @brief Calculate the z-score for a given distance. + /// @param d The distance value. + /// @return Z-score: (distance - ideal_value) / esd double calculate_z_(double d) const { return std::abs(d - restr->value) / restr->esd; } + /// @brief Calculate the z-score for the current bond distance. + /// @return Z-score of the observed distance relative to the restraint. double calculate_z() const { return calculate_z_(calculate()); } }; + /// @brief An angle restraint between three atoms. + /// + /// Holds a reference to the restraint specification and the three atoms, + /// and provides methods to calculate the angle and z-score. struct Angle { + /// @brief Pointer to the restraint specification from the monomer library. const Restraints::Angle* restr; + /// @brief The three atoms involved in the angle (atom[0]-atom[1]-atom[2]). std::array atoms; + /// @brief Calculate the angle value. + /// @return Angle in radians. double calculate() const { return calculate_angle(atoms[0]->pos, atoms[1]->pos, atoms[2]->pos); } + /// @brief Calculate the z-score for the current angle. + /// @return Z-score of the observed angle relative to the restraint. double calculate_z() const { return angle_z(calculate(), *restr); } }; + /// @brief A torsion (dihedral) angle restraint between four atoms. + /// + /// Holds a reference to the restraint specification and the four atoms, + /// and provides methods to calculate the dihedral angle and z-score. struct Torsion { + /// @brief Pointer to the restraint specification from the monomer library. const Restraints::Torsion* restr; + /// @brief The four atoms involved in the torsion (atoms[0]-atoms[1]-atoms[2]-atoms[3]). std::array atoms; + /// @brief Calculate the dihedral angle value. + /// @return Dihedral angle in radians. double calculate() const { return calculate_dihedral(atoms[0]->pos, atoms[1]->pos, atoms[2]->pos, atoms[3]->pos); } + /// @brief Calculate the z-score for the current dihedral angle. + /// + /// The z-score accounts for the periodicity of the torsion restraint. + /// @return Z-score of the observed dihedral relative to the restraint. double calculate_z() const { return angle_z(calculate(), *restr, 360. / std::max(1, restr->period)); } }; + /// @brief A chirality restraint on the stereochemistry around a chiral center. + /// + /// Holds a reference to the restraint specification and the four atoms + /// (center and three substituents), and provides methods to calculate the + /// chiral volume and z-score. struct Chirality { + /// @brief Pointer to the restraint specification from the monomer library. const Restraints::Chirality* restr; + /// @brief The four atoms: atoms[0] is the chiral center, atoms[1-3] are substituents. std::array atoms; + /// @brief Calculate the chiral volume. + /// @return The signed chiral volume (a scalar triple product). double calculate() const { return calculate_chiral_volume(atoms[0]->pos, atoms[1]->pos, atoms[2]->pos, atoms[3]->pos); } + /// @brief Calculate the z-score for the chiral volume. + /// @param ideal_abs_vol Ideal absolute value of the chiral volume. + /// @param esd Standard deviation of the restraint. + /// @return Z-score: absolute deviation from ideal value divided by esd. double calculate_z(double ideal_abs_vol, double esd) const { double calc = calculate(); if (restr->sign == ChiralityType::Negative || @@ -71,77 +130,159 @@ struct GEMMI_DLL Topo { ideal_abs_vol *= -1; return std::abs(calc - ideal_abs_vol) / esd; } + /// @brief Check whether the chirality is correct. + /// @return True if the chirality sign matches the restraint specification. bool check() const { return !restr->is_wrong(calculate()); } }; + /// @brief A planar restraint on a group of atoms. + /// + /// Holds a reference to the restraint specification and the atoms that + /// should lie in a plane. struct Plane { + /// @brief Pointer to the restraint specification from the monomer library. const Restraints::Plane* restr; + /// @brief The atoms that should lie in the plane. std::vector atoms; + /// @brief Check whether an atom is part of this plane restraint. + /// @param atom The atom to check. + /// @return True if the atom is in the atoms vector. bool has(const Atom* atom) const { return in_vector(const_cast(atom), atoms); } }; - enum class RKind { Bond, Angle, Torsion, Chirality, Plane }; + /// @brief Type of restraint rule. + enum class RKind { + Bond, ///< Bond distance restraint. + Angle, ///< Angle restraint. + Torsion, ///< Torsion (dihedral) angle restraint. + Chirality, ///< Chirality restraint. + Plane ///< Planarity restraint. + }; + + /// @brief A reference to a restraint rule. + /// + /// Identifies which type of restraint and its index in the corresponding + /// vector (bonds, angles, torsions, chirs, or planes) in Topo. struct Rule { + /// @brief The kind of restraint. RKind rkind; - size_t index; // index in the respective vector (bonds, ...) in Topo + /// @brief Index in the respective vector (bonds, angles, torsions, chirs, or planes). + size_t index; }; + /// @brief A link between two residues with associated restraints. + /// + /// Describes a covalent link (such as a peptide bond or a disulfide bridge) + /// between two residues, including the restraint rules and bonding information. struct Link { + /// @brief Link name from the monomer library (e.g., "PLNK", "disulf"). std::string link_id; + /// @brief Pointer to the first residue. Residue* res1 = nullptr; + /// @brief Pointer to the second residue. Residue* res2 = nullptr; + /// @brief Restraint rules applied by this link. std::vector link_rules; + /// @brief Alternate location indicator for res1. char alt1 = '\0'; + /// @brief Alternate location indicator for res2. char alt2 = '\0'; - Asu asu = Asu::Any; // used only in Links in ChainInfo::extras - bool is_cis = false; // helper field for CISPEP record generation - - // helper fields used in Topo::find_polymer_link() + /// @brief Asymmetric unit relationship between res1 and res2. + /// + /// Used only in Links in ChainInfo::extras. + Asu asu = Asu::Any; + /// @brief Helper field for CISPEP record generation in output. + bool is_cis = false; + + /// @brief Cached atom name ID for res1 (used in find_polymer_link). int atom1_name_id = 0; + /// @brief Cached atom name ID for res2 (used in find_polymer_link). int atom2_name_id = 0; - // aliasing1/2 points to vector element in ChemComp::aliases. - // The pointers should stay valid even if a ChemComp is moved. + /// @brief Pointer to aliasing information for res1. + /// + /// Points to a vector element in ChemComp::aliases. + /// The pointer remains valid even if a ChemComp is moved. const ChemComp::Aliasing* aliasing1 = nullptr; + /// @brief Pointer to aliasing information for res2. + /// + /// Points to a vector element in ChemComp::aliases. + /// The pointer remains valid even if a ChemComp is moved. const ChemComp::Aliasing* aliasing2 = nullptr; - // only for polymer links, res1 and res2 must be in the same vector (Chain) + /// @brief Calculate the pointer difference between residues. + /// + /// Only valid for polymer links where res1 and res2 are in the same Chain. + /// @return Signed distance in residues (res1 - res2). std::ptrdiff_t res_distance() const { return res1 - res2; } }; + /// @brief A chemical modification applied to a residue. + /// + /// Describes a ChemMod from the monomer library and the specific atom + /// group (aliasing) to which it applies. struct Mod { - std::string id; // id of ChemMod from the dictionary (MonLib) - ChemComp::Group alias; // alias to be used when applying the modification - char altloc; // \0 = all conformers - + /// @brief ID of the ChemMod from the monomer library dictionary. + std::string id; + /// @brief Atom group alias to which the modification applies. + ChemComp::Group alias; + /// @brief Alternate location indicator ('\0' for all conformers). + char altloc; + + /// @brief Check equality between two modifications. + /// @param o The other modification to compare. + /// @return True if id, alias, and altloc are identical. bool operator==(const Mod& o) const { return id == o.id && alias == o.alias && altloc == o.altloc; } }; + /// @brief Final chemical component with modifications applied. + /// + /// Represents a ChemComp with all modifications already applied. struct FinalChemComp { - char altloc; // Restraints apply to this conformer + /// @brief Alternate location indicator for which these restraints apply. + char altloc; + /// @brief Pointer to the ChemComp with modifications applied. const ChemComp* cc; }; + /// @brief Information about a residue in the topology. + /// + /// Contains the residue, its chemical composition (with modifications), + /// link information, and hydrogen bonding data. struct ResInfo { + /// @brief Pointer to the residue in the model. Residue* res; - // in case of microheterogeneity we may have 2+ previous residues + /// @brief Links to previous residue(s). + /// + /// In case of microheterogeneity, there may be 2 or more previous residues. std::vector prev; + /// @brief Chemical modifications applied to this residue. std::vector mods; - // Pointer to ChemComp in MonLib::monomers. + /// @brief Pointer to the original ChemComp from MonLib::monomers. const ChemComp* orig_chemcomp = nullptr; - // Pointer to restraints with modifications applied (if any). + /// @brief ChemComps with modifications applied, per conformer. std::vector chemcomps; + /// @brief Restraint rules applied to this residue. std::vector monomer_rules; - // lowest-energy hydrogen bonds for DSSP + /// @brief Two hydrogen-bonded donors with lowest energy (for DSSP). std::array donors = {nullptr, nullptr}; + /// @brief Two hydrogen-bonded acceptors with lowest energy (for DSSP). std::array acceptors = {nullptr, nullptr}; + /// @brief Energies of the two donor hydrogen bonds. std::array donor_energies = {0.0, 0.0}; + /// @brief Energies of the two acceptor hydrogen bonds. std::array acceptor_energies = {0.0, 0.0}; + /// @brief Constructor. + /// @param r The residue to associate with this ResInfo. ResInfo(Residue* r) : res(r) {} + /// @brief Add a modification to this residue. + /// @param m The ID of the modification (from MonLib). + /// @param aliasing Pointer to the aliasing information, or nullptr. + /// @param altloc Alternate location indicator ('\0' for all conformers). void add_mod(const std::string& m, const ChemComp::Aliasing* aliasing, char altloc) { if (!m.empty()) { auto alias_group = aliasing ? aliasing->group : ChemComp::Group::Null; @@ -151,6 +292,9 @@ struct GEMMI_DLL Topo { } } + /// @brief Get the final ChemComp for a specific conformer. + /// @param altloc Alternate location indicator. + /// @return Reference to the ChemComp for the specified conformer, or the first one if not found. const ChemComp& get_final_chemcomp(char altloc) const { if (chemcomps.size() == 1) return *chemcomps[0].cc; @@ -162,17 +306,33 @@ struct GEMMI_DLL Topo { } }; - // corresponds to a sub-chain + /// @brief Information about a sub-chain (continuous polymer segment). struct ChainInfo { + /// @brief Reference to the full Chain. const Chain& chain_ref; + /// @brief Name of the sub-chain. std::string subchain_name; + /// @brief Entity ID from the PDB ENTITY_POLY record. std::string entity_id; + /// @brief Whether this sub-chain is a polymer. bool polymer; + /// @brief Type of polymer (protein, DNA, RNA, etc.). PolymerType polymer_type; + /// @brief Residue information for each residue in the sub-chain. std::vector res_infos; + /// @brief Constructor. + /// @param subchain The residue span for this sub-chain. + /// @param chain The full Chain. + /// @param ent Pointer to the Entity, or nullptr. ChainInfo(ResidueSpan& subchain, const Chain& chain, const Entity* ent); + /// @brief Iterator type for ResInfo. using iterator = std::vector::iterator; + /// @brief Find the end of a residue group. + /// + /// Residues belong to the same group if they have the same group_key(). + /// @param b Iterator to the start of the group. + /// @return Iterator to the first residue not in the same group. iterator group_end(iterator b) const { auto e = b + 1; while (e != res_infos.end() && e->res->group_key() == b->res->group_key()) @@ -181,6 +341,11 @@ struct GEMMI_DLL Topo { } }; + /// @brief Check whether an atom is part of a structure. + /// @tparam T A structure with an atoms member (e.g., Bond, Angle, etc.). + /// @param a The atom to search for. + /// @param t The structure to search in. + /// @return Index of the atom in t.atoms, or -1 if not found. template static int has_atom(const Atom* a, const T& t) { for (int i = 0; (size_t) i != t.atoms.size(); ++i) @@ -189,23 +354,46 @@ struct GEMMI_DLL Topo { return -1; } + /// @brief Logger for warnings and informational messages. Logger logger{}; - bool only_bonds = false; // an internal flag for apply_restraints() + /// @brief Internal flag for apply_restraints(). + bool only_bonds = false; + /// @brief Information about each sub-chain in the model. std::vector chain_infos; + /// @brief Extra links not bound to specific chains. std::vector extras; - // Restraints applied to Model + /// @brief Bond restraints applied to the model. std::vector bonds; + /// @brief Angle restraints applied to the model. std::vector angles; + /// @brief Torsion restraints applied to the model. std::vector torsions; + /// @brief Chirality restraints applied to the model. std::vector chirs; + /// @brief Planarity restraints applied to the model. std::vector planes; - std::multimap bond_index; // indexes both atoms - std::multimap angle_index; // only middle atom - std::multimap torsion_index; // two middle atoms - std::multimap plane_index; // all atoms - + /// @brief Index of bonds by atom. + /// + /// Maps each atom to the bonds it is part of. + std::multimap bond_index; + /// @brief Index of angles by center atom. + /// + /// Maps each atom to the angles where it is the center atom (atoms[1]). + std::multimap angle_index; + /// @brief Index of torsions by middle atoms. + /// + /// Maps atoms[1] and atoms[2] to the torsions containing them. + std::multimap torsion_index; + /// @brief Index of planes by atom. + /// + /// Maps each atom to the planes it is part of. + std::multimap plane_index; + + /// @brief Find the ResInfo for a residue. + /// @param res The residue to search for. + /// @return Pointer to the ResInfo, or nullptr if not found. ResInfo* find_resinfo(const Residue* res) { for (ChainInfo& ci : chain_infos) for (ResInfo& ri : ci.res_infos) @@ -214,6 +402,9 @@ struct GEMMI_DLL Topo { return nullptr; } + /// @brief Get the first bond restraint in a link. + /// @param link The link to search. + /// @return Pointer to the first Bond in link.link_rules, or nullptr if none. Bond* first_bond_in_link(const Link& link) { for (const Rule& rule : link.link_rules) if (rule.rkind == RKind::Bond) @@ -221,6 +412,10 @@ struct GEMMI_DLL Topo { return nullptr; } + /// @brief Find a bond restraint between two atoms. + /// @param a First atom. + /// @param b Second atom. + /// @return Pointer to the bond restraint, or nullptr if no bond is restrained. const Restraints::Bond* take_bond(const Atom* a, const Atom* b) const { auto range = bond_index.equal_range(a); for (auto i = range.first; i != range.second; ++i) { @@ -232,6 +427,11 @@ struct GEMMI_DLL Topo { return nullptr; } + /// @brief Find an angle restraint between three atoms. + /// @param a First atom. + /// @param b Center atom. + /// @param c Third atom. + /// @return Pointer to the angle restraint, or nullptr if no angle is restrained. const Restraints::Angle* take_angle(const Atom* a, const Atom* b, const Atom* c) const { @@ -245,6 +445,9 @@ struct GEMMI_DLL Topo { return nullptr; } + /// @brief Get the chirality restraint for a chiral center. + /// @param ctr The chiral center atom. + /// @return Pointer to the Chirality restraint, or nullptr if none. const Chirality* get_chirality(const Atom* ctr) const { for (const Chirality& chir : chirs) if (chir.atoms[0] == ctr) @@ -252,31 +455,70 @@ struct GEMMI_DLL Topo { return nullptr; } + /// @brief Get the ideal absolute chiral volume for a chirality restraint. + /// @param ch The chirality restraint. + /// @return Ideal absolute value of the chiral volume. double ideal_chiral_abs_volume(const Chirality &ch) const; + /// @brief Apply restraints from a Restraints object to a residue. + /// @param rt The restraints specification. + /// @param res The residue to apply restraints to. + /// @param res2 Second residue (for inter-residue restraints), or nullptr. + /// @param asu Asymmetric unit relationship between atoms. + /// @param altloc1 Alternate location indicator for atoms in res. + /// @param altloc2 Alternate location indicator for atoms in res2. + /// @param require_alt If true, only apply restraints matching the altloc. + /// @return Vector of restraint rules applied. std::vector apply_restraints(const Restraints& rt, Residue& res, Residue* res2, Asu asu, char altloc1, char altloc2, bool require_alt); + /// @brief Apply restraints from a Link. + /// @param link The Link to apply. + /// @param monlib The monomer library (used to get ChemComp information). void apply_restraints_from_link(Link& link, const MonLib& monlib); - // Structure is non-const b/c connections may have link_id assigned. - // Model is non-const b/c we store non-const pointers to residues in Topo. - // Because of the pointers, don't add or remove residues after this step. - // Monlib may get modified by addition of extra links from the model. + /// @brief Initialize the topology from a Structure and MonLib. + /// + /// This method populates the internal topology state from the model and monomer library. + /// + /// @param st The Structure (non-const to assign link_id to connections). + /// @param model0 The Model (non-const to store pointers to residues). + /// @param monlib The MonLib (non-const; may be modified by addition of extra links). + /// @param ignore_unknown_links If true, skip links not in the library. + /// + /// @note After this step, do not add or remove residues from the model, + /// as Topo holds internal pointers to them. + /// @note The monlib may be modified by the addition of extra links from the model. void initialize_refmac_topology(Structure& st, Model& model0, MonLib& monlib, bool ignore_unknown_links=false); - // This step stores pointers to gemmi::Atom's from model0, - // so after this step don't add or remove atoms. - // monlib is needed only for links. + /// @brief Apply all restraints from the monomer library to the model. + /// + /// This populates the bonds, angles, torsions, chirs, and planes vectors. + /// + /// @param monlib The monomer library (used only for link information). + /// + /// @note This step stores pointers to gemmi::Atom's from model0, + /// so after this step do not add or remove atoms from the model. void apply_all_restraints(const MonLib& monlib); - // prepare bond_index, angle_index, torsion_index, plane_index + /// @brief Prepare the atom-to-restraint indices. + /// + /// Populates bond_index, angle_index, torsion_index, and plane_index + /// for efficient lookups. void create_indices(); - // Searches for matching Link in ResInfo::prev lists. + /// @brief Find a polymer link between two atoms. + /// + /// Searches for a matching Link in ResInfo::prev lists. + /// + /// @param a1 First atom address. + /// @param a2 Second atom address. + /// @return Pointer to the Link, or nullptr if no matching link is found. Link* find_polymer_link(const AtomAddress& a1, const AtomAddress& a2); + /// @brief Generate CISPEP records in the Structure based on cis peptide bonds. + /// @param st The Structure (non-const to add CISPEP records). void set_cispeps_in_structure(Structure& st); private: @@ -291,15 +533,43 @@ struct GEMMI_DLL Topo { bool ignore_unknown_links); }; +/// @brief Prepare the topology for a Structure. +/// +/// Creates and initializes a Topo object for the given Structure and model, +/// with optional hydrogen atom adjustments. +/// +/// @param st The Structure. +/// @param monlib The monomer library. +/// @param model_index Index of the model to use (typically 0). +/// @param h_change Specification for how to modify hydrogen atoms. +/// @param reorder If true, reorder atoms in the model. +/// @param logger Logger for warnings and informational messages. +/// @param ignore_unknown_links If true, skip links not in the library. +/// @param use_cispeps If true, identify and mark cis peptide bonds. +/// @return A unique_ptr to the initialized Topo. GEMMI_DLL std::unique_ptr prepare_topology(Structure& st, MonLib& monlib, size_t model_index, HydrogenChange h_change, bool reorder, const Logger& logger={}, bool ignore_unknown_links=false, bool use_cispeps=false); - +/// @brief Create a ChemComp with restraints for a residue. +/// +/// Generates a ChemComp with default restraints (bonds, angles, torsions, chiral volumes) +/// inferred from the residue's atom geometry. +/// +/// @param res The residue to create a ChemComp for. +/// @return A unique_ptr to the generated ChemComp. GEMMI_DLL std::unique_ptr make_chemcomp_with_restraints(const Residue& res); +/// @brief Find atoms in the model that are missing from the restraints. +/// +/// Identifies atoms that are present in the model but not found in the +/// corresponding chemical component definitions. +/// +/// @param topo The topology with applied restraints. +/// @param including_hydrogen If true, include hydrogen atoms in the search. +/// @return Vector of addresses of missing atoms. GEMMI_DLL std::vector find_missing_atoms(const Topo& topo, bool including_hydrogen=false); From b375b7d24481c4bc869e0d75c1f5766264c5d035 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Thu, 23 Apr 2026 08:06:00 +0200 Subject: [PATCH 10/15] docs: add Doxygen API documentation to riding_h.hpp Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/riding_h.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/gemmi/riding_h.hpp b/include/gemmi/riding_h.hpp index 1774090f4..99964f87c 100644 --- a/include/gemmi/riding_h.hpp +++ b/include/gemmi/riding_h.hpp @@ -10,8 +10,14 @@ namespace gemmi { +/// @brief Place hydrogen atoms using ideal bond lengths and angles from monomer library. +/// @param topo The topology containing atoms and bond restraints. GEMMI_DLL void place_hydrogens_on_all_atoms(Topo& topo); +/// @brief Scale hydrogen-atom bond distances to ideal target values. +/// @param topo The topology containing atoms and bond restraints. +/// @param of Which ideal distance to use: electron cloud or nuclear. +/// @param default_scale Fallback scale factor when computed scale is invalid (NaN or infinite). inline void adjust_hydrogen_distances(Topo& topo, Restraints::DistanceOf of, double default_scale=1.) { for (const Topo::Bond& t : topo.bonds) { From bd3a8a0074da22be4b4211cded8cd0311985e0c8 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Thu, 23 Apr 2026 08:07:51 +0200 Subject: [PATCH 11/15] docs: add Doxygen API documentation to linkhunt.hpp Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/linkhunt.hpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/include/gemmi/linkhunt.hpp b/include/gemmi/linkhunt.hpp index f4458a29d..74cc0648f 100644 --- a/include/gemmi/linkhunt.hpp +++ b/include/gemmi/linkhunt.hpp @@ -14,22 +14,27 @@ namespace gemmi { +/// @brief Searches for inter-residue chemical links using monomer library bond definitions. struct LinkHunt { + /// @brief Result of a link search; describes a candidate inter-residue link. struct Match { - const ChemLink* chem_link = nullptr; - int chem_link_count = 0; - int score = -1000; - CRA cra1; - CRA cra2; - bool same_image; - double bond_length = 0; - Connection* conn = nullptr; + const ChemLink* chem_link = nullptr; ///< Best matching ChemLink or nullptr if none found. + int chem_link_count = 0; ///< Number of matching ChemLink definitions found. + int score = -1000; ///< Best link score. + CRA cra1; ///< First bonded atom in order matching the link definition. + CRA cra2; ///< Second bonded atom in order matching the link definition. + bool same_image; ///< True if atoms are in the same crystal image. + double bond_length = 0; ///< Bond distance in Angstroms. + Connection* conn = nullptr; ///< Pointer to existing Connection in Structure if present, else nullptr. }; - double global_max_dist = 2.34; // ZN-CYS - const MonLib* monlib_ptr = nullptr; - std::multimap links; + double global_max_dist = 2.34; ///< Maximum bond distance across all indexed links; updated by index_chem_links(). + const MonLib* monlib_ptr = nullptr; ///< Pointer to the monomer library used for link matching. + std::multimap links; ///< Multimap from lexicographic atom-name pair to ChemLink pointers. + /// @brief Index all links from monlib into the links multimap for fast lookup. + /// @param monlib The monomer library to index. + /// @param use_alias Whether to expand atom name aliases when indexing. void index_chem_links(const MonLib& monlib, bool use_alias=true) { std::map>> aliases; if (use_alias) @@ -89,6 +94,12 @@ struct LinkHunt { monlib_ptr = &monlib; } + /// @brief Find all candidate inter-residue links within the structure using neighbor search. + /// @param st The structure to search for possible links. + /// @param bond_margin Fraction of ideal bond length used as distance cutoff for dictionary links. + /// @param radius_margin Fraction of sum of covalent radii for non-dictionary links. + /// @param ignore Which contacts to skip. + /// @return Vector of Match results describing candidate links. std::vector find_possible_links(Structure& st, double bond_margin, double radius_margin, From 0f8b0f291b3d9bcc749dbb1bbad49ae63e50592e Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Thu, 23 Apr 2026 08:07:58 +0200 Subject: [PATCH 12/15] docs: add Doxygen API documentation to to_chemcomp.hpp Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/to_chemcomp.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/gemmi/to_chemcomp.hpp b/include/gemmi/to_chemcomp.hpp index d49ece5d6..2cb2cc2f3 100644 --- a/include/gemmi/to_chemcomp.hpp +++ b/include/gemmi/to_chemcomp.hpp @@ -11,6 +11,11 @@ namespace gemmi { +/// @brief Write ChemComp restraint data into a CIF block as _chem_comp_* categories. +/// @param cc The chemical component to serialise. +/// @param block The CIF block to write into; rows are appended. +/// @param acedrg_types Optional per-atom ACEDRG type strings; if empty, stored types in cc.atoms are used. +/// @param no_angles If true, skip writing the _chem_comp_angle table. inline void add_chemcomp_to_block(const ChemComp& cc, cif::Block& block, const std::vector& acedrg_types = {}, bool no_angles = false) { From e6329f90deb9c4a3a857121108ae7cbf174e3e33 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Thu, 23 Apr 2026 08:08:07 +0200 Subject: [PATCH 13/15] docs: add Doxygen API documentation to mmcif_impl.hpp Co-authored-by: C. Vonrhein / CV-GPhL --- include/gemmi/mmcif_impl.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/gemmi/mmcif_impl.hpp b/include/gemmi/mmcif_impl.hpp index 505a32c27..661e3f793 100644 --- a/include/gemmi/mmcif_impl.hpp +++ b/include/gemmi/mmcif_impl.hpp @@ -13,6 +13,10 @@ namespace gemmi { namespace impl { +/// @brief Populate a UnitCell from _cell.* tags in a CIF block. +/// @param block The CIF block to read from. +/// @param cell Output — set from the CIF data. +/// @param mmcif If true use "_cell." prefix, else use "_cell_" for legacy PDB CIF style. inline void set_cell_from_mmcif(cif::Block& block, UnitCell& cell, bool mmcif=true) { cif::Table tab = block.find((mmcif ? "_cell." : "_cell_"), @@ -26,6 +30,9 @@ inline void set_cell_from_mmcif(cif::Block& block, UnitCell& cell, } } +/// @brief Return pointer to the _symmetry.space_group_name_H-M value in the block. +/// @param block The CIF block to search. +/// @return Pointer to the H-M space group name string, or nullptr if absent. inline const std::string* find_spacegroup_hm_value(const cif::Block& block) { const char* hm_tag = "_symmetry.space_group_name_H-M"; return block.find_value(hm_tag); From ee06fbf125d1eb2d0b73575aa2f2a0c72dba20f9 Mon Sep 17 00:00:00 2001 From: "C. Vonrhein" Date: Thu, 23 Apr 2026 08:26:37 +0200 Subject: [PATCH 14/15] docs: add Chemistry and Restraints section to api.rst (PR 8) Registers all 9 headers documented in api-docs/chemistry: chemcomp.hpp, chemcomp_xyz.hpp, ener_lib.hpp, monlib.hpp, topo.hpp, riding_h.hpp, linkhunt.hpp, to_chemcomp.hpp, mmcif_impl.hpp. Co-authored-by: C. Vonrhein / CV-GPhL --- docs/api.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 2386adfca..a855c7a44 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,3 +29,38 @@ Map and Grid Data .. doxygenfile:: grid.hpp :project: gemmi + +Chemistry and Restraints +------------------------ + +Chemical component definitions, monomer library, topology of restraints +applied to a model, hydrogen placement, link hunting, and related I/O helpers. + +*(Full documentation added in PR 8.)* + +.. doxygenfile:: chemcomp.hpp + :project: gemmi + +.. doxygenfile:: chemcomp_xyz.hpp + :project: gemmi + +.. doxygenfile:: ener_lib.hpp + :project: gemmi + +.. doxygenfile:: monlib.hpp + :project: gemmi + +.. doxygenfile:: topo.hpp + :project: gemmi + +.. doxygenfile:: riding_h.hpp + :project: gemmi + +.. doxygenfile:: linkhunt.hpp + :project: gemmi + +.. doxygenfile:: to_chemcomp.hpp + :project: gemmi + +.. doxygenfile:: mmcif_impl.hpp + :project: gemmi From e181b2be27f7f0a9be8184cb027d2bb339ba8a05 Mon Sep 17 00:00:00 2001 From: Clemens Vonrhein Date: Thu, 23 Apr 2026 13:17:45 +0200 Subject: [PATCH 15/15] ci: add breathe to AppVeyor pip install conf.py runs Doxygen when available (which it is on VS2022 workers) and conditionally loads breathe when the XML output dir exists. Without breathe installed, the Sphinx build fails with an ImportError. Co-authored-by: C. Vonrhein / CV-GPhL --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index f44d333f1..9241f29bc 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -53,7 +53,7 @@ for: - py -m unittest discover -v -s tests/ - cd docs - set PYTHONPATH=.. - - py -m pip install --no-warn-script-location sphinx sphinx-inline-tabs + - py -m pip install --no-warn-script-location sphinx sphinx-inline-tabs breathe - py -m sphinx -M doctest . _build -n -E artifacts: