@@ -1014,12 +1014,15 @@ def plot_torsion_angles(torsion_angles,
10141014 """
10151015 num_comb = None
10161016 torsions = list (torsion_angles .keys ()) if torsions_sampling_points is None \
1017- else list (torsions_sampling_points .keys ())
1017+ else list (torsions_sampling_points .keys ())
1018+ torsions = [torsion for torsion in torsions if not (isinstance (torsion , tuple ) and all (isinstance (sub_t , tuple ) for sub_t in torsion ))]
1019+
10181020 ticks = [0 , 60 , 120 , 180 , 240 , 300 , 360 ]
10191021 sampling_points = dict ()
10201022 if torsions_sampling_points is not None :
10211023 for tor , points in torsions_sampling_points .items ():
1022- sampling_points [tor ] = [point if point <= 360 else point - 360 for point in points ]
1024+ if not isinstance (points [0 ],list ):
1025+ sampling_points [tor ] = [point if point <= 360 else point - 360 for point in points ]
10231026 if not torsions :
10241027 return
10251028 if len (torsions ) == 1 :
@@ -1048,6 +1051,8 @@ def plot_torsion_angles(torsion_angles,
10481051 fig .dpi = 120
10491052 num_comb = 1
10501053 for i , torsion in enumerate (torsions ):
1054+ if tuple (torsion ) not in torsion_angles :
1055+ continue
10511056 axs [i ].plot (np .array (torsion_angles [tuple (torsion )]),
10521057 np .zeros_like (np .arange (len (torsion_angles [tuple (torsion )]))), 'g.' )
10531058 if wells_dict is not None :
@@ -1121,6 +1126,76 @@ def plot_torsion_angles(torsion_angles,
11211126 return num_comb
11221127
11231128
1129+ def plot_ring_torsion_angles (conformers , plot_path = None , tolerance = 15 ):
1130+ """
1131+ Plot the torsion angles of the generated conformers for each ring,
1132+ considering torsion similarity within a given tolerance.
1133+
1134+ Args:
1135+ conformers (list): A list of conformers, each containing a 'puckering' key with ring torsion angles.
1136+ plot_path (str, optional): The path for saving the plot.
1137+ tolerance (float, optional): The angular tolerance to consider two torsion angle sets as similar.
1138+ """
1139+ if 'puckering' not in conformers [0 ]:
1140+ return
1141+
1142+ # Dictionary to store unique angle sets for each ring
1143+ ring_angle_data = {}
1144+
1145+ # Process each conformer
1146+ for conformer in conformers :
1147+ rings = conformer ['puckering' ] # Retrieve the puckering angles for rings
1148+ for torsions , angle_set in rings .items ():
1149+ rounded_angle_set = tuple (round (angle ) for angle in angle_set ) # Round angles
1150+ if torsions not in ring_angle_data :
1151+ ring_angle_data [torsions ] = []
1152+
1153+ # Check for similarity within the current ring
1154+ is_similar = False
1155+ for i , (existing_set , count ) in enumerate (ring_angle_data [torsions ]):
1156+ if all (abs (a1 - a2 ) <= tolerance for a1 , a2 in zip (rounded_angle_set , existing_set )):
1157+ # If similar, increment count
1158+ ring_angle_data [torsions ][i ] = (existing_set , count + 1 )
1159+ is_similar = True
1160+ break
1161+ if not is_similar :
1162+ # Add unique angle set with a count
1163+ ring_angle_data [torsions ].append ((rounded_angle_set , 1 ))
1164+
1165+
1166+ # Plot data for each ring
1167+ for ring , angle_counts in ring_angle_data .items ():
1168+ # Extract and sort data
1169+ angles , counts = zip (* angle_counts )
1170+ angles_counts_sorted = sorted (zip (angles , counts ), key = lambda x : x [1 ], reverse = True )
1171+ angles_sorted , counts_sorted = zip (* angles_counts_sorted )
1172+
1173+ # Create bar plot for this ring
1174+ fig , ax = plt .subplots (figsize = (10 , 5 ))
1175+ x = np .arange (len (angles_sorted )) # Label positions
1176+ ax .bar (x , counts_sorted , color = 'blue' )
1177+ ax .set_xlabel ('Rounded Angle Sets (Nearest Integer)' )
1178+ ax .set_ylabel ('Frequency' )
1179+ ax .set_title (f'Frequency of Different Angle Sets for Ring { ring } ' )
1180+ ax .set_xticks (x )
1181+ ax .set_xticklabels ([f'{ angle } ' for angle in angles_sorted ], rotation = 45 , ha = 'right' )
1182+
1183+ # Save or display the plot
1184+ if plot_path is not None :
1185+ ring_plot_path = os .path .join (plot_path , f'conformer_ring_torsions_{ ring } .png' )
1186+ if not os .path .isdir (plot_path ):
1187+ os .makedirs (plot_path )
1188+ try :
1189+ plt .savefig (ring_plot_path , bbox_inches = 'tight' )
1190+ except FileNotFoundError :
1191+ pass
1192+ if is_notebook ():
1193+ plt .show ()
1194+ plt .close (fig )
1195+
1196+ return ring_angle_data
1197+
1198+
11241199def plot_1d_rotor_scan (angles : Optional [Union [list , tuple , np .array ]] = None ,
11251200 energies : Optional [Union [list , tuple , np .array ]] = None ,
11261201 results : Optional [dict ] = None ,
0 commit comments