MultiOptPy 1.20.5__py3-none-any.whl → 1.20.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -125,7 +125,69 @@ class RSIRFO:
125
125
  self.alpha_init_values = [0.001 + (10.0 - 0.001) * i / 14 for i in range(15)]
126
126
  self.NEB_mode = False
127
127
 
128
-
128
+ def _project_grad_tr_rot(self, gradient, geometry):
129
+ """
130
+ [Quick Implementation] Project out translation and rotation components from the gradient.
131
+ Uses QR decomposition for basis orthonormalization.
132
+ """
133
+ # Data preparation
134
+ coords = geometry.reshape(-1, 3)
135
+ n_atoms = coords.shape[0]
136
+
137
+ # 1. Center coordinates (Critical for rotation)
138
+ center = np.mean(coords, axis=0)
139
+ coords_centered = coords - center
140
+
141
+ # 2. Construct basis vectors for TR/ROT (Total 6 vectors)
142
+ # Flattened vectors of size 3*N
143
+ basis = []
144
+
145
+ # Translation (x, y, z)
146
+ basis.append(np.tile([1, 0, 0], n_atoms))
147
+ basis.append(np.tile([0, 1, 0], n_atoms))
148
+ basis.append(np.tile([0, 0, 1], n_atoms))
149
+
150
+ # Rotation (Rx, Ry, Rz via cross product)
151
+ # Rx: (1,0,0) x r -> (0, -z, y)
152
+ rx = np.zeros_like(coords)
153
+ rx[:, 1] = -coords_centered[:, 2]
154
+ rx[:, 2] = coords_centered[:, 1]
155
+ basis.append(rx.flatten())
156
+
157
+ # Ry: (0,1,0) x r -> (z, 0, -x)
158
+ ry = np.zeros_like(coords)
159
+ ry[:, 0] = coords_centered[:, 2]
160
+ ry[:, 2] = -coords_centered[:, 0]
161
+ basis.append(ry.flatten())
162
+
163
+ # Rz: (0,0,1) x r -> (-y, x, 0)
164
+ rz = np.zeros_like(coords)
165
+ rz[:, 0] = -coords_centered[:, 1]
166
+ rz[:, 1] = coords_centered[:, 0]
167
+ basis.append(rz.flatten())
168
+
169
+ # 3. Orthonormalize basis (QR decomposition)
170
+ # A: Matrix of shape (3N, 6)
171
+ A = np.array(basis).T
172
+
173
+ # 'reduced' returns Q of shape (3N, K) where K <= 6
174
+ # This handles linear molecules automatically (rank deficient)
175
+ Q, _ = np.linalg.qr(A, mode='reduced')
176
+
177
+ # 4. Project out components
178
+ # P = I - Q * Q.T
179
+ # g_proj = g - Q * (Q.T * g)
180
+
181
+ # Calculate overlap (scalar components along TR/ROT modes)
182
+ overlaps = np.dot(Q.T, gradient)
183
+
184
+ # Reconstruct the TR/ROT part of the gradient
185
+ tr_rot_part = np.dot(Q, overlaps)
186
+
187
+ # Subtract from original gradient
188
+ projected_gradient = gradient - tr_rot_part
189
+
190
+ return projected_gradient
129
191
 
130
192
  def _build_hessian_updater_list(self):
131
193
  """
@@ -267,6 +329,22 @@ class RSIRFO:
267
329
  # Ensure gradient is properly shaped as a 1D array (reuse existing array without copy)
268
330
  gradient = np.asarray(B_g).ravel()
269
331
 
332
+ # === [ADDED] Project out TR/ROT from Gradient ===
333
+ # Calculate norm before projection
334
+ raw_norm = np.linalg.norm(gradient)
335
+
336
+ # Apply projection
337
+ gradient = self._project_grad_tr_rot(gradient, geom_num_list)
338
+
339
+ # Calculate norm after projection
340
+ proj_norm = np.linalg.norm(gradient)
341
+ diff_norm = raw_norm - proj_norm
342
+
343
+ # Log the effect (Even small changes matter for RFO stability)
344
+ if abs(diff_norm) > 1e-12:
345
+ self.log(f"Gradient TR/ROT Projection: Norm {raw_norm:.6e} -> {proj_norm:.6e} (Diff: {diff_norm:.6e})", force=True)
346
+ # ================================================
347
+
270
348
  # Use effective Hessian
271
349
  tmp_hess = self.hessian
272
350
  if self.bias_hessian is not None:
@@ -280,11 +358,24 @@ class RSIRFO:
280
358
  H = 0.5 * (H + H.T) # Ensure symmetry
281
359
  # Use new method that applies/removes shift for numerical stability
282
360
  eigvals, eigvecs = self.compute_eigendecomposition_with_shift(H)
283
-
361
+
362
+ # === [CRITICAL FIX] Handle NaN/Inf in Hessian ===
363
+ # If Hessian is broken (common in linear molecules or initial guesses),
364
+ # force fallback to identity matrix to act as Steepest Descent.
365
+ if not np.all(np.isfinite(eigvals)) or not np.all(np.isfinite(eigvecs)):
366
+ self.log("CRITICAL ERROR: Hessian eigendecomposition failed (NaNs detected).", force=True)
367
+ self.log("Resetting to Identity Hessian to force Steepest Descent fallback.", force=True)
368
+ eigvals = np.ones_like(eigvals)
369
+ eigvecs = np.eye(len(eigvals))
370
+ # =================================================
284
371
  # Always check conditioning (provides useful diagnostic information)
285
372
  condition_number, is_ill_conditioned = self.check_hessian_conditioning(eigvals)
286
- print(f"Condition number of Hessian: {condition_number:.2f}, Ill-conditioned: {is_ill_conditioned}")
287
-
373
+ # === [MODIFIED START] Handle None value for condition_number ===
374
+ if condition_number is not None:
375
+ print(f"Condition number of Hessian: {condition_number:.2f}, Ill-conditioned: {is_ill_conditioned}")
376
+ else:
377
+ print(f"Condition number of Hessian: N/A, Ill-conditioned: {is_ill_conditioned}")
378
+ # === [MODIFIED END] ===
288
379
 
289
380
  # Trust Radius Adjustment (Moved here to use eigenvalues)
290
381
  if not self.Initialization:
@@ -335,6 +426,13 @@ class RSIRFO:
335
426
 
336
427
  eigvals_star, eigvecs_star = self.compute_eigendecomposition_with_shift(H_star)
337
428
 
429
+ # === [CRITICAL FIX] Handle NaN/Inf in Projected Hessian ===
430
+ if not np.all(np.isfinite(eigvals_star)) or not np.all(np.isfinite(eigvecs_star)):
431
+ self.log("CRITICAL ERROR: Projected Hessian is broken. Falling back to identity.", force=True)
432
+ eigvals_star = np.ones_like(eigvals_star)
433
+ eigvecs_star = np.eye(len(eigvals_star))
434
+ # ==========================================================
435
+
338
436
  # === Apply existing small eigenvalue filter ===
339
437
  # This is INDEPENDENT of level-shifting.
340
438
  # Level-shifting affects numerical stability during computation.
@@ -354,6 +452,16 @@ class RSIRFO:
354
452
  # Get the RS step using the image Hessian and gradient
355
453
  move_vector = self.get_rs_step(eigvals_star, eigvecs_star, grad_star)
356
454
 
455
+ # === [CRITICAL FIX] Final NaN Check on Step ===
456
+ if not np.all(np.isfinite(move_vector)):
457
+ self.log("CRITICAL ERROR: Calculated RS-I-RFO step is NaN. Forcing steepest descent.", force=True)
458
+ # Steepest descent fallback
459
+ move_vector = -gradient
460
+ norm = np.linalg.norm(move_vector)
461
+ if norm > self.trust_radius:
462
+ move_vector *= (self.trust_radius / norm)
463
+ # ==============================================
464
+
357
465
  # Update prev_eigvec_size for next iteration
358
466
  self.prev_eigvec_size = current_eigvec_size
359
467