Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .github/workflows/daily.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CodeEntropy Daily
name: Daily Unit Tests

on:
schedule:
Expand Down
21 changes: 15 additions & 6 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CodeEntropy CI
name: Pull Request CI

on:
pull_request:
Expand Down Expand Up @@ -79,27 +79,36 @@ jobs:

docs:
name: Docs
needs: unit
runs-on: ubuntu-24.04
timeout-minutes: 25

steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8

- name: Set up Python 3.14
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.14"
cache: pip

- name: Install (docs)
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install -e .[docs]

- name: Build docs
run: |
cd docs
make
make -C docs clean
make -C docs html SPHINXOPTS="-W --keep-going"

- name: Upload docs artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: docs-html
path: docs/_build/html

pre-commit:
name: Pre-commit
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: release
name: Release CodeEntropy

on:
workflow_dispatch:
Expand Down
13 changes: 8 additions & 5 deletions .github/workflows/weekly-docs.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CodeEntropy Weekly Docs
name: Weekly Docs Build

on:
schedule:
Expand All @@ -19,6 +19,7 @@ jobs:
os: [ubuntu-24.04, windows-2025, macos-15]
python-version: ["3.12", "3.13", "3.14"]
timeout-minutes: 30

steps:
- name: Checkout repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
Expand All @@ -31,11 +32,13 @@ jobs:

- name: Install python dependencies
run: |
pip install --upgrade pip
pip install -e .[docs]
python -m pip install --upgrade pip
python -m pip install -e .[docs]

- name: Build docs
run: cd docs && make
- name: Build docs (warnings as errors)
run: |
make -C docs clean
make -C docs html SPHINXOPTS="-W --keep-going"

- name: Upload docs artifacts on failure
if: failure()
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/weekly-regression.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CodeEntropy Weekly Regression
name: Weekly Regression Tests

on:
schedule:
Expand Down
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,20 @@ repos:
- id: check-ast
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/rstcheck/rstcheck
rev: v6.2.0
hooks:
- id: rstcheck
name: rstcheck (docstrings only)
files: \.py$
args: ["--report-level", "warning"]

- repo: local
hooks:
- id: sphinx-docs
name: sphinx-build (pre-push, warnings as errors)
entry: bash -lc 'make -C docs clean && make -C docs html SPHINXOPTS="-W --keep-going"'
language: system
pass_filenames: false
stages: [pre-push]
3 changes: 3 additions & 0 deletions CodeEntropy/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

1) A declarative argument specification (`ARG_SPECS`) used to build an
``argparse.ArgumentParser``.

2) A `ConfigResolver` that:

- loads YAML configuration (if present),
- merges YAML values with CLI values (CLI wins),
- adjusts logging verbosity,
- validates a subset of runtime inputs against the trajectory.

Notes:

- Boolean arguments are parsed via `str2bool` to support YAML/CLI interop and
common string forms like "true"/"false".
"""
Expand Down
12 changes: 7 additions & 5 deletions CodeEntropy/entropy/configurational.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Conformational entropy utilities.

This module provides:
* Assigning discrete conformational states for a single dihedral time series.
* Computing conformational entropy from a sequence of state labels.

The public surface area is intentionally small to keep responsibilities clear.
- Assignment of discrete conformational states for a single dihedral time series.
- Computation of conformational entropy from a sequence of state labels.

"""

from __future__ import annotations
Expand Down Expand Up @@ -127,15 +127,17 @@ def conformational_entropy_calculation(
"""Compute conformational entropy for a sequence of state labels.

Entropy is computed as:

S = -R * sum_i p_i * ln(p_i)
where p_i is the observed probability of state i in `states`.

where p_i is the observed probability of state i in ``states``.

Args:
states: Sequence/array of discrete state labels. Empty/None yields 0.0.
number_frames: Frame count metadata.

Returns:
Conformational entropy in J/mol/K.
float: Conformational entropy in J/mol/K.
"""
_ = number_frames

Expand Down
14 changes: 7 additions & 7 deletions CodeEntropy/entropy/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

This module defines `EntropyWorkflow`, which coordinates the end-to-end entropy
workflow:
* Determine trajectory bounds and frame count.
* Build a reduced universe based on atom selection.
* Identify molecule groups and hierarchy levels.
* Optionally compute water entropy and adjust selection.
* Execute the level DAG (matrix/state preparation).
* Execute the entropy graph (entropy calculations and aggregation).
* Finalize and persist results.
- Determine trajectory bounds and frame count.
- Build a reduced universe based on atom selection.
- Identify molecule groups and hierarchy levels.
- Optionally compute water entropy and adjust selection.
- Execute the level DAG (matrix/state preparation).
- Execute the entropy graph (entropy calculations and aggregation).
- Finalize and persist results.

The manager intentionally delegates calculations to dedicated components.
"""
Expand Down
43 changes: 24 additions & 19 deletions CodeEntropy/levels/axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ class AxesCalculator:
rotation, and handling bead-based representations of molecular systems.

Provides utility methods to:
- extract averaged positions,
- convert coordinates to spherical systems (future/legacy scope),
- compute axes used to rotate forces around,
- compute custom moments of inertia,
- manipulate vectors under periodic boundary conditions (PBC),
- construct custom moment-of-inertia tensors and principal axes.

- Extract averaged positions.
- Convert coordinates to spherical systems (future/legacy scope).
- Compute axes used to rotate forces around.
- Compute custom moments of inertia.
- Manipulate vectors under periodic boundary conditions (PBC).
- Construct custom moment-of-inertia tensors and principal axes.

Notes:
This class deliberately does **not**:
- compute weighted forces/torques (that belongs in ForceTorqueCalculator),
- build covariances,
- compute entropies.
This class deliberately does not:

- Compute weighted forces/torques (that belongs in ForceTorqueCalculator).
- Build covariances.
- Compute entropies.
"""

def __init__(self) -> None:
Expand Down Expand Up @@ -363,7 +365,7 @@ def get_vanilla_axes(self, molecule):
Returns:
Tuple[np.ndarray, np.ndarray]:
- principal_axes: (3, 3) axes.
- moment_of_inertia: (3,) moments sorted descending by |value|.
- moment_of_inertia: (3,) moments sorted descending by absolute value.
"""
moment_of_inertia_tensor = molecule.moment_of_inertia(unwrap=True)
make_whole(molecule.atoms)
Expand All @@ -388,13 +390,13 @@ def get_custom_axes(

- axis1: use the normalised vector ab as axis1. If there is more than one
bonded heavy atom (HA), average over all the normalised vectors
calculated from b_list and use this as axis1). b_list contains all the
calculated from b_list and use this as axis1. b_list contains all the
bonded heavy atom coordinates.

- axis2: use the cross product of normalised vector ac and axis1 as axis2.
If there are more than two bonded heavy atoms, then use normalised vector
b[0]c to cross product with axis1, this gives the axis perpendicular
(represented by |_ symbol below) to axis1.
b[0]c to cross product with axis1. This gives the axis perpendicular
to axis1.

- axis3: the cross product of axis1 and axis2, which is perpendicular to
axis1 and axis2.
Expand All @@ -405,12 +407,12 @@ def get_custom_axes(
c: Coordinates of a second heavy atom or a hydrogen atom.
dimensions: Simulation box dimensions (3,).

::
.. code-block:: text

a 1 = norm_ab
/ \ 2 = |_ norm_ab and norm_ac (use bc if more than 2 HAs)
/ \ 3 = |_ 1 and 2
b c
/ \ 2 = perpendicular to norm_ab and norm_ac (or bc if >2 HAs)
/ \ 3 = perpendicular to 1 and 2
b c

Returns:
np.ndarray: (3, 3) array of the axes used to rotate forces.
Expand Down Expand Up @@ -458,8 +460,10 @@ def get_custom_moment_of_inertia(
heavy atom position in a UA).

Original behaviour preserved:

- Uses PBC-aware translated coordinates.
- Sums contributions from each atom: |axis x r|^2 * mass.
- Sums contributions from each atom using the squared norm of (axis × r)
multiplied by mass.
- Removes the lowest MOI degree of freedom if the UA only has a single
bonded H (i.e. UA has 2 atoms total).

Expand Down Expand Up @@ -581,6 +585,7 @@ def get_custom_principal_axes(
built-in MDAnalysis principal_axes() function.

Original behaviour preserved:

- Eigenvalues are sorted by descending absolute magnitude.
- Eigenvectors are transposed so axes are returned as rows.
- Z axis is flipped to enforce the same handedness convention as the
Expand Down
16 changes: 8 additions & 8 deletions CodeEntropy/levels/dihedrals.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def build_conformational_states(
residue-level state generation depending on which hierarchy levels are
enabled per molecule.

Progress reporting is optional and UI-agnostic: if a progress sink is
Progress reporting is optional and UI-agnostic. If a progress sink is
provided, the method will create a single task and advance it once per
molecule group.

Expand All @@ -66,17 +66,17 @@ def build_conformational_states(
Must expose add_task(), update(), and advance().

Returns:
Tuple of:
states_ua: Dict mapping (group_id, local_residue_id) -> list of state
labels (strings) across the analyzed trajectory.
states_res: List-like structure indexed by group_id (or equivalent)
containing residue-level state labels (strings) across the
analyzed trajectory.
tuple: (states_ua, states_res)

- states_ua: Dict mapping (group_id, local_residue_id) -> list of state
labels (strings) across the analyzed trajectory.
- states_res: Structure indexed by group_id (or equivalent) containing
residue-level state labels (strings) across the analyzed trajectory.

Notes:
- This function advances progress once per group_id.
- Frame slicing arguments (start/end/step) are forwarded to downstream
helpers as implemented in this module.
helpers as implemented in this module.
"""
number_groups = len(groups)
states_ua: Dict[UAKey, List[str]] = {}
Expand Down
16 changes: 9 additions & 7 deletions CodeEntropy/levels/hierarchy.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"""Hierarchy level selection and bead construction.

This module defines `HierarchyBuilder`, which is responsible for:
1) Determining which hierarchy levels apply to each molecule.
2) Constructing "beads" (AtomGroups) for a given molecule at a given level.
This module defines ``HierarchyBuilder``, which is responsible for:

- Determining which hierarchy levels apply to each molecule.
- Constructing "beads" (AtomGroups) for a given molecule at a given level.

Notes:
- The "residue" bead construction must use residues attached to the provided
AtomGroup/container. Using `resindex` selection strings is unsafe because
`resindex` is global to the Universe and can produce empty/incorrect beads
when operating on per-molecule containers beyond the first molecule.
The "residue" bead construction must use residues attached to the provided
AtomGroup/container. Using ``resindex`` selection strings is unsafe because
``resindex`` is global to the Universe and can produce empty or incorrect
beads when operating on per-molecule containers beyond the first molecule.

"""

from __future__ import annotations
Expand Down
24 changes: 14 additions & 10 deletions CodeEntropy/levels/mda.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,18 @@ def merge_forces(
"""Create a universe by merging coordinates and forces from different files.

This method loads:
- coordinates + dimensions from the coordinate trajectory (tprfile + trrfile)
- forces from the force trajectory (tprfile + forcefile)

If the force trajectory does not expose forces in MDAnalysis (e.g. the file
- Coordinates and dimensions from the coordinate trajectory
(``tprfile`` + ``trrfile``).
- Forces from the force trajectory (``tprfile`` + ``forcefile``).

If the force trajectory does not expose forces in MDAnalysis (e.g., the file
does not contain forces, or the reader does not provide them), then:
- if `fallback_to_positions_if_no_forces` is True, positions from the force
trajectory are used as the "forces" array (backwards-compatible behaviour
with earlier implementations).
- otherwise, the underlying `NoDataError` is raised.

- If ``fallback_to_positions_if_no_forces`` is True, positions from the
force trajectory are used as the "forces" array (backwards-compatible
behaviour with earlier implementations).
- Otherwise, the underlying ``NoDataError`` is raised.

Args:
tprfile: Topology input file.
Expand All @@ -155,14 +158,15 @@ def merge_forces(
recognised by MDAnalysis.
kcal: If True, scale the force array by 4.184 to convert from kcal to kJ.
force_format: Optional file format for the force trajectory. If not
provided, uses `fileformat`.
provided, uses ``fileformat``.
fallback_to_positions_if_no_forces: If True, and the force trajectory has
no accessible forces, use positions from the force trajectory as a
fallback (legacy behaviour).

Returns:
A new Universe containing coordinates, forces and dimensions loaded into
memory.
MDAnalysis.Universe: A new Universe containing coordinates, forces and
dimensions loaded into memory.

"""
logger.debug("Loading coordinate Universe with %s", trrfile)
u = mda.Universe(tprfile, trrfile, format=fileformat)
Expand Down
Loading