@@ -36,7 +36,7 @@ def __init__(self, **kwarg):
3636 self .START_FILE = kwarg .get ("START_FILE" , None )
3737 self .N_THREAD = kwarg .get ("N_THREAD" , 1 )
3838 self .SET_MEMORY = kwarg .get ("SET_MEMORY" , "2GB" )
39- self .FUNCTIONAL = kwarg .get ("FUNCTIONAL" , None ) # Used for method selection if needed
39+ self .FUNCTIONAL = kwarg .get ("FUNCTIONAL" , None )
4040 self .FC_COUNT = kwarg .get ("FC_COUNT" , 1 )
4141 self .BPA_FOLDER_DIRECTORY = kwarg .get ("BPA_FOLDER_DIRECTORY" , "./" )
4242 self .Model_hess = kwarg .get ("Model_hess" , None )
@@ -45,14 +45,19 @@ def __init__(self, **kwarg):
4545 self .hessian_flag = False
4646 self .cpcm_solv_model = kwarg .get ("cpcm_solv_model" , None )
4747 self .alpb_solv_model = kwarg .get ("alpb_solv_model" , None )
48- self .method = kwarg .get ("method" , "GFN2-xTB" ) # Default method
48+ self .method = kwarg .get ("method" , "GFN2-xTB" )
4949 self .verbosity = 0
5050 self .numerical_delivative_delta = 0.0001
5151
52+ # --- OPTIMIZATION: Enforce Thread Count for tblite/OpenMP ---
53+ os .environ ["OMP_NUM_THREADS" ] = str (self .N_THREAD )
54+ os .environ ["MKL_NUM_THREADS" ] = str (self .N_THREAD )
55+ os .environ ["OPENBLAS_NUM_THREADS" ] = str (self .N_THREAD )
56+
5257 def _get_calculator (self , positions_bohr , element_numbers , charge , uhf ):
5358 """Internal helper to create Calculator instance"""
5459 calc = Calculator (self .method , element_numbers , positions_bohr , charge = charge , uhf = uhf )
55- calc .set ("max-iter" , 500 ) # Or dynamic based on atoms
60+ calc .set ("max-iter" , 500 )
5661 calc .set ("verbosity" , self .verbosity )
5762
5863 if self .cpcm_solv_model is not None :
@@ -65,92 +70,68 @@ def _get_calculator(self, positions_bohr, element_numbers, charge, uhf):
6570 def run_calculation (self , positions_bohr , element_number_list , charge_mult ):
6671 """
6772 Execute TBLite calculation for a single geometry.
68-
69- Args:
70- positions_bohr (np.ndarray): Coordinates in Bohr.
71- element_number_list (np.ndarray): Array of atomic numbers.
72- charge_mult (list): [charge, multiplicity].
73-
74- Returns:
75- tuple: (energy, gradient)
7673 """
7774 charge = int (charge_mult [0 ])
7875 mult = int (charge_mult [1 ])
79- # xTB logic: uhf is number of unpaired electrons (mult - 1)
8076 uhf = max (mult - 1 , 0 )
8177
82- # Override uhf if unrestrict flag is set but mult is 1 (though usually 0 for singlet)
8378 if self .unrestrict and uhf == 0 :
84- # Logic depends on specific needs, usually 0 is fine for RHF/RKS equivalent
8579 pass
8680
8781 calc = self ._get_calculator (positions_bohr , element_number_list , charge , uhf )
8882
89- # Execute
9083 res = calc .singlepoint ()
9184 e = float (res .get ("energy" ))
9285 g = res .get ("gradient" ) # Hartree/Bohr
9386
94- np .save (self .BPA_FOLDER_DIRECTORY + "raw_grad.npy" , g )
95-
96- self .res = res # Store full result for later access to orbitals, etc.
87+ np .save (os .path .join (self .BPA_FOLDER_DIRECTORY , "raw_grad.npy" ), g )
88+ self .res = res
9789
9890 return e , g
9991
10092 def numerical_hessian (self , geom_num_list , element_number_list , electric_charge_and_multiplicity ):
10193 """
10294 Compute numerical Hessian using finite difference of gradients.
95+ (Highly optimized O(N) evaluation with 1D loop and in-place modification)
10396 """
10497 n_atoms = len (geom_num_list )
10598 hessian = np .zeros ((3 * n_atoms , 3 * n_atoms ))
10699
107100 charge = int (electric_charge_and_multiplicity [0 ])
108- uhf = int (electric_charge_and_multiplicity [1 ]) - 1
109-
110- # Pre-calculation setup
111- # Note: Optimization opportunity - parallelize this loop if possible in future
112- count = 0
113- for atom_num in range (n_atoms ):
114- for i in range (3 ): # x, y, z
115- for atom_num_2 in range (n_atoms ):
116- for j in range (3 ):
117- # Symmetric check to avoid double calculation
118- if count > 3 * atom_num_2 + j :
119- continue
120-
121- tmp_grad = []
122- for direction in [1 , - 1 ]:
123- # Perturb geometry
124- perturbed_geom = copy .copy (geom_num_list )
125- perturbed_geom [atom_num ][i ] += direction * self .numerical_delivative_delta
126-
127- calc = self ._get_calculator (perturbed_geom , element_number_list , charge , uhf )
128- res = calc .singlepoint ()
129- g = res .get ("gradient" )
130- tmp_grad .append (g [atom_num_2 ][j ])
131-
132- # Central difference
133- val = (tmp_grad [0 ] - tmp_grad [1 ]) / (2 * self .numerical_delivative_delta )
134- hessian [3 * atom_num + i ][3 * atom_num_2 + j ] = val
135- hessian [3 * atom_num_2 + j ][3 * atom_num + i ] = val
136-
137- count += 1
101+ uhf = max (int (electric_charge_and_multiplicity [1 ]) - 1 , 0 )
102+ delta = self .numerical_delivative_delta
103+
104+
105+ geom = np .array (geom_num_list , dtype = float )
106+
107+ for j in range (3 * n_atoms ):
108+ atom = j // 3
109+ coord = j % 3
110+
111+
112+ geom [atom , coord ] += delta
113+ calc_plus = self ._get_calculator (geom , element_number_list , charge , uhf )
114+ grad_plus = calc_plus .singlepoint ().get ("gradient" ).flatten ()
115+
116+
117+ geom [atom , coord ] -= 2 * delta
118+ calc_minus = self ._get_calculator (geom , element_number_list , charge , uhf )
119+ grad_minus = calc_minus .singlepoint ().get ("gradient" ).flatten ()
120+
121+
122+ geom [atom , coord ] += delta
123+
124+
125+ hessian [:, j ] = (grad_plus - grad_minus ) / (2 * delta )
126+
127+
128+ hessian = (hessian + hessian .T ) / 2.0
138129 return hessian
139130
140131 def calc_exact_hess (self , positions_bohr , element_number_list , charge_mult ):
141- """
142- Calculate Exact Hessian (Numerical) and project out TR modes.
143- """
144- # Compute raw numerical Hessian
145132 exact_hess = self .numerical_hessian (positions_bohr , element_number_list , charge_mult )
146- np .save (self .BPA_FOLDER_DIRECTORY + "raw_hessian.npy" , exact_hess )
147- # Project out Translation/Rotation
148- # Note: Calculationtools usually expects Bohr for projection if hessian is in au?
149- # Re-checking previous context: `project_out_hess_tr_and_rot_for_coord` seems to handle it.
150- # It takes `positions` which here are in Bohr.
133+ np .save (os .path .join (self .BPA_FOLDER_DIRECTORY , "raw_hessian.npy" ), exact_hess )
151134
152- # Ensure element_number_list is a list for the tool if needed, or array
153- # The original code passed `element_number_list.tolist()`
154135 elem_list_arg = element_number_list .tolist () if isinstance (element_number_list , np .ndarray ) else element_number_list
155136
156137 exact_hess = Calculationtools ().project_out_hess_tr_and_rot_for_coord (
0 commit comments