@@ -92,6 +92,36 @@ def _get_gradient(self, coords_ang):
9292
9393 return np .array (gradient )
9494
95+ def _guess_topology_protection (self , distmat_bohr ):
96+ """
97+ Guess 1-2 (bond) and 1-3 (angle) connectivity based on distances.
98+ Returns a boolean mask of pairs that should be protected.
99+ """
100+ N = distmat_bohr .shape [0 ]
101+
102+ # 1. Define bonding threshold (e.g., 1.3 * sum of covalent radii)
103+ # Using broadcasting for radii sums
104+ radii_sum = self .atom_radii [:, np .newaxis ] + self .atom_radii [np .newaxis , :]
105+ bond_thresh = 1.3 * radii_sum
106+
107+ # 2. Identify 1-2 bonds (exclude self-loop)
108+ # distmat < threshold AND distmat > 1e-3 (avoid self)
109+ bond_adj = (distmat_bohr < bond_thresh ) & (distmat_bohr > 1e-3 )
110+
111+ # 3. Identify 1-3 angles using matrix multiplication
112+ # If A-B is bonded and B-C is bonded, (bond_adj @ bond_adj)[A, C] > 0
113+ # converting to float for matmul, then back to bool
114+ bond_float = bond_adj .astype (float )
115+ angle_adj = (np .dot (bond_float , bond_float )) > 0.1
116+
117+ # Remove self-loops (A-B-A) introduced by matmul
118+ np .fill_diagonal (angle_adj , False )
119+
120+ # 4. Combine masks (Protects both Bonds and Angles)
121+ protected_mask = bond_adj | angle_adj
122+
123+ return protected_mask
124+
95125 def compute_hessian (self , current_coords_ang ):
96126 """
97127 Execute the O1NumHess algorithm.
@@ -112,7 +142,7 @@ def compute_hessian(self, current_coords_ang):
112142
113143 if self .verbosity > 0 :
114144 print (f"\n === Starting O1NumHess Calculation ===" )
115- print (f" Adaptive Cutoff Strategy: { self .rcov_scale } * (R_i + R_j) + 1.0" )
145+ print (f" Adaptive Cutoff Strategy: { self .rcov_scale } * (R_i + R_j) + 1.0 Bohr " )
116146 print (f" Min Cutoff: { np .min (cutoff_mat ):.2f} Bohr, Max Cutoff: { np .max (cutoff_mat ):.2f} Bohr" )
117147 print (f" Step size (eta): { self .delta } Bohr" )
118148
@@ -132,6 +162,18 @@ def compute_hessian(self, current_coords_ang):
132162 print (" [3/6] Building connectivity graph..." )
133163 distmat_bohr = cdist (coords_bohr , coords_bohr )
134164
165+ if self .verbosity > 0 :
166+ print (" -> Applying topology-based protection (1-2 & 1-3 pairs)..." )
167+
168+ protected_mask = self ._guess_topology_protection (distmat_bohr )
169+
170+ safe_margin = 2.0 # Bohr
171+
172+ # update only where protection is needed AND current cutoff is too small
173+ cutoff_mat [protected_mask ] = np .maximum (
174+ cutoff_mat [protected_mask ],
175+ distmat_bohr [protected_mask ] + safe_margin
176+ )
135177 # FIXED: Pass cutoff_mat instead of scalar dmax
136178 nblist , nbcounts = self ._build_neighbor_list (N_atom , distmat_bohr , cutoff_mat )
137179
0 commit comments