@@ -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