Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ae24d4d
docs: add Doxygen/Breathe infrastructure for C++ API reference
Apr 22, 2026
2e118b8
fix(conf.py): guard doxygen call — skip gracefully when not installed
Apr 22, 2026
737d937
ci: add docs-build job (Doxygen + Sphinx/Breathe HTML)
Apr 22, 2026
1f15652
docs: add Doxygen API documentation to fprime.hpp
Apr 23, 2026
e074cf3
docs: add Doxygen API documentation to formfact.hpp
Apr 23, 2026
32b41fc
docs: add Doxygen API documentation to it92.hpp
Apr 23, 2026
93ee9ad
docs: add Doxygen API documentation to c4322.hpp
Apr 23, 2026
74799e6
docs: add Doxygen API documentation to neutron92.hpp
Apr 23, 2026
0cc08d6
docs: add Doxygen API documentation to bessel.hpp
Apr 23, 2026
d23f4e4
docs: add Doxygen API documentation to math.hpp
Apr 23, 2026
cecc2c1
docs: add Doxygen API documentation to cellred.hpp
Apr 23, 2026
97f2072
docs: fix Mat33 element constructor @param syntax in math.hpp
CV-GPhL Apr 23, 2026
cfed8b7
docs: clarify Vec3_ const at() return in math.hpp
CV-GPhL Apr 23, 2026
294d76b
docs: add Doxygen API docs to twin.hpp
CV-GPhL Apr 23, 2026
734456a
docs: add Doxygen API docs to serialize.hpp
CV-GPhL Apr 23, 2026
24ac594
docs: add Doxygen API docs to seqtools.hpp
CV-GPhL Apr 23, 2026
0f71499
docs: add Doxygen API docs to seqalign.hpp
CV-GPhL Apr 23, 2026
3c79b33
docs: add @brief to OpObliquity type alias in twin.hpp
CV-GPhL Apr 23, 2026
2a9b738
docs: fix CIGAR op description and push_cigar detail in seqalign.hpp
CV-GPhL Apr 23, 2026
4ca0bfc
docs: add Doxygen API docs to interop.hpp
CV-GPhL Apr 23, 2026
3f7b996
docs: add Doxygen API docs to flat.hpp
CV-GPhL Apr 23, 2026
37d1b99
docs: add Doxygen API docs to smarts.hpp
CV-GPhL Apr 23, 2026
9b38560
docs: add Doxygen API docs to blob.hpp
CV-GPhL Apr 23, 2026
cd69419
docs: add Doxygen API docs to isosurface.hpp
CV-GPhL Apr 23, 2026
66468ae
docs: add Doxygen API docs to levmar.hpp
CV-GPhL Apr 23, 2026
73ff9cd
docs: add Doxygen API docs to qcp.hpp
CV-GPhL Apr 23, 2026
053388f
docs: add @brief tag to IsoSurface struct in isosurface.hpp
CV-GPhL Apr 23, 2026
40f85a7
docs: add api.rst entries for PR 9 (scattering-math)
CV-GPhL Apr 23, 2026
42e4171
ci: add breathe to AppVeyor pip install
CV-GPhL Apr 23, 2026
c586c0b
docs: add @par References citations to scattering-math headers
CV-GPhL Apr 23, 2026
c18417e
Merge branch 'master' into api-docs/scattering-math
wojdyr Apr 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,6 @@ fortran/*.a
# test programs
fortran/fsym
fortran/ftest

# Doxygen-generated output (consumed by Breathe/Sphinx, not committed)
docs/_doxygen/
2 changes: 2 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
@@ -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
88 changes: 88 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,94 @@ Core Data Structures
.. doxygenfile:: model.hpp
:project: gemmi

Map and Grid Data
-----------------

*(Stub — full documentation added in PR 6.)*

.. doxygenfile:: grid.hpp
:project: gemmi

Scattering, Math, and Geometry
-------------------------------

Form factor tables, anomalous scattering, Bessel function helpers, core
linear-algebra primitives, unit-cell reduction, and numerical tools used
throughout structure-factor and density calculations.

*(Full documentation added in PR 9.)*

.. doxygenfile:: fprime.hpp
:project: gemmi

.. doxygenfile:: formfact.hpp
:project: gemmi

.. doxygenfile:: it92.hpp
:project: gemmi

.. doxygenfile:: c4322.hpp
:project: gemmi

.. doxygenfile:: neutron92.hpp
:project: gemmi

.. doxygenfile:: bessel.hpp
:project: gemmi

.. doxygenfile:: math.hpp
:project: gemmi

.. doxygenfile:: cellred.hpp
:project: gemmi

Sequence Alignment and Twinning
--------------------------------

Sequence utilities, pairwise alignment, twinning-law discovery, and
interoperability helpers.

*(Full documentation added in PR 9.)*

.. doxygenfile:: seqtools.hpp
:project: gemmi

.. doxygenfile:: seqalign.hpp
:project: gemmi

.. doxygenfile:: twin.hpp
:project: gemmi

.. doxygenfile:: serialize.hpp
:project: gemmi

.. doxygenfile:: interop.hpp
:project: gemmi

.. doxygenfile:: flat.hpp
:project: gemmi

.. doxygenfile:: smarts.hpp
:project: gemmi

Density Analysis and Numerical Methods
---------------------------------------

Electron density blob finding, isosurface extraction, Levenberg-Marquardt
least-squares minimization, and quaternion-based superposition (QCP).

*(Full documentation added in PR 9.)*

.. doxygenfile:: blob.hpp
:project: gemmi

.. doxygenfile:: isosurface.hpp
:project: gemmi

.. doxygenfile:: levmar.hpp
:project: gemmi

.. doxygenfile:: qcp.hpp
Reflection Data
---------------

Expand Down
22 changes: 22 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
# -*- coding: utf-8 -*-

import os
import shutil
import subprocess

_docs_dir = os.path.dirname(os.path.abspath(__file__))
_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']
if os.path.isdir(_doxygen_xml_dir):
extensions.append('breathe')

templates_path = ['_templates']

Expand Down Expand Up @@ -125,3 +139,11 @@ def _compute_navigation_tree(context: Dict[str, Any]) -> str:
import gemmi
gemmi.set_leak_warnings(False)
'''

# -- Breathe configuration (Doxygen XML → Sphinx) -------------------------
# Only active when _doxygen/xml/ exists (i.e. doxygen was run).

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
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Contents

ChangeLog <https://github.com/project-gemmi/gemmi/releases>
Python API reference <https://project-gemmi.github.io/python-api/>
C++ API reference <https://project-gemmi.github.io/cxx-api/>
api

Credits
=======
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
sphinx >= 8.1.3
furo >= 2024.8.6
sphinx-inline-tabs
breathe >= 4.35
30 changes: 26 additions & 4 deletions include/gemmi/bessel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

namespace gemmi {

/// @brief Evaluate degree-(N-1) polynomial by Horner's method
/// @tparam N number of coefficients
/// @param poly coefficient array; poly[0] is the constant term
/// @param x evaluation point
/// @return polynomial value at x
template<int N>
inline double evaluate_polynomial(const double(&poly)[N], double x) {
static_assert(N > 1, "");
Expand All @@ -27,13 +32,20 @@ inline double evaluate_polynomial(const double(&poly)[N], double x) {
return result;
}

/// @brief Polynomial coefficient tables for Bessel function approximations
/// @details Wrapped in a template to enable header-only inline variables
template<class Dummy>
struct BesselTables_
{
/// @brief Polynomial coefficients for I0 approximation, small x
static const double P1[8];
/// @brief Polynomial coefficients for I0 approximation, small x
static const double Q1[9];
/// @brief Polynomial coefficients for I0 approximation, large x
static const double P2[5];
/// @brief Polynomial coefficients for I0 approximation, large x (x < 50)
static const double Q2[5];
/// @brief Polynomial coefficients for I0 approximation, very large x (x >= 50)
static const double Q3[3];
};
template<class Dummy> const double BesselTables_<Dummy>::P1[8] = {
Expand Down Expand Up @@ -78,6 +90,10 @@ template<class Dummy> const double BesselTables_<Dummy>::Q3[3] = {
};


/// @brief Compute I₁(x)/I₀(x) using polynomial approximations
/// @details Used in maximum-likelihood refinement of diffraction data
/// @param x argument
/// @return I₁(x)/I₀(x)
inline double bessel_i1_over_i0(double x) {
using B = BesselTables_<void>;
if (x < 0)
Expand All @@ -95,9 +111,11 @@ inline double bessel_i1_over_i0(double x) {
return p / q;
}

// Simplified function from Boost.Math.
// Similar to std::cyl_bessel_i(0, x), but much faster, less exact and doesn't
// throw out_of_range on negative argument. Relative error < 5.02e-08.
/// @brief Compute modified Bessel function I₀(x)
/// @details Simplified from Boost.Math; relative error < 5.02e-08.
/// Similar to std::cyl_bessel_i(0, x) but faster. Does not throw on negative argument.
/// @param x argument
/// @return I₀(x)
inline double bessel_i0(double x) {
using B = BesselTables_<void>;
x = std::fabs(x);
Expand All @@ -111,7 +129,11 @@ inline double bessel_i0(double x) {
return ex * evaluate_polynomial(B::Q3, 1 / x) / std::sqrt(x) * ex;
}

// Relative error < 4e-08.
/// @brief Compute ln(I₀(x))
/// @details Numerically stable computation via log1p for small x; relative error < 4e-08.
/// Avoids overflow for large x.
/// @param x argument
/// @return ln(I₀(x))
inline double log_bessel_i0(double x) {
using B = BesselTables_<void>;
x = std::fabs(x);
Expand Down
25 changes: 24 additions & 1 deletion include/gemmi/blob.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,32 @@

namespace gemmi {

/// @brief A local maximum or connected region ("blob") in a 3D density map.
/// Represents a contiguous region above a density threshold, computed by flood fill.
struct Blob {
/// @brief Volume of the blob in cubic Angstroms.
double volume = 0.0;
/// @brief Score (integrated density above the cutoff).
double score = 0.0;
/// @brief Peak electron density value within the blob.
double peak_value = 0.0;
/// @brief Centroid position (weighted average of coordinates).
gemmi::Position centroid;
/// @brief Position of the peak density point.
gemmi::Position peak_pos;
/// @brief Check if blob is non-empty (volume > 0).
explicit operator bool() const { return volume != 0. ; }
};

/// @brief Criteria for blob detection in density maps.
struct BlobCriteria {
/// @brief Minimum electron density threshold to include points in a blob.
double cutoff;
/// @brief Minimum volume (cubic Angstroms) for blob to be reported.
double min_volume = 10.0;
/// @brief Minimum integrated score for blob to be reported.
double min_score = 15.0;
/// @brief Minimum peak density value for blob to be reported.
double min_peak = 0.0;
};

Expand Down Expand Up @@ -78,7 +91,17 @@ inline Blob make_blob_of_points(const std::vector<GridConstPoint>& points,

} // namespace impl

// with negate=true grid negatives of grid values are used
/// @brief Find all blobs (density peaks) in a 3D grid using flood fill.
/// @details
/// Uses a flood fill algorithm to identify connected regions above a density threshold,
/// respecting crystallographic symmetry through the asymmetric unit mask.
/// Results are sorted by score (integrated density) in descending order.
/// The algorithm differs from FloodFill in floodfill.hpp by using symmetry operators
/// to exclude symmetric mates from separate blob detection.
/// @param grid The electron density grid to search.
/// @param criteria Thresholds for minimum volume, score, peak density.
/// @param negate If true, search for negative density (use -grid.data).
/// @return Vector of Blob objects, sorted by score (highest first).
inline std::vector<Blob> find_blobs_by_flood_fill(const gemmi::Grid<float>& grid,
const BlobCriteria& criteria,
bool negate=false) {
Expand Down
13 changes: 11 additions & 2 deletions include/gemmi/c4322.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,24 @@ namespace gemmi {
#pragma GCC diagnostic ignored "-Wfloat-conversion"
#endif

/// @brief Electron scattering factor coefficients from ITC Volume C (2011) table 4.3.2.2.
/// Five-Gaussian approximation for neutral atoms, valid for sinθ/λ ≤ 2 Å⁻¹.
/// @tparam Real floating-point type
template<class Real>
struct C4322 {
using Coef = GaussianCoef<5, 0, Real>;
static Coef data[99];
using Coef = GaussianCoef<5, 0, Real>; ///< Type alias for coefficient set
static Coef data[99]; ///< Electron scattering factor coefficients

/// @brief Check if coefficients are available for element.
/// @param el element
/// @return true if element has tabulated coefficients
static bool has(El el) {
return el <= El::Cf || el == El::D;
}

/// @brief Get coefficient set for element (charge is ignored — only neutral atoms tabulated).
/// @param el element
/// @return coefficient reference
static Coef& get(El el, signed char /*charge*/=0, int /*serial*/=0) {
// ordinal for X, H, ... Cf; H=1 for D; X=0 for Es, ... Og
int pos = el <= El::Cf ? (int)el : (int)(el == El::D);
Expand Down
Loading
Loading