Skip to content
Open
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: 2 additions & 0 deletions package/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ Chronological list of authors
- Apoorva Verma
- Aryaman Chaudhri
- Francesco Siciliani
- Akshit Boora


External code
-------------
Expand Down
4 changes: 4 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ Enhancements
* Adds support for parsing `.tpr` files produced by GROMACS 2026.0
* Enables parallelization for analysis.diffusionmap.DistanceMatrix
(Issue #4679, PR #4745)
* TPRReader now reads box vectors from TPR files and exposes them as
``Universe.dimensions``. Previously, ``u.dimensions`` returned ``None``
when a TPR file was used as the sole coordinate source
(``mda.Universe(tpr)`` or ``mda.Universe(tpr, tpr)``). (Issue #5375)

Changes
* The msd.py inside analysis is changed, and ProgressBar is implemented inside
Expand Down
6 changes: 5 additions & 1 deletion package/MDAnalysis/coordinates/TPR.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

from . import base
from ..lib import util
from ..lib.mdamath import triclinic_box
from .timestep import Timestep
import MDAnalysis.topology.tpr.utils as tpr_utils
import MDAnalysis.topology.tpr.setting as S
Expand Down Expand Up @@ -110,7 +111,10 @@ def _read_first_frame(self):

state_ngtc = th.ngtc # done init_state() in src/gmxlib/tpxio.c
if th.bBox:
tpr_utils.extract_box_info(data, th.fver)
box_info = tpr_utils.extract_box_info(data, th.fver)
ts.dimensions = triclinic_box(*box_info.size)
if self.convert_units and ts.dimensions is not None:
self.convert_pos_from_native(ts.dimensions[:3])

if state_ngtc > 0:
if th.fver < 69: # redundancy due to different versions
Expand Down
143 changes: 142 additions & 1 deletion testsuite/MDAnalysisTests/coordinates/test_tpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,10 @@ def test_different_versions():
assert_equal(u.atoms.velocities.shape, exp_shape)


@pytest.mark.parametrize("convert_units", [True, False])
@pytest.mark.parametrize(
"convert_units",
[True, False],
)
def test_unit_swapping(convert_units):
reader = TPRReader(TPR_xvf_2024_4, convert_units=convert_units)
ts = reader.ts
Expand All @@ -538,3 +541,141 @@ def test_unit_swapping(convert_units):
assert_allclose(actual_positions[-1, ...], expected_last_pos * factor)
assert_allclose(actual_velocities[0, ...], expected_first_vel * factor)
assert_allclose(actual_velocities[-1, ...], expected_last_vel * factor)


# ----- Tests for issue #5375: box vectors from TPR files -----


@pytest.mark.parametrize(
"tpr_file, expected_dimensions",
[
# adk_oplsaa.tpr: rhombic dodecahedron box
# expected values verified against mda.Universe(TPR, XTC).dimensions
# see issue #5375
(
TPR_xvf_2024_4,
[52.763, 52.763, 52.763, 90.0, 90.0, 90.0],
),
(
TPR2024_4,
[79.1, 79.1, 37.9, 90.0, 90.0, 90.0],
),
(
TPR2020,
[79.1, 79.1, 37.9, 90.0, 90.0, 90.0],
),
(
TPR455Double,
[43.7388, 43.7388, 107.9261, 90.0, 90.0, 90.0],
),
],
)
@pytest.mark.parametrize("double_incantation", [True, False])
def test_tpr_box_vectors(tpr_file, expected_dimensions, double_incantation):
"""TPR coordinate reader should expose box dimensions (issue #5375).

When a TPR file is used as the sole coordinate source (either
``mda.Universe(tpr)`` or ``mda.Universe(tpr, tpr)``), the resulting
Universe should have non-None ``dimensions`` that match the box stored
inside the TPR file.
"""
if double_incantation:
u = mda.Universe(tpr_file, tpr_file)
else:
u = mda.Universe(tpr_file)
assert u.dimensions is not None, (
"u.dimensions should not be None when using a TPR file as the "
"coordinate source (issue #5375)"
)
assert_allclose(u.dimensions, expected_dimensions, atol=1e-3)


def test_tpr_box_vectors_unit_conversion():
"""Box lengths from TPR reader should respect the convert_units flag.

With ``convert_units=True`` (default) lengths are in Angstroms; with
``convert_units=False`` they remain in GROMACS native nanometres.
Angles are dimensionless and must be identical in both cases.
"""
reader_angstrom = TPRReader(TPR2024_4, convert_units=True)
reader_nm = TPRReader(TPR2024_4, convert_units=False)

dims_ang = reader_angstrom.ts.dimensions
dims_nm = reader_nm.ts.dimensions

assert dims_ang is not None
assert dims_nm is not None

# lengths scale by factor 10 (nm -> Å)
assert_allclose(dims_ang[:3], dims_nm[:3] * 10, rtol=1e-5)
# angles are unchanged
assert_allclose(dims_ang[3:], dims_nm[3:], rtol=1e-5)


# ----- Tests for issue #5375: box vectors from TPR files -----


@pytest.mark.parametrize(
"tpr_file, expected_dimensions",
[
# adk_oplsaa.tpr: rhombic dodecahedron box
# expected values verified against mda.Universe(TPR, XTC).dimensions
# see issue #5375
(
TPR_xvf_2024_4,
[52.763, 52.763, 52.763, 90.0, 90.0, 90.0],
),
(
TPR2024_4,
[79.1, 79.1, 37.9, 90.0, 90.0, 90.0],
),
(
TPR2020,
[79.1, 79.1, 37.9, 90.0, 90.0, 90.0],
),
(
TPR455Double,
[43.7388, 43.7388, 107.9261, 90.0, 90.0, 90.0],
),
],
)
@pytest.mark.parametrize("double_incantation", [True, False])
def test_tpr_box_vectors(tpr_file, expected_dimensions, double_incantation):
"""TPR coordinate reader should expose box dimensions (issue #5375).

When a TPR file is used as the sole coordinate source (either
``mda.Universe(tpr)`` or ``mda.Universe(tpr, tpr)``), the resulting
Universe should have non-None ``dimensions`` that match the box stored
inside the TPR file.
"""
if double_incantation:
u = mda.Universe(tpr_file, tpr_file)
else:
u = mda.Universe(tpr_file)
assert u.dimensions is not None, (
"u.dimensions should not be None when using a TPR file as the "
"coordinate source (issue #5375)"
)
assert_allclose(u.dimensions, expected_dimensions, atol=1e-3)


def test_tpr_box_vectors_unit_conversion():
"""Box lengths from TPR reader should respect the convert_units flag.

With ``convert_units=True`` (default) lengths are in Angstroms; with
``convert_units=False`` they remain in GROMACS native nanometres.
Angles are dimensionless and must be identical in both cases.
"""
reader_angstrom = TPRReader(TPR2024_4, convert_units=True)
reader_nm = TPRReader(TPR2024_4, convert_units=False)

dims_ang = reader_angstrom.ts.dimensions
dims_nm = reader_nm.ts.dimensions

assert dims_ang is not None
assert dims_nm is not None

# lengths scale by factor 10 (nm -> Å)
assert_allclose(dims_ang[:3], dims_nm[:3] * 10, rtol=1e-5)
# angles are unchanged
assert_allclose(dims_ang[3:], dims_nm[3:], rtol=1e-5)
Loading