2727except :
2828 print ("You can't use dxtb." )
2929
30-
30+ from scipy . signal import argrelmax
3131
3232from interface import force_data_parser
3333from parameter import element_number
@@ -91,6 +91,29 @@ def __init__(self, args):
9191 self .bneb2 = args .BNEB2
9292 self .ewbneb = args .EWBNEB
9393 self .qsm = args .QSM
94+ tmp_aneb = args .ANEB
95+
96+ if tmp_aneb is None :
97+ self .aneb_flag = False
98+ self .aneb_interpolation_num = 0
99+ self .aneb_frequency = 100000000000000000 # approximate infinite number
100+
101+ elif len (tmp_aneb ) == 2 :
102+ self .aneb_flag = True
103+ self .aneb_interpolation_num = int (tmp_aneb [0 ])
104+ self .aneb_frequency = int (tmp_aneb [1 ])
105+ if self .aneb_frequency < 1 or self .aneb_interpolation_num < 1 :
106+ print ("invalid input (-aneb)" )
107+ print ("Recommended setting is applied." )
108+ self .aneb_interpolation_num = 1
109+ self .aneb_frequency = 1
110+ else :
111+ self .aneb_flag = False
112+ self .aneb_interpolation_num = 0
113+ self .aneb_frequency = 100000000000000000 # approximate infinite number
114+ print ("invalid input (-aneb)" )
115+ exit ()
116+
94117
95118 # Optimization settings
96119 self .FC_COUNT = args .calc_exact_hess
@@ -338,7 +361,6 @@ def execute(self):
338361 file_directory = self .make_input_files (geometry_list , 0 )
339362
340363 # Initialize calculation variables
341- pre_total_velocity = [[[]]]
342364 force_data = force_data_parser (self .args )
343365
344366 # Check for projection constraints
@@ -366,13 +388,13 @@ def execute(self):
366388 else :
367389 fix_atom_flag = False
368390
369- # Initialize optimization variables - FIXED: Proper initialization order
391+ # Initialize optimization variables
370392 pre_geom = None
371393 pre_total_force = None
372394 pre_biased_gradient_list = None
373395 pre_total_velocity = []
374396 total_velocity = []
375- pre_biased_energy_list = None # FIXED: Initialize this variable
397+ pre_biased_energy_list = None
376398
377399 # Check for conflicting optimization methods
378400 if self .config .cg_method and self .config .lbfgs_method :
@@ -381,7 +403,7 @@ def execute(self):
381403
382404 # Setup optimizer
383405 optimizer = self ._setup_optimizer ()
384-
406+ adaptive_neb_count = 0
385407 # Main NEB iteration loop
386408 for optimize_num in range (self .config .NEB_NUM ):
387409 exit_file_detect = os .path .exists (self .config .NEB_FOLDER_DIRECTORY + "end.txt" )
@@ -395,30 +417,30 @@ def execute(self):
395417
396418 # Calculate energy and gradients
397419 energy_list , gradient_list , geometry_num_list , pre_total_velocity = \
398- self .calculation_engine .calculate (file_directory , optimize_num ,
420+ self .calculation_engine .calculate (file_directory , adaptive_neb_count ,
399421 pre_total_velocity , self .config )
400422
401- if optimize_num == 0 :
423+ if adaptive_neb_count == 0 :
402424 init_geometry_num_list = geometry_num_list
403425
404426 # Apply bias potential - FIXED: Check if hessian files exist before using them
405427 biased_energy_list , biased_gradient_list = self ._apply_bias_potential (
406428 energy_list , gradient_list , geometry_num_list , element_list , force_data , optimize_num )
407429
408- # FIXED: Initialize pre_biased_energy_list on first iteration
409- if optimize_num == 0 :
430+ # Initialize pre_biased_energy_list on first iteration
431+ if adaptive_neb_count == 0 :
410432 pre_biased_energy_list = copy .copy (biased_energy_list )
411433
412434 # Calculate model hessian if needed
413- if (self .config .FC_COUNT == - 1 and self .config .model_hessian and
414- optimize_num % self .config .MFC_COUNT == 0 ):
435+ if (self .config .FC_COUNT == - 1 and self .config .model_hessian and
436+ adaptive_neb_count % self .config .MFC_COUNT == 0 ):
415437 for i in range (len (geometry_num_list )):
416438 hessian = ApproxHessian ().main (geometry_num_list [i ], element_list ,
417439 gradient_list [i ], approx_hess_type = self .config .model_hessian )
418440 np .save (self .config .NEB_FOLDER_DIRECTORY + "tmp_hessian_" + str (i ) + ".npy" , hessian )
419441
420442 # Initialize projection constraints
421- if projection_constraint_flag and optimize_num == 0 :
443+ if projection_constraint_flag and adaptive_neb_count == 0 :
422444 PC_list = []
423445 for i in range (len (energy_list )):
424446 PC_list .append (ProjectOutConstrain (
@@ -435,7 +457,7 @@ def execute(self):
435457
436458 # Calculate forces
437459 total_force = STRING_FORCE_CALC .calc_force (
438- geometry_num_list , biased_energy_list , biased_gradient_list , optimize_num , element_list )
460+ geometry_num_list , biased_energy_list , biased_gradient_list , adaptive_neb_count , element_list )
439461
440462 # Calculate analysis metrics
441463 cos_list , tot_force_rms_list , tot_force_max_list , bias_force_rms_list , path_length_list = \
@@ -450,7 +472,7 @@ def execute(self):
450472 # Perform optimization step
451473 new_geometry = self ._perform_optimization_step (
452474 optimizer , geometry_num_list , total_force , biased_gradient_list ,
453- pre_geom , pre_biased_gradient_list , optimize_num , biased_energy_list ,
475+ pre_geom , pre_biased_gradient_list , adaptive_neb_count , biased_energy_list ,
454476 pre_biased_energy_list , pre_total_velocity , total_velocity ,
455477 cos_list , pre_geom , STRING_FORCE_CALC )
456478
@@ -465,41 +487,95 @@ def execute(self):
465487 projection_constraint_flag , PC_list if projection_constraint_flag else None )
466488
467489 # Align geometries if needed
468- if self .config .align_distances >= 1 and optimize_num % self .config .align_distances == 0 and optimize_num > 0 :
469- print ("Aligning geometries..." )
470- tmp_new_geometry = distribute_geometry (np .array (new_geometry ))
471- for k in range (len (new_geometry )):
472- new_geometry [k ] = copy .copy (tmp_new_geometry [k ])
473-
474- if self .config .align_distances_spline >= 1 and optimize_num % self .config .align_distances_spline == 0 and optimize_num > 0 :
475- print ("Aligning geometries using spline..." )
476- tmp_new_geometry = distribute_geometry_spline (np .array (new_geometry ))
477- for k in range (len (new_geometry )):
478- new_geometry [k ] = copy .copy (tmp_new_geometry [k ])
490+ new_geometry = self ._align_geometries (new_geometry , optimize_num )
479491
480492 # Save analysis files
481493 tmp_instance_fileio = FileIO (file_directory + "/" , "dummy.txt" )
482494 tmp_instance_fileio .argrelextrema_txt_save (biased_energy_list , "approx_TS_node" , "max" )
483495 tmp_instance_fileio .argrelextrema_txt_save (biased_energy_list , "approx_EQ_node" , "min" )
484496 tmp_instance_fileio .argrelextrema_txt_save (bias_force_rms_list , "local_min_bias_grad_node" , "min" )
485497
486- # Prepare for next iteration - FIXED: Proper variable assignment order
487- pre_geom = copy .copy (geometry_num_list )
488- geometry_list = self .print_geometry_list (new_geometry , element_list , electric_charge_and_multiplicity )
489- file_directory = self .make_input_files (geometry_list , optimize_num + 1 )
490- pre_total_force = copy .copy (total_force )
491- pre_biased_gradient_list = copy .copy (biased_gradient_list )
492- pre_total_velocity = copy .copy (total_velocity )
493- pre_biased_energy_list = copy .copy (biased_energy_list )
494-
495- # Save energy data
496- with open (self .config .NEB_FOLDER_DIRECTORY + "energy_plot.csv" , "a" ) as f :
497- f .write ("," .join (list (map (str , biased_energy_list .tolist ()))) + "\n " )
498+ # Prepare for next iteration
499+ if adaptive_neb_count % self .config .aneb_frequency == 0 and adaptive_neb_count > 0 and self .config .aneb_flag :
500+ pre_geom = None
501+ pre_total_force = None
502+ pre_biased_gradient_list = None
503+ pre_total_velocity = []
504+ total_velocity = []
505+ pre_biased_energy_list = None
506+ new_geometry = self ._exec_adaptive_neb (new_geometry , biased_energy_list )
507+ geometry_list = self .print_geometry_list (new_geometry , element_list , electric_charge_and_multiplicity )
508+ file_directory = self .make_input_files (geometry_list , optimize_num + 1 )
509+ adaptive_neb_count = 0
510+
511+ else :
512+ pre_geom = copy .copy (geometry_num_list )
513+ geometry_list = self .print_geometry_list (new_geometry , element_list , electric_charge_and_multiplicity )
514+ file_directory = self .make_input_files (geometry_list , optimize_num + 1 )
515+ pre_total_force = copy .copy (total_force )
516+ pre_biased_gradient_list = copy .copy (biased_gradient_list )
517+ pre_total_velocity = copy .copy (total_velocity )
518+ pre_biased_energy_list = copy .copy (biased_energy_list )
519+ adaptive_neb_count += 1
498520
521+
499522 self .make_traj_file (file_directory )
500523 print ("Complete..." )
501524 return
502-
525+
526+ def _exec_adaptive_neb (self , new_geometry , energy_list ):
527+ """Execute the adaptive NEB algorithm (private method)"""
528+ # ref.: P. Maragakis, S. A. Andreev, Y. Brumer, D. R. Reichman, E. Kaxiras, J. Chem. Phys. 117, 4651 (2002)
529+ ene_max_val_indices = argrelmax (energy_list )[0 ]
530+ print ("Using Adaptive NEB method..." )
531+ if len (ene_max_val_indices ) == 0 :
532+ print ("Maxima not found." )
533+ return new_geometry
534+ if self .config .aneb_interpolation_num < 1 :
535+ print ("Interpolation number is 0." )
536+ return new_geometry
537+
538+ adaptive_neb_applied_new_geometry = []
539+ for i in range (len (new_geometry )):
540+
541+ if i in ene_max_val_indices :
542+ delta_geom_minus = new_geometry [i ] - new_geometry [i - 1 ]
543+ delta_geom_plus = new_geometry [i + 1 ] - new_geometry [i ]
544+
545+ for j in range (self .config .aneb_interpolation_num ):
546+ alpha = (j + 1 ) / (self .config .aneb_interpolation_num + 1 )
547+ new_point = new_geometry [i - 1 ] + alpha * delta_geom_minus
548+ adaptive_neb_applied_new_geometry .append (new_point )
549+
550+ adaptive_neb_applied_new_geometry .append (new_geometry [i ])
551+
552+ for j in range (self .config .aneb_interpolation_num ):
553+ alpha = (j + 1 ) / (self .config .aneb_interpolation_num + 1 )
554+ new_point = new_geometry [i ] + alpha * delta_geom_plus
555+ adaptive_neb_applied_new_geometry .append (new_point )
556+
557+ else :
558+ adaptive_neb_applied_new_geometry .append (new_geometry [i ])
559+
560+ adaptive_neb_applied_new_geometry = np .array (adaptive_neb_applied_new_geometry , dtype = "float64" )
561+ print ("Interpolated nodes: " , ene_max_val_indices )
562+ print ("The number of interpolated nodes: " , len (ene_max_val_indices )* 2 )
563+ return adaptive_neb_applied_new_geometry
564+
565+ def _align_geometries (self , new_geometry , optimize_num ):
566+ """Align geometries if needed (private method)"""
567+ if self .config .align_distances >= 1 and optimize_num % self .config .align_distances == 0 and optimize_num > 0 :
568+ print ("Aligning geometries..." )
569+ tmp_new_geometry = distribute_geometry (np .array (new_geometry ))
570+ for k in range (len (new_geometry )):
571+ new_geometry [k ] = copy .copy (tmp_new_geometry [k ])
572+ if self .config .align_distances_spline >= 1 and optimize_num % self .config .align_distances_spline == 0 and optimize_num > 0 :
573+ print ("Aligning geometries using spline..." )
574+ tmp_new_geometry = distribute_geometry_spline (np .array (new_geometry ))
575+ for k in range (len (new_geometry )):
576+ new_geometry [k ] = copy .copy (tmp_new_geometry [k ])
577+ return new_geometry
578+
503579 def _setup_force_calculation (self ):
504580 """Setup force calculation method"""
505581 if self .config .om :
@@ -640,6 +716,12 @@ def _save_analysis_data(self, cos_list, tot_force_rms_list, tot_force_max_list,
640716 # Save perpendicular gradient MAX data
641717 with open (self .config .NEB_FOLDER_DIRECTORY + "perp_max_gradient.csv" , "a" ) as f :
642718 f .write ("," .join (list (map (str , tot_force_max_list ))) + "\n " )
719+
720+ # Save energy data
721+ with open (self .config .NEB_FOLDER_DIRECTORY + "energy_plot.csv" , "a" ) as f :
722+ f .write ("," .join (list (map (str , biased_energy_list .tolist ()))) + "\n " )
723+
724+
643725
644726 def _perform_optimization_step (self , optimizer , geometry_num_list , total_force ,
645727 biased_gradient_list , pre_geom , pre_biased_gradient_list ,
0 commit comments