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
File without changes
@@ -0,0 +1,125 @@
1
+ import numpy as np
2
+ import os
3
+
4
+ def calc_unit_tangent_vector(gradient):
5
+ """
6
+ Calculate the unit tangent vector from the gradient of a path.
7
+
8
+ Parameters:
9
+ gradient (np.ndarray): A 1D array representing the gradient of the path.
10
+
11
+ Returns:
12
+ np.ndarray: A 1D array representing the unit tangent vector.
13
+ """
14
+ norm = np.linalg.norm(gradient)
15
+ if norm == 0:
16
+ raise ValueError("The gradient vector has zero magnitude; cannot compute unit tangent vector.")
17
+ unit_tangent_vector = gradient / norm
18
+ return unit_tangent_vector
19
+
20
+ def calc_curvature_vector(gradient, prev_gradient, step_size):
21
+ """
22
+ Calculate the curvature vector from the current and previous gradients of a path.
23
+
24
+ Parameters:
25
+ gradient (np.ndarray): A 1D array representing the current gradient of the path.
26
+ prev_gradient (np.ndarray): A 1D array representing the previous gradient of the path.
27
+ step_size (float): The step size between the current and previous points.
28
+
29
+ Returns:
30
+ np.ndarray: A 1D array representing the curvature vector.
31
+ """
32
+ if step_size <= 0:
33
+ raise ValueError("Step size must be a positive value.")
34
+
35
+ curvature_vector = (gradient - prev_gradient) / step_size
36
+ return curvature_vector
37
+
38
+
39
+ def calc_scalar_curvature(curvature_vector):
40
+ """
41
+ Calculate the scalar curvature from the current and previous gradients of a path.
42
+
43
+ Parameters:
44
+ gradient (np.ndarray): A 1D array representing the current gradient of the path.
45
+ prev_gradient (np.ndarray): A 1D array representing the previous gradient of the path.
46
+ step_size (float): The step size between the current and previous points.
47
+
48
+ Returns:
49
+ float: The scalar curvature.
50
+ """
51
+ scalar_curvature = np.linalg.norm(curvature_vector)
52
+ return scalar_curvature
53
+
54
+
55
+ def calc_curvature_coupling(curvature_vector, hessian):
56
+ """
57
+ Calculate the curvature coupling from the curvature vector and Hessian matrix.
58
+
59
+ Parameters:
60
+ curvature_vector (np.ndarray): A 1D array representing the curvature vector.
61
+ hessian (np.ndarray): A 2D array representing the Hessian matrix.
62
+
63
+ Returns:
64
+ float: The curvature coupling.
65
+ """
66
+ eigvals, eigvecs = np.linalg.eigh(hessian)
67
+
68
+ sorted_indices = np.argsort(eigvals)
69
+ eigvals = eigvals[sorted_indices]
70
+ eigvecs = eigvecs[:, sorted_indices]
71
+
72
+ curvature_vector = curvature_vector.reshape(-1, 1) # Ensure it's a column vector
73
+ print("Only considering positive eigenvalue modes for curvature coupling.")
74
+ # Mask out eigenvalues <= 1e-8 (positive eigenvalue components)
75
+ mask = eigvals > 1e-8
76
+ eigvecs_masked = eigvecs[:, mask]
77
+ curvature_coupling = np.dot(eigvecs_masked.T, curvature_vector)
78
+ return curvature_coupling
79
+
80
+ def calc_irc_curvature_properties(gradient, prev_gradient, hessian, step_size):
81
+ """
82
+ Calculate the unit tangent vector, curvature vector, scalar curvature, and curvature coupling
83
+ for a given point along a path.
84
+
85
+ Parameters:
86
+ gradient (np.ndarray): A 1D array representing the current gradient of the path.
87
+ prev_gradient (np.ndarray): A 1D array representing the previous gradient of the path.
88
+ hessian (np.ndarray): A 2D array representing the Hessian matrix at the current point.
89
+ step_size (float): The step size between the current and previous points.
90
+
91
+ Returns:
92
+ tuple: A tuple containing:
93
+ - unit_tangent_vector (np.ndarray): The unit tangent vector.
94
+ - curvature_vector (np.ndarray): The curvature vector.
95
+ - scalar_curvature (float): The scalar curvature.
96
+ - curvature_coupling (np.ndarray): The curvature coupling.
97
+ """
98
+ unit_tangent_vector = calc_unit_tangent_vector(gradient)
99
+ curvature_vector = calc_curvature_vector(gradient, prev_gradient, step_size)
100
+ scalar_curvature = calc_scalar_curvature(curvature_vector)
101
+ curvature_coupling = calc_curvature_coupling(curvature_vector, hessian)
102
+
103
+ return unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling
104
+
105
+
106
+ def save_curvature_properties_to_file(filename, scalar_curvature, curvature_coupling):
107
+ """
108
+ Append curvature properties to a CSV file.
109
+
110
+ Parameters:
111
+ filename (str): Path to the CSV file to append to.
112
+ scalar_curvature (float): The scalar curvature value.
113
+ curvature_coupling (np.ndarray): The curvature coupling vector/array.
114
+ """
115
+
116
+ if not os.path.isfile(filename):
117
+ with open(filename, 'w', encoding='utf-8') as f:
118
+ header = ["Scalar_Curvature"] + [f"Curvature_Coupling_{i+1}" for i in range(len(curvature_coupling))]
119
+ f.write(",".join(header) + "\n")
120
+
121
+ row = [f"{scalar_curvature:.6f}"]
122
+ row += [f"{val:.6f}" for val in np.asarray(curvature_coupling).ravel()]
123
+ line = ",".join(row) + "\n"
124
+ with open(filename, 'a', encoding='utf-8') as f:
125
+ f.write(line)
@@ -0,0 +1,152 @@
1
+ import glob
2
+ import itertools
3
+ import matplotlib.pyplot as plt
4
+
5
+ import numpy as np
6
+
7
+
8
+ from multioptpy.Utils.calc_tools import Calculationtools #kabsch_algorithm
9
+ from multioptpy.Parameters.parameter import atomic_mass, UnitValueLib
10
+
11
+ #ref. Chem. Commun. 2021, 57 (89), 11734–11750.
12
+ #https://doi.org/10.1039/D1CC04667E
13
+ #CMDS = classical multidimensional scaling
14
+
15
+ class CMDSPathAnalysis:
16
+ def __init__(self, directory, energy_list, bias_energy_list):
17
+ self.directory = directory
18
+ energy_list = np.array(energy_list)
19
+ if len(energy_list) < 3:
20
+ self.energy_list = None
21
+ self.bias_energy_list = None
22
+ return
23
+ self.energy_list = energy_list[1:] - energy_list[1:][0]
24
+ bias_energy_list = np.array(bias_energy_list)
25
+ self.bias_energy_list = bias_energy_list[1:] - bias_energy_list[1:][0]
26
+ return
27
+
28
+ def read_xyz_file(self, struct_path_1, struct_path_2):
29
+
30
+ with open(struct_path_1, "r") as f:
31
+ words_1 = f.read().splitlines()
32
+
33
+ with open(struct_path_2, "r") as f:
34
+ words_2 = f.read().splitlines()
35
+
36
+ mass_weight_coord_1 = []
37
+
38
+ for word in words_1:
39
+ splited_word = word.split()
40
+ if len(splited_word) != 4:
41
+ continue
42
+ tmp = np.sqrt(atomic_mass(splited_word[0])) * np.array(splited_word[1:4], dtype="float64")
43
+ mass_weight_coord_1.append(tmp)
44
+ mass_weight_coord_2 = []
45
+ for word in words_2:
46
+ splited_word = word.split()
47
+ if len(splited_word) != 4:
48
+ continue
49
+ tmp = np.sqrt(atomic_mass(splited_word[0])) * np.array(splited_word[1:4], dtype="float64")
50
+ mass_weight_coord_2.append(tmp)
51
+
52
+ mass_weight_coord_1 = np.array(mass_weight_coord_1, dtype="float64")
53
+ mass_weight_coord_2 = np.array(mass_weight_coord_2, dtype="float64")
54
+
55
+ return mass_weight_coord_1, mass_weight_coord_2
56
+
57
+
58
+ def double_centering(self, distance_matrix, struct_num):
59
+ distance_matrix_2 = distance_matrix * distance_matrix
60
+ length_dist_mat = len(distance_matrix)
61
+ Q_matrix = -0.5 * np.dot((np.eye(length_dist_mat) - (1 / struct_num) * np.ones((length_dist_mat, length_dist_mat))), np.dot(distance_matrix_2, (np.eye(length_dist_mat) - (1 / struct_num) * np.ones((length_dist_mat, length_dist_mat))).T))
62
+
63
+ return Q_matrix
64
+
65
+
66
+ def cmds_visualization(self, result_list, energy_list, name=""):
67
+ plt.xlabel("PCo1 (ang. / amu^0.5)")
68
+ plt.ylabel("PCo2 (ang. / amu^0.5)")
69
+ plt.title("CMDS result ("+name+")")
70
+
71
+ x_array = np.array(result_list[1])
72
+ y_array = np.array(result_list[2])
73
+ xmin = min(x_array)
74
+ xmax = max(x_array)
75
+ ymin = min(y_array)
76
+ ymax = max(y_array)
77
+ delta_x = xmax - xmin
78
+ delta_y = ymax - ymin
79
+ plt.xlim(xmin-(delta_x/5), xmax+(delta_x/5))
80
+ plt.ylim(ymin-(delta_y/5), ymax+(delta_y/5))
81
+ for i in range(len(energy_list[:-1])):
82
+ data = plt.scatter(x_array[i], y_array[i], c=energy_list[i], vmin=min(energy_list), vmax=max(energy_list), cmap='jet', s=25, marker="o", linewidths=0.1, edgecolors="black")
83
+ plt.colorbar(data, label=name+" (kcal/mol)")
84
+ plt.savefig(self.directory+"cmds_result_visualization_"+str(name)+".png" ,dpi=300,format="png")
85
+ plt.close()
86
+ return
87
+
88
+ def main(self):
89
+ if self.energy_list is None and self.bias_energy_list is None:
90
+ print("There is not enough data to perform CMDS analysis...")
91
+ return
92
+
93
+
94
+ print("processing CMDS analysis to aprrox. reaction path ...")
95
+ file_list = sorted(glob.glob(self.directory+"samples_*_[0-9]/*.xyz")) + sorted(glob.glob(self.directory+"samples_*_[0-9][0-9]/*.xyz")) + sorted(glob.glob(self.directory+"samples_*_[0-9][0-9][0-9]/*.xyz")) + sorted(glob.glob(self.directory+"samples_*_[0-9][0-9][0-9][0-9]/*.xyz")) + sorted(glob.glob(self.directory+"samples_*_[0-9][0-9][0-9][0-9][0-9]/*.xyz")) + sorted(glob.glob(self.directory+"samples_*_[0-9][0-9][0-9][0-9][0-9][0-9]/*.xyz"))
96
+ file_list = file_list[1:]
97
+ idx_list = itertools.combinations([i for i in range(len(file_list))], 2)
98
+ struct_num = len(file_list)
99
+ D_matrix = np.zeros((struct_num, struct_num))
100
+
101
+ for i, j in idx_list:
102
+
103
+ mass_weight_coord_1, mass_weight_coord_2 = self.read_xyz_file(file_list[i], file_list[j])
104
+ modified_coord_1, modified_coord_2 = Calculationtools().kabsch_algorithm(mass_weight_coord_1, mass_weight_coord_2)
105
+ dist = np.linalg.norm(modified_coord_1 - modified_coord_2)
106
+ D_matrix[i][j] = dist
107
+ D_matrix[j][i] = dist
108
+
109
+ Q_matrix = self.double_centering(D_matrix, struct_num)
110
+
111
+ Q_eigenvalue, Q_eigenvector = np.linalg.eig(Q_matrix)
112
+ Q_eigenvector = Q_eigenvector.T
113
+
114
+ Q_eigenvalue = np.real_if_close(Q_eigenvalue, tol=1000)
115
+ Q_eigenvector = np.real_if_close(Q_eigenvector, tol=1000)
116
+
117
+ sorted_Q_eigenvalue = np.sort(Q_eigenvalue)
118
+ rank_1_value = sorted_Q_eigenvalue[-1]
119
+ rank_2_value = sorted_Q_eigenvalue[-2]
120
+ #print(Q_eigenvalue)
121
+
122
+ rank1_idx = np.where(Q_eigenvalue == rank_1_value)[0][0]
123
+ rank2_idx = np.where(Q_eigenvalue == rank_2_value)[0][0]
124
+
125
+ sum_of_eigenvalue = 0.0
126
+ for value in Q_eigenvalue:
127
+ if value < 0:
128
+ continue
129
+ sum_of_eigenvalue += value
130
+ print("dimensional reproducibility:", np.sum(Q_eigenvalue)/sum_of_eigenvalue)
131
+ print("Percentage contribution 1:", Q_eigenvalue[rank1_idx]/sum_of_eigenvalue)
132
+ print("Percentage contribution 2:", Q_eigenvalue[rank2_idx]/sum_of_eigenvalue)
133
+ PCo1 = np.sqrt(Q_eigenvalue[rank1_idx]) * Q_eigenvector[rank1_idx]
134
+ PCo2 = np.sqrt(Q_eigenvalue[rank2_idx]) * Q_eigenvector[rank2_idx]
135
+
136
+ result_list = []
137
+
138
+ with open(self.directory+"cmds_analysis_result.csv", "w") as f:
139
+ f.write("dimensional reproducibility:,"+str(float(np.sum(Q_eigenvalue)/sum_of_eigenvalue))+"\n")
140
+ f.write("Percentage contribution 1:,"+str(float(Q_eigenvalue[rank1_idx]/sum_of_eigenvalue))+"\n")
141
+ f.write("Percentage contribution 2:,"+str(float(Q_eigenvalue[rank2_idx]/sum_of_eigenvalue))+"\n")
142
+ f.write("itr., PCo1, PCo2, energy[kcal/mol], energy(bias)[kcal/mol]\n")
143
+ for i in range(len(PCo1)-1):
144
+ f.write(str(i)+", "+str(float(PCo1[i]))+", "+str(float(PCo2[i]))+","+str(self.energy_list[i])+", "+str(self.bias_energy_list[i])+"\n")
145
+ tmp = [i, float(PCo1[i]), float(PCo2[i])]
146
+ result_list.append(tmp)
147
+ result_list = np.array(result_list, dtype="float64").T
148
+ self.cmds_visualization(result_list, self.energy_list, name="energy")
149
+ self.cmds_visualization(result_list, self.bias_energy_list, name="bias_energy")
150
+ print("CMDS analysis completed...")
151
+ return
152
+
@@ -0,0 +1,268 @@
1
+ import numpy as np
2
+ import csv
3
+ import os
4
+
5
+ from multioptpy.Parameters.unit_values import UnitValueLib
6
+
7
+
8
+ class KoopmanAnalyzer:
9
+ def __init__(self, natom, window_size=10, num_frames=10, amplitude=1.5, rank_threshold=1e-5, poly_degree=2, file_directory=None):
10
+ self.natom = natom
11
+ self.window_size = window_size
12
+ self.num_frames = num_frames
13
+ self.amplitude = amplitude
14
+ self.rank_threshold = rank_threshold
15
+ self.poly_degree = poly_degree # Degree of polynomial terms in observation function
16
+ if file_directory is not None:
17
+ tmp_directory = file_directory + '/koopman_analysis'
18
+ os.makedirs(tmp_directory, exist_ok=True)
19
+ self.coord_file = tmp_directory + '/coordinates.csv'
20
+ self.eigs_file = tmp_directory + '/koopman_eigs.csv'
21
+ self.modes_file = tmp_directory + '/koopman_modes.log'
22
+ self.anim_file_prefix = tmp_directory + '/koopman_mode_anim_'
23
+ else:
24
+ self.coord_file = 'coordinates.csv'
25
+ self.eigs_file = 'koopman_eigs.csv'
26
+ self.modes_file = 'koopman_modes.log'
27
+ self.anim_file_prefix = 'koopman_mode_anim_'
28
+
29
+ self.coordinates = [] # List of (iteration, coords) tuples
30
+ self.gradients = [] # List of (iteration, grads) tuples
31
+
32
+ self.bohr2ang = UnitValueLib().bohr2angstroms
33
+
34
+ def observation_function(self, coords, grads=None):
35
+ """Extended observation function for EDMD: polynomial terms, interatomic distances, and gradients."""
36
+ x = coords.flatten()
37
+ # Polynomial terms from x^1 to x^poly_degree
38
+ poly_terms = np.concatenate([x**k for k in range(1, self.poly_degree + 1)])
39
+
40
+ # Add physical observables: interatomic distances
41
+ distances = []
42
+ for i in range(self.natom):
43
+ for j in range(i+1, self.natom):
44
+ dist = np.linalg.norm(coords[i] - coords[j])
45
+ distances.append(dist)
46
+ distances = np.array(distances)
47
+
48
+
49
+ # Concatenate all observables
50
+ psi = np.concatenate([poly_terms, distances])
51
+
52
+ # Add gradients if provided
53
+ if grads is not None:
54
+ g = grads.flatten()
55
+ psi = np.concatenate([psi, g])
56
+
57
+ return psi
58
+
59
+ def append_coordinates(self, iteration, coords, grads=None):
60
+ """Append coordinates and gradients to CSV log and internal lists."""
61
+ if len(coords) != 3 * self.natom:
62
+ raise ValueError("Coords must be of length 3*Natom")
63
+ if grads is not None and len(grads) != 3 * self.natom:
64
+ raise ValueError("Grads must be of length 3*Natom")
65
+
66
+ # Append to internal lists
67
+ self.coordinates.append((iteration, np.array(coords)))
68
+ if grads is not None:
69
+ self.gradients.append((iteration, np.array(grads)))
70
+
71
+ # Write to CSV
72
+ file_exists = os.path.isfile(self.coord_file)
73
+ with open(self.coord_file, 'a', newline='') as f:
74
+ writer = csv.writer(f)
75
+ if not file_exists:
76
+ header = ['iteration'] + [f'{axis}{i+1}' for i in range(self.natom) for axis in ['x', 'y', 'z']]
77
+ if grads is not None:
78
+ header += [f'grad_{axis}{i+1}' for i in range(self.natom) for axis in ['x', 'y', 'z']]
79
+ writer.writerow(header)
80
+ row = [iteration] + coords.tolist()
81
+ if grads is not None:
82
+ row += grads.tolist()
83
+ writer.writerow(row)
84
+
85
+ def perform_koopman_analysis(self):
86
+ """Perform EDMD-style Koopman analysis with rank truncation."""
87
+ if len(self.coordinates) < self.window_size + 1:
88
+ print("Not enough data for analysis.")
89
+ return None
90
+
91
+ # Get the last window_size + 1 coords and grads for EDMD
92
+ recent_coords = [coord[1] for coord in self.coordinates[-self.window_size-1:]]
93
+ recent_grads = [grad[1] for grad in self.gradients[-self.window_size-1:]] if self.gradients else [None] * len(recent_coords)
94
+
95
+ # Apply observation function to extend data
96
+ Psi_X = np.column_stack([self.observation_function(c, g) for c, g in zip(recent_coords[:-1], recent_grads[:-1])])
97
+ Psi_X_prime = np.column_stack([self.observation_function(c, g) for c, g in zip(recent_coords[1:], recent_grads[1:])])
98
+
99
+ # EDMD with rank truncation
100
+ U, Sigma, Vt = np.linalg.svd(Psi_X, full_matrices=False)
101
+
102
+ rank = np.sum(Sigma > self.rank_threshold)
103
+ if rank == 0:
104
+ print("Warning: All singular values are below threshold. No rank found.")
105
+ return None
106
+
107
+ U_r = U[:, :rank]
108
+ Sigma_r = np.diag(Sigma[:rank])
109
+ Vt_r = Vt[:rank, :]
110
+
111
+ # Corrected A_tilde calculation to match standard EDMD form: A_tilde = U_r^T Psi_X_prime Vt_r^T Sigma_r^{-1}
112
+ A_tilde = np.dot(np.dot(U_r.T, Psi_X_prime), Vt_r.T) @ np.linalg.inv(Sigma_r)
113
+ lambdas, W = np.linalg.eig(A_tilde)
114
+
115
+ # Compute modes (eigenfunctions of Koopman operator in extended space)
116
+ Phi = np.dot(np.dot(Psi_X_prime, Vt_r.T), np.linalg.solve(Sigma_r, W))
117
+
118
+ # Sort by eigenvalue absolute value (descending)
119
+ sort_indices = np.argsort(-np.abs(lambdas))
120
+ lambdas = lambdas[sort_indices]
121
+ Phi = Phi[:, sort_indices]
122
+
123
+ # Log eigenvalues to CSV: one row per iteration, with columns for each mode's real, imag, norm
124
+ current_iter = self.coordinates[-1][0]
125
+ file_exists = os.path.isfile(self.eigs_file)
126
+ with open(self.eigs_file, 'a', newline='') as f:
127
+ writer = csv.writer(f)
128
+ if not file_exists:
129
+ header = ['iteration', 'rank']
130
+ for i in range(len(lambdas)):
131
+ header.extend([f'mode_{i}_real', f'mode_{i}_imag', f'mode_{i}_norm'])
132
+ writer.writerow(header)
133
+ row = [current_iter, rank]
134
+ for lam in lambdas:
135
+ row.extend([lam.real, lam.imag, np.abs(lam)])
136
+ writer.writerow(row)
137
+
138
+ # Save modes to file
139
+ with open(self.modes_file, 'a') as f:
140
+ f.write(f"Iteration {current_iter}:\n")
141
+ for i in range(len(lambdas)):
142
+ lam = lambdas[i]
143
+ f.write(f"Eigenvalue {i}: Real={lam.real:.6f}, Imag={lam.imag:.6f}, Norm={np.abs(lam):.6f}\n")
144
+ f.write("Mode vector (real part):\n")
145
+ np.savetxt(f, Phi[:, i].real, fmt='%.6f')
146
+ f.write("Mode vector (imag part):\n")
147
+ np.savetxt(f, Phi[:, i].imag, fmt='%.6f')
148
+ f.write("\n")
149
+ f.write("\n")
150
+
151
+ # For animation, project back to original coordinate space (take the identity part of the modes)
152
+ # Modes are in extended space, so take the first 3*natom components (identity part)
153
+ modes = Phi[:3*self.natom, :].real
154
+ # Normalize each mode vector
155
+ for i in range(modes.shape[1]):
156
+ norm = np.linalg.norm(modes[:, i])
157
+ if norm > 1e-9: # Avoid division by zero
158
+ modes[:, i] /= norm
159
+ return modes, lambdas
160
+
161
+ def generate_animations(self, modes, element_list=None):
162
+ """Generate XYZ animations for all modes using simple sinusoidal oscillation."""
163
+ if len(self.coordinates) == 0:
164
+ print("No coordinates available.")
165
+ return
166
+
167
+ # Remove previous animation files
168
+ for filename in os.listdir('.'):
169
+ if filename.startswith(self.anim_file_prefix) and filename.endswith('.xyz'):
170
+ os.remove(filename)
171
+
172
+ latest_coords = self.coordinates[-1][1].reshape((self.natom, 3))
173
+
174
+ for mode_idx in range(modes.shape[1]):
175
+ mode = modes[:, mode_idx] # real mode vector in original space
176
+
177
+ anim_file = f"{self.anim_file_prefix}{mode_idx}.xyz"
178
+ with open(anim_file, 'w') as f:
179
+ for frame in range(self.num_frames):
180
+ # Use simple sinusoidal oscillation of the real part of the mode
181
+ displacement = self.amplitude * np.sin(2 * np.pi * frame / (self.num_frames - 1)) * mode.reshape((self.natom, 3))
182
+
183
+ displaced_coords = latest_coords + displacement
184
+ displaced_coords *= self.bohr2ang # Convert to Angstroms
185
+
186
+ f.write(f"{self.natom}\n")
187
+ f.write(f"Mode {mode_idx} Frame {frame}\n")
188
+ for i in range(self.natom):
189
+ if element_list is not None and i < len(element_list):
190
+ elem = element_list[i]
191
+ else:
192
+ elem = 'X' # Placeholder element
193
+ f.write(f"{elem} {displaced_coords[i, 0]:.6f} {displaced_coords[i, 1]:.6f} {displaced_coords[i, 2]:.6f}\n")
194
+
195
+ def run(self, iteration, coords, grad, element_list=None):#coords:Bohr. grad:Hartree/Bohr
196
+ """Convenience method to append data, perform analysis, and generate animations."""
197
+ if len(coords) != 3 * self.natom:
198
+ coords = coords.flatten()
199
+ if grad is not None and len(grad) != 3 * self.natom:
200
+ grad = grad.flatten()
201
+
202
+ self.append_coordinates(iteration, coords, grad)
203
+ result = self.perform_koopman_analysis()
204
+ if result is not None:
205
+ modes, lambdas = result
206
+ self.generate_animations(modes, element_list)
207
+ return modes, lambdas
208
+
209
+ return None
210
+
211
+ """
212
+ | Number | |λ| | Re(λ) | Im(λ) | Behavior | Result | Mathematical Basis and Example |
213
+ |--------|---------|--------|--------|---------------------------|----------------------------------|-------------------------------|
214
+ | 1 | =1 | >0 | =0 | Constant amplitude, monotonic progression | Does not converge, proceeds in the same direction | λ = positive real number (e.g., λ=1). Response: Increases like a ramp function. Equivalent to an integrator. |
215
+ | 2 | <1 | >0 | =0 | Damping, monotonic progression | Converges, straight toward the minimum value | λ = positive real number <1 (e.g., λ=0.5). Exponential decay: x(k) ~ (0.5)^k. Stable. |
216
+ | 3 | <1 | <0 | =0 | Damping, direction reversal | Converges with alternating sign changes (simple oscillation) | λ = negative real number >-1 (e.g., λ=-0.5). Damped oscillation: (-0.5)^k with sign alternation. |
217
+ | 4 | =1 | <0 | =0 | Constant amplitude, reversal | Oscillates without damping, does not converge | λ = -1. Sustained oscillation: (-1)^k with alternating signs. Oscillator. |
218
+ | 5 | >1 | >0 | =0 | Amplification, monotonic progression | Diverges, convergence impossible | λ >1 (e.g., λ=1.5). Exponential amplification: (1.5)^k diverges. |
219
+ | 6 | >1 | <0 | =0 | Amplification, reversal | Diverges with oscillation | λ < -1 (e.g., λ=-1.5). Amplified oscillation: (-1.5)^k with diverging vibration. |
220
+ | 7 | <1 | >0 | ≠0 | Damping, spiral/oscillation | Converges with decaying rotation and vibration | Complex λ, |λ|<1, Re>0. Spiral damping: ρ^k e^{jθk} (ρ<1). |
221
+ | 8 | <1 | <0 | ≠0 | Damping, reversal + spiral | Converges spirally with reversal | Complex λ, |λ|<1, Re<0. Reversal spiral damping. |
222
+ | 9 | =1 | >0 | ≠0 | Constant amplitude, spiral | Persists with rotation without convergence | Complex λ, |λ|=1, Re>0. Sustained rotation: e^{jθk}. |
223
+ | 10 | =1 | <0 | ≠0 | Constant amplitude, reversal + spiral | Persists with reversal and rotation, without convergence | Complex λ, |λ|=1, Re<0. Reversal sustained rotation. |
224
+ | 11 | >1 | >0 | ≠0 | Amplification, spiral | Diverges with rotation | Complex λ, |λ|>1, Re>0. Spiral divergence. |
225
+ | 12 | >1 | <0 | ≠0 | Amplification, reversal + spiral | Diverges with reversal + rotation | Complex λ, |λ|>1, Re<0. Reversal spiral divergence. |
226
+
227
+ """
228
+
229
+
230
+
231
+ def main():
232
+ natom = 5
233
+
234
+ analyzer = KoopmanAnalyzer(natom)
235
+
236
+ # Generate dummy data with stable oscillation and noise
237
+ np.random.seed(42)
238
+ base_coords = np.random.rand(3 * natom) * 5
239
+ slow_mode_vec = np.random.randn(3 * natom)
240
+ slow_mode_vec /= np.linalg.norm(slow_mode_vec) # Normalize
241
+
242
+ last_modes = None
243
+ for iteration in range(35):
244
+ # Artificially add a very slowly decaying oscillation mode
245
+ slow_oscillation = 0.5 * np.sin(iteration * 0.1) * slow_mode_vec
246
+ # Derivative of the sine curve for gradients
247
+ slow_gradient = 0.5 * 0.1 * np.cos(iteration * 0.1) * slow_mode_vec
248
+ # A bit of fast noise
249
+ noise = 0.05 * np.random.randn(3 * natom)
250
+ coords = base_coords + slow_oscillation + noise
251
+ grad = slow_gradient + noise # Add noise to gradients as well for realism
252
+ analyzer.append_coordinates(iteration, coords, grad)
253
+
254
+ # Perform analysis every 'window_size' steps
255
+ if (iteration + 1) >= analyzer.window_size:
256
+ result = analyzer.perform_koopman_analysis()
257
+ if result is not None:
258
+ modes, lambdas = result
259
+ print(f"\nAnalysis at iteration {iteration}:")
260
+ print(f"Top 3 mode norms: {[f'{np.abs(l):.4f}' for l in lambdas[:3]]}")
261
+ last_modes = modes # Store the latest modes
262
+
263
+ # Generate animations using the modes from the last iteration
264
+ if last_modes is not None:
265
+ analyzer.generate_animations(last_modes)
266
+
267
+ if __name__ == "__main__":
268
+ main()