Skip to content

Commit 5df8a00

Browse files
committed
Refactor water residue handling and add test for dynamic residue group:
- Replace manual filtering of water residues with MDAnalysis selection language (`select_atoms("resname WAT")`) for more robust residue handling - Dynamically determine `residue_group` from Sorient_dict keys, including only residues present in the water selection - Update group label logging to use the new selection method - Add unit test `test_calculate_water_entropy_adds_resname` to ensure the `residue_names.add(resname)` path is exercised
1 parent 0c6f1c1 commit 5df8a00

2 files changed

Lines changed: 65 additions & 15 deletions

File tree

CodeEntropy/entropy.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,18 +156,6 @@ def _handle_water_entropy(self, start, end, step, water_groups):
156156
return
157157

158158
for group_id, atom_indices in water_groups.items():
159-
water_selection = self._universe.atoms[atom_indices]
160-
161-
water_residues = [
162-
res for res in water_selection.residues if res.resname == "WAT"
163-
]
164-
165-
residue_group = "_".join(sorted({res.resname for res in water_residues}))
166-
residue_count = len(water_residues)
167-
atom_count = len(water_selection.atoms)
168-
self._data_logger.add_group_label(
169-
group_id, residue_group, residue_count, atom_count
170-
)
171159

172160
self._calculate_water_entropy(
173161
universe=self._universe,
@@ -657,17 +645,31 @@ def _calculate_water_entropy(self, universe, start, end, step, group_id=None):
657645
"Transvibrational": 0.0,
658646
"Rovibrational": 0.0,
659647
}
660-
661648
results[mol_id][entropy_type] += value
662649

663650
for mol_id, components in results.items():
664-
total = 0.0
665651
for entropy_type in ["Orientational", "Transvibrational", "Rovibrational"]:
666652
S_component = components[entropy_type]
667653
self._data_logger.add_results_data(
668654
group_id, "water", entropy_type, S_component
669655
)
670-
total += S_component
656+
657+
water_selection = universe.select_atoms("resname WAT")
658+
actual_water_residues = len(water_selection.residues)
659+
660+
residue_names = set()
661+
for res_dict in Sorient_dict.values():
662+
for resname in res_dict.keys():
663+
if resname.upper() in water_selection.residues.resnames:
664+
residue_names.add(resname)
665+
666+
residue_group = "_".join(sorted(residue_names)) if residue_names else "WAT"
667+
residue_count = actual_water_residues
668+
atom_count = len(water_selection.atoms)
669+
670+
self._data_logger.add_group_label(
671+
group_id, residue_group, residue_count, atom_count
672+
)
671673

672674
def _calculate_water_orientational_entropy(
673675
self, Sorient_dict, group_id, water_count

tests/test_CodeEntropy/test_entropy.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,54 @@ def test_calculate_water_entropy_minimal(self, mock_get_entropy):
14801480
]
14811481
)
14821482

1483+
@patch(
1484+
"waterEntropy.recipes.interfacial_solvent.get_interfacial_water_orient_entropy"
1485+
)
1486+
def test_calculate_water_entropy_adds_resname(self, mock_get_entropy):
1487+
"""
1488+
Test _calculate_water_entropy with Sorient_dict containing a water residue
1489+
so that residue_names.add(resname) is executed.
1490+
"""
1491+
# Mock vibrations object
1492+
mock_vibrations = MagicMock()
1493+
mock_vibrations.translational_S = {("res1", "WAT"): 2.0}
1494+
mock_vibrations.rotational_S = {("res1", "WAT"): 3.0}
1495+
1496+
# Sorient_dict contains a water residue key "WAT"
1497+
mock_get_entropy.return_value = (
1498+
{1: {"WAT": [1.0, 5]}}, # orientational
1499+
None,
1500+
mock_vibrations,
1501+
None,
1502+
1, # water_count
1503+
)
1504+
1505+
# Mock universe.select_atoms to return a selection containing "WAT"
1506+
mock_water_selection = MagicMock()
1507+
mock_residues_group = MagicMock()
1508+
mock_residues_group.resnames = ["WAT"] # this is key
1509+
mock_water_selection.residues = mock_residues_group
1510+
mock_water_selection.atoms = [1, 2, 3] # mock atom count
1511+
mock_universe = MagicMock()
1512+
mock_universe.select_atoms.return_value = mock_water_selection
1513+
mock_universe.trajectory = [1, 2] # 2 frames
1514+
1515+
group_id = 0
1516+
1517+
# Call the function
1518+
self.entropy_manager._data_logger = MagicMock() # mock logger
1519+
self.entropy_manager._calculate_water_entropy(
1520+
mock_universe, start=0, end=1, step=1, group_id=group_id
1521+
)
1522+
1523+
# Check that residue_group is "WAT" and residue_names.add was triggered
1524+
self.entropy_manager._data_logger.add_group_label.assert_called_with(
1525+
group_id,
1526+
"WAT",
1527+
len(mock_water_selection.residues),
1528+
len(mock_water_selection.atoms),
1529+
)
1530+
14831531
# TODO test for error handling on invalid inputs
14841532

14851533

0 commit comments

Comments
 (0)