@@ -38,6 +38,8 @@ def __init__(
3838 universe: MDAnalysis universe representing the simulation system.
3939 data_logger: Logger for storing and exporting entropy data.
4040 level_manager: Provides level-specific data such as matrices and dihedrals.
41+ group_molecules: includes the grouping functions for averaging over
42+ molecules.
4143 """
4244 self ._run_manager = run_manager
4345 self ._args = args
@@ -58,6 +60,7 @@ def execute(self):
5860 - Computing vibrational and conformational entropies.
5961 - Finalizing and logging results.
6062 """
63+ # Set up initial information
6164 start , end , step = self ._get_trajectory_bounds ()
6265 number_frames = self ._get_number_frames (start , end , step )
6366
@@ -118,6 +121,8 @@ def execute(self):
118121 )
119122 )
120123
124+ # Identify the conformational states from dihedral angles for the
125+ # conformational entropy calculations
121126 states_ua , states_res = self ._level_manager .build_conformational_states (
122127 self ,
123128 reduced_atom ,
@@ -131,6 +136,7 @@ def execute(self):
131136 ce ,
132137 )
133138
139+ # Complete the entropy calculations
134140 self ._compute_entropies (
135141 reduced_atom ,
136142 levels ,
@@ -145,6 +151,7 @@ def execute(self):
145151 ce ,
146152 )
147153
154+ # Print the results in a nicely formated way
148155 self ._finalize_molecule_results ()
149156 self ._data_logger .log_tables ()
150157
@@ -190,9 +197,15 @@ def _initialize_molecules(self):
190197 - reduced_atom (Universe): The reduced atom selection.
191198 - number_molecules (int): Number of molecules in the system.
192199 - levels (list): List of entropy levels per molecule.
200+ - groups (dict): Groups for averaging over molecules.
193201 """
202+ # Based on the selection string, create a new MDAnalysis universe
194203 reduced_atom = self ._get_reduced_universe ()
204+
205+ # Count the molecules and identify the length scale levels for each one
195206 number_molecules , levels = self ._level_manager .select_levels (reduced_atom )
207+
208+ # Group the molecules for averaging
196209 grouping = self ._args .grouping
197210 groups = self ._group_molecules .grouping_molecules (reduced_atom , grouping )
198211
@@ -226,14 +239,16 @@ def _compute_entropies(
226239
227240 Parameters:
228241 reduced_atom (Universe): The reduced atom selection from the trajectory.
229- number_molecules (int): Number of molecules in the system.
230242 levels (list): List of entropy levels per molecule.
243+ groups (dict): Groups for averaging over molecules.
231244 force_matrices (dict): Precomputed force covariance matrices.
232245 torque_matrices (dict): Precomputed torque covariance matrices.
233246 states_ua (dict): Dictionary to store united-atom conformational states.
234247 states_res (list): List to store residue-level conformational states.
235248 frames_count (dict): Dictionary to store the frame counts
236249 number_frames (int): Total number of trajectory frames to process.
250+ ve: Vibrational Entropy object
251+ ce: Conformational Entropy object
237252 """
238253 with Progress (
239254 SpinnerColumn (),
@@ -363,8 +378,11 @@ def _get_reduced_universe(self):
363378 Returns:
364379 MDAnalysis.Universe: Selected subset of the system.
365380 """
381+ # If selection string is "all" the universe does not change
366382 if self ._args .selection_string == "all" :
367383 return self ._universe
384+
385+ # Otherwise create a new (smaller) universe based on the selection
368386 reduced = self ._run_manager .new_U_select_atom (
369387 self ._universe , self ._args .selection_string
370388 )
@@ -383,8 +401,11 @@ def _get_molecule_container(self, universe, molecule_id):
383401 Returns:
384402 MDAnalysis.Universe: Universe containing only the selected molecule.
385403 """
404+ # Identify the atoms in the molecule
386405 frag = universe .atoms .fragments [molecule_id ]
387406 selection_string = f"index { frag .indices [0 ]} :{ frag .indices [- 1 ]} "
407+
408+ # Build a new universe with only the one molecule
388409 return self ._run_manager .new_U_select_atom (universe , selection_string )
389410
390411 def _process_united_atom_entropy (
@@ -406,7 +427,7 @@ def _process_united_atom_entropy(
406427 united-atom level.
407428
408429 Args:
409- mol_id (int): ID of the molecule .
430+ group_id (int): ID of the group .
410431 mol_container (Universe): Universe for the selected molecule.
411432 ve: VibrationalEntropy object.
412433 ce: ConformationalEntropy object.
@@ -415,43 +436,56 @@ def _process_united_atom_entropy(
415436 n_frames (int): Number of trajectory frames.
416437 frame_counts: Number of frames counted
417438 highest (bool): Whether this is the highest level of resolution for
418- the molecule.
439+ the molecule.
440+ number_frames (int): The number of frames analysed.
419441 """
420442 S_trans , S_rot , S_conf = 0 , 0 , 0
421443
444+ # The united atom entropy is calculated separately for each residue
445+ # This is to allow residue by residue information
446+ # and prevents the matrices from becoming too large
422447 for residue_id , residue in enumerate (mol_container .residues ):
423448
424449 key = (group_id , residue_id )
425450
451+ # Find the relevant force and torque matrices and tidy them up
452+ # by removing rows and columns that are all zeros
426453 f_matrix = force_matrix [key ]
427454 f_matrix = self ._level_manager .filter_zero_rows_columns (f_matrix )
428455
429456 t_matrix = torque_matrix [key ]
430457 t_matrix = self ._level_manager .filter_zero_rows_columns (t_matrix )
431458
459+ # Calculate the vibrational entropy
432460 S_trans_res = ve .vibrational_entropy_calculation (
433461 f_matrix , "force" , self ._args .temperature , highest
434462 )
435463 S_rot_res = ve .vibrational_entropy_calculation (
436464 t_matrix , "torque" , self ._args .temperature , highest
437465 )
438466
467+ # Get the relevant conformational states
439468 values = states [key ]
440-
469+ # Check if there is information in the states array
441470 contains_non_empty_states = (
442471 np .any (values ) if isinstance (values , np .ndarray ) else any (values )
443472 )
444473
474+ # Calculate the conformational entropy
475+ # If there are no conformational states (i.e. no dihedrals)
476+ # then the conformational entropy is zero
445477 S_conf_res = (
446478 ce .conformational_entropy_calculation (values , number_frames )
447479 if contains_non_empty_states
448480 else 0
449481 )
450482
483+ # Add the data to the united atom level entropy
451484 S_trans += S_trans_res
452485 S_rot += S_rot_res
453486 S_conf += S_conf_res
454487
488+ # Print out the data for each residue
455489 self ._data_logger .add_residue_data (
456490 group_id ,
457491 residue .resname ,
@@ -477,6 +511,7 @@ def _process_united_atom_entropy(
477511 S_conf_res ,
478512 )
479513
514+ # Print the total united atom level data for the molecule group
480515 self ._data_logger .add_results_data (group_id , level , "Transvibrational" , S_trans )
481516 self ._data_logger .add_results_data (group_id , level , "Rovibrational" , S_rot )
482517 self ._data_logger .add_results_data (group_id , level , "Conformational" , S_conf )
@@ -502,8 +537,7 @@ def _process_vibrational_entropy(
502537 Calculates vibrational entropy.
503538
504539 Args:
505- mol_id (int): Molecule ID.
506- mol_container (Universe): Selected molecule's universe.
540+ group_id (int): Group ID.
507541 ve: VibrationalEntropy object.
508542 level (str): Current granularity level.
509543 force_matrix : Force covariance matrix
@@ -512,17 +546,21 @@ def _process_vibrational_entropy(
512546 highest (bool): Flag indicating if this is the highest granularity
513547 level.
514548 """
549+ # Find the relevant force and torque matrices and tidy them up
550+ # by removing rows and columns that are all zeros
515551 force_matrix = self ._level_manager .filter_zero_rows_columns (force_matrix )
516552
517553 torque_matrix = self ._level_manager .filter_zero_rows_columns (torque_matrix )
518554
555+ # Calculate the vibrational entropy
519556 S_trans = ve .vibrational_entropy_calculation (
520557 force_matrix , "force" , self ._args .temperature , highest
521558 )
522559 S_rot = ve .vibrational_entropy_calculation (
523560 torque_matrix , "torque" , self ._args .temperature , highest
524561 )
525562
563+ # Print the vibrational entropy for the molecule group
526564 self ._data_logger .add_results_data (group_id , level , "Transvibrational" , S_trans )
527565 self ._data_logger .add_results_data (group_id , level , "Rovibrational" , S_rot )
528566
@@ -547,9 +585,11 @@ def _process_conformational_entropy(
547585 mol_container (Universe): Selected molecule's universe.
548586 ce: ConformationalEntropy object.
549587 level (str): Level name (should be 'residue').
550- start, end, step (int ): Frame bounds .
551- n_frames (int): Number of frames used.
588+ states (array ): The conformational states .
589+ number_frames (int): Number of frames used.
552590 """
591+ # Get the relevant conformational states
592+ # Check if there is information in the states array
553593 group_states = states [group_id ] if group_id < len (states ) else None
554594
555595 if group_states is not None :
@@ -561,6 +601,9 @@ def _process_conformational_entropy(
561601 else :
562602 contains_state_data = False
563603
604+ # Calculate the conformational entropy
605+ # If there are no conformational states (i.e. no dihedrals)
606+ # then the conformational entropy is zero
564607 S_conf = (
565608 ce .conformational_entropy_calculation (group_states , number_frames )
566609 if contains_state_data
@@ -784,14 +827,12 @@ def frequency_calculation(self, lambdas, temp):
784827
785828 frequency=sqrt(λ/kT)/2π
786829
787- Input
788- -----
789- lambdas : array of floats - eigenvalues of the covariance matrix
790- temp: float - temperature
830+ Args:
831+ lambdas : array of floats - eigenvalues of the covariance matrix
832+ temp: float - temperature
791833
792- Returns
793- -------
794- frequencies : array of floats - corresponding vibrational frequencies
834+ Returns:
835+ frequencies : array of floats - corresponding vibrational frequencies
795836 """
796837 pi = np .pi
797838 # get kT in Joules from given temperature
@@ -801,11 +842,16 @@ def frequency_calculation(self, lambdas, temp):
801842 lambdas = np .array (lambdas ) # Ensure input is a NumPy array
802843 logger .debug (f"Eigenvalues (lambdas): { lambdas } " )
803844
845+ # Filter out lambda values that are negative or imaginary numbers
846+ # As these will produce supurious entropy results that can crash
847+ # the calculation
804848 lambdas = np .real_if_close (lambdas , tol = 1000 )
805849 valid_mask = (
806850 np .isreal (lambdas ) & (lambdas > 0 ) & (~ np .isclose (lambdas , 0 , atol = 1e-07 ))
807851 )
808852
853+ # If any lambdas were removed by the filter, warn the user
854+ # as this will suggest insufficient sampling in the simulation data
809855 if len (lambdas ) > np .count_nonzero (valid_mask ):
810856 logger .warning (
811857 f"{ len (lambdas ) - np .count_nonzero (valid_mask )} "
@@ -827,16 +873,14 @@ def vibrational_entropy_calculation(self, matrix, matrix_type, temp, highest_lev
827873 Physics, 2018, 116, 1965–1976 / eq. (2) in A. Chakravorty, J. Higham and
828874 R. H. Henchman, J. Chem. Inf. Model., 2020, 60, 5540–5551.
829875
830- Input
831- -----
832- matrix : matrix - force/torque covariance matrix
833- matrix_type: string
834- temp: float - temperature
835- highest_level: bool - is this the highest level of the heirarchy
876+ Args:
877+ matrix : matrix - force/torque covariance matrix
878+ matrix_type: string
879+ temp: float - temperature
880+ highest_level: bool - is this the highest level of the heirarchy
836881
837- Returns
838- -------
839- S_vib_total : float - transvibrational/rovibrational entropy
882+ Returns:
883+ S_vib_total : float - transvibrational/rovibrational entropy
840884 """
841885 # N beads at a level => 3N x 3N covariance matrix => 3N eigenvalues
842886 # Get eigenvalues of the given matrix and change units to SI units
@@ -917,18 +961,18 @@ def assign_conformation(
917961 Based on the identified TPs, states are assigned to each configuration of the
918962 dihedral.
919963
920- Input
921- -----
922- dihedral_atom_group : the group of 4 atoms defining the dihedral
923- number_frames : number of frames in the trajectory
924- bin_width : the width of the histogram bit, default 30 degrees
925- start : int, starting frame, will default to 0
926- end : int, ending frame, will default to -1 (last frame in trajectory)
927- step : int, spacing between frames, will default to 1
964+ Args:
965+ data_container (MDAnalysis Universe): data for the molecule/residue unit
966+ dihedral (array): The dihedral angles in the unit
967+ number_frames (int) : number of frames in the trajectory
968+ bin_width (int) : the width of the histogram bit, default 30 degrees
969+ start ( int): starting frame, will default to 0
970+ end ( int): ending frame, will default to -1 (last frame in trajectory)
971+ step ( int): spacing between frames, will default to 1
928972
929- Return
930- ------
931- A timeseries with integer labels describing the state at each point in time.
973+ Returns:
974+ conformations (array): A timeseries with integer labels describing the
975+ state at each point in time.
932976
933977 """
934978 conformations = np .zeros (number_frames )
@@ -943,7 +987,7 @@ def assign_conformation(
943987 timestep_index = timestep_index
944988 value = dihedral .value ()
945989 # we want postive values in range 0 to 360 to make the peak assignment
946- # work using the fact that dihedrals have circular symetry
990+ # works using the fact that dihedrals have circular symetry
947991 # (i.e. -15 degrees = +345 degrees)
948992 if value < 0 :
949993 value += 360
@@ -958,7 +1002,8 @@ def assign_conformation(
9581002
9591003 # identify "convex turning-points" and populate a list of peaks
9601004 # peak : a bin whose neighboring bins have smaller population
961- # NOTE might have problems if the peak is wide with a flat or sawtooth top
1005+ # NOTE might have problems if the peak is wide with a flat or sawtooth
1006+ # top in which case check you have a sensible bin width
9621007 peak_values = []
9631008
9641009 for bin_index in range (number_bins ):
@@ -1001,12 +1046,12 @@ def conformational_entropy_calculation(self, states, number_frames):
10011046
10021047 Uses the adaptive enumeration method (AEM).
10031048
1004- Input
1005- -----
1006- dihedrals : array - array of dihedrals in the molecule
1007- Returns
1008- -------
1009- S_conf_total : float - conformational entropy
1049+ Args:
1050+ states (array): Conformational states in the molecule
1051+ number_frames (int): The number of frames analysed
1052+
1053+ Returns:
1054+ S_conf_total ( float) : conformational entropy
10101055 """
10111056
10121057 S_conf_total = 0
0 commit comments