From de9cd4c6fe986538095e274cdaeea1cc7f13fc7f Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Thu, 20 Nov 2025 12:54:07 +0100 Subject: [PATCH 01/24] added parallelization to WaterBridgeAnalysis --- .../hydrogenbonds/wbridge_analysis.py | 20 +++++++- .../MDAnalysisTests/analysis/conftest.py | 11 +++++ .../MDAnalysisTests/analysis/test_wbridge.py | 47 +++++++++++++++++-- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 027c0b71255..ba6ecd3cdb7 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -716,7 +716,7 @@ def analysis(current, output, u, **kwargs): from MDAnalysis.lib.distances import calc_angles, capped_distance from MDAnalysis.lib.NeighborSearch import AtomNeighborSearch -from ..base import AnalysisBase +from ..base import AnalysisBase, ResultsGroup logger = logging.getLogger("MDAnalysis.analysis.WaterBridgeAnalysis") @@ -804,6 +804,16 @@ class WaterBridgeAnalysis(AnalysisBase): lambda: 1.5, N=1.31, O=1.31, P=1.58, S=1.55 # default value ) # noqa: E741 + _analysis_algorithm_is_parallelizable = True + + @classmethod + def get_supported_backends(cls): + return ( + "serial", + "multiprocessing", + "dask", + ) + def __init__( self, universe, @@ -2152,6 +2162,14 @@ def generate_table(self, output_format=None): def _conclude(self): self.results.timeseries = self._generate_timeseries() + def _get_aggregator(self): + return ResultsGroup( + lookup={ + "timeseries": ResultsGroup.ndarray_hstack, # Get positions + "network": ResultsGroup.ndarray_hstack, # Get positions + } + ) + @property def network(self): wmsg = ( diff --git a/testsuite/MDAnalysisTests/analysis/conftest.py b/testsuite/MDAnalysisTests/analysis/conftest.py index 7918d25f137..2c9233cc7d3 100644 --- a/testsuite/MDAnalysisTests/analysis/conftest.py +++ b/testsuite/MDAnalysisTests/analysis/conftest.py @@ -14,6 +14,9 @@ from MDAnalysis.analysis.hydrogenbonds.hbond_analysis import ( HydrogenBondAnalysis, ) +from MDAnalysis.analysis.hydrogenbonds.wbridge_analysis import ( + WaterBridgeAnalysis, +) from MDAnalysis.analysis.nucleicacids import NucPairDist from MDAnalysis.analysis.contacts import Contacts from MDAnalysis.analysis.density import DensityAnalysis @@ -208,3 +211,11 @@ def client_InterRDF(request): @pytest.fixture(scope="module", params=params_for_cls(InterRDF_s)) def client_InterRDF_s(request): return request.param + + +# MDAnalysis.analysis.rdf + + +@pytest.fixture(scope="module", params=params_for_cls(WaterBridgeAnalysis)) +def client_WaterBridgeAnalysis(request): + return request.param diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 2b53d2f1d35..a308a306109 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -45,7 +45,7 @@ def universe_DA(): @staticmethod @pytest.fixture(scope="class") - def universe_DA_PBC(): + def universe_DA_PBC(tmp_path_factory): """A universe with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition""" grofile = """Test gro file @@ -54,9 +54,28 @@ def universe_DA_PBC(): 1ALA H 2 0.900 0.000 0.000 4ALA O 3 0.100 0.000 0.000 1.0 1.0 1.0""" - u = MDAnalysis.Universe(StringIO(grofile), format="gro") + tmp_path = tmp_path_factory.mktemp("DA_PBC") + gro_path_pbc = tmp_path / "DA_PBC.gro" + gro_path_pbc.write_text(grofile) + + # build a file-backed Universe (works with multiprocessing) + u = MDAnalysis.Universe(str(gro_path_pbc)) return u + @staticmethod + @pytest.fixture(scope="class") + def universe_DA_PBC_10frames(universe_DA_PBC, tmp_path_factory): + u_single = universe_DA_PBC + n_atoms = u_single.atoms.n_atoms + + tmp_path = tmp_path_factory.mktemp("DA_PBC_10") + dcd_path = tmp_path / "DA_PBC_10.dcd" + with MDAnalysis.Writer(str(dcd_path), n_atoms=n_atoms) as W: + for _ in range(10): + W.write(u_single) # same coords each frame + + return MDAnalysis.Universe(u_single.filename, str(dcd_path)) + @staticmethod @pytest.fixture(scope="class") def universe_AD(): @@ -392,7 +411,7 @@ def test_donor_accepter(self, universe_DA, distance_type): assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_accepter_pbc(self, universe_DA_PBC, distance_type): + def test_donor_accepter_pbc(self, universe_DA_PBC, distance_type, client_WaterBridgeAnalysis): """Test zeroth order donor to acceptor hydrogen bonding in PBC conditions""" wb = WaterBridgeAnalysis( universe_DA_PBC, @@ -402,10 +421,30 @@ def test_donor_accepter_pbc(self, universe_DA_PBC, distance_type): pbc=True, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) + def test_donor_accepter_pbc_multi(self, universe_DA_PBC_10frames, distance_type, client_WaterBridgeAnalysis): + """Test zeroth order donor to acceptor hydrogen bonding in PBC conditions""" + wb = WaterBridgeAnalysis( + universe_DA_PBC_10frames, + "protein and (resid 1)", + "protein and (resid 4)", + order=0, + pbc=True, + distance_type=distance_type, + ) + wb.run(**client_WaterBridgeAnalysis, verbose=False) + + # One network entry per frame + assert len(wb.results.network) == 10 + + # Check that the first frame still has the expected connectivity + network0 = wb.results.network[0] + assert_equal(list(network0.keys())[0][:4], (1, 0, 2, None)) + @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) def test_accepter_donor(self, universe_AD, distance_type): """Test zeroth order acceptor to donor hydrogen bonding""" From 84c74d0cd137359a646e1e93bf5c8990774b6034 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 25 Nov 2025 03:30:08 +0100 Subject: [PATCH 02/24] adjusted the test files for WB --- testsuite/MDAnalysisTests/analysis/WB_AD.gro | 6 + testsuite/MDAnalysisTests/analysis/WB_AWA.gro | 8 + .../MDAnalysisTests/analysis/WB_AWA_AWWA.gro | 15 + testsuite/MDAnalysisTests/analysis/WB_AWD.gro | 8 + .../MDAnalysisTests/analysis/WB_AWWA.gro | 10 + .../MDAnalysisTests/analysis/WB_AWWWA.gro | 12 + .../MDAnalysisTests/analysis/WB_AWWWWA.gro | 14 + testsuite/MDAnalysisTests/analysis/WB_DA.gro | 6 + .../MDAnalysisTests/analysis/WB_DA_PBC.gro | 6 + testsuite/MDAnalysisTests/analysis/WB_DWA.gro | 8 + testsuite/MDAnalysisTests/analysis/WB_DWD.gro | 8 + .../MDAnalysisTests/analysis/WB_branch.gro | 12 + .../analysis/WB_duplicate_water.gro | 10 + .../MDAnalysisTests/analysis/WB_empty.gro | 8 + .../MDAnalysisTests/analysis/WB_loop.gro | 8 + .../analysis/WB_multiframe.dcd | Bin 0 -> 1300 bytes .../analysis/WB_multiframe.gro | 16 + .../MDAnalysisTests/analysis/test_wbridge.py | 415 ++++-------------- .../data/waterbridge/wb_ad.gro | 6 + .../data/waterbridge/wb_awa.gro | 8 + .../data/waterbridge/wb_awa_awwa.gro | 15 + .../data/waterbridge/wb_awd.gro | 8 + .../data/waterbridge/wb_awwa.gro | 10 + .../data/waterbridge/wb_awwwa.gro | 12 + .../data/waterbridge/wb_awwwwa.gro | 14 + .../data/waterbridge/wb_branch.gro | 12 + .../data/waterbridge/wb_da.gro | 6 + .../data/waterbridge/wb_da_pbc.gro | 6 + .../data/waterbridge/wb_duplicate_water.gro | 10 + .../data/waterbridge/wb_dwa.gro | 8 + .../data/waterbridge/wb_dwd.gro | 8 + .../data/waterbridge/wb_empty.gro | 8 + .../data/waterbridge/wb_loop.gro | 8 + .../data/waterbridge/wb_multiframe.dcd | Bin 0 -> 1300 bytes .../data/waterbridge/wb_multiframe.gro | 16 + testsuite/MDAnalysisTests/datafiles.py | 36 +- 36 files changed, 421 insertions(+), 340 deletions(-) create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AD.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWD.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_DA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_DWA.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_DWD.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_branch.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_empty.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_loop.gro create mode 100644 testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd create mode 100644 testsuite/MDAnalysisTests/analysis/WB_multiframe.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_ad.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awa_awwa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awd.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awwa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awwwa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_awwwwa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_branch.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_da.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_da_pbc.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_duplicate_water.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_dwa.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_dwd.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_empty.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_loop.gro create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_multiframe.dcd create mode 100644 testsuite/MDAnalysisTests/data/waterbridge/wb_multiframe.gro diff --git a/testsuite/MDAnalysisTests/analysis/WB_AD.gro b/testsuite/MDAnalysisTests/analysis/WB_AD.gro new file mode 100644 index 00000000000..62058bb2539 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AD.gro @@ -0,0 +1,6 @@ +Test gro file +3 + 1ALA O 1 0.000 0.000 0.000 + 4ALA H 2 0.200 0.000 0.000 + 4ALA N 3 0.300 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWA.gro new file mode 100644 index 00000000000..cc1cd188bc9 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWA.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro new file mode 100644 index 00000000000..d40311bd070 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro @@ -0,0 +1,15 @@ +Test gro file +12 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 5ALA O 6 0.000 1.000 0.000 + 6SOL OW 7 0.300 1.000 0.000 + 6SOL HW1 8 0.200 1.000 0.000 + 6SOL HW2 9 0.400 1.000 0.000 + 7SOL OW 10 0.600 1.000 0.000 + 7SOL HW1 11 0.700 1.000 0.000 + 8ALA O 12 0.900 1.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWD.gro b/testsuite/MDAnalysisTests/analysis/WB_AWD.gro new file mode 100644 index 00000000000..4e2488d622e --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWD.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 4ALA H 4 0.500 0.000 0.000 + 4ALA N 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro new file mode 100644 index 00000000000..7e7f6e5f926 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro @@ -0,0 +1,10 @@ +Test gro file +7 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4ALA O 7 0.900 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro new file mode 100644 index 00000000000..78a065cf9d3 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro @@ -0,0 +1,12 @@ +Test gro file +9 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5ALA O 9 1.200 0.000 0.000 + 10.0 10.0 10.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro new file mode 100644 index 00000000000..22068b5f7be --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro @@ -0,0 +1,14 @@ +Test gro file +11 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 4SOL OW 7 0.900 0.000 0.000 + 4SOL HW1 8 1.000 0.000 0.000 + 5SOL OW 9 1.200 0.000 0.000 + 5SOL HW1 10 1.300 0.000 0.000 + 6ALA O 11 1.400 0.000 0.000 + 10.0 10.0 10.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DA.gro b/testsuite/MDAnalysisTests/analysis/WB_DA.gro new file mode 100644 index 00000000000..4ba43076985 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_DA.gro @@ -0,0 +1,6 @@ +Test gro file +3 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 4ALA O 3 0.300 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro b/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro new file mode 100644 index 00000000000..f231b93abb4 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro @@ -0,0 +1,6 @@ +Test gro file +3 + 1ALA N 1 0.800 0.000 0.000 + 1ALA H 2 0.900 0.000 0.000 + 4ALA O 3 0.100 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DWA.gro b/testsuite/MDAnalysisTests/analysis/WB_DWA.gro new file mode 100644 index 00000000000..76849bce17f --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_DWA.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DWD.gro b/testsuite/MDAnalysisTests/analysis/WB_DWD.gro new file mode 100644 index 00000000000..b0f864e488b --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_DWD.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 0.300 0.000 0.000 + 4ALA H 4 0.500 0.000 0.000 + 4ALA N 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_branch.gro b/testsuite/MDAnalysisTests/analysis/WB_branch.gro new file mode 100644 index 00000000000..a488330fb36 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_branch.gro @@ -0,0 +1,12 @@ +Test gro file +9 + 1ALA O 1 0.000 0.000 0.000 + 2SOL OW 2 0.300 0.000 0.000 + 2SOL HW1 3 0.200 0.000 0.000 + 2SOL HW2 4 0.400 0.000 0.000 + 3SOL OW 5 0.600 0.000 0.000 + 3SOL HW1 6 0.700 0.000 0.000 + 3SOL HW2 7 0.600 0.100 0.000 + 4ALA O 8 0.900 0.000 0.000 + 5ALA O 9 0.600 0.300 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro b/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro new file mode 100644 index 00000000000..daf237d7d17 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro @@ -0,0 +1,10 @@ +Test gro file + 7 + 1LEU O 1 1.876 0.810 1.354 + 117SOL HW1 2 1.853 0.831 1.162 + 117SOL OW 3 1.877 0.890 1.081 + 117SOL HW2 4 1.908 0.828 1.007 + 135SOL OW 5 1.924 0.713 0.845 + 1LEU H 6 1.997 0.991 1.194 + 1LEU N 7 2.041 1.030 1.274 + 2.22092 2.22092 2.22092 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_empty.gro b/testsuite/MDAnalysisTests/analysis/WB_empty.gro new file mode 100644 index 00000000000..d920f81fc2c --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_empty.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA N 1 0.000 0.000 0.000 + 1ALA H 2 0.100 0.000 0.000 + 2SOL OW 3 3.000 0.000 0.000 + 4ALA H 4 0.500 0.000 0.000 + 4ALA N 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_loop.gro b/testsuite/MDAnalysisTests/analysis/WB_loop.gro new file mode 100644 index 00000000000..2cbb57eb741 --- /dev/null +++ b/testsuite/MDAnalysisTests/analysis/WB_loop.gro @@ -0,0 +1,8 @@ +Test gro file +5 + 1ALA O 1 0.000 0.001 0.000 + 2SOL OW 2 0.300 0.001 0.000 + 2SOL HW1 3 0.200 0.002 0.000 + 2SOL HW2 4 0.200 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd b/testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd new file mode 100644 index 0000000000000000000000000000000000000000..88c6cf130a3c58cb8d89c77c67fa3951c9b9f560 GIT binary patch literal 1300 zcmWGxU|?|e4{~7v(jdSHp|Fy5ix)fMlZR=N05U>=_zMs-!vseV4e~cAJb9tC0o?DA z4sa%zfzF4?Mmm7>n83JT+Q9)x0dWHm9{}P9KrG=_zMs-!vseV4e~cAJb9tC0o?DA z4sa%zfzF4?Mmm7>n83JT+Q9)x0dWHm9{}P9KrG Date: Wed, 26 Nov 2025 02:22:54 +0100 Subject: [PATCH 03/24] black formatting --- .../MDAnalysisTests/analysis/test_wbridge.py | 129 ++++++++++-------- testsuite/MDAnalysisTests/datafiles.py | 5 +- 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 1bbc6df1fcf..68f27cf6e99 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -33,6 +33,7 @@ WB_MULTIFRAME_DCD, ) + class TestWaterBridgeAnalysis(object): @staticmethod @pytest.fixture(scope="class") @@ -123,7 +124,7 @@ def test_selection2_type_error(self): selection2_type="aaa", ) - def test_empty_selection(self): + def test_empty_selection(self, client_WaterBridgeAnalysis): """Test the case when selection yields empty result""" universe_DA = MDAnalysis.Universe(WB_DA) wb = WaterBridgeAnalysis( @@ -132,10 +133,10 @@ def test_empty_selection(self): "protein and (resid 10)", order=0, ) - wb.run() + wb.run(**client_WaterBridgeAnalysis) assert wb.results.network == [{}] - def test_loop(self): + def test_loop(self, client_WaterBridgeAnalysis): """Test if loop can be handled correctly""" universe_loop = MDAnalysis.Universe(WB_LOOP) wb = WaterBridgeAnalysis( @@ -143,11 +144,11 @@ def test_loop(self): "protein and (resid 1)", "protein and (resid 1 or resid 4)", ) - wb.run() + wb.run(**client_WaterBridgeAnalysis) assert_equal(len(wb.results.network[0].keys()), 2) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_accepter(self, distance_type): + def test_donor_accepter(self, distance_type, client_WaterBridgeAnalysis): """Test zeroth order donor to acceptor hydrogen bonding""" universe_DA = MDAnalysis.Universe(WB_DA) wb = WaterBridgeAnalysis( @@ -159,12 +160,14 @@ def test_donor_accepter(self, distance_type): debug=True, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_accepter_pbc(self, distance_type, client_WaterBridgeAnalysis): + def test_donor_accepter_pbc( + self, distance_type, client_WaterBridgeAnalysis + ): """Test zeroth order donor to acceptor hydrogen bonding in PBC conditions""" universe_DA_PBC = MDAnalysis.Universe(WB_DA_PBC) wb = WaterBridgeAnalysis( @@ -180,7 +183,7 @@ def test_donor_accepter_pbc(self, distance_type, client_WaterBridgeAnalysis): assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_accepter_donor(self, distance_type): + def test_accepter_donor(self, distance_type, client_WaterBridgeAnalysis): """Test zeroth order acceptor to donor hydrogen bonding""" universe_AD = MDAnalysis.Universe(WB_AD) wb = WaterBridgeAnalysis( @@ -190,12 +193,14 @@ def test_accepter_donor(self, distance_type): order=0, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 1, 2)) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_water_accepter(self, distance_type): + def test_acceptor_water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form water bridge with hydrogen bond acceptor from selection 2""" universe_AWA = MDAnalysis.Universe(WB_AWA) @@ -205,7 +210,7 @@ def test_acceptor_water_accepter(self, distance_type): "protein and (resid 4)", distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -213,7 +218,9 @@ def test_acceptor_water_accepter(self, distance_type): assert_equal(second[list(second.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_water_accepter(self, distance_type): + def test_donor_water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond donor from selection 1 form water bridge with hydrogen bond acceptor from selection 2""" universe_DWA = MDAnalysis.Universe(WB_DWA) @@ -223,7 +230,7 @@ def test_donor_water_accepter(self, distance_type): "protein and (resid 4)", distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) second = network[list(network.keys())[0]] @@ -231,7 +238,9 @@ def test_donor_water_accepter(self, distance_type): assert_equal(second[list(second.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_water_donor(self, distance_type): + def test_acceptor_water_donor( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form water bridge with hydrogen bond donor from selection 2""" universe_AWD = MDAnalysis.Universe(WB_AWD) @@ -241,7 +250,7 @@ def test_acceptor_water_donor(self, distance_type): "protein and (resid 4)", distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -249,7 +258,9 @@ def test_acceptor_water_donor(self, distance_type): assert_equal(second[list(second.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_water_donor(self, distance_type): + def test_donor_water_donor( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond donor from selection 1 form water bridge with hydrogen bond donor from selection 2""" universe_DWD = MDAnalysis.Universe(WB_DWD) @@ -259,21 +270,21 @@ def test_donor_water_donor(self, distance_type): "protein and (resid 4)", distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (1, 0, 2, None)) second = network[list(network.keys())[0]] assert_equal(list(second.keys())[0][:4], (2, None, 3, 4)) assert_equal(second[list(second.keys())[0]], None) - def test_empty(self): + def test_empty(self, client_WaterBridgeAnalysis): """Test case where no water bridge exists""" universe_empty = MDAnalysis.Universe(WB_EMPTY) wb = WaterBridgeAnalysis(universe_empty, "protein", "protein") - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) - def test_same_selection(self): + def test_same_selection(self, client_WaterBridgeAnalysis): """ This test tests that if the selection 1 and selection 2 are both protein. However, the protein only forms one hydrogen bond with the water. @@ -283,11 +294,13 @@ def test_same_selection(self): wb = WaterBridgeAnalysis( universe_DWA, "protein and resid 1", "protein and resid 1" ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_2water_accepter(self, distance_type): + def test_acceptor_2water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form second order water bridge with hydrogen bond acceptor from selection 2""" # test first order @@ -298,7 +311,7 @@ def test_acceptor_2water_accepter(self, distance_type): "protein and (resid 4)", distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) # test second order wb = WaterBridgeAnalysis( @@ -308,7 +321,7 @@ def test_acceptor_2water_accepter(self, distance_type): order=2, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -324,7 +337,7 @@ def test_acceptor_2water_accepter(self, distance_type): order=3, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -334,7 +347,9 @@ def test_acceptor_2water_accepter(self, distance_type): assert_equal(third[list(third.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_3water_accepter(self, distance_type): + def test_acceptor_3water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form third order water bridge with hydrogen bond acceptor from selection 2""" universe_AWWWA = MDAnalysis.Universe(WB_AWWWA) @@ -345,7 +360,7 @@ def test_acceptor_3water_accepter(self, distance_type): order=2, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) wb = WaterBridgeAnalysis( @@ -355,7 +370,7 @@ def test_acceptor_3water_accepter(self, distance_type): order=3, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -373,7 +388,7 @@ def test_acceptor_3water_accepter(self, distance_type): order=4, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -385,7 +400,9 @@ def test_acceptor_3water_accepter(self, distance_type): assert_equal(fourth[list(fourth.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_4water_accepter(self, distance_type): + def test_acceptor_4water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form fourth order water bridge with hydrogen bond acceptor from selection 2""" universe_AWWWWA = MDAnalysis.Universe(WB_AWWWWA) @@ -396,7 +413,7 @@ def test_acceptor_4water_accepter(self, distance_type): order=3, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal(wb.results.network[0], defaultdict(dict)) wb = WaterBridgeAnalysis( @@ -406,7 +423,7 @@ def test_acceptor_4water_accepter(self, distance_type): order=4, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -426,7 +443,7 @@ def test_acceptor_4water_accepter(self, distance_type): order=5, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -440,7 +457,9 @@ def test_acceptor_4water_accepter(self, distance_type): assert_equal(fifth[list(fifth.keys())[0]], None) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_22water_accepter(self, distance_type): + def test_acceptor_22water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test case where the hydrogen bond acceptor from selection 1 form a second order water bridge with hydrogen bond acceptor from selection 2 and the last water is linked to two residues in selection 2""" @@ -452,7 +471,7 @@ def test_acceptor_22water_accepter(self, distance_type): order=2, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -463,7 +482,7 @@ def test_acceptor_22water_accepter(self, distance_type): sorted([key[:4] for key in list(third.keys())]), ) - def test_timeseries_wba(self): + def test_timeseries_wba(self, client_WaterBridgeAnalysis): """Test if the time series data is correctly generated in water bridge analysis format""" universe_branch = MDAnalysis.Universe(WB_BRANCH) wb = WaterBridgeAnalysis( @@ -473,7 +492,7 @@ def test_timeseries_wba(self): order=2, ) wb.output_format = "sele1_sele2" - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) timeseries = sorted(wb.results.timeseries[0]) assert_equal( @@ -489,7 +508,7 @@ def test_timeseries_wba(self): timeseries[3][:4], (6, 8, ("SOL", 3, "HW2"), ("ALA", 5, "O")) ) - def test_timeseries_hba(self): + def test_timeseries_hba(self, client_WaterBridgeAnalysis): """Test if the time series data is correctly generated in hydrogen bond analysis format""" universe_branch = MDAnalysis.Universe(WB_BRANCH) wb = WaterBridgeAnalysis( @@ -499,7 +518,7 @@ def test_timeseries_hba(self): order=2, ) wb.output_format = "donor_acceptor" - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) timeseries = sorted(wb.results.timeseries[0]) assert_equal( @@ -516,7 +535,9 @@ def test_timeseries_hba(self): ) @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_acceptor_12water_accepter(self, distance_type): + def test_acceptor_12water_accepter( + self, distance_type, client_WaterBridgeAnalysis + ): """Test of independent first order and second can be recognised correctely""" universe_AWA_AWWA = MDAnalysis.Universe(WB_AWA_AWWA) wb = WaterBridgeAnalysis( @@ -526,7 +547,7 @@ def test_acceptor_12water_accepter(self, distance_type): order=1, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal(list(network.keys())[0][:4], (0, None, 2, 1)) second = network[list(network.keys())[0]] @@ -540,14 +561,14 @@ def test_acceptor_12water_accepter(self, distance_type): order=2, distance_type=distance_type, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) network = wb.results.network[0] assert_equal( [(0, None, 2, 1), (5, None, 7, 6)], sorted([key[:4] for key in list(network.keys())]), ) - def test_count_by_type_single_link(self): + def test_count_by_type_single_link(self, client_WaterBridgeAnalysis): """ This test tests the simplest water bridge to see if count_by_type() works. """ @@ -555,12 +576,12 @@ def test_count_by_type_single_link(self): wb = WaterBridgeAnalysis( universe_DWA, "protein and (resid 1)", "protein and (resid 4)" ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal( wb.count_by_type(), [(1, 4, "ALA", 1, "H", "ALA", 4, "O", 1.0)] ) - def test_count_by_type_multiple_link(self): + def test_count_by_type_multiple_link(self, client_WaterBridgeAnalysis): """ This test tests if count_by_type() can give the correct result for more than 1 links. """ @@ -571,7 +592,7 @@ def test_count_by_type_multiple_link(self): "protein and (resid 4 or resid 8)", order=2, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) assert_equal( sorted(wb.count_by_type()), [ @@ -725,7 +746,7 @@ def test_count_by_time(self, wb_multiframe): wb_multiframe.count_by_time(), [(0, 1), (1, 1), (2, 1), (3, 1)] ) - def test_count_by_time_weight(self): + def test_count_by_time_weight(self, client_WaterBridgeAnalysis): """ This test tests if modyfing the analysis_func allows the weight to be changed in count_by_type(). @@ -738,7 +759,7 @@ def test_count_by_time_weight(self): "protein and (resid 4 or resid 8)", order=2, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) def analysis(current, output, u): sele1_index, sele1_heavy_index, atom2, heavy_atom2, dist, angle = ( @@ -769,7 +790,7 @@ def analysis(current, output, u): ], ) - def test_count_by_time_empty(self): + def test_count_by_time_empty(self, client_WaterBridgeAnalysis): """ See if count_by_time() can handle zero well. :return: @@ -781,7 +802,7 @@ def test_count_by_time_empty(self): "protein and (resid 4 or resid 8)", order=2, ) - wb.run(verbose=False) + wb.run(**client_WaterBridgeAnalysis, verbose=False) def analysis(current, output, u): pass @@ -817,15 +838,15 @@ def test_timesteps_by_type(self, wb_multiframe): timesteps[3], [1, 12, "ALA", 1, "H", "ALA", 6, "O", 0, 2] ) - def test_duplicate_water(self): + def test_duplicate_water(self, client_WaterBridgeAnalysis): u = MDAnalysis.Universe(WB_DUPLICATE_WATER) wb = WaterBridgeAnalysis( u, "resname LEU and name O", "resname LEU and name N H", order=4 ) - wb.run() + wb.run(**client_WaterBridgeAnalysis) assert len(wb.results.timeseries[0]) == 2 - def test_warn_results_deprecated(self): + def test_warn_results_deprecated(self, client_WaterBridgeAnalysis): universe_DA = MDAnalysis.Universe(WB_DA) wb = WaterBridgeAnalysis( universe_DA, @@ -833,7 +854,7 @@ def test_warn_results_deprecated(self): "protein and (resid 10)", order=0, ) - wb.run() + wb.run(**client_WaterBridgeAnalysis) wmsg = "The `network` attribute was deprecated in MDAnalysis 2.0.0" with pytest.warns(DeprecationWarning, match=wmsg): diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 48b26d7c668..3bd5d0eba8b 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -409,7 +409,6 @@ "WB_DUPLICATE_WATER", "WB_MULTIFRAME_GRO", "WB_MULTIFRAME_DCD", - ] from importlib import resources import MDAnalysisTests.data @@ -949,7 +948,9 @@ WB_DWD = (_data_ref / "waterbridge/wb_dwd.gro").as_posix() WB_EMPTY = (_data_ref / "waterbridge/wb_empty.gro").as_posix() WB_LOOP = (_data_ref / "waterbridge/wb_loop.gro").as_posix() -WB_DUPLICATE_WATER = (_data_ref / "waterbridge/wb_duplicate_water.gro").as_posix() +WB_DUPLICATE_WATER = ( + _data_ref / "waterbridge/wb_duplicate_water.gro" +).as_posix() WB_MULTIFRAME_GRO = (_data_ref / "waterbridge/wb_multiframe.gro").as_posix() WB_MULTIFRAME_DCD = (_data_ref / "waterbridge/wb_multiframe.dcd").as_posix() # This should be the last line: clean up namespace From 946ec67e3ffb7107d5d86b03b31a573e554b5960 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 26 Nov 2025 03:07:24 +0100 Subject: [PATCH 04/24] removed files that were moved --- testsuite/MDAnalysisTests/analysis/WB_AD.gro | 6 -- testsuite/MDAnalysisTests/analysis/WB_AWA.gro | 8 --- .../MDAnalysisTests/analysis/WB_AWA_AWWA.gro | 15 ----- testsuite/MDAnalysisTests/analysis/WB_AWD.gro | 8 --- .../MDAnalysisTests/analysis/WB_AWWA.gro | 10 ---- .../MDAnalysisTests/analysis/WB_AWWWA.gro | 12 ---- .../MDAnalysisTests/analysis/WB_AWWWWA.gro | 14 ----- testsuite/MDAnalysisTests/analysis/WB_DA.gro | 6 -- .../MDAnalysisTests/analysis/WB_DA_PBC.gro | 6 -- testsuite/MDAnalysisTests/analysis/WB_DWA.gro | 8 --- testsuite/MDAnalysisTests/analysis/WB_DWD.gro | 8 --- .../MDAnalysisTests/analysis/WB_branch.gro | 12 ---- .../analysis/WB_duplicate_water.gro | 10 ---- .../MDAnalysisTests/analysis/WB_empty.gro | 8 --- .../MDAnalysisTests/analysis/WB_loop.gro | 8 --- .../analysis/WB_multiframe.dcd | Bin 1300 -> 0 bytes .../analysis/WB_multiframe.gro | 16 ------ .../MDAnalysisTests/analysis/test_wbridge.py | 54 ++++++++++++++++++ 18 files changed, 54 insertions(+), 155 deletions(-) delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AD.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWD.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_DA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_DWA.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_DWD.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_branch.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_empty.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_loop.gro delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd delete mode 100644 testsuite/MDAnalysisTests/analysis/WB_multiframe.gro diff --git a/testsuite/MDAnalysisTests/analysis/WB_AD.gro b/testsuite/MDAnalysisTests/analysis/WB_AD.gro deleted file mode 100644 index 62058bb2539..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AD.gro +++ /dev/null @@ -1,6 +0,0 @@ -Test gro file -3 - 1ALA O 1 0.000 0.000 0.000 - 4ALA H 2 0.200 0.000 0.000 - 4ALA N 3 0.300 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWA.gro deleted file mode 100644 index cc1cd188bc9..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWA.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro deleted file mode 100644 index d40311bd070..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWA_AWWA.gro +++ /dev/null @@ -1,15 +0,0 @@ -Test gro file -12 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 5ALA O 6 0.000 1.000 0.000 - 6SOL OW 7 0.300 1.000 0.000 - 6SOL HW1 8 0.200 1.000 0.000 - 6SOL HW2 9 0.400 1.000 0.000 - 7SOL OW 10 0.600 1.000 0.000 - 7SOL HW1 11 0.700 1.000 0.000 - 8ALA O 12 0.900 1.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWD.gro b/testsuite/MDAnalysisTests/analysis/WB_AWD.gro deleted file mode 100644 index 4e2488d622e..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWD.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 4ALA H 4 0.500 0.000 0.000 - 4ALA N 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro deleted file mode 100644 index 7e7f6e5f926..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWWA.gro +++ /dev/null @@ -1,10 +0,0 @@ -Test gro file -7 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 4ALA O 7 0.900 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro deleted file mode 100644 index 78a065cf9d3..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWWWA.gro +++ /dev/null @@ -1,12 +0,0 @@ -Test gro file -9 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 4SOL OW 7 0.900 0.000 0.000 - 4SOL HW1 8 1.000 0.000 0.000 - 5ALA O 9 1.200 0.000 0.000 - 10.0 10.0 10.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro b/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro deleted file mode 100644 index 22068b5f7be..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_AWWWWA.gro +++ /dev/null @@ -1,14 +0,0 @@ -Test gro file -11 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 4SOL OW 7 0.900 0.000 0.000 - 4SOL HW1 8 1.000 0.000 0.000 - 5SOL OW 9 1.200 0.000 0.000 - 5SOL HW1 10 1.300 0.000 0.000 - 6ALA O 11 1.400 0.000 0.000 - 10.0 10.0 10.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DA.gro b/testsuite/MDAnalysisTests/analysis/WB_DA.gro deleted file mode 100644 index 4ba43076985..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_DA.gro +++ /dev/null @@ -1,6 +0,0 @@ -Test gro file -3 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 4ALA O 3 0.300 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro b/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro deleted file mode 100644 index f231b93abb4..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_DA_PBC.gro +++ /dev/null @@ -1,6 +0,0 @@ -Test gro file -3 - 1ALA N 1 0.800 0.000 0.000 - 1ALA H 2 0.900 0.000 0.000 - 4ALA O 3 0.100 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DWA.gro b/testsuite/MDAnalysisTests/analysis/WB_DWA.gro deleted file mode 100644 index 76849bce17f..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_DWA.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_DWD.gro b/testsuite/MDAnalysisTests/analysis/WB_DWD.gro deleted file mode 100644 index b0f864e488b..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_DWD.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 0.300 0.000 0.000 - 4ALA H 4 0.500 0.000 0.000 - 4ALA N 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_branch.gro b/testsuite/MDAnalysisTests/analysis/WB_branch.gro deleted file mode 100644 index a488330fb36..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_branch.gro +++ /dev/null @@ -1,12 +0,0 @@ -Test gro file -9 - 1ALA O 1 0.000 0.000 0.000 - 2SOL OW 2 0.300 0.000 0.000 - 2SOL HW1 3 0.200 0.000 0.000 - 2SOL HW2 4 0.400 0.000 0.000 - 3SOL OW 5 0.600 0.000 0.000 - 3SOL HW1 6 0.700 0.000 0.000 - 3SOL HW2 7 0.600 0.100 0.000 - 4ALA O 8 0.900 0.000 0.000 - 5ALA O 9 0.600 0.300 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro b/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro deleted file mode 100644 index daf237d7d17..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_duplicate_water.gro +++ /dev/null @@ -1,10 +0,0 @@ -Test gro file - 7 - 1LEU O 1 1.876 0.810 1.354 - 117SOL HW1 2 1.853 0.831 1.162 - 117SOL OW 3 1.877 0.890 1.081 - 117SOL HW2 4 1.908 0.828 1.007 - 135SOL OW 5 1.924 0.713 0.845 - 1LEU H 6 1.997 0.991 1.194 - 1LEU N 7 2.041 1.030 1.274 - 2.22092 2.22092 2.22092 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_empty.gro b/testsuite/MDAnalysisTests/analysis/WB_empty.gro deleted file mode 100644 index d920f81fc2c..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_empty.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA N 1 0.000 0.000 0.000 - 1ALA H 2 0.100 0.000 0.000 - 2SOL OW 3 3.000 0.000 0.000 - 4ALA H 4 0.500 0.000 0.000 - 4ALA N 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_loop.gro b/testsuite/MDAnalysisTests/analysis/WB_loop.gro deleted file mode 100644 index 2cbb57eb741..00000000000 --- a/testsuite/MDAnalysisTests/analysis/WB_loop.gro +++ /dev/null @@ -1,8 +0,0 @@ -Test gro file -5 - 1ALA O 1 0.000 0.001 0.000 - 2SOL OW 2 0.300 0.001 0.000 - 2SOL HW1 3 0.200 0.002 0.000 - 2SOL HW2 4 0.200 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0 \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd b/testsuite/MDAnalysisTests/analysis/WB_multiframe.dcd deleted file mode 100644 index 88c6cf130a3c58cb8d89c77c67fa3951c9b9f560..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1300 zcmWGxU|?|e4{~7v(jdSHp|Fy5ix)fMlZR=N05U>=_zMs-!vseV4e~cAJb9tC0o?DA z4sa%zfzF4?Mmm7>n83JT+Q9)x0dWHm9{}P9KrG Date: Wed, 26 Nov 2025 03:16:44 +0100 Subject: [PATCH 05/24] black formatting --- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index b01c66e4f04..782f2ad90f9 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -314,8 +314,18 @@ def test_donor_water_donor_multi( ): """Test case where the hydrogen bond donor from selection 1 form water bridge with hydrogen bond donor from selection 2""" - universe_multi = MDAnalysis.Universe(WB_MULTIFRAME_GRO, WB_MULTIFRAME_DCD) - print("Residues:", list(zip(universe_multi.residues.resids, universe_multi.residues.resnames))) + universe_multi = MDAnalysis.Universe( + WB_MULTIFRAME_GRO, WB_MULTIFRAME_DCD + ) + print( + "Residues:", + list( + zip( + universe_multi.residues.resids, + universe_multi.residues.resnames, + ) + ), + ) sel1 = universe_multi.select_atoms("protein and (resid 1)") sel2 = universe_multi.select_atoms("protein and (resid 6)") print("sel1 len:", sel1.n_atoms) From 3b4812ff053fac88a4d2c760f221358828842126 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 26 Nov 2025 17:17:44 +0100 Subject: [PATCH 06/24] fixed case when empty results with parallelization --- .../hydrogenbonds/wbridge_analysis.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index ba6ecd3cdb7..0860c1a7a97 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -1961,25 +1961,32 @@ def count_by_time(self, analysis_func=None, **kwargs): """ if analysis_func is None: analysis_func = self._count_by_time_analysis - if self.results.network: - result = [] - for time, frame in zip(self.timesteps, self.results.network): - result_dict = defaultdict(int) - self._traverse_water_network( - frame, - [], - analysis_func=analysis_func, - output=result_dict, - link_func=self._full_link, - **kwargs, - ) - result.append( - (time, sum([result_dict[key] for key in result_dict])) - ) - return result - else: + + if not self.results.network: return None + # --- simple fallback for missing timesteps --- + if self.timesteps is None: + timesteps = range(len(self.results.network)) + else: + timesteps = self.timesteps + + result = [] + for time, frame in zip(timesteps, self.results.network): + result_dict = defaultdict(int) + self._traverse_water_network( + frame, + [], + analysis_func=analysis_func, + output=result_dict, + link_func=self._full_link, + **kwargs, + ) + result.append( + (time, sum([result_dict[key] for key in result_dict])) + ) + return result + def _timesteps_by_type_analysis(self, current, output, *args, **kwargs): s1_index, to_index, s1, to_residue, dist, angle = ( self._expand_timeseries(current[0]) From dd2e21bb8d6e55593254eebe647c2d7e6292053d Mon Sep 17 00:00:00 2001 From: Egor Marin Date: Thu, 27 Nov 2025 00:44:45 +0100 Subject: [PATCH 07/24] remove unused imports --- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 782f2ad90f9..ac5c6c7af71 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -1,4 +1,3 @@ -from io import StringIO from collections import defaultdict from numpy.testing import ( @@ -6,7 +5,6 @@ assert_array_equal, ) import pytest -from pathlib import Path import MDAnalysis from MDAnalysis.analysis.hydrogenbonds.wbridge_analysis import ( From 6fab4c6e26a42c0513adff2e94f092b32fd5b5f9 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Thu, 27 Nov 2025 12:57:03 +0100 Subject: [PATCH 08/24] got back StringIO case for showcase --- .../MDAnalysisTests/analysis/test_wbridge.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 782f2ad90f9..359c069f0af 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -35,6 +35,22 @@ class TestWaterBridgeAnalysis(object): + @staticmethod + @pytest.fixture(scope="class") + def universe_loop(): + """A universe with one hydrogen bond acceptor bonding to a water which + bonds back to the first hydrogen bond acceptor and thus form a loop""" + grofile = """Test gro file +5 + 1ALA O 1 0.000 0.001 0.000 + 2SOL OW 2 0.300 0.001 0.000 + 2SOL HW1 3 0.200 0.002 0.000 + 2SOL HW2 4 0.200 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") + return u + @staticmethod @pytest.fixture(scope="class") def wb_multiframe(): @@ -148,9 +164,8 @@ def test_empty_selection(self, client_WaterBridgeAnalysis): wb.run(**client_WaterBridgeAnalysis) assert wb.results.network == [{}] - def test_loop(self, client_WaterBridgeAnalysis): + def test_loop(self, universe_loop, client_WaterBridgeAnalysis): """Test if loop can be handled correctly""" - universe_loop = MDAnalysis.Universe(WB_LOOP) wb = WaterBridgeAnalysis( universe_loop, "protein and (resid 1)", From d6bdae950b69cdadd73d427012b7d1b999ec828c Mon Sep 17 00:00:00 2001 From: Valerij Talagayev <82884038+talagayev@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:02:03 +0100 Subject: [PATCH 09/24] Update test_wbridge.py get one StringIO case back for showcase --- .../MDAnalysisTests/analysis/test_wbridge.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index ac5c6c7af71..0ceee764e28 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -33,6 +33,22 @@ class TestWaterBridgeAnalysis(object): + @staticmethod + @pytest.fixture(scope="class") + def universe_loop(): + """A universe with one hydrogen bond acceptor bonding to a water which + bonds back to the first hydrogen bond acceptor and thus form a loop""" + grofile = """Test gro file +5 + 1ALA O 1 0.000 0.001 0.000 + 2SOL OW 2 0.300 0.001 0.000 + 2SOL HW1 3 0.200 0.002 0.000 + 2SOL HW2 4 0.200 0.000 0.000 + 4ALA O 5 0.600 0.000 0.000 + 1.0 1.0 1.0""" + u = MDAnalysis.Universe(StringIO(grofile), format="gro") + return u + @staticmethod @pytest.fixture(scope="class") def wb_multiframe(): @@ -146,9 +162,8 @@ def test_empty_selection(self, client_WaterBridgeAnalysis): wb.run(**client_WaterBridgeAnalysis) assert wb.results.network == [{}] - def test_loop(self, client_WaterBridgeAnalysis): + def test_loop(self, universe_loop, client_WaterBridgeAnalysis): """Test if loop can be handled correctly""" - universe_loop = MDAnalysis.Universe(WB_LOOP) wb = WaterBridgeAnalysis( universe_loop, "protein and (resid 1)", From e10772c1d1b9398495e02b77fd410dd0af04aa92 Mon Sep 17 00:00:00 2001 From: Valerij Talagayev <82884038+talagayev@users.noreply.github.com> Date: Thu, 27 Nov 2025 14:07:32 +0100 Subject: [PATCH 10/24] Update conftest.py add missing --- testsuite/MDAnalysisTests/analysis/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/analysis/conftest.py b/testsuite/MDAnalysisTests/analysis/conftest.py index ea1d3a8febb..47dd0ab1f4f 100644 --- a/testsuite/MDAnalysisTests/analysis/conftest.py +++ b/testsuite/MDAnalysisTests/analysis/conftest.py @@ -226,4 +226,5 @@ def client_DistanceMatrix(request): @pytest.fixture(scope="module", params=params_for_cls(WaterBridgeAnalysis)) -def client_WaterBridgeAnalysis(request): \ No newline at end of file +def client_WaterBridgeAnalysis(request): + return request.param From fca6ca20885d631eef28bae82f258a473165af94 Mon Sep 17 00:00:00 2001 From: Valerij Talagayev <82884038+talagayev@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:50:28 +0100 Subject: [PATCH 11/24] Update test_wbridge.py import addition --- testsuite/MDAnalysisTests/analysis/test_wbridge.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 0ceee764e28..e26e962a508 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -1,3 +1,4 @@ +from io import StringIO from collections import defaultdict from numpy.testing import ( From d26ec16d6ffd70df3469a290f2e57bd77c2049a6 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 3 Feb 2026 00:44:58 +0100 Subject: [PATCH 12/24] suggested PR adjustment --- .../hydrogenbonds/wbridge_analysis.py | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 0860c1a7a97..8227908d892 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -1024,7 +1024,8 @@ def __init__( # final result accessed as self.results.network self.results.network = [] self.results.timeseries = None - self.timesteps = None # time for each frame + self.results.timesteps = None + self._timesteps = [] self._log_parameters() @@ -1311,7 +1312,7 @@ def _prepare(self): self._update_selection() - self.timesteps = [] + self._timesteps = [] if len(self._s1) and len(self._s2): self._update_water_selection() else: @@ -1405,8 +1406,37 @@ def _donor2acceptor(self, donors, h_donors, acceptor): ) return result + def _iter_timesteps(self): + """Iterable timesteps aligned with results.network. + + In parallel backends, aggregation can occasionally yield a 0-d object + array containing None (e.g. array(None, dtype=object)). This helper + normalizes such cases and falls back to frame indices. + """ + n = len(self.results.network) + ts = self.results.timesteps + + if ts is None: + return range(n) + + ts = np.asarray(ts) + + # e.g. array(None, dtype=object) or scalar time + if ts.ndim == 0: + item = ts.item() + if item is None: + return range(n) + # if only one frame, accept scalar; otherwise fall back + return [item] if n == 1 else range(n) + + # empty or mismatched length -> fall back + if ts.size != n: + return range(n) + + return ts + def _single_frame(self): - self.timesteps.append(self._ts.time) + self._timesteps.append(self._ts.time) self.box = self.u.dimensions if self.pbc else None if self.update_selection: @@ -1965,11 +1995,22 @@ def count_by_time(self, analysis_func=None, **kwargs): if not self.results.network: return None - # --- simple fallback for missing timesteps --- - if self.timesteps is None: - timesteps = range(len(self.results.network)) + # Fallback when missing/empty/mismatched timesteps as missing + # happens when some parts end up contributing no timesteps + # Calculate frames and how many timesteps produced + n = len(self.results.network) + timesteps = self.results.timesteps + + # Fallback if None are produced + if timesteps is None: + timesteps = range(n) + self.results.timesteps = np.asarray(list(timesteps), dtype=float) else: - timesteps = self.timesteps + timesteps = np.asarray(timesteps) + # Check lenght for validation + if timesteps.ndim != 1 or timesteps.size != n: + timesteps = range(n) + self.results.timesteps = np.asarray(list(timesteps), dtype=float) result = [] for time, frame in zip(timesteps, self.results.network): @@ -2033,11 +2074,12 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): if self.results.network: result = defaultdict(list) - if self.timesteps is None: + timesteps = self.results.timesteps + if timesteps is None: timesteps = range(len(self.results.network)) - else: - timesteps = self.timesteps for time, frame in zip(timesteps, self.results.network): + if isinstance(time, (float, np.floating)) and float(time).is_integer(): + time = int(time) self._traverse_water_network( frame, [], @@ -2137,7 +2179,11 @@ def generate_table(self, output_format=None): # standard array, like this: out = np.empty((num_records,), dtype=dtype) cursor = 0 # current row - for t, hframe in zip(self.timesteps, timeseries): + timesteps = self.results.timesteps + if timesteps is None: + timesteps = range(len(timeseries)) + + for t, hframe in zip(timesteps, timeseries): for ( donor_index, acceptor_index, @@ -2167,13 +2213,17 @@ def generate_table(self, output_format=None): return table def _conclude(self): + # saving timesteps in results for parallelization + self.results.timesteps = np.asarray(self._timesteps) + self.results.timeseries = self._generate_timeseries() def _get_aggregator(self): return ResultsGroup( lookup={ - "timeseries": ResultsGroup.ndarray_hstack, # Get positions - "network": ResultsGroup.ndarray_hstack, # Get positions + "timeseries": ResultsGroup.ndarray_hstack, + "timesteps": ResultsGroup.ndarray_hstack, + "network": ResultsGroup.ndarray_hstack, } ) @@ -2196,3 +2246,12 @@ def timeseries(self): ) warnings.warn(wmsg, DeprecationWarning) return self.results.timeseries + + @property + def timesteps(self): + wmsg = ( + "The `timesteps` attribute is deprecated and will be removed in " + "MDAnalysis 3.0.0. Please use `results.timesteps` instead." + ) + warnings.warn(wmsg, DeprecationWarning) + return self.results.timesteps \ No newline at end of file From 7f06d512d1592448d5b079acf6266c24648b89cb Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 3 Feb 2026 00:45:55 +0100 Subject: [PATCH 13/24] modified test --- .../MDAnalysisTests/analysis/test_wbridge.py | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 359c069f0af..892431bc174 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -1,4 +1,4 @@ -from io import StringIO +import numpy as np from collections import defaultdict from numpy.testing import ( @@ -8,6 +8,8 @@ import pytest from pathlib import Path +from MDAnalysis.lib.util import NamedStream + import MDAnalysis from MDAnalysis.analysis.hydrogenbonds.wbridge_analysis import ( WaterBridgeAnalysis, @@ -35,22 +37,6 @@ class TestWaterBridgeAnalysis(object): - @staticmethod - @pytest.fixture(scope="class") - def universe_loop(): - """A universe with one hydrogen bond acceptor bonding to a water which - bonds back to the first hydrogen bond acceptor and thus form a loop""" - grofile = """Test gro file -5 - 1ALA O 1 0.000 0.001 0.000 - 2SOL OW 2 0.300 0.001 0.000 - 2SOL HW1 3 0.200 0.002 0.000 - 2SOL HW2 4 0.200 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0""" - u = MDAnalysis.Universe(StringIO(grofile), format="gro") - return u - @staticmethod @pytest.fixture(scope="class") def wb_multiframe(): @@ -75,7 +61,7 @@ def wb_multiframe(): } } ) - wb.timesteps = range(len(wb.results.network)) + wb.results.timesteps = np.arange(len(wb.results.network)) return wb @staticmethod @@ -164,8 +150,9 @@ def test_empty_selection(self, client_WaterBridgeAnalysis): wb.run(**client_WaterBridgeAnalysis) assert wb.results.network == [{}] - def test_loop(self, universe_loop, client_WaterBridgeAnalysis): + def test_loop(self, client_WaterBridgeAnalysis): """Test if loop can be handled correctly""" + universe_loop = MDAnalysis.Universe(WB_LOOP) wb = WaterBridgeAnalysis( universe_loop, "protein and (resid 1)", From 412c68236b886cb32d38f7a66b84b225e4a573c0 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 3 Feb 2026 00:47:44 +0100 Subject: [PATCH 14/24] modified test --- .../MDAnalysisTests/analysis/test_wbridge.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index f65c1762352..90764f7c85b 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -36,22 +36,6 @@ class TestWaterBridgeAnalysis(object): - @staticmethod - @pytest.fixture(scope="class") - def universe_loop(): - """A universe with one hydrogen bond acceptor bonding to a water which - bonds back to the first hydrogen bond acceptor and thus form a loop""" - grofile = """Test gro file -5 - 1ALA O 1 0.000 0.001 0.000 - 2SOL OW 2 0.300 0.001 0.000 - 2SOL HW1 3 0.200 0.002 0.000 - 2SOL HW2 4 0.200 0.000 0.000 - 4ALA O 5 0.600 0.000 0.000 - 1.0 1.0 1.0""" - u = MDAnalysis.Universe(StringIO(grofile), format="gro") - return u - @staticmethod @pytest.fixture(scope="class") def wb_multiframe(): @@ -165,8 +149,9 @@ def test_empty_selection(self, client_WaterBridgeAnalysis): wb.run(**client_WaterBridgeAnalysis) assert wb.results.network == [{}] - def test_loop(self, universe_loop, client_WaterBridgeAnalysis): + def test_loop(self, client_WaterBridgeAnalysis): """Test if loop can be handled correctly""" + universe_loop = MDAnalysis.Universe(WB_LOOP) wb = WaterBridgeAnalysis( universe_loop, "protein and (resid 1)", From 49133c5ffd0d9652e1556be17c16f3bf31ad29d7 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 3 Feb 2026 00:51:54 +0100 Subject: [PATCH 15/24] black formatting --- .../analysis/hydrogenbonds/wbridge_analysis.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 8227908d892..4f614c946a7 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -2010,7 +2010,9 @@ def count_by_time(self, analysis_func=None, **kwargs): # Check lenght for validation if timesteps.ndim != 1 or timesteps.size != n: timesteps = range(n) - self.results.timesteps = np.asarray(list(timesteps), dtype=float) + self.results.timesteps = np.asarray( + list(timesteps), dtype=float + ) result = [] for time, frame in zip(timesteps, self.results.network): @@ -2078,7 +2080,10 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): if timesteps is None: timesteps = range(len(self.results.network)) for time, frame in zip(timesteps, self.results.network): - if isinstance(time, (float, np.floating)) and float(time).is_integer(): + if ( + isinstance(time, (float, np.floating)) + and float(time).is_integer() + ): time = int(time) self._traverse_water_network( frame, @@ -2254,4 +2259,4 @@ def timesteps(self): "MDAnalysis 3.0.0. Please use `results.timesteps` instead." ) warnings.warn(wmsg, DeprecationWarning) - return self.results.timesteps \ No newline at end of file + return self.results.timesteps From 200843c36f9c647fcf7f451670a6b234b2f1d674 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Tue, 3 Feb 2026 00:56:40 +0100 Subject: [PATCH 16/24] black formatting --- testsuite/MDAnalysisTests/analysis/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/analysis/conftest.py b/testsuite/MDAnalysisTests/analysis/conftest.py index 47dd0ab1f4f..067ee5702cc 100644 --- a/testsuite/MDAnalysisTests/analysis/conftest.py +++ b/testsuite/MDAnalysisTests/analysis/conftest.py @@ -221,7 +221,7 @@ def client_InterRDF_s(request): def client_DistanceMatrix(request): return request.param - + # MDAnalysis.analysis.hydrogenbonds.wbridge_analysis From 5871f5c876bc873c19f9fc8c9694e40c1f2021a7 Mon Sep 17 00:00:00 2001 From: Valerij Talagayev <82884038+talagayev@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:01:54 +0100 Subject: [PATCH 17/24] Update datafiles.py addition of comments to the files --- testsuite/MDAnalysisTests/datafiles.py | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 3bd5d0eba8b..2d3d692a882 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -392,23 +392,23 @@ "SURFACE_PDB", # 111 FCC lattice topology for NSGrid bug #2345 "SURFACE_TRR", # full precision coordinates for NSGrid bug #2345 "DSSP", # DSSP test suite - "WB_AD", - "WB_AWA", - "WB_AWA_AWWA", - "WB_AWD", - "WB_AWWA", - "WB_AWWWA", - "WB_AWWWWA", - "WB_BRANCH", - "WB_DA", - "WB_DA_PBC", - "WB_DWA", - "WB_DWD", - "WB_EMPTY", - "WB_LOOP", - "WB_DUPLICATE_WATER", - "WB_MULTIFRAME_GRO", - "WB_MULTIFRAME_DCD", + "WB_AD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor + "WB_AWA", # GRO file with two hydrogen bond acceptors that are joined by a water + "WB_AWA_AWWA", # GRO file with one hydrogen bond acceptors are bonded through one or two water + "WB_AWD", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor through a water + "WB_AWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters + "WB_AWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters + "WB_AWWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through four waters + "WB_BRANCH", # GRO file with one hydrogen bond acceptor bonding to two hydrogen bond acceptor in selection 2 + "WB_DA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor + "WB_DA_PBC", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition + "WB_DWA", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor through a water + "WB_DWD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond donor through a water + "WB_EMPTY", # GRO file with no hydrogen bonds + "WB_LOOP", # GRO with hydrogen bond acceptor bonding to water which bonds back to the first hydrogen bond acceptor to form a loop + "WB_DUPLICATE_WATER", # GRO file with duplicate waters, reference to case #3119 + "WB_MULTIFRAME_GRO", # topology file of containing water bridges for multiple frame tests + "WB_MULTIFRAME_DCD", # trajectory file of containing water bridges for multiple frame tests ] from importlib import resources import MDAnalysisTests.data From cc634800aa2c12342941b76beb41fdbd228fc10e Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 4 Feb 2026 01:20:24 +0100 Subject: [PATCH 18/24] comments + black formatting --- .../MDAnalysisTests/analysis/test_wbridge.py | 3 ++ testsuite/MDAnalysisTests/datafiles.py | 37 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 90764f7c85b..26788a7c98c 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -14,6 +14,9 @@ WaterBridgeAnalysis, ) + +# Note: Currently the datafiles are added as a go-around for the +# incompatibility with StringIO with a fix in the future. Issue #5221 from MDAnalysisTests.datafiles import ( WB_AD, WB_AWA, diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 2d3d692a882..b805002962d 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -392,23 +392,23 @@ "SURFACE_PDB", # 111 FCC lattice topology for NSGrid bug #2345 "SURFACE_TRR", # full precision coordinates for NSGrid bug #2345 "DSSP", # DSSP test suite - "WB_AD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor - "WB_AWA", # GRO file with two hydrogen bond acceptors that are joined by a water - "WB_AWA_AWWA", # GRO file with one hydrogen bond acceptors are bonded through one or two water - "WB_AWD", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor through a water - "WB_AWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters - "WB_AWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters - "WB_AWWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through four waters - "WB_BRANCH", # GRO file with one hydrogen bond acceptor bonding to two hydrogen bond acceptor in selection 2 - "WB_DA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor - "WB_DA_PBC", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition - "WB_DWA", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor through a water - "WB_DWD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond donor through a water - "WB_EMPTY", # GRO file with no hydrogen bonds - "WB_LOOP", # GRO with hydrogen bond acceptor bonding to water which bonds back to the first hydrogen bond acceptor to form a loop - "WB_DUPLICATE_WATER", # GRO file with duplicate waters, reference to case #3119 - "WB_MULTIFRAME_GRO", # topology file of containing water bridges for multiple frame tests - "WB_MULTIFRAME_DCD", # trajectory file of containing water bridges for multiple frame tests + "WB_AD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor + "WB_AWA", # GRO file with two hydrogen bond acceptors that are joined by a water + "WB_AWA_AWWA", # GRO file with one hydrogen bond acceptors are bonded through one or two water + "WB_AWD", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor through a water + "WB_AWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through two waters + "WB_AWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through three waters + "WB_AWWWWA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond acceptor through four waters + "WB_BRANCH", # GRO file with one hydrogen bond acceptor bonding to two hydrogen bond acceptor in selection 2 + "WB_DA", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor + "WB_DA_PBC", # GRO file with one hydrogen bond acceptor bonding to a hydrogen bond donor but in a PBC condition + "WB_DWA", # GRO file with one hydrogen bond donor bonding to a hydrogen bond acceptor through a water + "WB_DWD", # GRO file with one hydrogen bond donor bonding to a hydrogen bond donor through a water + "WB_EMPTY", # GRO file with no hydrogen bonds + "WB_LOOP", # GRO with hydrogen bond acceptor bonding to water which bonds back to the first hydrogen bond acceptor to form a loop + "WB_DUPLICATE_WATER", # GRO file with duplicate waters, reference to case #3119 + "WB_MULTIFRAME_GRO", # topology file of containing water bridges for multiple frame tests + "WB_MULTIFRAME_DCD", # trajectory file of containing water bridges for multiple frame tests ] from importlib import resources import MDAnalysisTests.data @@ -934,6 +934,9 @@ # DSSP testing: from https://github.com/ShintaroMinami/PyDSSP DSSP = (_data_ref / "dssp").as_posix() + +# Water Bridge files. Currently used due to the +# incompatibility of StringIO with parallelization backends. Issue #5221 WB_AD = (_data_ref / "waterbridge/wb_ad.gro").as_posix() WB_AWA = (_data_ref / "waterbridge/wb_awa.gro").as_posix() WB_AWA_AWWA = (_data_ref / "waterbridge/wb_awa_awwa.gro").as_posix() From 07fff268ea96d4b388b678d43134992f822c7778 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 11 Feb 2026 22:59:26 +0100 Subject: [PATCH 19/24] removal of teststeps and use of times --- .../hydrogenbonds/wbridge_analysis.py | 131 +++++------------- .../MDAnalysisTests/analysis/test_wbridge.py | 2 +- 2 files changed, 35 insertions(+), 98 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 4f614c946a7..d0342ac2b3b 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -1024,8 +1024,6 @@ def __init__( # final result accessed as self.results.network self.results.network = [] self.results.timeseries = None - self.results.timesteps = None - self._timesteps = [] self._log_parameters() @@ -1312,7 +1310,6 @@ def _prepare(self): self._update_selection() - self._timesteps = [] if len(self._s1) and len(self._s2): self._update_water_selection() else: @@ -1406,37 +1403,7 @@ def _donor2acceptor(self, donors, h_donors, acceptor): ) return result - def _iter_timesteps(self): - """Iterable timesteps aligned with results.network. - - In parallel backends, aggregation can occasionally yield a 0-d object - array containing None (e.g. array(None, dtype=object)). This helper - normalizes such cases and falls back to frame indices. - """ - n = len(self.results.network) - ts = self.results.timesteps - - if ts is None: - return range(n) - - ts = np.asarray(ts) - - # e.g. array(None, dtype=object) or scalar time - if ts.ndim == 0: - item = ts.item() - if item is None: - return range(n) - # if only one frame, accept scalar; otherwise fall back - return [item] if n == 1 else range(n) - - # empty or mismatched length -> fall back - if ts.size != n: - return range(n) - - return ts - def _single_frame(self): - self._timesteps.append(self._ts.time) self.box = self.u.dimensions if self.pbc else None if self.update_selection: @@ -1991,44 +1958,29 @@ def count_by_time(self, analysis_func=None, **kwargs): """ if analysis_func is None: analysis_func = self._count_by_time_analysis - - if not self.results.network: - return None - - # Fallback when missing/empty/mismatched timesteps as missing - # happens when some parts end up contributing no timesteps - # Calculate frames and how many timesteps produced - n = len(self.results.network) - timesteps = self.results.timesteps - - # Fallback if None are produced - if timesteps is None: - timesteps = range(n) - self.results.timesteps = np.asarray(list(timesteps), dtype=float) - else: - timesteps = np.asarray(timesteps) - # Check lenght for validation - if timesteps.ndim != 1 or timesteps.size != n: - timesteps = range(n) - self.results.timesteps = np.asarray( - list(timesteps), dtype=float + if self.results.network: + result = [] + n = len(self.results.network) + times = np.asarray(self.times) + if times.size != n: + raise ValueError("times/results length mismatch") + + for time, frame in zip(times, self.results.network): + result_dict = defaultdict(int) + self._traverse_water_network( + frame, + [], + analysis_func=analysis_func, + output=result_dict, + link_func=self._full_link, + **kwargs, ) - - result = [] - for time, frame in zip(timesteps, self.results.network): - result_dict = defaultdict(int) - self._traverse_water_network( - frame, - [], - analysis_func=analysis_func, - output=result_dict, - link_func=self._full_link, - **kwargs, - ) - result.append( - (time, sum([result_dict[key] for key in result_dict])) - ) - return result + result.append( + (time, sum([result_dict[key] for key in result_dict])) + ) + return result + else: + return None def _timesteps_by_type_analysis(self, current, output, *args, **kwargs): s1_index, to_index, s1, to_residue, dist, angle = ( @@ -2076,15 +2028,12 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): if self.results.network: result = defaultdict(list) - timesteps = self.results.timesteps - if timesteps is None: - timesteps = range(len(self.results.network)) - for time, frame in zip(timesteps, self.results.network): - if ( - isinstance(time, (float, np.floating)) - and float(time).is_integer() - ): - time = int(time) + n = len(self.results.network) + times = np.asarray(self.times) + if times.size != n: + raise ValueError("times/results length mismatch") + + for time, frame in zip(times, self.results.network): self._traverse_water_network( frame, [], @@ -2184,11 +2133,12 @@ def generate_table(self, output_format=None): # standard array, like this: out = np.empty((num_records,), dtype=dtype) cursor = 0 # current row - timesteps = self.results.timesteps - if timesteps is None: - timesteps = range(len(timeseries)) + n = len(timeseries) + times = np.asarray(self.times) + if times.size != n: + raise ValueError("times/results length mismatch") - for t, hframe in zip(timesteps, timeseries): + for t, hframe in zip(times, timeseries): for ( donor_index, acceptor_index, @@ -2218,16 +2168,12 @@ def generate_table(self, output_format=None): return table def _conclude(self): - # saving timesteps in results for parallelization - self.results.timesteps = np.asarray(self._timesteps) - self.results.timeseries = self._generate_timeseries() def _get_aggregator(self): return ResultsGroup( lookup={ "timeseries": ResultsGroup.ndarray_hstack, - "timesteps": ResultsGroup.ndarray_hstack, "network": ResultsGroup.ndarray_hstack, } ) @@ -2250,13 +2196,4 @@ def timeseries(self): "`results.timeseries` instead" ) warnings.warn(wmsg, DeprecationWarning) - return self.results.timeseries - - @property - def timesteps(self): - wmsg = ( - "The `timesteps` attribute is deprecated and will be removed in " - "MDAnalysis 3.0.0. Please use `results.timesteps` instead." - ) - warnings.warn(wmsg, DeprecationWarning) - return self.results.timesteps + return self.results.timeseries \ No newline at end of file diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 26788a7c98c..3130b561241 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -63,7 +63,7 @@ def wb_multiframe(): } } ) - wb.results.timesteps = np.arange(len(wb.results.network)) + wb.times = np.arange(len(wb.results.network)) return wb @staticmethod From 3303da579cfde018db7f0f4d478f7d66462b65b0 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 11 Feb 2026 23:00:35 +0100 Subject: [PATCH 20/24] black format --- package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index d0342ac2b3b..58c359b1201 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -2196,4 +2196,4 @@ def timeseries(self): "`results.timeseries` instead" ) warnings.warn(wmsg, DeprecationWarning) - return self.results.timeseries \ No newline at end of file + return self.results.timeseries From 4398e139f3f355b86469ef136053e876f7e54dc9 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Wed, 11 Feb 2026 23:51:53 +0100 Subject: [PATCH 21/24] adjusted tests --- .../MDAnalysisTests/analysis/test_wbridge.py | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_wbridge.py b/testsuite/MDAnalysisTests/analysis/test_wbridge.py index 3130b561241..97ae26889a4 100644 --- a/testsuite/MDAnalysisTests/analysis/test_wbridge.py +++ b/testsuite/MDAnalysisTests/analysis/test_wbridge.py @@ -66,18 +66,6 @@ def wb_multiframe(): wb.times = np.arange(len(wb.results.network)) return wb - @staticmethod - @pytest.fixture(scope="class") - def universe_DWD_multi(): - multi_universe = MDAnalysis.Universe(WB_DWD) - u_single = multi_universe - n_atoms = u_single.atoms.n_atoms - dcd_path = "WB_DWD_multi.dcd" - with MDAnalysis.Writer(dcd_path, n_atoms=n_atoms) as W: - for _ in range(10): - W.write(u_single) - return MDAnalysis.Universe(multi_universe.filename, dcd_path) - def test_nodata(self): """Test if the funtions can run when there is no data. This is achieved by not runing the run() first.""" @@ -293,58 +281,6 @@ def test_donor_water_donor( assert_equal(list(second.keys())[0][:4], (2, None, 3, 4)) assert_equal(second[list(second.keys())[0]], None) - @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_water_donor_multi( - self, universe_DWD_multi, distance_type, client_WaterBridgeAnalysis - ): - wb = WaterBridgeAnalysis( - universe_DWD_multi, - "protein and (resid 1)", - "protein and (resid 4)", - distance_type=distance_type, - ) - wb.run(**client_WaterBridgeAnalysis, verbose=False) - - assert len(wb.results.network) == 10 - network0 = wb.results.network[0] - assert_equal(list(network0.keys())[0][:4], (1, 0, 2, None)) - second = network0[list(network0.keys())[0]] - assert_equal(list(second.keys())[0][:4], (2, None, 3, 4)) - assert_equal(second[list(second.keys())[0]], None) - - @pytest.mark.parametrize("distance_type", ["hydrogen", "heavy"]) - def test_donor_water_donor_multi( - self, distance_type, client_WaterBridgeAnalysis - ): - """Test case where the hydrogen bond donor from selection 1 form - water bridge with hydrogen bond donor from selection 2""" - universe_multi = MDAnalysis.Universe( - WB_MULTIFRAME_GRO, WB_MULTIFRAME_DCD - ) - print( - "Residues:", - list( - zip( - universe_multi.residues.resids, - universe_multi.residues.resnames, - ) - ), - ) - sel1 = universe_multi.select_atoms("protein and (resid 1)") - sel2 = universe_multi.select_atoms("protein and (resid 6)") - print("sel1 len:", sel1.n_atoms) - print("sel2 len:", sel2.n_atoms) - wb = WaterBridgeAnalysis( - universe_multi, - "protein and (resid 1)", - "protein and (resid 5)", - distance_type=distance_type, - order=2, - ) - wb.run(**client_WaterBridgeAnalysis, verbose=False) - network = wb.results.network[0] - assert_equal(wb.results.network[0], defaultdict(dict)) - def test_empty(self, client_WaterBridgeAnalysis): """Test case where no water bridge exists""" universe_empty = MDAnalysis.Universe(WB_EMPTY) From b1d32eea6820b7d5d1a4dba6f3d22da7a8253fcd Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Thu, 12 Feb 2026 00:17:12 +0100 Subject: [PATCH 22/24] adjusted code --- .../analysis/hydrogenbonds/wbridge_analysis.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index 58c359b1201..e9750904ad2 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -1960,12 +1960,8 @@ def count_by_time(self, analysis_func=None, **kwargs): analysis_func = self._count_by_time_analysis if self.results.network: result = [] - n = len(self.results.network) - times = np.asarray(self.times) - if times.size != n: - raise ValueError("times/results length mismatch") - for time, frame in zip(times, self.results.network): + for time, frame in zip(self.times, self.results.network): result_dict = defaultdict(int) self._traverse_water_network( frame, @@ -2028,12 +2024,8 @@ def timesteps_by_type(self, analysis_func=None, **kwargs): if self.results.network: result = defaultdict(list) - n = len(self.results.network) - times = np.asarray(self.times) - if times.size != n: - raise ValueError("times/results length mismatch") - for time, frame in zip(times, self.results.network): + for time, frame in zip(self.times, self.results.network): self._traverse_water_network( frame, [], @@ -2133,12 +2125,8 @@ def generate_table(self, output_format=None): # standard array, like this: out = np.empty((num_records,), dtype=dtype) cursor = 0 # current row - n = len(timeseries) - times = np.asarray(self.times) - if times.size != n: - raise ValueError("times/results length mismatch") - for t, hframe in zip(times, timeseries): + for t, hframe in zip(self.times, timeseries): for ( donor_index, acceptor_index, From 504cee58f0595e356cfc0737da0f135b8b202e05 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Thu, 12 Feb 2026 01:10:01 +0100 Subject: [PATCH 23/24] added versionchanged --- package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py index e9750904ad2..aba9bd152b9 100644 --- a/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py +++ b/package/MDAnalysis/analysis/hydrogenbonds/wbridge_analysis.py @@ -731,6 +731,10 @@ class WaterBridgeAnalysis(AnalysisBase): .. versionadded:: 0.17.0 + .. versionchanged:: 2.11.0 + Enabled **parallel execution** with the ``multiprocessing`` and ``dask`` + backends; use the new method :meth:`get_supported_backends` to see all + supported backends. """ # use tuple(set()) here so that one can just copy&paste names from the From 6dbee6c6a4869ab033824270aed7bd44f6e28440 Mon Sep 17 00:00:00 2001 From: "talagayv95@gmail.com" Date: Thu, 12 Feb 2026 01:14:32 +0100 Subject: [PATCH 24/24] added CHANGELOG entry --- package/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/CHANGELOG b/package/CHANGELOG index 2f023d1337a..d58f997f62a 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -37,6 +37,8 @@ Fixes DSSP by porting upstream PyDSSP 0.9.1 fix (Issue #4913) Enhancements + * Enables parallelization for analysis.hydrogenbonds.wbridge_analysis.WaterBridgeAnalysis + (Issue #4666, PR #5151) * Enables parallelization for analysis.diffusionmap.DistanceMatrix (Issue #4679, PR #4745)