Skip to content

ENH: Speedup the DSSP constructor - #5504

Open
BradyAJohnston wants to merge 1 commit into
developfrom
enh-dssp-construction
Open

BradyAJohnston wants to merge 1 commit into
developfrom
enh-dssp-construction

Conversation

@BradyAJohnston

Copy link
Copy Markdown
Member

Supersedes #5503 so that this PR can be stacked with later PRs.

The DSSP() constructor's select_atoms was called on every single residue creating many atom groups and being a performance bottleneck especially on larger systems. Creating multiple atom groups has been replaced by numpy arrays providing a significant speedup for the constructor (~50x in my benchmarks).

Changes made in this Pull Request:

  • Numpy array operations instead of atom groups for constructor
  • _get_coords is now one gather instead of a per-residue loop that ran every frame
  • Some general cleanup of comments not needing to be inside brackets and unnecessary set(map(lambda...))).
  • Updated typing hints, using X | Y instead

LLM / AI generated code disclosure

LLMs or other AI-powered tools (beyond simple IDE use cases) were used in this contribution: no

I did have Claude write the benchmarking script (attached at bottom) but not the code or tests.

PR Checklist

  • Issue raised/referenced?
  • Tests updated/added?
  • Documentation updated/added?
  • package/CHANGELOG file updated?
  • Is your name in package/AUTHORS? (If it is not, add it!)
  • I have read and understand the current AI Policy
  • LLM/AI disclosure was updated.

Developers Certificate of Origin

I certify that I can submit this code contribution as described in the Developer Certificate of Origin, under the MDAnalysis LICENSE.


Benchmarks

Residues Construction: develop → branch Run, guess_hydrogens=True Run, guess_hydrogens=False
214 10.3 → 0.33 ms (31x) 2.40 → 1.43 (1.7x) 2.68 → 1.43 (1.9x)
856 42 → 0.90 ms (46x) 25.3 → 21.4 (1.2x) 26.7 → 21.4 (1.25x)
3,424 167 → 3.2 ms (52x) 330 → 316 (1.04x) 335 → 315 (1.07x)

Script

Details
"""Time DSSP construction and full runs, with and without guessed hydrogens.

Run it on develop and on this branch: the timings show the change, and the
checksums of the results must be the same on both.
"""

import hashlib
import time

import MDAnalysis as mda
import numpy as np
from MDAnalysis.analysis.dssp import DSSP
from MDAnalysis.coordinates.memory import MemoryReader
from MDAnalysisTests.datafiles import TPR, XTC

# of the N-terminal hydrogens (H1-H3) only H1 is named: guess_hydrogens=False
# accepts one hydrogen per residue after the first, so the later copies'
# N-termini would fail with all three
HYDROGEN_NAME = "H HN H1"


def best(fn, repeats):
    """Shortest time of ``repeats`` calls, and the last result"""
    times = []
    for _ in range(repeats):
        start = time.perf_counter()
        out = fn()
        times.append(time.perf_counter() - start)
    return min(times), out


def copies_of(protein, copies):
    """``copies`` of ``protein`` as one system, 100 A apart along x, with
    all frames of its trajectory"""
    merged = mda.Merge(*[protein] * copies)
    frames = np.array([protein.positions for _ in protein.universe.trajectory])
    coords = np.concatenate([frames] * copies, axis=1)
    coords[:, :, 0] += np.repeat(np.arange(copies) * 100.0, protein.n_atoms)
    merged.load_new(coords.astype(np.float32), format=MemoryReader)
    return merged


protein = mda.Universe(TPR, XTC).select_atoms("protein")
for copies in (1, 4, 16):
    u = copies_of(protein, copies)
    n_res, n_frames = u.residues.n_residues, u.trajectory.n_frames
    print(f"{n_res} residues, {n_frames} frames")
    for guess in (True, False):
        kwargs = dict(guess_hydrogens=guess, hydrogen_name=HYDROGEN_NAME)
        t_init, _ = best(lambda: DSSP(u, **kwargs), 5)
        t_run, run = best(lambda: DSSP(u, **kwargs).run(), 3)
        digest = hashlib.sha1(run.results.dssp_ndarray.tobytes()).hexdigest()
        print(
            f"  guess_hydrogens={guess!s:5}: construction {1e3 * t_init:7.2f} "
            f"ms, construction and run {1e3 * t_run / n_frames:7.2f} ms per "
            f"frame (results {digest[:12]})"
        )

@BradyAJohnston BradyAJohnston mentioned this pull request Oct 20, 2026
7 tasks done
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 MDAnalysis | 🛠️ Build #35273575 | 📁 Comparing a7f7048 against latest (6cd1f7e)

  🔍 Preview build  

3 files changed
± index.html
± documentation_pages/analysis/wbridge_analysis.html
± _modules/MDAnalysis/analysis/dssp/dssp.html

@BradyAJohnston
BradyAJohnston added this pull request to stack #5507 October 20, 2026 15:26
@BradyAJohnston BradyAJohnston added this to the 2.12 milestone Oct 20, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant