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.
- multioptpy/Calculator/__init__.py +0 -0
- multioptpy/Calculator/ase_calculation_tools.py +424 -0
- multioptpy/Calculator/ase_tools/__init__.py +0 -0
- multioptpy/Calculator/ase_tools/fairchem.py +28 -0
- multioptpy/Calculator/ase_tools/gamess.py +19 -0
- multioptpy/Calculator/ase_tools/gaussian.py +165 -0
- multioptpy/Calculator/ase_tools/mace.py +28 -0
- multioptpy/Calculator/ase_tools/mopac.py +19 -0
- multioptpy/Calculator/ase_tools/nwchem.py +31 -0
- multioptpy/Calculator/ase_tools/orca.py +22 -0
- multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
- multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
- multioptpy/Calculator/emt_calculation_tools.py +458 -0
- multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
- multioptpy/Calculator/lj_calculation_tools.py +314 -0
- multioptpy/Calculator/psi4_calculation_tools.py +334 -0
- multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
- multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
- multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
- multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
- multioptpy/Calculator/tblite_calculation_tools.py +352 -0
- multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
- multioptpy/Constraint/__init__.py +0 -0
- multioptpy/Constraint/constraint_condition.py +834 -0
- multioptpy/Coordinate/__init__.py +0 -0
- multioptpy/Coordinate/polar_coordinate.py +199 -0
- multioptpy/Coordinate/redundant_coordinate.py +638 -0
- multioptpy/IRC/__init__.py +0 -0
- multioptpy/IRC/converge_criteria.py +28 -0
- multioptpy/IRC/dvv.py +544 -0
- multioptpy/IRC/euler.py +439 -0
- multioptpy/IRC/hpc.py +564 -0
- multioptpy/IRC/lqa.py +540 -0
- multioptpy/IRC/modekill.py +662 -0
- multioptpy/IRC/rk4.py +579 -0
- multioptpy/Interpolation/__init__.py +0 -0
- multioptpy/Interpolation/adaptive_interpolation.py +283 -0
- multioptpy/Interpolation/binomial_interpolation.py +179 -0
- multioptpy/Interpolation/geodesic_interpolation.py +785 -0
- multioptpy/Interpolation/interpolation.py +156 -0
- multioptpy/Interpolation/linear_interpolation.py +473 -0
- multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
- multioptpy/Interpolation/spline_interpolation.py +353 -0
- multioptpy/MD/__init__.py +0 -0
- multioptpy/MD/thermostat.py +185 -0
- multioptpy/MEP/__init__.py +0 -0
- multioptpy/MEP/pathopt_bneb_force.py +443 -0
- multioptpy/MEP/pathopt_dmf_force.py +448 -0
- multioptpy/MEP/pathopt_dneb_force.py +130 -0
- multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
- multioptpy/MEP/pathopt_gpneb_force.py +512 -0
- multioptpy/MEP/pathopt_lup_force.py +113 -0
- multioptpy/MEP/pathopt_neb_force.py +225 -0
- multioptpy/MEP/pathopt_nesb_force.py +205 -0
- multioptpy/MEP/pathopt_om_force.py +153 -0
- multioptpy/MEP/pathopt_qsm_force.py +174 -0
- multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
- multioptpy/ModelFunction/__init__.py +7 -0
- multioptpy/ModelFunction/avoiding_model_function.py +29 -0
- multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
- multioptpy/ModelFunction/conical_model_function.py +26 -0
- multioptpy/ModelFunction/opt_meci.py +50 -0
- multioptpy/ModelFunction/opt_mesx.py +47 -0
- multioptpy/ModelFunction/opt_mesx_2.py +49 -0
- multioptpy/ModelFunction/seam_model_function.py +27 -0
- multioptpy/ModelHessian/__init__.py +0 -0
- multioptpy/ModelHessian/approx_hessian.py +147 -0
- multioptpy/ModelHessian/calc_params.py +227 -0
- multioptpy/ModelHessian/fischer.py +236 -0
- multioptpy/ModelHessian/fischerd3.py +360 -0
- multioptpy/ModelHessian/fischerd4.py +398 -0
- multioptpy/ModelHessian/gfn0xtb.py +633 -0
- multioptpy/ModelHessian/gfnff.py +709 -0
- multioptpy/ModelHessian/lindh.py +165 -0
- multioptpy/ModelHessian/lindh2007d2.py +707 -0
- multioptpy/ModelHessian/lindh2007d3.py +822 -0
- multioptpy/ModelHessian/lindh2007d4.py +1030 -0
- multioptpy/ModelHessian/morse.py +106 -0
- multioptpy/ModelHessian/schlegel.py +144 -0
- multioptpy/ModelHessian/schlegeld3.py +322 -0
- multioptpy/ModelHessian/schlegeld4.py +559 -0
- multioptpy/ModelHessian/shortrange.py +346 -0
- multioptpy/ModelHessian/swartd2.py +496 -0
- multioptpy/ModelHessian/swartd3.py +706 -0
- multioptpy/ModelHessian/swartd4.py +918 -0
- multioptpy/ModelHessian/tshess.py +40 -0
- multioptpy/Optimizer/QHAdam.py +61 -0
- multioptpy/Optimizer/__init__.py +0 -0
- multioptpy/Optimizer/abc_fire.py +83 -0
- multioptpy/Optimizer/adabelief.py +58 -0
- multioptpy/Optimizer/adabound.py +68 -0
- multioptpy/Optimizer/adadelta.py +65 -0
- multioptpy/Optimizer/adaderivative.py +56 -0
- multioptpy/Optimizer/adadiff.py +68 -0
- multioptpy/Optimizer/adafactor.py +70 -0
- multioptpy/Optimizer/adam.py +65 -0
- multioptpy/Optimizer/adamax.py +62 -0
- multioptpy/Optimizer/adamod.py +83 -0
- multioptpy/Optimizer/adamw.py +65 -0
- multioptpy/Optimizer/adiis.py +523 -0
- multioptpy/Optimizer/afire_neb.py +282 -0
- multioptpy/Optimizer/block_hessian_update.py +709 -0
- multioptpy/Optimizer/c2diis.py +491 -0
- multioptpy/Optimizer/component_wise_scaling.py +405 -0
- multioptpy/Optimizer/conjugate_gradient.py +82 -0
- multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
- multioptpy/Optimizer/coordinate_locking.py +405 -0
- multioptpy/Optimizer/dic_rsirfo.py +1015 -0
- multioptpy/Optimizer/ediis.py +417 -0
- multioptpy/Optimizer/eve.py +76 -0
- multioptpy/Optimizer/fastadabelief.py +61 -0
- multioptpy/Optimizer/fire.py +77 -0
- multioptpy/Optimizer/fire2.py +249 -0
- multioptpy/Optimizer/fire_neb.py +92 -0
- multioptpy/Optimizer/gan_step.py +486 -0
- multioptpy/Optimizer/gdiis.py +609 -0
- multioptpy/Optimizer/gediis.py +203 -0
- multioptpy/Optimizer/geodesic_step.py +433 -0
- multioptpy/Optimizer/gpmin.py +633 -0
- multioptpy/Optimizer/gpr_step.py +364 -0
- multioptpy/Optimizer/gradientdescent.py +78 -0
- multioptpy/Optimizer/gradientdescent_neb.py +52 -0
- multioptpy/Optimizer/hessian_update.py +433 -0
- multioptpy/Optimizer/hybrid_rfo.py +998 -0
- multioptpy/Optimizer/kdiis.py +625 -0
- multioptpy/Optimizer/lars.py +21 -0
- multioptpy/Optimizer/lbfgs.py +253 -0
- multioptpy/Optimizer/lbfgs_neb.py +355 -0
- multioptpy/Optimizer/linesearch.py +236 -0
- multioptpy/Optimizer/lookahead.py +40 -0
- multioptpy/Optimizer/nadam.py +64 -0
- multioptpy/Optimizer/newton.py +200 -0
- multioptpy/Optimizer/prodigy.py +70 -0
- multioptpy/Optimizer/purtubation.py +16 -0
- multioptpy/Optimizer/quickmin_neb.py +245 -0
- multioptpy/Optimizer/radam.py +75 -0
- multioptpy/Optimizer/rfo_neb.py +302 -0
- multioptpy/Optimizer/ric_rfo.py +842 -0
- multioptpy/Optimizer/rl_step.py +627 -0
- multioptpy/Optimizer/rmspropgrave.py +65 -0
- multioptpy/Optimizer/rsirfo.py +1647 -0
- multioptpy/Optimizer/rsprfo.py +1056 -0
- multioptpy/Optimizer/sadam.py +60 -0
- multioptpy/Optimizer/samsgrad.py +63 -0
- multioptpy/Optimizer/tr_lbfgs.py +678 -0
- multioptpy/Optimizer/trim.py +273 -0
- multioptpy/Optimizer/trust_radius.py +207 -0
- multioptpy/Optimizer/trust_radius_neb.py +121 -0
- multioptpy/Optimizer/yogi.py +60 -0
- multioptpy/OtherMethod/__init__.py +0 -0
- multioptpy/OtherMethod/addf.py +1150 -0
- multioptpy/OtherMethod/dimer.py +895 -0
- multioptpy/OtherMethod/elastic_image_pair.py +629 -0
- multioptpy/OtherMethod/modelfunction.py +456 -0
- multioptpy/OtherMethod/newton_traj.py +454 -0
- multioptpy/OtherMethod/twopshs.py +1095 -0
- multioptpy/PESAnalyzer/__init__.py +0 -0
- multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
- multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
- multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
- multioptpy/PESAnalyzer/pca_analysis.py +314 -0
- multioptpy/Parameters/__init__.py +0 -0
- multioptpy/Parameters/atomic_mass.py +20 -0
- multioptpy/Parameters/atomic_number.py +22 -0
- multioptpy/Parameters/covalent_radii.py +44 -0
- multioptpy/Parameters/d2.py +61 -0
- multioptpy/Parameters/d3.py +63 -0
- multioptpy/Parameters/d4.py +103 -0
- multioptpy/Parameters/dreiding.py +34 -0
- multioptpy/Parameters/gfn0xtb_param.py +137 -0
- multioptpy/Parameters/gfnff_param.py +315 -0
- multioptpy/Parameters/gnb.py +104 -0
- multioptpy/Parameters/parameter.py +22 -0
- multioptpy/Parameters/uff.py +72 -0
- multioptpy/Parameters/unit_values.py +20 -0
- multioptpy/Potential/AFIR_potential.py +55 -0
- multioptpy/Potential/LJ_repulsive_potential.py +345 -0
- multioptpy/Potential/__init__.py +0 -0
- multioptpy/Potential/anharmonic_keep_potential.py +28 -0
- multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
- multioptpy/Potential/electrostatic_potential.py +69 -0
- multioptpy/Potential/flux_potential.py +30 -0
- multioptpy/Potential/gaussian_potential.py +101 -0
- multioptpy/Potential/idpp.py +516 -0
- multioptpy/Potential/keep_angle_potential.py +146 -0
- multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
- multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
- multioptpy/Potential/keep_potential.py +99 -0
- multioptpy/Potential/mechano_force_potential.py +74 -0
- multioptpy/Potential/nanoreactor_potential.py +52 -0
- multioptpy/Potential/potential.py +896 -0
- multioptpy/Potential/spacer_model_potential.py +221 -0
- multioptpy/Potential/switching_potential.py +258 -0
- multioptpy/Potential/universal_potential.py +34 -0
- multioptpy/Potential/value_range_potential.py +36 -0
- multioptpy/Potential/void_point_potential.py +25 -0
- multioptpy/SQM/__init__.py +0 -0
- multioptpy/SQM/sqm1/__init__.py +0 -0
- multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
- multioptpy/SQM/sqm2/__init__.py +0 -0
- multioptpy/SQM/sqm2/calc_tools.py +95 -0
- multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
- multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
- multioptpy/SQM/sqm2/sqm2_core.py +303 -0
- multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
- multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
- multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
- multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
- multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
- multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
- multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
- multioptpy/Thermo/__init__.py +0 -0
- multioptpy/Thermo/normal_mode_analyzer.py +865 -0
- multioptpy/Utils/__init__.py +0 -0
- multioptpy/Utils/bond_connectivity.py +264 -0
- multioptpy/Utils/calc_tools.py +884 -0
- multioptpy/Utils/oniom.py +96 -0
- multioptpy/Utils/pbc.py +48 -0
- multioptpy/Utils/riemann_curvature.py +208 -0
- multioptpy/Utils/symmetry_analyzer.py +482 -0
- multioptpy/Visualization/__init__.py +0 -0
- multioptpy/Visualization/visualization.py +156 -0
- multioptpy/WFAnalyzer/MO_analysis.py +104 -0
- multioptpy/WFAnalyzer/__init__.py +0 -0
- multioptpy/Wrapper/__init__.py +0 -0
- multioptpy/Wrapper/autots.py +1239 -0
- multioptpy/Wrapper/ieip_wrapper.py +93 -0
- multioptpy/Wrapper/md_wrapper.py +92 -0
- multioptpy/Wrapper/neb_wrapper.py +94 -0
- multioptpy/Wrapper/optimize_wrapper.py +76 -0
- multioptpy/__init__.py +5 -0
- multioptpy/entrypoints.py +916 -0
- multioptpy/fileio.py +660 -0
- multioptpy/ieip.py +340 -0
- multioptpy/interface.py +1086 -0
- multioptpy/irc.py +529 -0
- multioptpy/moleculardynamics.py +432 -0
- multioptpy/neb.py +1267 -0
- multioptpy/optimization.py +1553 -0
- multioptpy/optimizer.py +709 -0
- multioptpy-1.20.2.dist-info/METADATA +438 -0
- multioptpy-1.20.2.dist-info/RECORD +246 -0
- multioptpy-1.20.2.dist-info/WHEEL +5 -0
- multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
- multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
- 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()
|