Skip to content

Commit 74e2807

Browse files
authored
Merge pull request #137 from CCPBioSim/43-version-1-documentation
Update Documentation for `v1.0` Release Candidate of `CodeEntropy`
2 parents d7015e8 + af99c49 commit 74e2807

File tree

45 files changed

+632
-6032
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+632
-6032
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ instance/
6464

6565
# Sphinx documentation
6666
docs/_build/
67+
docs/autosummary/
6768

6869
# PyBuilder
6970
target/

CodeEntropy/entropy.py

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)