Skip to content

Commit e744dc0

Browse files
committed
Refinements of the integration of waterEntropy into CodeEntropy:
- More robust way of calculating the total entropy of each molecule if it is water - Refactored how `_finalize_molecule_results` function operates, rather than a call per molecule it is run after all molecules have been processed - Additional test cases to fully capture the refactored `_calculate_water_entropy` function
1 parent bc4e400 commit e744dc0

2 files changed

Lines changed: 106 additions & 18 deletions

File tree

CodeEntropy/entropy.py

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def execute(self):
125125
number_frames,
126126
)
127127

128-
self._finalize_molecule_results(molecule_id, level)
128+
self._finalize_molecule_results()
129129

130130
self._data_logger.log_tables()
131131

@@ -312,21 +312,24 @@ def _process_conformational_residue_level(
312312
)
313313
self._log_result(mol_id, level, "Conformational", S_conf)
314314

315-
def _finalize_molecule_results(self, mol_id, level):
315+
def _finalize_molecule_results(self):
316316
"""
317317
Summarizes entropy for a molecule and saves results to file.
318318
319319
Args:
320320
mol_id (int): ID of the molecule.
321-
level (str): Current level name (used for tagging final results).
322-
"""
323-
S_total = self._results_df[self._results_df["Molecule ID"] == mol_id][
324-
"Result"
325-
].sum()
326-
self._log_result(mol_id, "Molecule Total", "Molecule Total Entropy", S_total)
327-
self._data_logger.save_dataframes_as_json(
328-
self._results_df, self._residue_results_df, self._args.output_file
329-
)
321+
"""
322+
logger.info(f"len(self._results_df) {len(self._results_df)}")
323+
for mol_id in range(len(self._results_df)):
324+
S_total = self._results_df[self._results_df["Molecule ID"] == mol_id][
325+
"Result"
326+
].sum()
327+
self._log_result(
328+
mol_id, "Molecule Total", "Molecule Total Entropy", S_total
329+
)
330+
self._data_logger.save_dataframes_as_json(
331+
self._results_df, self._residue_results_df, self._args.output_file
332+
)
330333

331334
def _log_result(self, mol_id, level, entropy_type, value):
332335
"""
@@ -392,13 +395,36 @@ def _calculate_water_entropy(self, universe, start, end, step):
392395
self._calculate_water_vibrational_translational_entropy(vibrations)
393396
self._calculate_water_vibrational_rotational_entropy(vibrations)
394397

395-
# Compute and log per-molecule totals
396-
unique_mol_ids = set(self._residue_results_df["Molecule ID"])
397-
for mol_id in unique_mol_ids:
398-
S_total = self._residue_results_df[
399-
self._residue_results_df["Molecule ID"] == mol_id
400-
]["Result"].sum()
401-
self._log_result(mol_id, "water", "Molecule Total Entropy", S_total)
398+
# Aggregate entropy components per molecule
399+
results = {}
400+
401+
for _, row in self._residue_results_df.iterrows():
402+
entropy_type = row["Type"].split()[0]
403+
value = row["Result"]
404+
405+
if entropy_type == "Orientational":
406+
mol_id = row["Molecule ID"]
407+
else:
408+
mol_id = row["Residue"].split("_")[0]
409+
410+
if mol_id not in results:
411+
results[mol_id] = {
412+
"Orientational": 0.0,
413+
"Transvibrational": 0.0,
414+
"Rovibrational": 0.0,
415+
}
416+
417+
results[mol_id][entropy_type] += value
418+
419+
# Log per-molecule entropy components and total
420+
for mol_id, components in results.items():
421+
total = 0.0
422+
for entropy_type in ["Orientational", "Transvibrational", "Rovibrational"]:
423+
S_component = components[entropy_type]
424+
self._log_result(mol_id, "water", entropy_type, S_component)
425+
total += S_component
426+
427+
self._log_result(mol_id, "Molecule Total", "Molecule Total Entropy", total)
402428

403429
def _calculate_water_orientational_entropy(self, Sorient_dict):
404430
"""

tests/test_CodeEntropy/test_entropy.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from unittest.mock import MagicMock, call, patch
66

77
import numpy as np
8+
import pandas as pd
89
import pytest
910

1011
from CodeEntropy.entropy import EntropyManager, VibrationalEntropy
@@ -258,6 +259,67 @@ def test_calculate_water_entropy(self, mock_get_entropy):
258259
)
259260
self.entropy_manager._log_result.assert_not_called()
260261

262+
@patch(
263+
"waterEntropy.recipes.interfacial_solvent.get_interfacial_water_orient_entropy"
264+
)
265+
def test_calculate_water_entropy_minimal(self, mock_get_entropy):
266+
"""
267+
This twst verifies that _calculate_water_entropy correctly logs
268+
entropy components and total for a single molecule with minimal data.
269+
"""
270+
self.entropy_manager._log_residue_data = MagicMock()
271+
self.entropy_manager._log_result = MagicMock()
272+
273+
# Minimal mocked return from get_interfacial_water_orient_entropy
274+
mock_get_entropy.return_value = (
275+
{}, # Sorient_dict (not used here)
276+
None,
277+
MagicMock(
278+
translational_S={("ACE_1", "WAT"): 10.0},
279+
rotational_S={("ACE_1", "WAT"): 2.0},
280+
),
281+
None,
282+
)
283+
284+
# Minimal internal state
285+
self.entropy_manager._residue_results_df = pd.DataFrame(
286+
[
287+
{
288+
"Molecule ID": "ACE",
289+
"Residue": "1",
290+
"Type": "Orientational (J/mol/K)",
291+
"Result": 5.0,
292+
},
293+
{
294+
"Molecule ID": "WAT",
295+
"Residue": "ACE_1",
296+
"Type": "Transvibrational (J/mol/K)",
297+
"Result": 10.0,
298+
},
299+
{
300+
"Molecule ID": "WAT",
301+
"Residue": "ACE_1",
302+
"Type": "Rovibrational (J/mol/K)",
303+
"Result": 2.0,
304+
},
305+
]
306+
)
307+
308+
# Call the real method
309+
mock_universe = MagicMock()
310+
self.entropy_manager._calculate_water_entropy(mock_universe, 0, 10, 1)
311+
312+
# Assert that only ACE is logged with correct values
313+
self.entropy_manager._log_result.assert_has_calls(
314+
[
315+
call("ACE", "water", "Orientational", 5.0),
316+
call("ACE", "water", "Transvibrational", 10.0),
317+
call("ACE", "water", "Rovibrational", 2.0),
318+
call("ACE", "Molecule Total", "Molecule Total Entropy", 17.0),
319+
],
320+
any_order=False,
321+
)
322+
261323
# TODO test for error handling on invalid inputs
262324

263325

0 commit comments

Comments
 (0)