Skip to content

Commit 7fce699

Browse files
committed
Extract unique puckering mode and make plots
1 parent b4b7b08 commit 7fce699

File tree

2 files changed

+92
-9
lines changed

2 files changed

+92
-9
lines changed

arc/plotter.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
11241199
def 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,

arc/species/conformers.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -348,18 +348,25 @@ def deduce_new_conformers(label, conformers, torsions, tops, mol_list, smeared_s
348348
symmetries[tuple(torsion)] = symmetry
349349
logger.debug(f'Identified {len([s for s in symmetries.values() if s > 1])} symmetric wells for {label}')
350350

351-
torsions_sampling_points, wells_dict = dict(), dict()
351+
torsions_sampling_points, wells_dict, ring_sampling_points = dict(), dict(), dict()
352352
for tor, tor_angles in torsion_angles.items():
353353
torsions_sampling_points[tor], wells_dict[tor] = \
354354
determine_torsion_sampling_points(label, tor_angles, smeared_scan_res=smeared_scan_res,
355355
symmetry=symmetries[tor])
356-
357356
if plot_path is not None:
358357
arc.plotter.plot_torsion_angles(torsion_angles, torsions_sampling_points, wells_dict=wells_dict,
359358
plot_path=plot_path)
359+
angles_sorted = arc.plotter.plot_ring_torsion_angles(conformers=conformers, plot_path=plot_path)
360+
# Process the ring angle data
361+
if angles_sorted is not None:
362+
for ring, angle_counts in angles_sorted.items():
363+
angles = [list(angle) for angle, _ in angle_counts]
364+
ring_sampling_points[tuple(ring)] = angles
365+
366+
combined_sampling_points = {**torsions_sampling_points, **ring_sampling_points}
360367

361368
hypothetical_num_comb = 1
362-
for points in torsions_sampling_points.values():
369+
for points in combined_sampling_points.values():
363370
hypothetical_num_comb *= len(points)
364371
number_of_chiral_centers = get_number_of_chiral_centers(label, mol, conformer=conformers[0],
365372
just_get_the_number=True)
@@ -370,10 +377,10 @@ def deduce_new_conformers(label, conformers, torsions, tops, mol_list, smeared_s
370377
hypothetical_num_comb_str = str(hypothetical_num_comb)
371378
logger.info(f'\nHypothetical number of conformer combinations for {label}: {hypothetical_num_comb_str}')
372379

373-
# split torsions_sampling_points into two lists, use combinations only for those with multiple sampling points
380+
# split combined_sampling_points into two lists, use combinations only for those with multiple sampling points
374381
single_tors, multiple_tors, single_sampling_point, multiple_sampling_points = list(), list(), list(), list()
375382
multiple_sampling_points_dict = dict() # used for plotting an energy "scan"
376-
for tor, points in torsions_sampling_points.items():
383+
for tor, points in combined_sampling_points.items():
377384
if len(points) == 1:
378385
single_tors.append(tor)
379386
single_sampling_point.append((points[0]))
@@ -536,7 +543,8 @@ def conformers_combinations_by_lowest_conformer(label, mol, base_xyz, multiple_t
536543
'FF energy': round(energy, 3),
537544
'source': f'Changing dihedrals on most stable conformer, iteration {i}',
538545
'torsion': tor,
539-
'dihedral': round(dihedral, 2),
546+
'dihedral': round(dihedral, 2) if isinstance(dihedral, float)
547+
else [round(angle, 2) for angle in dihedral],
540548
'dmat': dmat1,
541549
'fl_distance': fl_distance1}
542550
newest_conformers_dict[tor].append(conformer)
@@ -552,7 +560,7 @@ def conformers_combinations_by_lowest_conformer(label, mol, base_xyz, multiple_t
552560
'FF energy': None,
553561
'source': f'Changing dihedrals on most stable conformer, iteration {i}, but FF energy is None',
554562
'torsion': tor,
555-
'dihedral': round(dihedral, 2),
563+
'dihedral': round(dihedral, 2) if isinstance(dihedral, float) else [round(angle, 2) for angle in dihedral],
556564
'dmat': dmat1,
557565
'fl_distance': fl_distance1})
558566
new_conformers.extend(newest_conformer_list)

0 commit comments

Comments
 (0)