From 35b9cbc8787839726dbf1b155d98b01c2e0b0979 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 2 May 2026 17:56:03 -0600 Subject: [PATCH] ENH: support GMX linear angle potential * Fixes gh-5361. * Add support for parsing linear angle potentials in GMX TPR files. As described in the GMX docs linked in the matching issue, these are useful for planar molecules like CO2. * TPR position and angle data-related regression tests have been added. I did check that the angle data test fails before the patch applied here. * I believe there may be a serious unit/scaling issue in the TPR coordinate parser, but I'll open a separate ticket about that since it is not directly related. --- package/MDAnalysis/topology/tpr/utils.py | 2 +- .../MDAnalysisTests/coordinates/test_tpr.py | 10 ++++++++++ .../MDAnalysisTests/data/tprs/Linear_Angle.tpr | Bin 0 -> 3284 bytes testsuite/MDAnalysisTests/datafiles.py | 2 ++ .../MDAnalysisTests/topology/test_tprparser.py | 12 +++++++++++- 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/tprs/Linear_Angle.tpr diff --git a/package/MDAnalysis/topology/tpr/utils.py b/package/MDAnalysis/topology/tpr/utils.py index a6158333de..e959d71677 100644 --- a/package/MDAnalysis/topology/tpr/utils.py +++ b/package/MDAnalysis/topology/tpr/utils.py @@ -826,7 +826,7 @@ def do_moltype(data, symtab, fver): bonds += list(ik_obj.process(ias)) elif ik_obj.name in ['ANGLES', 'G96ANGLES', 'CROSS_BOND_BOND', 'CROSS_BOND_ANGLE', 'UREY_BRADLEY', 'QANGLES', - 'RESTRANGLES', 'TABANGLES']: + 'RESTRANGLES', 'TABANGLES', 'LINEAR_ANGLES']: angles += list(ik_obj.process(ias)) elif ik_obj.name in ['PDIHS', 'RBDIHS', 'RESTRDIHS', 'CBTDIHS', 'FOURDIHS', 'TABDIHS']: diff --git a/testsuite/MDAnalysisTests/coordinates/test_tpr.py b/testsuite/MDAnalysisTests/coordinates/test_tpr.py index 8f27c3fdea..a12b08c392 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_tpr.py +++ b/testsuite/MDAnalysisTests/coordinates/test_tpr.py @@ -68,6 +68,7 @@ TPR2020B2, INPCRD, TPR_gh_5145, + TPR_linear_angle, ) import MDAnalysis as mda @@ -80,6 +81,15 @@ @pytest.mark.parametrize( "tpr_file, exp_first_atom, exp_last_atom, exp_shape, exp_vel_first_atom, exp_vel_last_atom", [ + # see gh-5361 for CO2/linear angle: + ( + TPR_linear_angle, + [1.250, 1.250, 1.250], + [1.250, 1.260, 1.136], + (3, 3), + [0, 0, 0], + [0, 0, 0], + ), # this case is an alanine dipeptide # with neural network potential active # and nonzero velocities diff --git a/testsuite/MDAnalysisTests/data/tprs/Linear_Angle.tpr b/testsuite/MDAnalysisTests/data/tprs/Linear_Angle.tpr new file mode 100644 index 0000000000000000000000000000000000000000..28fb0436897e3fd9ab8598025ee3d726381bb3c3 GIT binary patch literal 3284 zcmd^B%WD&15dYS;8Y3!LLGe@&ABak|t=ekcq-)v~n@w$efETUpRs*dKNx;Xc;8{dO zv?8Jif*_&?@o})CCPg!HXXBB&*IftLs-auUtbGW2%YE#0!XH#M6qwV9;9%2%0Kl2OuP1%`iKOI*5pVrbnK? zjnWi}-lnt_ImZ-wIvzu@dr4N<$7@qqoW#Ca*)t0J3U5)kN8zmsZ&!GS!pjt{XUu)J zGc=5`5hCs%q8&KDXB!GXv|xwLIM zt;DP!Vb-mW)n&b@V0Bq%o$F_0Mm4lvo$=C`s$icn@4?%Z>dwfVWKWTfyP(RGArspvT}Xw^y8+NkDS&w-Pb$3ZewNp*@ST^)=Gj7F>0b;Cz)9K!Cib~hLvQi zq^ZdfWA0uepHPwma)2$x?3$Id@Rd(!+eC6Xagw+#WR1}qV(W-86U-O{o5h&duM%Qw zh>-{MJ2@os|9;}!fG|hQ5IPv&9|#!l&Fa<ll{ut5@O-El|Q zW*}SN-#)8U4Mu_A2~YRbGD% zave$s$&5XFCuyX6%lUm`?`Kz@)1D=cZS^xk5 literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 7083c10e30..0289d64aed 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -137,6 +137,7 @@ "TPR2024_4", "TPR2025_0", "TPR2026_0", + "TPR_linear_angle", "TPR510_bonded", "TPR2016_bonded", "TPR2018_bonded", @@ -574,6 +575,7 @@ TPR2024_4 = (_data_ref / "tprs/2lyz_gmx_2024_4.tpr").as_posix() TPR2025_0 = (_data_ref / "tprs/2lyz_gmx_2025_0.tpr").as_posix() TPR2026_0 = (_data_ref / "tprs/2lyz_gmx_2026_0.tpr").as_posix() +TPR_linear_angle = (_data_ref / "tprs/Linear_Angle.tpr").as_posix() # double precision TPR455Double = (_data_ref / "tprs/drew_gmx_4.5.5.double.tpr").as_posix() TPR460 = (_data_ref / "tprs/ab42_gmx_4.6.tpr").as_posix() diff --git a/testsuite/MDAnalysisTests/topology/test_tprparser.py b/testsuite/MDAnalysisTests/topology/test_tprparser.py index 354b41cbed..c53241b52d 100644 --- a/testsuite/MDAnalysisTests/topology/test_tprparser.py +++ b/testsuite/MDAnalysisTests/topology/test_tprparser.py @@ -51,7 +51,8 @@ TPR2023_bonded, TPR2024_4_bonded, TPR2025_0_bonded, TPR2024_bonded, TPR2026_0_bonded, - TPR_NNPOT_2025_0, TPR_NNPOT_2026_0) + TPR_NNPOT_2025_0, TPR_NNPOT_2026_0, + TPR_linear_angle) from numpy.testing import assert_equal # fmt: on @@ -438,3 +439,12 @@ def test_resids(resid_from_one, resid_addition): resids, err_msg="tpr_resid_from_one kwarg not switching resids", ) + + +def test_gh_5361(): + # GROMACS linear angle potential handling with CO2 example + u = mda.Universe(TPR_linear_angle) + actual = u.angles.to_indices() + expected = [[1, 0, 2]] + assert_equal(u.atoms.names, ["C", "O1", "O2"]) + assert_equal(actual, expected)