MultiOptPy 1.20.2__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.
Files changed (246) hide show
  1. multioptpy/Calculator/__init__.py +0 -0
  2. multioptpy/Calculator/ase_calculation_tools.py +424 -0
  3. multioptpy/Calculator/ase_tools/__init__.py +0 -0
  4. multioptpy/Calculator/ase_tools/fairchem.py +28 -0
  5. multioptpy/Calculator/ase_tools/gamess.py +19 -0
  6. multioptpy/Calculator/ase_tools/gaussian.py +165 -0
  7. multioptpy/Calculator/ase_tools/mace.py +28 -0
  8. multioptpy/Calculator/ase_tools/mopac.py +19 -0
  9. multioptpy/Calculator/ase_tools/nwchem.py +31 -0
  10. multioptpy/Calculator/ase_tools/orca.py +22 -0
  11. multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
  12. multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
  13. multioptpy/Calculator/emt_calculation_tools.py +458 -0
  14. multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
  15. multioptpy/Calculator/lj_calculation_tools.py +314 -0
  16. multioptpy/Calculator/psi4_calculation_tools.py +334 -0
  17. multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
  18. multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
  19. multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
  20. multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
  21. multioptpy/Calculator/tblite_calculation_tools.py +352 -0
  22. multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
  23. multioptpy/Constraint/__init__.py +0 -0
  24. multioptpy/Constraint/constraint_condition.py +834 -0
  25. multioptpy/Coordinate/__init__.py +0 -0
  26. multioptpy/Coordinate/polar_coordinate.py +199 -0
  27. multioptpy/Coordinate/redundant_coordinate.py +638 -0
  28. multioptpy/IRC/__init__.py +0 -0
  29. multioptpy/IRC/converge_criteria.py +28 -0
  30. multioptpy/IRC/dvv.py +544 -0
  31. multioptpy/IRC/euler.py +439 -0
  32. multioptpy/IRC/hpc.py +564 -0
  33. multioptpy/IRC/lqa.py +540 -0
  34. multioptpy/IRC/modekill.py +662 -0
  35. multioptpy/IRC/rk4.py +579 -0
  36. multioptpy/Interpolation/__init__.py +0 -0
  37. multioptpy/Interpolation/adaptive_interpolation.py +283 -0
  38. multioptpy/Interpolation/binomial_interpolation.py +179 -0
  39. multioptpy/Interpolation/geodesic_interpolation.py +785 -0
  40. multioptpy/Interpolation/interpolation.py +156 -0
  41. multioptpy/Interpolation/linear_interpolation.py +473 -0
  42. multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
  43. multioptpy/Interpolation/spline_interpolation.py +353 -0
  44. multioptpy/MD/__init__.py +0 -0
  45. multioptpy/MD/thermostat.py +185 -0
  46. multioptpy/MEP/__init__.py +0 -0
  47. multioptpy/MEP/pathopt_bneb_force.py +443 -0
  48. multioptpy/MEP/pathopt_dmf_force.py +448 -0
  49. multioptpy/MEP/pathopt_dneb_force.py +130 -0
  50. multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
  51. multioptpy/MEP/pathopt_gpneb_force.py +512 -0
  52. multioptpy/MEP/pathopt_lup_force.py +113 -0
  53. multioptpy/MEP/pathopt_neb_force.py +225 -0
  54. multioptpy/MEP/pathopt_nesb_force.py +205 -0
  55. multioptpy/MEP/pathopt_om_force.py +153 -0
  56. multioptpy/MEP/pathopt_qsm_force.py +174 -0
  57. multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
  58. multioptpy/ModelFunction/__init__.py +7 -0
  59. multioptpy/ModelFunction/avoiding_model_function.py +29 -0
  60. multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
  61. multioptpy/ModelFunction/conical_model_function.py +26 -0
  62. multioptpy/ModelFunction/opt_meci.py +50 -0
  63. multioptpy/ModelFunction/opt_mesx.py +47 -0
  64. multioptpy/ModelFunction/opt_mesx_2.py +49 -0
  65. multioptpy/ModelFunction/seam_model_function.py +27 -0
  66. multioptpy/ModelHessian/__init__.py +0 -0
  67. multioptpy/ModelHessian/approx_hessian.py +147 -0
  68. multioptpy/ModelHessian/calc_params.py +227 -0
  69. multioptpy/ModelHessian/fischer.py +236 -0
  70. multioptpy/ModelHessian/fischerd3.py +360 -0
  71. multioptpy/ModelHessian/fischerd4.py +398 -0
  72. multioptpy/ModelHessian/gfn0xtb.py +633 -0
  73. multioptpy/ModelHessian/gfnff.py +709 -0
  74. multioptpy/ModelHessian/lindh.py +165 -0
  75. multioptpy/ModelHessian/lindh2007d2.py +707 -0
  76. multioptpy/ModelHessian/lindh2007d3.py +822 -0
  77. multioptpy/ModelHessian/lindh2007d4.py +1030 -0
  78. multioptpy/ModelHessian/morse.py +106 -0
  79. multioptpy/ModelHessian/schlegel.py +144 -0
  80. multioptpy/ModelHessian/schlegeld3.py +322 -0
  81. multioptpy/ModelHessian/schlegeld4.py +559 -0
  82. multioptpy/ModelHessian/shortrange.py +346 -0
  83. multioptpy/ModelHessian/swartd2.py +496 -0
  84. multioptpy/ModelHessian/swartd3.py +706 -0
  85. multioptpy/ModelHessian/swartd4.py +918 -0
  86. multioptpy/ModelHessian/tshess.py +40 -0
  87. multioptpy/Optimizer/QHAdam.py +61 -0
  88. multioptpy/Optimizer/__init__.py +0 -0
  89. multioptpy/Optimizer/abc_fire.py +83 -0
  90. multioptpy/Optimizer/adabelief.py +58 -0
  91. multioptpy/Optimizer/adabound.py +68 -0
  92. multioptpy/Optimizer/adadelta.py +65 -0
  93. multioptpy/Optimizer/adaderivative.py +56 -0
  94. multioptpy/Optimizer/adadiff.py +68 -0
  95. multioptpy/Optimizer/adafactor.py +70 -0
  96. multioptpy/Optimizer/adam.py +65 -0
  97. multioptpy/Optimizer/adamax.py +62 -0
  98. multioptpy/Optimizer/adamod.py +83 -0
  99. multioptpy/Optimizer/adamw.py +65 -0
  100. multioptpy/Optimizer/adiis.py +523 -0
  101. multioptpy/Optimizer/afire_neb.py +282 -0
  102. multioptpy/Optimizer/block_hessian_update.py +709 -0
  103. multioptpy/Optimizer/c2diis.py +491 -0
  104. multioptpy/Optimizer/component_wise_scaling.py +405 -0
  105. multioptpy/Optimizer/conjugate_gradient.py +82 -0
  106. multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
  107. multioptpy/Optimizer/coordinate_locking.py +405 -0
  108. multioptpy/Optimizer/dic_rsirfo.py +1015 -0
  109. multioptpy/Optimizer/ediis.py +417 -0
  110. multioptpy/Optimizer/eve.py +76 -0
  111. multioptpy/Optimizer/fastadabelief.py +61 -0
  112. multioptpy/Optimizer/fire.py +77 -0
  113. multioptpy/Optimizer/fire2.py +249 -0
  114. multioptpy/Optimizer/fire_neb.py +92 -0
  115. multioptpy/Optimizer/gan_step.py +486 -0
  116. multioptpy/Optimizer/gdiis.py +609 -0
  117. multioptpy/Optimizer/gediis.py +203 -0
  118. multioptpy/Optimizer/geodesic_step.py +433 -0
  119. multioptpy/Optimizer/gpmin.py +633 -0
  120. multioptpy/Optimizer/gpr_step.py +364 -0
  121. multioptpy/Optimizer/gradientdescent.py +78 -0
  122. multioptpy/Optimizer/gradientdescent_neb.py +52 -0
  123. multioptpy/Optimizer/hessian_update.py +433 -0
  124. multioptpy/Optimizer/hybrid_rfo.py +998 -0
  125. multioptpy/Optimizer/kdiis.py +625 -0
  126. multioptpy/Optimizer/lars.py +21 -0
  127. multioptpy/Optimizer/lbfgs.py +253 -0
  128. multioptpy/Optimizer/lbfgs_neb.py +355 -0
  129. multioptpy/Optimizer/linesearch.py +236 -0
  130. multioptpy/Optimizer/lookahead.py +40 -0
  131. multioptpy/Optimizer/nadam.py +64 -0
  132. multioptpy/Optimizer/newton.py +200 -0
  133. multioptpy/Optimizer/prodigy.py +70 -0
  134. multioptpy/Optimizer/purtubation.py +16 -0
  135. multioptpy/Optimizer/quickmin_neb.py +245 -0
  136. multioptpy/Optimizer/radam.py +75 -0
  137. multioptpy/Optimizer/rfo_neb.py +302 -0
  138. multioptpy/Optimizer/ric_rfo.py +842 -0
  139. multioptpy/Optimizer/rl_step.py +627 -0
  140. multioptpy/Optimizer/rmspropgrave.py +65 -0
  141. multioptpy/Optimizer/rsirfo.py +1647 -0
  142. multioptpy/Optimizer/rsprfo.py +1056 -0
  143. multioptpy/Optimizer/sadam.py +60 -0
  144. multioptpy/Optimizer/samsgrad.py +63 -0
  145. multioptpy/Optimizer/tr_lbfgs.py +678 -0
  146. multioptpy/Optimizer/trim.py +273 -0
  147. multioptpy/Optimizer/trust_radius.py +207 -0
  148. multioptpy/Optimizer/trust_radius_neb.py +121 -0
  149. multioptpy/Optimizer/yogi.py +60 -0
  150. multioptpy/OtherMethod/__init__.py +0 -0
  151. multioptpy/OtherMethod/addf.py +1150 -0
  152. multioptpy/OtherMethod/dimer.py +895 -0
  153. multioptpy/OtherMethod/elastic_image_pair.py +629 -0
  154. multioptpy/OtherMethod/modelfunction.py +456 -0
  155. multioptpy/OtherMethod/newton_traj.py +454 -0
  156. multioptpy/OtherMethod/twopshs.py +1095 -0
  157. multioptpy/PESAnalyzer/__init__.py +0 -0
  158. multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
  159. multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
  160. multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
  161. multioptpy/PESAnalyzer/pca_analysis.py +314 -0
  162. multioptpy/Parameters/__init__.py +0 -0
  163. multioptpy/Parameters/atomic_mass.py +20 -0
  164. multioptpy/Parameters/atomic_number.py +22 -0
  165. multioptpy/Parameters/covalent_radii.py +44 -0
  166. multioptpy/Parameters/d2.py +61 -0
  167. multioptpy/Parameters/d3.py +63 -0
  168. multioptpy/Parameters/d4.py +103 -0
  169. multioptpy/Parameters/dreiding.py +34 -0
  170. multioptpy/Parameters/gfn0xtb_param.py +137 -0
  171. multioptpy/Parameters/gfnff_param.py +315 -0
  172. multioptpy/Parameters/gnb.py +104 -0
  173. multioptpy/Parameters/parameter.py +22 -0
  174. multioptpy/Parameters/uff.py +72 -0
  175. multioptpy/Parameters/unit_values.py +20 -0
  176. multioptpy/Potential/AFIR_potential.py +55 -0
  177. multioptpy/Potential/LJ_repulsive_potential.py +345 -0
  178. multioptpy/Potential/__init__.py +0 -0
  179. multioptpy/Potential/anharmonic_keep_potential.py +28 -0
  180. multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
  181. multioptpy/Potential/electrostatic_potential.py +69 -0
  182. multioptpy/Potential/flux_potential.py +30 -0
  183. multioptpy/Potential/gaussian_potential.py +101 -0
  184. multioptpy/Potential/idpp.py +516 -0
  185. multioptpy/Potential/keep_angle_potential.py +146 -0
  186. multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
  187. multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
  188. multioptpy/Potential/keep_potential.py +99 -0
  189. multioptpy/Potential/mechano_force_potential.py +74 -0
  190. multioptpy/Potential/nanoreactor_potential.py +52 -0
  191. multioptpy/Potential/potential.py +896 -0
  192. multioptpy/Potential/spacer_model_potential.py +221 -0
  193. multioptpy/Potential/switching_potential.py +258 -0
  194. multioptpy/Potential/universal_potential.py +34 -0
  195. multioptpy/Potential/value_range_potential.py +36 -0
  196. multioptpy/Potential/void_point_potential.py +25 -0
  197. multioptpy/SQM/__init__.py +0 -0
  198. multioptpy/SQM/sqm1/__init__.py +0 -0
  199. multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
  200. multioptpy/SQM/sqm2/__init__.py +0 -0
  201. multioptpy/SQM/sqm2/calc_tools.py +95 -0
  202. multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
  203. multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
  204. multioptpy/SQM/sqm2/sqm2_core.py +303 -0
  205. multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
  206. multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
  207. multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
  208. multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
  209. multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
  210. multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
  211. multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
  212. multioptpy/Thermo/__init__.py +0 -0
  213. multioptpy/Thermo/normal_mode_analyzer.py +865 -0
  214. multioptpy/Utils/__init__.py +0 -0
  215. multioptpy/Utils/bond_connectivity.py +264 -0
  216. multioptpy/Utils/calc_tools.py +884 -0
  217. multioptpy/Utils/oniom.py +96 -0
  218. multioptpy/Utils/pbc.py +48 -0
  219. multioptpy/Utils/riemann_curvature.py +208 -0
  220. multioptpy/Utils/symmetry_analyzer.py +482 -0
  221. multioptpy/Visualization/__init__.py +0 -0
  222. multioptpy/Visualization/visualization.py +156 -0
  223. multioptpy/WFAnalyzer/MO_analysis.py +104 -0
  224. multioptpy/WFAnalyzer/__init__.py +0 -0
  225. multioptpy/Wrapper/__init__.py +0 -0
  226. multioptpy/Wrapper/autots.py +1239 -0
  227. multioptpy/Wrapper/ieip_wrapper.py +93 -0
  228. multioptpy/Wrapper/md_wrapper.py +92 -0
  229. multioptpy/Wrapper/neb_wrapper.py +94 -0
  230. multioptpy/Wrapper/optimize_wrapper.py +76 -0
  231. multioptpy/__init__.py +5 -0
  232. multioptpy/entrypoints.py +916 -0
  233. multioptpy/fileio.py +660 -0
  234. multioptpy/ieip.py +340 -0
  235. multioptpy/interface.py +1086 -0
  236. multioptpy/irc.py +529 -0
  237. multioptpy/moleculardynamics.py +432 -0
  238. multioptpy/neb.py +1267 -0
  239. multioptpy/optimization.py +1553 -0
  240. multioptpy/optimizer.py +709 -0
  241. multioptpy-1.20.2.dist-info/METADATA +438 -0
  242. multioptpy-1.20.2.dist-info/RECORD +246 -0
  243. multioptpy-1.20.2.dist-info/WHEEL +5 -0
  244. multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
  245. multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
  246. multioptpy-1.20.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,842 @@
1
+ import numpy as np
2
+ from scipy import linalg
3
+ from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib
4
+ from .hessian_update import ModelHessianUpdate
5
+
6
+ """
7
+ Redundant Internal Coordinate RFO implementation
8
+ Implementation without dihedral angles in primitive coordinates
9
+ """
10
+
11
+ class RedundantInternalRFO:
12
+ def __init__(self, **config):
13
+ self.config = config
14
+ self.hess_update = ModelHessianUpdate()
15
+
16
+ # Initialize RIC specific parameters
17
+ self.Initialization = True
18
+ self.saddle_order = self.config.get("saddle_order", 0)
19
+ self.trust_radius = float(self.config.get("trust_radius", 0.1))
20
+ self.trust_radius /= UnitValueLib().bohr2angstroms
21
+ self.DELTA = 0.1 # Step scale factor
22
+
23
+ # For internal coordinate handling
24
+ self.B_matrix = None # Wilson B matrix (transformation matrix)
25
+ self.G_matrix = None # G matrix (metric matrix)
26
+ self.primitive_coords = None
27
+
28
+ # RIC specific parameters
29
+ self.redundant_thresh = 1e-5 # Threshold for numerical stability
30
+ self.coord_type = self.config.get("coord_type", "redundant")
31
+ self.iter = 0
32
+ self.max_backsteps = 200
33
+ self.max_micro_cycles = 1000
34
+ self.FC_COUNT = self.config.get("FC_COUNT", -1)
35
+ self.backconv_method = self.config.get("backconv_method", "scf").lower()
36
+
37
+ # Tracking variables
38
+ self.prev_cartesian = None
39
+ self.prev_gradient = None
40
+ self.internal_hessian = None
41
+ self.hessian = None
42
+ self.bias_hessian = None
43
+ self.prev_connectivity = None
44
+
45
+ self.element_list = self.config.get("element_list", None)
46
+
47
+ def define_internal_coordinates(self, geometry):
48
+ """
49
+ Define primitive internal coordinates based on molecular connectivity
50
+ Returns the connectivity table and primitive internal coordinates
51
+ Using only bonds and angles (no dihedrals as requested)
52
+ """
53
+ atomic_numbers = self.element_list
54
+ # Convert geometry from (3N, 1) to (N, 3) format
55
+ natoms = len(atomic_numbers)
56
+ geom_reshaped = geometry.reshape(natoms, 3)
57
+
58
+ primitive_coords = []
59
+ connectivity = np.zeros((natoms, natoms), dtype=int)
60
+
61
+ # Build connectivity based on covalent radii
62
+ for i in range(natoms):
63
+ for j in range(i+1, natoms):
64
+ # Calculate distance between atoms
65
+ dist = np.linalg.norm(geom_reshaped[i] - geom_reshaped[j])
66
+
67
+ # Get covalent radii based on atomic numbers
68
+ r_i = covalent_radii_lib(atomic_numbers[i])
69
+ r_j = covalent_radii_lib(atomic_numbers[j])
70
+
71
+ # Check if atoms are bonded (using a bond tolerance factor of 1.2)
72
+ if dist < 1.2 * (r_i + r_j):
73
+ connectivity[i, j] = 1
74
+ connectivity[j, i] = 1
75
+
76
+ # Add bond as primitive coordinate
77
+ primitive_coords.append({
78
+ 'type': 'bond',
79
+ 'atoms': [i, j]
80
+ })
81
+
82
+ # Add angle coordinates
83
+ for j in range(natoms):
84
+ bonded_to_j = np.where(connectivity[j] > 0)[0]
85
+ for i in bonded_to_j:
86
+ for k in bonded_to_j:
87
+ if i < k: # Avoid duplicates
88
+ primitive_coords.append({
89
+ 'type': 'angle',
90
+ 'atoms': [i, j, k]
91
+ })
92
+
93
+ # Note: As requested, no dihedral angles are added
94
+
95
+ self.primitive_coords = primitive_coords
96
+ return connectivity, primitive_coords
97
+
98
+
99
+
100
+ def calc_center(self, geometry):
101
+ """Calculate center of mass of the geometry"""
102
+ n_atoms = len(geometry) // 3
103
+ return np.mean(geometry.reshape(n_atoms, 3), axis=0)
104
+
105
+ def build_B_matrix(self, geometry, primitive_coords=None):
106
+ """
107
+ Build Wilson B matrix (∂q/∂x) for coordinate transformation
108
+ q: internal coordinates
109
+ x: Cartesian coordinates
110
+
111
+ The geometry is expected in (3N, 1) format
112
+ """
113
+ if primitive_coords is None:
114
+ primitive_coords = self.primitive_coords
115
+
116
+ natoms = len(geometry) // 3
117
+ geom_reshaped = geometry.reshape(natoms, 3)
118
+ ncoords = len(primitive_coords)
119
+
120
+ B = np.zeros((ncoords, 3*natoms))
121
+
122
+ for i, coord in enumerate(primitive_coords):
123
+ if coord['type'] == 'bond':
124
+ a1, a2 = coord['atoms']
125
+ B[i] = self._bond_B_elements(geom_reshaped, a1, a2)
126
+ elif coord['type'] == 'angle':
127
+ a1, a2, a3 = coord['atoms']
128
+ B[i] = self._angle_B_elements(geom_reshaped, a1, a2, a3)
129
+
130
+ return B
131
+
132
+ def _bond_B_elements(self, geometry, a1, a2):
133
+ """Calculate B matrix elements for bond stretching"""
134
+ r1 = geometry[a1]
135
+ r2 = geometry[a2]
136
+
137
+ # Vector pointing from atom a1 to atom a2
138
+ vec = r2 - r1
139
+
140
+ # Distance between the atoms
141
+ distance = np.linalg.norm(vec)
142
+
143
+ if distance < 1e-10:
144
+ unit_vec = np.zeros(3)
145
+ else:
146
+ unit_vec = vec / distance
147
+
148
+ # B matrix elements (derivatives of the bond distance w.r.t. Cartesian coordinates)
149
+ B_elements = np.zeros(3 * len(geometry))
150
+
151
+ # For atom a1
152
+ B_elements[3*a1:3*a1+3] = -unit_vec
153
+
154
+ # For atom a2
155
+ B_elements[3*a2:3*a2+3] = unit_vec
156
+
157
+ return B_elements
158
+
159
+ def _angle_B_elements(self, geometry, a1, a2, a3):
160
+ """Calculate B matrix elements for angle bending"""
161
+ r1 = geometry[a1]
162
+ r2 = geometry[a2]
163
+ r3 = geometry[a3]
164
+
165
+ # Vectors from central atom to the other two atoms
166
+ v1 = r1 - r2
167
+ v3 = r3 - r2
168
+
169
+ # Normalize vectors
170
+ d1 = np.linalg.norm(v1)
171
+ d3 = np.linalg.norm(v3)
172
+
173
+ if d1 < 1e-10 or d3 < 1e-10:
174
+ return np.zeros(3 * len(geometry))
175
+
176
+ v1_normalized = v1 / d1
177
+ v3_normalized = v3 / d3
178
+
179
+ # Cosine of the angle
180
+ cos_angle = np.dot(v1_normalized, v3_normalized)
181
+
182
+ # Ensure numerical stability
183
+ if cos_angle > 1.0:
184
+ cos_angle = 1.0
185
+ elif cos_angle < -1.0:
186
+ cos_angle = -1.0
187
+
188
+ sin_angle = np.sqrt(1.0 - cos_angle**2)
189
+
190
+ if sin_angle < 1e-10:
191
+ return np.zeros(3 * len(geometry))
192
+
193
+ # Calculate derivatives
194
+ term1 = 1.0 / (d1 * sin_angle)
195
+ term3 = 1.0 / (d3 * sin_angle)
196
+
197
+ # Cross products
198
+ cp1 = np.cross(v1_normalized, v3_normalized)
199
+ cp3 = np.cross(v3_normalized, v1_normalized)
200
+
201
+ # B matrix elements
202
+ B_elements = np.zeros(3 * len(geometry))
203
+
204
+ # For atom a1
205
+ B_elements[3*a1:3*a1+3] = term1 * cp1
206
+
207
+ # For atom a3
208
+ B_elements[3*a3:3*a3+3] = term3 * cp3
209
+
210
+ # For central atom a2
211
+ B_elements[3*a2:3*a2+3] = -(B_elements[3*a1:3*a1+3] + B_elements[3*a3:3*a3+3])
212
+
213
+ return B_elements
214
+
215
+ def build_G_matrix(self, B_matrix):
216
+ """
217
+ Build G matrix (metric matrix) from Wilson B matrix
218
+ G = B·B^T
219
+ """
220
+ G = np.dot(B_matrix, B_matrix.T)
221
+ return G
222
+
223
+ def check_connectivity_change(self, geometry):
224
+ """
225
+ Check if the molecular connectivity has changed significantly
226
+ """
227
+ if self.prev_connectivity is None:
228
+ # First call, just store the current connectivity
229
+ connectivity, _ = self.define_internal_coordinates(geometry)
230
+ self.prev_connectivity = connectivity
231
+ return False
232
+
233
+ # Check current connectivity
234
+ current_connectivity, _ = self.define_internal_coordinates(geometry)
235
+
236
+ # Compare with previous connectivity matrix
237
+ diff = np.sum(np.abs(current_connectivity - self.prev_connectivity))
238
+
239
+ # If there are changes in connectivity, reset coordinate system
240
+ if diff > 0:
241
+ print(f"WARNING: Detected {diff} changes in molecular connectivity")
242
+ print("Resetting internal coordinates due to bond rearrangement")
243
+ self.prev_connectivity = current_connectivity
244
+ return True
245
+
246
+ return False
247
+
248
+ def update_coordinates(self, geometry):
249
+ """
250
+ Update the internal coordinates for the current geometry
251
+ """
252
+ # Build B matrix for current geometry
253
+ B = self.build_B_matrix(geometry)
254
+ self.B_matrix = B
255
+
256
+ # Build G matrix
257
+ G = self.build_G_matrix(B)
258
+ self.G_matrix = G
259
+
260
+ return B
261
+
262
+ def transform_gradient(self, cart_gradient):
263
+ """
264
+ Transform Cartesian gradient to internal coordinate gradient
265
+ g_int = B·g_cart (direct transformation using Wilson B matrix)
266
+ """
267
+ B = self.B_matrix
268
+
269
+ # Direct transformation using B matrix
270
+ int_gradient = np.dot(B, cart_gradient)
271
+ return int_gradient
272
+
273
+ def transform_hessian(self, cart_hessian):
274
+ """
275
+ Transform Cartesian Hessian to internal coordinate Hessian
276
+ H_int = B·H_cart·B^T (direct transformation)
277
+ """
278
+ B = self.B_matrix
279
+
280
+ # Direct transformation
281
+ int_hessian = np.dot(B, np.dot(cart_hessian, B.T))
282
+ return int_hessian
283
+
284
+ def internal_to_cartesian_scf(self, step_int, geometry,
285
+ max_iterations=100,
286
+ convergence_threshold=1e-8):
287
+ """
288
+ SCF-inspired algorithm for internal to Cartesian coordinate conversion.
289
+ Uses techniques from electronic structure theory: line search when far from
290
+ convergence and Newton-Raphson near convergence, with DIIS acceleration.
291
+
292
+ Parameters:
293
+ -----------
294
+ step_int : ndarray
295
+ Target step in internal coordinates
296
+ geometry : ndarray
297
+ Starting geometry in Cartesian coordinates
298
+ max_iterations : int
299
+ Maximum number of iterations
300
+ convergence_threshold : float
301
+ Convergence criterion for RMS error
302
+
303
+ Returns:
304
+ --------
305
+ ndarray
306
+ Step in Cartesian coordinates
307
+ """
308
+ # Initialize variables
309
+ reference_geom = geometry.copy()
310
+ current_geom = geometry.copy()
311
+ best_geom = current_geom.copy()
312
+ best_error = float('inf')
313
+
314
+ # DIIS parameters
315
+ diis_start = 3
316
+ diis_max = 8
317
+ diis_errors = []
318
+ diis_geometries = []
319
+
320
+ # Dynamic algorithm control
321
+ use_newton = False # Start with line search, switch to Newton later
322
+ damping_factor = 0.7 # Initial damping
323
+ level_shift = 0.0 # Level shifting parameter
324
+
325
+ # Last iteration data for backtracks
326
+ prev_error = float('inf')
327
+ prev_geom = None
328
+
329
+ # Calculate initial values
330
+ B = self.build_B_matrix(current_geom)
331
+ current_q = np.dot(B, current_geom - reference_geom)
332
+ delta_q = step_int - current_q
333
+ error = np.linalg.norm(delta_q)
334
+ rms_error = error / np.sqrt(len(delta_q))
335
+
336
+ print(f"Starting SCF-like optimization: initial RMS error = {rms_error:.3e}")
337
+
338
+ # Main iteration loop
339
+ for iteration in range(max_iterations):
340
+ # Save best solution
341
+ if error < best_error:
342
+ best_error = error
343
+ best_geom = current_geom.copy()
344
+
345
+ # Report progress
346
+ if iteration % 20 == 0 or iteration < 2:
347
+ print(f"Iteration {iteration}: RMS error = {rms_error:.3e}, "
348
+ f"{'Newton' if use_newton else 'LineSearch'}, damping={damping_factor:.2f}")
349
+
350
+ # Check convergence
351
+ if rms_error < convergence_threshold:
352
+ print(f"Conversion converged in {iteration+1} iterations")
353
+ break
354
+
355
+ # Prepare for this iteration
356
+ B = self.build_B_matrix(current_geom)
357
+
358
+ # Calculate step based on current method
359
+ if use_newton:
360
+ # Calculate B+ (pseudoinverse of B) using SVD with level shifting
361
+ try:
362
+ U, s, Vh = np.linalg.svd(B, full_matrices=False)
363
+
364
+ # Apply level shifting to singular values (similar to SCF level shifting)
365
+ s_inv = np.where(s > 1e-7, 1.0 / (s + level_shift), 0.0)
366
+
367
+ B_pinv = np.dot(Vh.T * s_inv, U.T)
368
+
369
+ # Calculate Newton step
370
+ step = np.dot(B_pinv, delta_q)
371
+ except np.linalg.LinAlgError:
372
+ # Fall back to a more stable approach
373
+ print("SVD failed, using pinv with increased regularization")
374
+ B_pinv = np.linalg.pinv(B, rcond=1e-6)
375
+ step = np.dot(B_pinv, delta_q)
376
+ else:
377
+ # Line search along steepest descent direction
378
+ gradient = np.dot(B.T, delta_q)
379
+ grad_norm = np.linalg.norm(gradient)
380
+
381
+ if grad_norm < 1e-10:
382
+ # Gradient too small, try Newton step
383
+ use_newton = True
384
+ B_pinv = np.linalg.pinv(B, rcond=1e-7)
385
+ step = np.dot(B_pinv, delta_q)
386
+ else:
387
+ # Normalize gradient and scale by dynamic step size
388
+ step_dir = gradient / grad_norm
389
+
390
+ # Determine step size (larger when further from convergence)
391
+ # Similar to trust radius in SCF
392
+ step_size = min(0.2, 0.1 * (1.0 + 10.0 * rms_error))
393
+
394
+ step = step_size * step_dir
395
+
396
+ # Apply DIIS acceleration if we have enough iterations
397
+ diis_step = None
398
+ if iteration >= diis_start:
399
+ # Store current error and geometry for DIIS
400
+ diis_errors.append(delta_q.flatten())
401
+ diis_geometries.append(current_geom.copy())
402
+
403
+ # Limit DIIS vector storage
404
+ if len(diis_errors) > diis_max:
405
+ diis_errors.pop(0)
406
+ diis_geometries.pop(0)
407
+
408
+ # Apply DIIS if we have at least 2 vectors
409
+ if len(diis_errors) >= 2:
410
+ try:
411
+ diis_step = self._compute_diis_solution(diis_errors, diis_geometries)
412
+ except Exception as e:
413
+ print(f"DIIS failed: {e}")
414
+ diis_step = None
415
+
416
+ # Try step with current damping
417
+ damped_step = step * damping_factor
418
+ trial_geom = current_geom + damped_step
419
+
420
+ # Evaluate trial geometry
421
+ trial_B = self.build_B_matrix(trial_geom)
422
+ trial_q = np.dot(trial_B, trial_geom - reference_geom)
423
+ trial_delta_q = step_int - trial_q
424
+ trial_error = np.linalg.norm(trial_delta_q)
425
+ trial_rms = trial_error / np.sqrt(len(trial_delta_q))
426
+
427
+ # If we have a DIIS solution, evaluate it too
428
+ if diis_step is not None:
429
+ diis_B = self.build_B_matrix(diis_step)
430
+ diis_q = np.dot(diis_B, diis_step - reference_geom)
431
+ diis_delta_q = step_int - diis_q
432
+ diis_error = np.linalg.norm(diis_delta_q)
433
+ diis_rms = diis_error / np.sqrt(len(diis_delta_q))
434
+
435
+ # Use DIIS if it's better
436
+ if diis_error < trial_error and diis_error < error:
437
+ #print(f"DIIS improvement: {trial_rms:.3e} -> {diis_rms:.3e}")
438
+ current_geom = diis_step
439
+ delta_q = diis_delta_q
440
+ error = diis_error
441
+ rms_error = diis_rms
442
+
443
+ # DIIS was successful, try to reduce level shifting
444
+ if level_shift > 0:
445
+ level_shift = max(0, level_shift * 0.5)
446
+ continue # Skip to next iteration
447
+
448
+ # Dynamically adjust the algorithm based on progress
449
+ if trial_error < error:
450
+ # Step is good, accept it
451
+ current_geom = trial_geom
452
+ delta_q = trial_delta_q
453
+ prev_error = error
454
+ error = trial_error
455
+ rms_error = trial_rms
456
+
457
+ # Increase damping for more aggressive steps
458
+ damping_factor = min(1.0, damping_factor * 1.2)
459
+
460
+ # Switch to Newton method when close enough to solution
461
+ if not use_newton and rms_error < 0.1:
462
+ #print(f"Switching to Newton-Raphson at iteration {iteration+1}")
463
+ use_newton = True
464
+
465
+ # Reduce level shifting since things are going well
466
+ if level_shift > 0:
467
+ level_shift = max(0, level_shift * 0.5)
468
+
469
+ else:
470
+ # Step is bad, adjust strategy
471
+ if use_newton:
472
+ # Newton step made things worse
473
+ if level_shift == 0:
474
+ # Start with modest level shifting
475
+ level_shift = 0.1
476
+ else:
477
+ # Increase level shifting (similar to SCF when oscillating)
478
+ level_shift = min(1.0, level_shift * 2.0)
479
+
480
+ #print(f"Increasing level shift to {level_shift:.3e}")
481
+
482
+ # If level shift is getting too high, try line search
483
+ if level_shift > 0.5:
484
+ #print("Switching to line search due to unstable Newton steps")
485
+ use_newton = False
486
+
487
+ # Reduce damping for more conservative steps
488
+ damping_factor = max(0.2, damping_factor * 0.5)
489
+
490
+ # If we have previous good geometry, backtrack halfway
491
+ if prev_geom is not None:
492
+ #print(f"Backtracking: {error:.3e} -> {prev_error:.3e}")
493
+ current_geom = 0.5 * (current_geom + prev_geom)
494
+
495
+ # Recalculate at backtracked position
496
+ B = self.build_B_matrix(current_geom)
497
+ current_q = np.dot(B, current_geom - reference_geom)
498
+ delta_q = step_int - current_q
499
+ error = np.linalg.norm(delta_q)
500
+ rms_error = error / np.sqrt(len(delta_q))
501
+
502
+ # Save current position for potential backtracking
503
+ prev_geom = current_geom.copy()
504
+
505
+ # Handle non-convergence
506
+ if iteration == max_iterations - 1 and rms_error > convergence_threshold:
507
+ print(f"Warning: Conversion did not converge. Best RMS error = {best_error/np.sqrt(len(step_int)):.3e}")
508
+ current_geom = best_geom
509
+
510
+ # Return the Cartesian step
511
+ return current_geom - reference_geom
512
+
513
+ def _compute_diis_solution(self, error_vectors, geometries):
514
+ """
515
+ Compute DIIS extrapolated solution using Pulay's method
516
+
517
+ Parameters:
518
+ -----------
519
+ error_vectors : list
520
+ List of error vectors (flattened)
521
+ geometries : list
522
+ List of corresponding geometries
523
+
524
+ Returns:
525
+ --------
526
+ ndarray
527
+ DIIS extrapolated geometry
528
+ """
529
+ n_vecs = len(error_vectors)
530
+
531
+ # Build DIIS B matrix for Pulay method
532
+ B_diis = np.zeros((n_vecs + 1, n_vecs + 1))
533
+
534
+ # Fill error vector dot products
535
+ for i in range(n_vecs):
536
+ for j in range(n_vecs):
537
+ B_diis[i, j] = np.dot(error_vectors[i], error_vectors[j])
538
+
539
+ # Add constraint rows/columns
540
+ B_diis[n_vecs, :n_vecs] = 1.0
541
+ B_diis[:n_vecs, n_vecs] = 1.0
542
+ B_diis[n_vecs, n_vecs] = 0.0
543
+
544
+ # RHS vector [0,0,...,0,1]
545
+ rhs = np.zeros(n_vecs + 1)
546
+ rhs[n_vecs] = 1.0
547
+
548
+ # Add small regularization to diagonal for numerical stability
549
+ for i in range(n_vecs):
550
+ B_diis[i, i] += 1e-8 * (1.0 + abs(B_diis[i, i]))
551
+
552
+ # Solve DIIS equations
553
+ try:
554
+ c = np.linalg.solve(B_diis, rhs)
555
+ except np.linalg.LinAlgError:
556
+ # Use SVD-based solution if direct solve fails
557
+ c = np.linalg.lstsq(B_diis, rhs, rcond=1e-10)[0]
558
+
559
+ # Construct DIIS solution
560
+ diis_geom = np.zeros_like(geometries[0])
561
+ for i in range(n_vecs):
562
+ diis_geom += c[i] * geometries[i]
563
+
564
+ return diis_geom
565
+
566
+
567
+ def _evaluate_error(self, geom, reference_geom, target_step):
568
+ """
569
+ Helper function to evaluate the error for a given geometry
570
+ """
571
+ B = self.build_B_matrix(geom)
572
+ current_q = np.dot(B, geom - reference_geom)
573
+ delta_q = target_step - current_q
574
+ return np.linalg.norm(delta_q)
575
+
576
+
577
+ def internal_to_cartesian(self, step_int, geometry):
578
+ """
579
+ Transform step in internal coordinates to Cartesian coordinates
580
+ using enhanced iterative methods
581
+ """
582
+ # Choose the algorithm based on configuration or problem characteristics
583
+
584
+ if self.backconv_method == "scf":
585
+ return self.internal_to_cartesian_scf(step_int, geometry)
586
+ else:
587
+ return self.internal_to_cartesian_scf(step_int, geometry)
588
+
589
+ def get_cleaned_hessian(self, hessian):
590
+ """Ensure the Hessian is clean and well-conditioned"""
591
+ # Ensure symmetry
592
+ hessian = 0.5 * (hessian + hessian.T)
593
+
594
+ try:
595
+ # Use more stable eigenvalue decomposition
596
+ eigval, eigvec = linalg.eigh(hessian)
597
+ except np.linalg.LinAlgError:
598
+ # Fallback to more robust algorithm if standard fails
599
+ print("Warning: Using more robust eigenvalue decomposition")
600
+ eigval, eigvec = linalg.eigh(hessian, driver='evr')
601
+
602
+ # Find valid eigenvalues (|λ| > 1e-7)
603
+ valid_mask = np.abs(eigval) > 1e-7
604
+ n_removed = np.sum(~valid_mask)
605
+
606
+ # Create diagonal matrix with only valid eigenvalues
607
+ # Replace small eigenvalues with small positive values
608
+ cleaned_eigval = np.where(valid_mask, eigval, 1e-7)
609
+
610
+ # Reconstruct Hessian using only valid components
611
+ # H = U Λ U^T where Λ contains only valid eigenvalues
612
+ cleaned_hessian = np.dot(np.dot(eigvec, np.diag(cleaned_eigval)), eigvec.T)
613
+
614
+ # Ensure symmetry of final result
615
+ cleaned_hessian = 0.5 * (cleaned_hessian + cleaned_hessian.T)
616
+
617
+ return cleaned_hessian, n_removed
618
+
619
+ def run_rfo_step(self, int_gradient, int_hessian):
620
+ """
621
+ Calculate the RFO step in internal coordinates with improved stability
622
+ """
623
+ n_coords = len(int_gradient)
624
+
625
+ # Ensure symmetry and clean the Hessian
626
+ new_hess = 0.5 * (int_hessian + int_hessian.T)
627
+ new_hess, _ = self.get_cleaned_hessian(new_hess)
628
+
629
+ # Construct RFO matrix
630
+ matrix_for_RFO = np.block([
631
+ [new_hess, int_gradient.reshape(n_coords, 1)],
632
+ [int_gradient.reshape(1, n_coords), np.zeros((1, 1))]
633
+ ])
634
+
635
+ # Get eigenvalues of the RFO matrix
636
+ try:
637
+ RFO_eigenvalues, RFO_eigenvectors = linalg.eigh(matrix_for_RFO)
638
+ except np.linalg.LinAlgError:
639
+ print("Warning: Using more robust eigenvalue algorithm")
640
+ RFO_eigenvalues, RFO_eigenvectors = linalg.eigh(matrix_for_RFO, driver='evr')
641
+
642
+ # Sort eigenvalues
643
+ idx = np.argsort(RFO_eigenvalues)
644
+ RFO_eigenvalues = RFO_eigenvalues[idx]
645
+ RFO_eigenvectors = RFO_eigenvectors[:, idx]
646
+
647
+ # Select appropriate eigenvalue based on saddle order
648
+ lambda_for_calc = float(RFO_eigenvalues[self.saddle_order])
649
+
650
+ # Calculate step using direct RFO approach
651
+ shifted_hessian = new_hess - lambda_for_calc * np.eye(n_coords)
652
+
653
+ # Solve the RFO equations
654
+ try:
655
+ # LU decomposition for stable solving
656
+ move_vector = -np.linalg.solve(shifted_hessian, int_gradient)
657
+ except np.linalg.LinAlgError:
658
+ print("Warning: Linear solve failed, using pseudoinverse")
659
+ # Use pseudoinverse as fallback
660
+ shifted_hessian_inv = np.linalg.pinv(shifted_hessian, rcond=1e-10)
661
+ move_vector = -np.dot(shifted_hessian_inv, int_gradient)
662
+
663
+ print(f"Lambda for RFO step: {lambda_for_calc}")
664
+ print(f"Gradient RMS: {np.sqrt(np.mean(int_gradient**2))}")
665
+ print(f"Step RMS: {np.sqrt(np.mean(move_vector**2))}")
666
+
667
+ # Limit step size if it exceeds trust radius
668
+ step_norm = np.linalg.norm(move_vector)
669
+ if step_norm > self.trust_radius:
670
+ scale_factor = self.trust_radius / step_norm
671
+ move_vector *= scale_factor
672
+ print(f"Step scaled by {scale_factor} to meet trust radius")
673
+
674
+ return move_vector.reshape(-1, 1)
675
+
676
+ def reset_system(self, geometry):
677
+ """
678
+ Reset internal coordinates when molecular structure changes significantly
679
+ """
680
+ print("Resetting redundant internal coordinate system")
681
+
682
+ # Clear previous coordinates
683
+ self.primitive_coords = None
684
+
685
+ # Define new internal coordinates
686
+ _, self.primitive_coords = self.define_internal_coordinates(geometry)
687
+ print(f"Defined {len(self.primitive_coords)} new primitive coordinates")
688
+
689
+ # Update B matrix
690
+ B = self.update_coordinates(geometry)
691
+
692
+ # Reset Hessian to identity
693
+ self.internal_hessian = np.eye(len(self.primitive_coords))
694
+ self.Initialization = False
695
+
696
+ return B
697
+
698
+ def run(self, geom_num_list, B_g, pre_B_g, pre_geom, B_e, pre_B_e, pre_move_vector, initial_geom_num_list, g, pre_g):
699
+ """
700
+ Main optimization step function using Redundant Internal Coordinates
701
+ """
702
+ print(f"======= RIC-RFO Iteration {self.iter} =======")
703
+
704
+ # Define internal coordinates if not already defined
705
+ if self.primitive_coords is None:
706
+ _, self.primitive_coords = self.define_internal_coordinates(geom_num_list)
707
+ print(f"Defined {len(self.primitive_coords)} primitive internal coordinates")
708
+
709
+ # Check if molecular connectivity has changed
710
+ coords_reset = False
711
+ if pre_geom is not None:
712
+ coords_reset = self.check_connectivity_change(geom_num_list)
713
+
714
+ if coords_reset:
715
+ # Reset coordinate system if connectivity changed
716
+ B = self.reset_system(geom_num_list)
717
+ else:
718
+ # Update coordinate system for current geometry
719
+ B = self.update_coordinates(geom_num_list)
720
+
721
+ # Transform gradient to internal coordinates
722
+ int_gradient = self.transform_gradient(B_g)
723
+
724
+ # Initialize or update the Hessian
725
+ if self.Initialization or self.internal_hessian is None:
726
+ # Start with identity Hessian in internal coordinates
727
+ self.internal_hessian = np.eye(len(self.primitive_coords))
728
+ self.Initialization = False
729
+ else:
730
+ # Update the internal coordinate Hessian if we have previous geometry and gradient
731
+ if pre_geom is not None and pre_B_g is not None and not coords_reset:
732
+ # Calculate previous internal gradient
733
+ prev_B = self.build_B_matrix(pre_geom)
734
+ prev_G = self.build_G_matrix(prev_B)
735
+
736
+ try:
737
+ U, s, Vh = np.linalg.svd(prev_G, full_matrices=False)
738
+ s_inv = np.array([1.0/x if x > self.redundant_thresh else 0.0 for x in s])
739
+ G_inv = np.dot(U * s_inv[:, np.newaxis], U.T)
740
+
741
+ prev_B_pinv = np.dot(G_inv, prev_B)
742
+ except np.linalg.LinAlgError:
743
+ prev_B_pinv = np.linalg.pinv(prev_B, rcond=1e-8)
744
+
745
+ prev_int_gradient = np.dot(prev_B_pinv, pre_B_g)
746
+
747
+ # Calculate displacement in internal coordinates
748
+ cart_displacement = (geom_num_list - pre_geom).reshape(-1, 1)
749
+ int_displacement = np.dot(B, cart_displacement)
750
+
751
+ # Delta gradient in internal coordinates
752
+ delta_grad = (int_gradient - prev_int_gradient).reshape(-1, 1)
753
+
754
+ # Update Hessian using the hess_update methods
755
+ if "msp" in self.config.get("method", "").lower():
756
+ print("RIC-RFO: Using MSP Hessian update")
757
+ delta_hess = self.hess_update.MSP_hessian_update(
758
+ self.internal_hessian, int_displacement, delta_grad
759
+ )
760
+ elif "bfgs" in self.config.get("method", "").lower():
761
+ print("RIC-RFO: Using BFGS Hessian update")
762
+ delta_hess = self.hess_update.BFGS_hessian_update(
763
+ self.internal_hessian, int_displacement, delta_grad
764
+ )
765
+ elif "fsb" in self.config.get("method", "").lower():
766
+ print("RIC-RFO: Using FSB Hessian update")
767
+ delta_hess = self.hess_update.FSB_hessian_update(
768
+ self.internal_hessian, int_displacement, delta_grad
769
+ )
770
+ elif "bofill" in self.config.get("method", "").lower():
771
+ print("RIC-RFO: Using Bofill Hessian update")
772
+ delta_hess = self.hess_update.Bofill_hessian_update(
773
+ self.internal_hessian, int_displacement, delta_grad
774
+ )
775
+ elif "sr1" in self.config.get("method", "").lower():
776
+ print("RIC-RFO: Using SR1 Hessian update")
777
+ delta_hess = self.hess_update.SR1_hessian_update(
778
+ self.internal_hessian, int_displacement, delta_grad
779
+ )
780
+ elif "psb" in self.config.get("method", "").lower():
781
+ print("RIC-RFO: Using PSB Hessian update")
782
+ delta_hess = self.hess_update.PSB_hessian_update(
783
+ self.internal_hessian, int_displacement, delta_grad
784
+ )
785
+ elif "flowchart" in self.config.get("method", "").lower():
786
+ print("RIC-RFO: Using flowchart Hessian update")
787
+ delta_hess = self.hess_update.flowchart_hessian_update(
788
+ self.internal_hessian, int_displacement, delta_grad, self.config["method"]
789
+ )
790
+ else:
791
+ # Default to BFGS if no method is specified
792
+ print("RIC-RFO: Using BFGS Hessian update (default)")
793
+ delta_hess = self.hess_update.BFGS_hessian_update(
794
+ self.internal_hessian, int_displacement, delta_grad
795
+ )
796
+
797
+ # Apply the Hessian update
798
+ self.internal_hessian += delta_hess
799
+
800
+ # Ensure the Hessian is symmetric
801
+ self.internal_hessian = 0.5 * (self.internal_hessian + self.internal_hessian.T)
802
+
803
+ # Apply bias Hessian if provided
804
+ working_hessian = self.internal_hessian.copy()
805
+ if self.bias_hessian is not None:
806
+ int_bias_hessian = self.transform_hessian(self.bias_hessian)
807
+ working_hessian += int_bias_hessian
808
+
809
+ # Calculate RFO step in internal coordinates
810
+ int_step = self.run_rfo_step(int_gradient, working_hessian)
811
+
812
+ # Transform step back to Cartesian coordinates
813
+ cart_step = self.internal_to_cartesian(int_step, geom_num_list)
814
+
815
+ # Store current state for next iteration
816
+ self.prev_cartesian = geom_num_list.copy()
817
+ self.prev_gradient = B_g.copy()
818
+
819
+ # Increment iteration counter
820
+ self.iter += 1
821
+
822
+ # Apply DELTA scaling and reshape
823
+ return -1 * self.DELTA * cart_step.reshape(-1, 1)
824
+
825
+ def set_hessian(self, hessian):
826
+ """Set Cartesian Hessian"""
827
+ self.hessian = hessian.copy()
828
+ if self.B_matrix is not None:
829
+ # Transform to internal coordinates
830
+ self.internal_hessian = self.transform_hessian(hessian)
831
+
832
+ def set_bias_hessian(self, bias_hessian):
833
+ """Set bias Hessian (in Cartesian coordinates)"""
834
+ self.bias_hessian = bias_hessian.copy()
835
+
836
+ def get_hessian(self):
837
+ """Return current Hessian (in Cartesian coordinates)"""
838
+ return self.hessian if self.hessian is not None else None
839
+
840
+
841
+ def get_bias_hessian(self):
842
+ return self.bias_hessian