Skip to content

Commit d268968

Browse files
authored
Add files via upload
- new option to align image distances (-aid N, nebmain.py)
1 parent d08b55b commit d268968

2 files changed

Lines changed: 152 additions & 3 deletions

File tree

multioptpy/interface.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ def nebparser(parser):
197197
parser.add_argument("-ewbneb", "--EWBNEB", action='store_true', help='Energy-weighted Nudged elastic band method')
198198

199199
parser.add_argument("-idpp", "--use_image_dependent_pair_potential", action='store_true', help='use image dependent pair potential (IDPP) method (ref. arXiv:1406.1512v1)')
200-
parser.add_argument("-ad", "--align_distances", type=int, default=0, help='distribute images at equal intervals linearly')
200+
parser.add_argument("-ad", "--align_distances", type=int, default=0, help='distribute images at equal intervals on the reaction coordinate')
201+
parser.add_argument("-aid", "--align_image_distances", type=int, default=0, help='distribute images at equal intervals between images')
202+
201203
parser.add_argument("-nd", "--node_distance", type=float, default=None, help='distribute images at equal intervals linearly based ont specific distance (ex.) [distance (ang.)] (default: None)')
202204
parser.add_argument("-p", "--partition", type=int, default='0', help='number of nodes')
203205
parser.add_argument("-core", "--N_THREAD", type=int, default='8', help='threads')

multioptpy/neb.py

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def __init__(self, args):
104104
# Flags
105105
self.IDPP_flag = args.use_image_dependent_pair_potential
106106
self.align_distances = args.align_distances
107+
self.align_image_distances = args.align_image_distances
107108
self.excited_state = args.excited_state
108109
self.unrestrict = args.unrestrict
109110
self.save_pict = args.save_pict
@@ -465,7 +466,12 @@ def execute(self):
465466
tmp_new_geometry = distribute_geometry(np.array(new_geometry))
466467
for k in range(len(new_geometry)):
467468
new_geometry[k] = copy.copy(tmp_new_geometry[k])
468-
469+
if self.config.align_image_distances >= 1 and optimize_num % self.config.align_image_distances == 0 and optimize_num > 0:
470+
print("Aligning images...")
471+
tmp_new_geometry = align_geometry_by_image_distance(np.array(new_geometry))
472+
for k in range(len(new_geometry)):
473+
new_geometry[k] = copy.copy(tmp_new_geometry[k])
474+
469475
# Save analysis files
470476
tmp_instance_fileio = FileIO(file_directory + "/", "dummy.txt")
471477
tmp_instance_fileio.argrelextrema_txt_save(biased_energy_list, "approx_TS_node", "max")
@@ -896,4 +902,145 @@ def distribute_geometry_by_length(geometry_list, angstrom_spacing):
896902
new_geometry_list.append(new_geometry)
897903

898904
new_geometry_list.append(geometry_list[-1])
899-
return new_geometry_list
905+
return new_geometry_list
906+
907+
908+
def align_geometry_by_image_distance(geometry_list, spacing_dist=None, max_iterations=1000, tolerance=1e-6):
909+
"""Distribute geometries by specified distance spacing"""
910+
911+
for i in range(len(geometry_list)-1):
912+
geom_i = geometry_list[i]
913+
geom_j = geometry_list[i+1]
914+
geom_i_after_kabsch, geom_j_after_kabsch = Calculationtools().kabsch_algorithm(geom_i, geom_j)
915+
geometry_list[i] = geom_i_after_kabsch
916+
geometry_list[-1] = geom_j_after_kabsch
917+
918+
# 1. Create path length list
919+
path_length_list = calc_path_length_list(geometry_list)
920+
total_length = path_length_list[-1]
921+
nnode = len(geometry_list)
922+
#print("Number of nodes : ", nnode)
923+
#print("Path length list (before the process) : ", path_length_list)
924+
#print("Total path length : ", total_length)
925+
926+
# 2. Calculate equal spacing distance
927+
if spacing_dist is not None:
928+
node_distance = spacing_dist
929+
n_new_nodes = int(total_length / node_distance) + 1
930+
else:
931+
n_new_nodes = nnode
932+
node_distance = total_length / (n_new_nodes - 1)
933+
#print("Node distance : ", node_distance)
934+
935+
# 3. Create target distance list with equal spacing
936+
target_distances = [i * node_distance for i in range(n_new_nodes)]
937+
938+
# 4. Generate nodes with accurate equal spacing using iterative method
939+
new_geometry_list = []
940+
941+
# Fix the first node
942+
new_geometry_list.append(geometry_list[0])
943+
944+
# Generate remaining nodes (excluding first and last nodes)
945+
for i in range(1, n_new_nodes-1):
946+
# Target distance
947+
target_distance = node_distance
948+
949+
# Initial estimated position
950+
# Find which segment it belongs to
951+
for j in range(len(path_length_list) - 1):
952+
if path_length_list[j] <= target_distances[i] <= path_length_list[j+1]:
953+
# Linear interpolation
954+
t = (target_distances[i] - path_length_list[j]) / (path_length_list[j+1] - path_length_list[j])
955+
current_geom = geometry_list[j] + t * (geometry_list[j+1] - geometry_list[j])
956+
segment_start = j
957+
break
958+
959+
# Iteratively adjust to achieve exact distance from previous node
960+
prev_geom = new_geometry_list[-1]
961+
iteration = 0
962+
963+
# Track current position parameters along the path
964+
current_segment = segment_start
965+
current_t = t
966+
967+
while iteration < max_iterations:
968+
# Calculate distance to previous node
969+
prev_geom_centered = prev_geom - np.mean(prev_geom, axis=0)
970+
current_geom_centered = current_geom - np.mean(current_geom, axis=0)
971+
current_distance = np.linalg.norm(current_geom_centered - prev_geom_centered)
972+
973+
# Exit if distance is accurate enough
974+
if abs(current_distance - target_distance) < tolerance:
975+
break
976+
977+
# Move forward along the path if distance is too small, backward if too large
978+
if current_distance < target_distance:
979+
# Move forward
980+
delta_dist = target_distance - current_distance
981+
982+
# Advance within current segment
983+
segment_length = path_length_list[current_segment+1] - path_length_list[current_segment]
984+
remaining_segment = (1 - current_t) * segment_length
985+
986+
if delta_dist <= remaining_segment:
987+
# Adjustment possible within current segment
988+
move_t = delta_dist / segment_length
989+
current_t += move_t
990+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
991+
else:
992+
# Need to move to next segment
993+
remaining_dist = delta_dist - remaining_segment
994+
995+
# If next segment exists
996+
if current_segment + 1 < len(geometry_list) - 1:
997+
current_segment += 1
998+
next_segment_length = path_length_list[current_segment+1] - path_length_list[current_segment]
999+
current_t = min(remaining_dist / next_segment_length, 0.99) # Prevent boundary crossing
1000+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
1001+
else:
1002+
# If we can't move further, stay at end of last segment
1003+
current_t = 0.99
1004+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
1005+
else:
1006+
# Move backward
1007+
delta_dist = current_distance - target_distance
1008+
1009+
# Retreat within current segment
1010+
segment_length = path_length_list[current_segment+1] - path_length_list[current_segment]
1011+
traversed_segment = current_t * segment_length
1012+
1013+
if delta_dist <= traversed_segment:
1014+
# Adjustment possible within current segment
1015+
move_t = delta_dist / segment_length
1016+
current_t -= move_t
1017+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
1018+
else:
1019+
# Need to move to previous segment
1020+
remaining_dist = delta_dist - traversed_segment
1021+
1022+
# If previous segment exists
1023+
if current_segment > 0:
1024+
current_segment -= 1
1025+
prev_segment_length = path_length_list[current_segment+1] - path_length_list[current_segment]
1026+
current_t = max(1 - (remaining_dist / prev_segment_length), 0.01) # Prevent boundary crossing
1027+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
1028+
else:
1029+
# If we can't move further, stay at start of first segment
1030+
current_t = 0.01
1031+
current_geom = geometry_list[current_segment] + current_t * (geometry_list[current_segment+1] - geometry_list[current_segment])
1032+
1033+
iteration += 1
1034+
1035+
new_geometry_list.append(current_geom)
1036+
1037+
# Use last node from input
1038+
new_geometry_list.append(geometry_list[-1])
1039+
1040+
new_path_length_list = calc_path_length_list(new_geometry_list)
1041+
#print("Path length list (after the process) : ", new_path_length_list)
1042+
#print("Distances between nodes:")
1043+
#for x in range(len(new_path_length_list)-1):
1044+
# print(new_path_length_list[x+1]-new_path_length_list[x])
1045+
1046+
return new_geometry_list

0 commit comments

Comments
 (0)