@@ -1078,39 +1078,33 @@ def project_rotors(conformer, hessian, rotors, linear, is_ts, get_projected_out_
10781078 d [3 * i + 2 , 5 ] = (p [i , 0 ] * inertia_xyz [2 , 1 ] - p [i , 1 ] * inertia_xyz [2 , 0 ]) * amass [i ]
10791079
10801080 # Make sure projection matrix is orthonormal
1081- identity = np .identity (n_atoms * 3 , float )
1082- p = np .zeros ((n_atoms * 3 , 3 * n_atoms + external ), float )
1083- p [:, 0 :external ] = d [:, 0 :external ]
1084- p [:, external :external + 3 * n_atoms ] = identity [:, 0 :3 * n_atoms ]
1081+ p = np .hstack ((d , np .identity (n_atoms * 3 , float )))
10851082
1086- # modified Gram–Schmidt orthonormalization
1087- for i in range (3 * n_atoms + external ):
1088- norm = 0.0
1089- for j in range (3 * n_atoms ):
1090- norm += p [j , i ] * p [j , i ]
1091- for j in range (3 * n_atoms ):
1092- if norm > 1E-15 :
1093- p [j , i ] /= np .sqrt (norm )
1094- else :
1095- p [j , i ] = 0.0 # zeroing out vectors that are nearly zero or dependent, could lose a basis
1096- for j in range (i + 1 , 3 * n_atoms + external ):
1097- proj = 0.0
1098- for k in range (3 * n_atoms ):
1099- proj += p [k , i ] * p [k , j ]
1100- for k in range (3 * n_atoms ):
1101- p [k , j ] -= proj * p [k , i ]
1083+ # Compute the QR decomposition in reduced mode
1084+ Q , R = np .linalg .qr (p , mode = 'reduced' )
1085+
1086+ # Verify that Q has orthonormal columns:
1087+ if np .isclose (np .dot (Q .T , Q ), np .eye (Q .shape [1 ])).all ():
1088+ logging .debug ("Q has orthonormal columns" )
1089+ else :
1090+ logging .warning ("Q does not have orthonormal columns" )
1091+ # Verify the reconstruction
1092+ reconstructed_p = Q @ R
1093+ if np .isclose (p , reconstructed_p ).all ():
1094+ logging .debug ("Reconstruction of projection matrix is correct" )
1095+ else :
1096+ logging .warning ("Reconstruction of projection matrix is incorrect" )
1097+
1098+ # Check for nearly zero columns in the QR decomposition
1099+ for i , rkk in enumerate (R .diagonal ()):
1100+ if abs (rkk ) < 1E-15 :
1101+ logging .warning (f'Column { i } of the QR decomposition is nearly zero, could lose a basis' )
11021102
1103- # Order p, since there will be vectors that are 0.0
1104- i = 0
1105- is_zero_column = (p * p ).sum (axis = 0 ) < 0.5
1106- # put the columns that are zeros at the end
1107- temporary = np .hstack ((p [:, ~ is_zero_column ], p [:, is_zero_column ]))
1108- p [:, :] = temporary
11091103
11101104 # T is the transformation vector from cartesian to internal coordinates
11111105 T = np .zeros ((n_atoms * 3 , 3 * n_atoms - external ), float )
11121106
1113- T [:, 0 : 3 * n_atoms - external ] = p [:, external :3 * n_atoms ]
1107+ T [:, : ] = Q [:, external :3 * n_atoms ]
11141108
11151109 # Generate mass-weighted force constant matrix
11161110 # This converts the axes to mass-weighted Cartesian axes
0 commit comments