@@ -44,7 +44,7 @@ def build_conformational_states(
4444 step : int ,
4545 bin_width : float ,
4646 progress : _RichProgressSink | None = None ,
47- ) -> tuple [dict [UAKey , list [str ]], list [list [str ]]]:
47+ ) -> tuple [dict [UAKey , list [str ]], list [list [str ]], dict [ UAKey , int ], list [ int ] ]:
4848 """Build conformational state labels from trajectory dihedrals.
4949
5050 This method constructs discrete conformational state descriptors used in
@@ -72,7 +72,7 @@ def build_conformational_states(
7272 and advance().
7373
7474 Returns:
75- tuple: (states_ua, states_res)
75+ tuple: (states_ua, states_res, flexible_ua, flexible_res )
7676
7777 - states_ua: Dict mapping (group_id, local_residue_id) -> list of state
7878 labels (strings) across the analyzed trajectory.
@@ -87,6 +87,8 @@ def build_conformational_states(
8787 number_groups = len (groups )
8888 states_ua : dict [UAKey , list [str ]] = {}
8989 states_res : list [list [str ]] = [[] for _ in range (number_groups )]
90+ flexible_ua : dict [UAKey , int ] = {}
91+ flexible_res : list [int ] = [0 ] * number_groups
9092
9193 task : TaskID | None = None
9294 if progress is not None :
@@ -149,12 +151,14 @@ def build_conformational_states(
149151 level_list = levels [molecules [0 ]],
150152 states_ua = states_ua ,
151153 states_res = states_res ,
154+ flexible_ua = flexible_ua ,
155+ flexible_res = flexible_res ,
152156 )
153157
154158 if progress is not None and task is not None :
155159 progress .advance (task )
156160
157- return states_ua , states_res
161+ return states_ua , states_res , flexible_ua , flexible_res
158162
159163 def _collect_dihedrals_for_group (
160164 self , mol : Any , level_list : list [str ]
@@ -268,6 +272,7 @@ def _collect_peaks_for_group(
268272 if level == "united_atom" :
269273 for res_id in range (len (dihedrals_ua )):
270274 if len (dihedrals_ua [res_id ]) == 0 :
275+ # No dihedrals means no peaks
271276 peaks_ua [res_id ] = []
272277 else :
273278 peaks_ua [res_id ] = self ._identify_peaks (
@@ -282,6 +287,7 @@ def _collect_peaks_for_group(
282287
283288 elif level == "residue" :
284289 if len (dihedrals_res ) == 0 :
290+ # No dihedrals means no peaks
285291 peaks_res = []
286292 else :
287293 peaks_res = self ._identify_peaks (
@@ -353,7 +359,9 @@ def _identify_peaks(
353359 peaks = self ._find_histogram_peaks (popul = popul , bin_value = bin_value )
354360 peak_values .append (peaks )
355361
356- logger .debug (f"Dihedral: { dihedral_index } , Peak Values: { peak_values } " )
362+ logger .debug (
363+ f"Dihedral: { dihedral_index } Peak Values: { peak_values [dihedral_index ]} "
364+ )
357365
358366 return peak_values
359367
@@ -404,6 +412,8 @@ def _assign_states_for_group(
404412 level_list : list [str ],
405413 states_ua : dict [UAKey , list [str ]],
406414 states_res : list [list [str ]],
415+ flexible_ua : dict [UAKey , list [int ]],
416+ flexible_res : list [int ],
407417 ) -> None :
408418 """Assign UA and residue states for a group into output containers."""
409419 for level in level_list :
@@ -412,8 +422,9 @@ def _assign_states_for_group(
412422 key = (group_id , res_id )
413423 if len (dihedrals_ua [res_id ]) == 0 :
414424 states_ua [key ] = []
425+ flexible_ua [key ] = 0
415426 else :
416- states_ua [key ] = self ._assign_states (
427+ states_ua [key ], flexible_ua [ key ] = self ._assign_states (
417428 data_container = data_container ,
418429 molecules = molecules ,
419430 dihedrals = dihedrals_ua [res_id ],
@@ -426,8 +437,9 @@ def _assign_states_for_group(
426437 elif level == "residue" :
427438 if len (dihedrals_res ) == 0 :
428439 states_res [group_id ] = []
440+ flexible_res [group_id ] = 0
429441 else :
430- states_res [group_id ] = self ._assign_states (
442+ states_res [group_id ], flexible_res [ group_id ] = self ._assign_states (
431443 data_container = data_container ,
432444 molecules = molecules ,
433445 dihedrals = dihedrals_res ,
@@ -467,6 +479,7 @@ def _assign_states(
467479 List of state labels (strings).
468480 """
469481 states : list [str ] = []
482+ num_flexible = 0
470483
471484 for molecule in molecules :
472485 conformations : list [list [Any ]] = []
@@ -477,16 +490,29 @@ def _assign_states(
477490
478491 for dihedral_index in range (len (dihedrals )):
479492 conformation : list [Any ] = []
493+
494+ # Check for flexible dihedrals
495+ if len (peaks [dihedral_index ]) > 1 and molecule == 0 :
496+ num_flexible += 1
497+
498+ # Get conformations
480499 for timestep in range (number_frames ):
481500 value = dihedral_results .results .angles [timestep ][dihedral_index ]
501+ # We want postive values in range 0 to 360 to make
502+ # the peak assignment.
503+ # works using the fact that dihedrals have circular symmetry
504+ # (i.e. -15 degrees = +345 degrees)
482505 if value < 0 :
483506 value += 360
484507
508+ # Find the peak closest to the dihedral value
485509 distances = [abs (value - peak ) for peak in peaks [dihedral_index ]]
486510 conformation .append (np .argmin (distances ))
487511
488512 conformations .append (conformation )
489513
514+ # Concatenate all the dihedrals in the molecule into the state
515+ # for the frame.
490516 mol_states = [
491517 state
492518 for state in (
@@ -501,4 +527,6 @@ def _assign_states(
501527 states .extend (mol_states )
502528
503529 logger .debug (f"States: { states } " )
504- return states
530+ logger .debug (f"Number of flexible dihedrals: { num_flexible } " )
531+
532+ return states , num_flexible
0 commit comments