@@ -131,51 +131,84 @@ def fischer_angle(self, coord, element_list):
131131 self .cart_hess [3 * k + n , 3 * j + m ] += force_const * b_vec [2 ][n ] * b_vec [1 ][m ]
132132
133133 def fischer_dihedral (self , coord , element_list , bond_mat ):
134- """Calculate Hessian components for dihedral torsions"""
134+ """Calculate Hessian components for dihedral torsions with linearity check """
135135 BC = BondConnectivity ()
136136 b_c_mat = BC .bond_connect_matrix (element_list , coord )
137137 dihedral_indices = BC .dihedral_angle_connect_table (b_c_mat )
138138
139+ # Helper to calculate sin^2 of bond angle
140+ def get_sin_sq_angle (idx1 , idx2 , idx3 ):
141+ v1 = coord [idx1 ] - coord [idx2 ]
142+ v2 = coord [idx3 ] - coord [idx2 ]
143+ # Cross product magnitude squared
144+ cross_prod = np .cross (v1 , v2 )
145+ cross_sq = np .dot (cross_prod , cross_prod )
146+ # Squared norms
147+ n1_sq = np .dot (v1 , v1 )
148+ n2_sq = np .dot (v2 , v2 )
149+ if n1_sq * n2_sq < 1e-12 :
150+ return 0.0
151+ return cross_sq / (n1_sq * n2_sq )
152+
139153 for idx in dihedral_indices :
140- i , j , k , l = idx # i-j-k-l dihedral
154+ # i-j-k-l dihedral
155+ i , j , k , l = idx
156+
157+ # --- Linearity Check ---
158+ # Check bond angles I-J-K and J-K-L.
159+ # If atoms are linear, the torsion definition is singular (B-matrix blows up).
160+ # We use sin^2(theta) because it avoids acos and is efficient.
161+ sin_sq_ijk = get_sin_sq_angle (i , j , k )
162+ sin_sq_jkl = get_sin_sq_angle (j , k , l )
141163
142- # Central bond in dihedral
164+ # Threshold: if sin(theta) < 0.17 (approx 10 degrees from linear), dampen or skip.
165+ # Using sin^2 < 0.03 as cutoff.
166+ # The Wilson B-matrix scales as 1/sin(theta).
167+ # If we are too close to linear, we must skip to avoid numerical explosion.
168+ if sin_sq_ijk < 1.0e-3 or sin_sq_jkl < 1.0e-3 :
169+ continue
170+ # -----------------------
171+
172+ # Central bond in dihedral (j-k)
143173 r_jk = np .linalg .norm (coord [j ] - coord [k ])
144174 r_jk_cov = covalent_radii_lib (element_list [j ]) + covalent_radii_lib (element_list [k ])
145175
146176 # Count bonds to central atoms
147177 bond_sum = self .count_bonds_for_dihedral (bond_mat , (j , k ))
148178
149- # Calculate force constant using Fischer's formula
179+ # Calculate force constant
150180 force_const = self .calc_dihedral_force_const (r_jk , r_jk_cov , bond_sum )
151181
182+ # Optional: Additional damping can be applied here if desired,
183+ # but the skip above is the most robust fix for the singularity.
184+
152185 # Convert to Cartesian coordinates
153186 t_xyz = np .array ([coord [i ], coord [j ], coord [k ], coord [l ]])
154- tau , b_vec = torsion2 (t_xyz )
155187
156- for n in range ( 3 ):
157- for m in range ( 3 ) :
158- self . cart_hess [ 3 * i + n , 3 * i + m ] += force_const * b_vec [ 0 ][ n ] * b_vec [ 0 ][ m ]
159- self . cart_hess [ 3 * j + n , 3 * j + m ] += force_const * b_vec [ 1 ][ n ] * b_vec [ 1 ][ m ]
160- self . cart_hess [ 3 * k + n , 3 * k + m ] += force_const * b_vec [ 2 ][ n ] * b_vec [ 2 ][ m ]
161- self . cart_hess [ 3 * l + n , 3 * l + m ] += force_const * b_vec [ 3 ][ n ] * b_vec [ 3 ][ m ]
162-
163- self . cart_hess [ 3 * i + n , 3 * j + m ] += force_const * b_vec [ 0 ][ n ] * b_vec [ 1 ][ m ]
164- self . cart_hess [ 3 * i + n , 3 * k + m ] += force_const * b_vec [ 0 ][ n ] * b_vec [ 2 ][ m ]
165- self . cart_hess [ 3 * i + n , 3 * l + m ] += force_const * b_vec [ 0 ][ n ] * b_vec [ 3 ][ m ]
166-
167- self . cart_hess [ 3 * j + n , 3 * i + m ] += force_const * b_vec [ 1 ][ n ] * b_vec [ 0 ][ m ]
168- self . cart_hess [ 3 * j + n , 3 * k + m ] += force_const * b_vec [ 1 ][ n ] * b_vec [ 2 ][ m ]
169- self . cart_hess [ 3 * j + n , 3 * l + m ] += force_const * b_vec [ 1 ][ n ] * b_vec [ 3 ][ m ]
188+ # Note: torsion2 might still be unstable if not skipped
189+ try :
190+ tau , b_vec = torsion2 ( t_xyz )
191+ except Exception :
192+ # Fallback if torsion2 fails numerically
193+ continue
194+
195+ # Iterate over all pairs of atoms in the dihedral
196+ atom_indices = [ i , j , k , l ]
197+
198+ for a in range ( 4 ):
199+ for b in range ( 4 ):
200+ atom_a = atom_indices [ a ]
201+ atom_b = atom_indices [ b ]
170202
171- self .cart_hess [3 * k + n , 3 * i + m ] += force_const * b_vec [2 ][n ] * b_vec [0 ][m ]
172- self .cart_hess [3 * k + n , 3 * j + m ] += force_const * b_vec [2 ][n ] * b_vec [1 ][m ]
173- self .cart_hess [3 * k + n , 3 * l + m ] += force_const * b_vec [2 ][n ] * b_vec [3 ][m ]
203+ vec_a = b_vec [a ]
204+ vec_b = b_vec [b ]
174205
175- self .cart_hess [3 * l + n , 3 * i + m ] += force_const * b_vec [3 ][n ] * b_vec [0 ][m ]
176- self .cart_hess [3 * l + n , 3 * j + m ] += force_const * b_vec [3 ][n ] * b_vec [1 ][m ]
177- self .cart_hess [3 * l + n , 3 * k + m ] += force_const * b_vec [3 ][n ] * b_vec [2 ][m ]
178-
206+ # Update the 3x3 Hessian block
207+ for n in range (3 ):
208+ for m in range (3 ):
209+ val = force_const * vec_a [n ] * vec_b [m ]
210+ self .cart_hess [3 * atom_a + n , 3 * atom_b + m ] += val
211+
179212 def main (self , coord , element_list , cart_gradient ):
180213 """Main function to generate Fischer's approximate Hessian"""
181214 print ("Generating Fischer's approximate hessian..." )
0 commit comments