Skip to content

Commit 6a932b6

Browse files
authored
Add files via upload
1 parent 800af3f commit 6a932b6

4 files changed

Lines changed: 2038 additions & 1270 deletions

File tree

multioptpy/Optimizer/crsirfo.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import numpy as np
2+
import scipy.linalg
3+
from multioptpy.Optimizer.rsirfo import RSIRFO
4+
5+
class CRSIRFO(RSIRFO):
6+
def __init__(self, constraints=None, **config):
7+
"""
8+
Constrained RS-I-RFO Optimizer (CRS-I-RFO)
9+
"""
10+
super().__init__(**config)
11+
self.constraints_obj = constraints
12+
self.null_space_basis = None
13+
self.svd_threshold = config.get("svd_threshold", 1e-5)
14+
15+
def _get_null_space_basis(self, geom):
16+
if self.constraints_obj is None:
17+
return np.eye(len(geom) * 3)
18+
19+
geom_reshaped = geom.reshape(-1, 3)
20+
B_mat = self.constraints_obj._get_all_constraint_vectors(geom_reshaped)
21+
22+
if B_mat is None or len(B_mat) == 0:
23+
return np.eye(len(geom) * 3)
24+
25+
norms = np.linalg.norm(B_mat, axis=1)
26+
norms[norms < 1e-12] = 1.0
27+
B_mat_normalized = B_mat / norms[:, np.newaxis]
28+
29+
try:
30+
U, S, Vt = scipy.linalg.svd(B_mat_normalized.T, full_matrices=True)
31+
max_s = S[0] if len(S) > 0 else 1.0
32+
threshold = max(self.svd_threshold, max_s * 1e-6)
33+
rank = np.sum(S > threshold)
34+
null_space_basis = U[:, rank:]
35+
36+
if null_space_basis.shape[1] == 0:
37+
self.log("Warning: System is fully constrained.", force=True)
38+
return np.zeros((len(geom)*3, 0))
39+
40+
return null_space_basis
41+
42+
except np.linalg.LinAlgError:
43+
return np.eye(len(geom) * 3)
44+
45+
def run(self, geom_num_list, B_g, pre_B_g=[], pre_geom=[], B_e=0.0, pre_B_e=0.0, pre_move_vector=[], initial_geom_num_list=[], g=[], pre_g=[]):
46+
self.log(f"\n{'='*50}\nCRS-I-RFO Iteration {self.iteration}\n{'='*50}", force=True)
47+
48+
if self.Initialization:
49+
self.prev_eigvec_min = None
50+
self.prev_eigvec_size = None
51+
self.predicted_energy_changes = []
52+
self.actual_energy_changes = []
53+
self.prev_geometry = None
54+
self.prev_gradient = None
55+
self.prev_energy = None
56+
self.converged = False
57+
self.iteration = 0
58+
self.Initialization = False
59+
60+
# --- 0. SHAKE-like Correction & Gradient Transport ---
61+
gradient_full = np.asarray(B_g).ravel()
62+
original_shape = geom_num_list.shape
63+
geom_flat = geom_num_list.ravel()
64+
65+
if self.constraints_obj is not None:
66+
geom_reshaped = geom_num_list.reshape(-1, 3)
67+
corrected_geom_3d = self.constraints_obj.adjust_init_coord(geom_reshaped)
68+
corrected_geom_flat = corrected_geom_3d.ravel()
69+
70+
shake_displacement = corrected_geom_flat - geom_flat
71+
diff_norm = np.linalg.norm(shake_displacement)
72+
73+
if diff_norm > 1e-6:
74+
self.log(f"SHAKE Correction: {diff_norm:.6e}", force=True)
75+
H_eff = self.hessian
76+
if self.bias_hessian is not None:
77+
H_eff += self.bias_hessian
78+
grad_correction = np.dot(H_eff, shake_displacement)
79+
gradient_full += grad_correction
80+
81+
geom_num_list = corrected_geom_3d.reshape(original_shape)
82+
83+
# --- 1. Hessian Update ---
84+
if self.prev_geometry is not None and self.prev_gradient is not None and len(pre_g) > 0 and len(pre_geom) > 0:
85+
self.update_hessian(geom_num_list, g, pre_geom, pre_g)
86+
87+
hessian_full = self.hessian
88+
if self.bias_hessian is not None:
89+
hessian_full += self.bias_hessian
90+
91+
current_energy = B_e
92+
93+
# --- 2. Projection to Subspace ---
94+
U = self._get_null_space_basis(geom_num_list.reshape(-1, 3))
95+
96+
if U.shape[1] == 0:
97+
return np.zeros_like(gradient_full).reshape(-1, 1)
98+
99+
gradient_sub = np.dot(U.T, gradient_full)
100+
hessian_sub = np.dot(U.T, np.dot(hessian_full, U))
101+
102+
subspace_dim = len(gradient_sub)
103+
grad_sub_norm = np.linalg.norm(gradient_sub)
104+
105+
self.log(f"Subspace Dim: {subspace_dim}, Projected Grad Norm: {grad_sub_norm:.6e}", force=True)
106+
107+
# === CRITICAL FIX: Explicit Convergence Check in Subspace ===
108+
# If the projected gradient is effectively zero, we are done.
109+
# Don't try to calculate RFO step, it will be numerically unstable.
110+
if grad_sub_norm < self.gradient_norm_threshold:
111+
self.log(f"*** CONVERGED in Subspace (Grad: {grad_sub_norm:.6e}) ***", force=True)
112+
self.converged = True
113+
114+
# Reset history to clean state
115+
self.prev_geometry = geom_num_list
116+
self.prev_gradient = B_g
117+
self.prev_energy = current_energy
118+
119+
return np.zeros_like(gradient_full).reshape(-1, 1)
120+
# ============================================================
121+
122+
# --- 3. RFO in Subspace ---
123+
hessian_sub = 0.5 * (hessian_sub + hessian_sub.T)
124+
125+
eigvals_sub, eigvecs_sub = self.compute_eigendecomposition_with_shift(hessian_sub)
126+
127+
# Trust Radius
128+
if not self.Initialization and self.prev_energy is not None:
129+
actual_energy_change = B_e - self.prev_energy
130+
if len(self.actual_energy_changes) >= 3:
131+
self.actual_energy_changes.pop(0)
132+
self.actual_energy_changes.append(actual_energy_change)
133+
134+
if self.predicted_energy_changes:
135+
min_eigval = eigvals_sub[0] if len(eigvals_sub) > 0 else None
136+
self.adjust_trust_radius(
137+
actual_energy_change,
138+
self.predicted_energy_changes[-1],
139+
min_eigval,
140+
grad_sub_norm
141+
)
142+
143+
P_rfo = np.eye(subspace_dim)
144+
root_num = 0
145+
i = 0
146+
while root_num < len(self.roots) and i < len(eigvals_sub):
147+
if np.abs(eigvals_sub[i]) > 1e-10:
148+
trans_vec = eigvecs_sub[:, i]
149+
if self.NEB_mode:
150+
P_rfo -= np.outer(trans_vec, trans_vec)
151+
else:
152+
P_rfo -= 2 * np.outer(trans_vec, trans_vec)
153+
root_num += 1
154+
i += 1
155+
156+
H_star_sub = np.dot(P_rfo, hessian_sub)
157+
H_star_sub = 0.5 * (H_star_sub + H_star_sub.T)
158+
grad_star_sub = np.dot(P_rfo, gradient_sub)
159+
160+
eigvals_star, eigvecs_star = self.compute_eigendecomposition_with_shift(H_star_sub)
161+
eigvals_star, eigvecs_star = self.filter_small_eigvals(eigvals_star, eigvecs_star)
162+
163+
step_sub = self.get_rs_step(eigvals_star, eigvecs_star, grad_star_sub)
164+
165+
# --- 4. Reconstruct Step ---
166+
step_full = np.dot(U, step_sub)
167+
168+
predicted_energy_change = self.rfo_model(gradient_sub, hessian_sub, step_sub)
169+
170+
if len(self.predicted_energy_changes) >= 3:
171+
self.predicted_energy_changes.pop(0)
172+
self.predicted_energy_changes.append(predicted_energy_change)
173+
174+
if self.actual_energy_changes and len(self.predicted_energy_changes) > 1:
175+
self.evaluate_step_quality()
176+
177+
self.prev_geometry = geom_num_list
178+
self.prev_gradient = B_g
179+
self.prev_energy = current_energy
180+
self.iteration += 1
181+
182+
return -1 * step_full.reshape(-1, 1)

multioptpy/fileio.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,21 +383,25 @@ def make_geometry_list(self, args_electric_charge_and_multiplicity):
383383
return [start_data], element_list, electric_charge_and_multiplicity
384384

385385

386-
def print_geometry_list(self, new_geometry, element_list, electric_charge_and_multiplicity):
386+
def print_geometry_list(self, new_geometry, element_list, electric_charge_and_multiplicity, display_flag=True):
387387
"""load structure updated geometry for next QM calculation"""
388388
new_geometry = new_geometry.tolist()
389-
print("\n")
389+
390390

391391
# Process all geometries at once with list comprehension
392392
formatted_geometries = []
393393
for num, geometry in enumerate(new_geometry):
394394
element = element_list[num]
395395
formatted_geometry = [element] + list(map(str, geometry))
396396
formatted_geometries.append(formatted_geometry)
397-
print(f"{element:2} {float(geometry[0]):>17.12f} {float(geometry[1]):>17.12f} {float(geometry[2]):>17.12f}")
397+
398+
if display_flag:
399+
for num, geometry in enumerate(new_geometry):
400+
element = element_list[num]
401+
print(f"{element:2} {float(geometry[0]):>17.12f} {float(geometry[1]):>17.12f} {float(geometry[2]):>17.12f}")
398402

399403
geometry_list = [[electric_charge_and_multiplicity, *formatted_geometries]]
400-
print("")
404+
print("\n")
401405

402406
return geometry_list
403407

0 commit comments

Comments
 (0)