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
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from math import log, sqrt
|
|
5
|
+
import numpy as np
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
9
|
+
from multioptpy.Parameters.parameter import UnitValueLib, element_number, number_element
|
|
10
|
+
from multioptpy.fileio import xyz2list
|
|
11
|
+
from multioptpy.Visualization.visualization import NEBVisualizer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EMT:
|
|
15
|
+
"""
|
|
16
|
+
A standalone Python implementation of the Effective Medium Theory (EMT)
|
|
17
|
+
potential.
|
|
18
|
+
|
|
19
|
+
This implementation is suitable for calculating energy, forces, and Hessians
|
|
20
|
+
on atomic clusters. The Hessian is calculated via finite differences.
|
|
21
|
+
|
|
22
|
+
Notes on units:
|
|
23
|
+
- Input coordinates for calculation are expected in Angstroms.
|
|
24
|
+
- Internal calculations are performed using eV and Angstroms.
|
|
25
|
+
- Output energy is in Hartree.
|
|
26
|
+
- Output forces are in Hartree/Bohr.
|
|
27
|
+
- Output Hessian is in Hartree/Bohr^2.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Physical constants for internal use and unit conversion
|
|
31
|
+
BOHR = UnitValueLib().bohr2angstroms # Angstroms
|
|
32
|
+
EV_TO_HARTREE = 1 / UnitValueLib().hartree2eV
|
|
33
|
+
EV_PER_ANG_TO_HARTREE_PER_BOHR = EV_TO_HARTREE / BOHR
|
|
34
|
+
HARTREE_PER_BOHR_SQ_TO_EV_PER_ANG_SQ = 1 / (EV_TO_HARTREE / BOHR**2)
|
|
35
|
+
|
|
36
|
+
# Atomic data
|
|
37
|
+
CHEMICAL_SYMBOLS = {
|
|
38
|
+
1: 'H', 6: 'C', 7: 'N', 8: 'O', 13: 'Al', 29: 'Cu', 46: 'Pd',
|
|
39
|
+
47: 'Ag', 78: 'Pt', 79: 'Au', 28: 'Ni'
|
|
40
|
+
}
|
|
41
|
+
ATOMIC_NUMBERS = {v: k for k, v in CHEMICAL_SYMBOLS.items()}
|
|
42
|
+
|
|
43
|
+
PARAMETERS = {
|
|
44
|
+
# E0 s0 V0 eta2 kappa lambda n0
|
|
45
|
+
# eV bohr eV bohr^-1 bohr^-1 bohr^-1 bohr^-3
|
|
46
|
+
'Al': (-3.28, 3.00, 1.493, 1.240, 2.000, 1.169, 0.00700),
|
|
47
|
+
'Cu': (-3.51, 2.67, 2.476, 1.652, 2.740, 1.906, 0.00910),
|
|
48
|
+
'Ag': (-2.96, 3.01, 2.132, 1.652, 2.790, 1.892, 0.00547),
|
|
49
|
+
'Au': (-3.80, 3.00, 2.321, 1.674, 2.873, 2.182, 0.00703),
|
|
50
|
+
'Ni': (-4.44, 2.60, 3.673, 1.669, 2.757, 1.948, 0.01030),
|
|
51
|
+
'Pd': (-3.90, 2.87, 2.773, 1.818, 3.107, 2.155, 0.00688),
|
|
52
|
+
'Pt': (-5.85, 2.90, 4.067, 1.812, 3.145, 2.192, 0.00802),
|
|
53
|
+
'H': (-3.21, 1.31, 0.132, 2.652, 2.790, 3.892, 0.00547),
|
|
54
|
+
'C': (-3.50, 1.81, 0.332, 1.652, 2.790, 1.892, 0.01322),
|
|
55
|
+
'N': (-5.10, 1.88, 0.132, 1.652, 2.790, 1.892, 0.01222),
|
|
56
|
+
'O': (-4.60, 1.95, 0.332, 1.652, 2.790, 1.892, 0.00850)
|
|
57
|
+
}
|
|
58
|
+
BETA = 1.809 # (16 * pi / 3)**(1.0 / 3) / 2**0.5
|
|
59
|
+
|
|
60
|
+
def __init__(self, symbols, asap_cutoff=False):
|
|
61
|
+
self.symbols = symbols
|
|
62
|
+
self.numbers = np.array([self.ATOMIC_NUMBERS[s] for s in symbols])
|
|
63
|
+
self.asap_cutoff = asap_cutoff
|
|
64
|
+
|
|
65
|
+
self.positions = None
|
|
66
|
+
self.energy_ev = 0.0
|
|
67
|
+
self.forces_ev_per_ang = None
|
|
68
|
+
|
|
69
|
+
self._initialize_parameters()
|
|
70
|
+
|
|
71
|
+
def _initialize_parameters(self):
|
|
72
|
+
self.rc, self.rc_list, self.acut = self._calc_cutoff()
|
|
73
|
+
|
|
74
|
+
unique_numbers, self.ia2iz = np.unique(self.numbers, return_inverse=True)
|
|
75
|
+
self.par = defaultdict(lambda: np.empty(len(unique_numbers)))
|
|
76
|
+
for i, Z in enumerate(unique_numbers):
|
|
77
|
+
sym = self.CHEMICAL_SYMBOLS[Z]
|
|
78
|
+
if sym not in self.PARAMETERS:
|
|
79
|
+
raise NotImplementedError(f'No EMT-potential for {sym}')
|
|
80
|
+
|
|
81
|
+
p = self.PARAMETERS[sym]
|
|
82
|
+
s0 = p[1] * self.BOHR
|
|
83
|
+
eta2 = p[3] / self.BOHR
|
|
84
|
+
kappa = p[4] / self.BOHR
|
|
85
|
+
gamma1, gamma2 = self._calc_gammas(s0, eta2, kappa)
|
|
86
|
+
|
|
87
|
+
self.par['Z'][i] = Z
|
|
88
|
+
self.par['E0'][i] = p[0]
|
|
89
|
+
self.par['s0'][i] = s0
|
|
90
|
+
self.par['V0'][i] = p[2]
|
|
91
|
+
self.par['eta2'][i] = eta2
|
|
92
|
+
self.par['kappa'][i] = kappa
|
|
93
|
+
self.par['lambda'][i] = p[5] / self.BOHR
|
|
94
|
+
self.par['n0'][i] = p[6] / self.BOHR**3
|
|
95
|
+
self.par['inv12gamma1'][i] = 1.0 / (12.0 * gamma1)
|
|
96
|
+
self.par['neghalfv0overgamma2'][i] = -0.5 * p[2] / gamma2
|
|
97
|
+
|
|
98
|
+
self.chi = self.par['n0'][None, :] / self.par['n0'][:, None]
|
|
99
|
+
|
|
100
|
+
def _calc_cutoff(self):
|
|
101
|
+
if self.asap_cutoff:
|
|
102
|
+
relevant_pars = {
|
|
103
|
+
symb: p for symb, p in self.PARAMETERS.items()
|
|
104
|
+
if self.ATOMIC_NUMBERS[symb] in self.numbers
|
|
105
|
+
}
|
|
106
|
+
else:
|
|
107
|
+
relevant_pars = self.PARAMETERS
|
|
108
|
+
|
|
109
|
+
max_s0_bohr = max(par[1] for par in relevant_pars.values())
|
|
110
|
+
maxseq = max_s0_bohr * self.BOHR
|
|
111
|
+
r1nn = self.BETA * maxseq
|
|
112
|
+
rc = r1nn * 0.5 * (sqrt(3.0) + 2.0)
|
|
113
|
+
r4nn = r1nn * 2.0
|
|
114
|
+
eps = 1e-4
|
|
115
|
+
acut = log(1.0 / eps - 1.0) / (r4nn - rc)
|
|
116
|
+
rc_list = rc * 1.045 if self.asap_cutoff else rc + 0.5
|
|
117
|
+
return rc, rc_list, acut
|
|
118
|
+
|
|
119
|
+
def _calc_gammas(self, s0, eta2, kappa):
|
|
120
|
+
n = np.array([12, 6, 24])
|
|
121
|
+
r = self.BETA * s0 * np.sqrt([1.0, 2.0, 3.0])
|
|
122
|
+
w = 1.0 / (1.0 + np.exp(self.acut * (r - self.rc)))
|
|
123
|
+
x = n * w / 12.0
|
|
124
|
+
gamma1 = np.dot(x, np.exp(-eta2 * (r - self.BETA * s0)))
|
|
125
|
+
gamma2 = np.dot(x, np.exp(-kappa / self.BETA * (r - self.BETA * s0)))
|
|
126
|
+
return gamma1, gamma2
|
|
127
|
+
|
|
128
|
+
def _get_energy_and_forces_internal(self, positions_angstrom):
|
|
129
|
+
"""Calculates energy and forces in internal units (eV, eV/A)."""
|
|
130
|
+
self.positions = positions_angstrom
|
|
131
|
+
natoms = len(self.positions)
|
|
132
|
+
|
|
133
|
+
self.energies = np.zeros(natoms)
|
|
134
|
+
self.forces_ev_per_ang = np.zeros((natoms, 3))
|
|
135
|
+
self.deds = np.zeros(natoms)
|
|
136
|
+
|
|
137
|
+
ps = {}
|
|
138
|
+
for a1 in range(natoms):
|
|
139
|
+
diffs = self.positions - self.positions[a1]
|
|
140
|
+
dists = np.linalg.norm(diffs, axis=1)
|
|
141
|
+
neighbor_indices = np.where((dists > 1e-9) & (dists < self.rc_list))[0]
|
|
142
|
+
if len(neighbor_indices) == 0: continue
|
|
143
|
+
|
|
144
|
+
a2, d, r = neighbor_indices, diffs[neighbor_indices], dists[neighbor_indices]
|
|
145
|
+
w, dwdroverw = self._calc_theta(r)
|
|
146
|
+
dsigma1s, dsigma1o = self._calc_dsigma1(a1, a2, r, w)
|
|
147
|
+
dsigma2s, dsigma2o = self._calc_dsigma2(a1, a2, r, w)
|
|
148
|
+
ps[a1] = {
|
|
149
|
+
'a2': a2, 'd': d, 'r': r, 'invr': 1.0 / r, 'w': w,
|
|
150
|
+
'dwdroverw': dwdroverw, 'dsigma1s': dsigma1s, 'dsigma1o': dsigma1o,
|
|
151
|
+
'dsigma2s': dsigma2s, 'dsigma2o': dsigma2o,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for a1, p in ps.items(): self._calc_e_c_a2(a1, p['dsigma1s'])
|
|
155
|
+
for a1, p in ps.items(): self._calc_pairwise_forces(a1, **p)
|
|
156
|
+
|
|
157
|
+
self.energies -= self.par['E0'][self.ia2iz]
|
|
158
|
+
self.energy_ev = np.sum(self.energies)
|
|
159
|
+
return self.energy_ev, self.forces_ev_per_ang
|
|
160
|
+
|
|
161
|
+
def calculate_energy_and_forces(self, positions_angstrom):
|
|
162
|
+
"""Calculates energy and forces, returning them in atomic units."""
|
|
163
|
+
energy_ev, forces_ev_ang = self._get_energy_and_forces_internal(positions_angstrom)
|
|
164
|
+
energy_hartree = energy_ev * self.EV_TO_HARTREE
|
|
165
|
+
forces_hartree_per_bohr = forces_ev_ang * self.EV_PER_ANG_TO_HARTREE_PER_BOHR
|
|
166
|
+
return energy_hartree, forces_hartree_per_bohr
|
|
167
|
+
|
|
168
|
+
def calculate_hessian(self, positions_angstrom, fd_step=1e-5):
|
|
169
|
+
"""
|
|
170
|
+
Calculates the Hessian matrix by vectorized finite differences, returning
|
|
171
|
+
it in atomic units (Hartree/Bohr^2).
|
|
172
|
+
A smaller finite difference step is used for better numerical accuracy.
|
|
173
|
+
"""
|
|
174
|
+
num_atoms = len(positions_angstrom)
|
|
175
|
+
hessian_ev_per_ang_sq = np.zeros((num_atoms * 3, num_atoms * 3))
|
|
176
|
+
|
|
177
|
+
for i in range(num_atoms):
|
|
178
|
+
for j in range(3):
|
|
179
|
+
# Central difference method for higher accuracy
|
|
180
|
+
pos_plus = positions_angstrom.copy()
|
|
181
|
+
pos_plus[i, j] += fd_step
|
|
182
|
+
_, forces_plus = self._get_energy_and_forces_internal(pos_plus)
|
|
183
|
+
|
|
184
|
+
pos_minus = positions_angstrom.copy()
|
|
185
|
+
pos_minus[i, j] -= fd_step
|
|
186
|
+
_, forces_minus = self._get_energy_and_forces_internal(pos_minus)
|
|
187
|
+
|
|
188
|
+
# Gradient is negative force: g = -F
|
|
189
|
+
# Hessian H_ij = d(g_i)/d(r_j) = d(-F_i)/d(r_j)
|
|
190
|
+
# Using central differences: H_ij approx - (F_i(r+h) - F_i(r-h)) / (2h)
|
|
191
|
+
# The column of the Hessian corresponding to displacement 'k' is d(g)/dr_k = -dF/dr_k
|
|
192
|
+
hessian_col = -(forces_plus.flatten() - forces_minus.flatten()) / (2 * fd_step)
|
|
193
|
+
hessian_ev_per_ang_sq[:, i * 3 + j] = hessian_col
|
|
194
|
+
|
|
195
|
+
# Symmetrize the Hessian to reduce numerical noise
|
|
196
|
+
hessian_ev_per_ang_sq = 0.5 * (hessian_ev_per_ang_sq + hessian_ev_per_ang_sq.T)
|
|
197
|
+
|
|
198
|
+
# Convert to atomic units
|
|
199
|
+
hessian_hartree_per_bohr_sq = hessian_ev_per_ang_sq / self.HARTREE_PER_BOHR_SQ_TO_EV_PER_ANG_SQ
|
|
200
|
+
return hessian_hartree_per_bohr_sq
|
|
201
|
+
|
|
202
|
+
def _calc_theta(self, r):
|
|
203
|
+
w = 1.0 / (1.0 + np.exp(self.acut * (r - self.rc)))
|
|
204
|
+
dwdroverw = self.acut * (w - 1.0)
|
|
205
|
+
return w, dwdroverw
|
|
206
|
+
|
|
207
|
+
def _calc_dsigma1(self, a1, a2, r, w):
|
|
208
|
+
s0s, s0o = self.par['s0'][self.ia2iz[a1]], self.par['s0'][self.ia2iz[a2]]
|
|
209
|
+
eta2s, eta2o = self.par['eta2'][self.ia2iz[a1]], self.par['eta2'][self.ia2iz[a2]]
|
|
210
|
+
chi = self.chi[self.ia2iz[a1], self.ia2iz[a2]]
|
|
211
|
+
dsigma1s = np.exp(-eta2o * (r - self.BETA * s0o)) * chi * w
|
|
212
|
+
dsigma1o = np.exp(-eta2s * (r - self.BETA * s0s)) / chi * w
|
|
213
|
+
return dsigma1s, dsigma1o
|
|
214
|
+
|
|
215
|
+
def _calc_dsigma2(self, a1, a2, r, w):
|
|
216
|
+
s0s, s0o = self.par['s0'][self.ia2iz[a1]], self.par['s0'][self.ia2iz[a2]]
|
|
217
|
+
kappas, kappao = self.par['kappa'][self.ia2iz[a1]], self.par['kappa'][self.ia2iz[a2]]
|
|
218
|
+
chi = self.chi[self.ia2iz[a1], self.ia2iz[a2]]
|
|
219
|
+
dsigma2s = np.exp(-kappao * (r / self.BETA - s0o)) * chi * w
|
|
220
|
+
dsigma2o = np.exp(-kappas * (r / self.BETA - s0s)) / chi * w
|
|
221
|
+
return dsigma2s, dsigma2o
|
|
222
|
+
|
|
223
|
+
def _calc_e_c_a2(self, a1, dsigma1s):
|
|
224
|
+
sigma1 = np.sum(dsigma1s)
|
|
225
|
+
if sigma1 < 1e-20: return
|
|
226
|
+
|
|
227
|
+
iz1 = self.ia2iz[a1]
|
|
228
|
+
e0s, v0s, eta2s = self.par['E0'][iz1], self.par['V0'][iz1], self.par['eta2'][iz1]
|
|
229
|
+
lmds, kappas, inv12gamma1s = self.par['lambda'][iz1], self.par['kappa'][iz1], self.par['inv12gamma1'][iz1]
|
|
230
|
+
|
|
231
|
+
ds = -np.log(sigma1 * inv12gamma1s) / (self.BETA * eta2s)
|
|
232
|
+
lmdsds = lmds * ds
|
|
233
|
+
expneglmdds = np.exp(-lmdsds)
|
|
234
|
+
self.energies[a1] += e0s * (1.0 + lmdsds) * expneglmdds
|
|
235
|
+
self.deds[a1] += -e0s * lmds * lmdsds * expneglmdds
|
|
236
|
+
|
|
237
|
+
sixv0expnegkppds = 6.0 * v0s * np.exp(-kappas * ds)
|
|
238
|
+
self.energies[a1] += sixv0expnegkppds
|
|
239
|
+
self.deds[a1] += -kappas * sixv0expnegkppds
|
|
240
|
+
self.deds[a1] /= -self.BETA * eta2s * sigma1
|
|
241
|
+
|
|
242
|
+
def _calc_pairwise_forces(self, a1, a2, d, invr, dwdroverw, dsigma1s, dsigma1o, dsigma2s, dsigma2o, **kwargs):
|
|
243
|
+
iz1, iz2 = self.ia2iz[a1], self.ia2iz[a2]
|
|
244
|
+
eta2s, eta2o = self.par['eta2'][iz1], self.par['eta2'][iz2]
|
|
245
|
+
ddsigma1sdr = dsigma1s * (dwdroverw - eta2o)
|
|
246
|
+
ddsigma1odr = dsigma1o * (dwdroverw - eta2s)
|
|
247
|
+
dedrs = self.deds[a1] * ddsigma1sdr
|
|
248
|
+
dedro = self.deds[a2] * ddsigma1odr
|
|
249
|
+
f_cohesive = (dedrs + dedro) * invr
|
|
250
|
+
|
|
251
|
+
neghalfv0overgamma2s, neghalfv0overgamma2o = self.par['neghalfv0overgamma2'][iz1], self.par['neghalfv0overgamma2'][iz2]
|
|
252
|
+
kappas, kappao = self.par['kappa'][iz1], self.par['kappa'][iz2]
|
|
253
|
+
es, eo = neghalfv0overgamma2s * dsigma2s, neghalfv0overgamma2o * dsigma2o
|
|
254
|
+
self.energies[a1] += 0.5 * np.sum(es)
|
|
255
|
+
self.energies[a2] += 0.5 * np.sum(eo)
|
|
256
|
+
dedrs_pair = es * (dwdroverw - kappao / self.BETA)
|
|
257
|
+
dedro_pair = eo * (dwdroverw - kappas / self.BETA)
|
|
258
|
+
f_pair = (dedrs_pair + dedro_pair) * invr
|
|
259
|
+
|
|
260
|
+
f_total_mag = f_cohesive + f_pair
|
|
261
|
+
f_pairs_vec = f_total_mag[:, None] * d
|
|
262
|
+
|
|
263
|
+
self.forces_ev_per_ang[a1] += np.sum(f_pairs_vec, axis=0)
|
|
264
|
+
np.add.at(self.forces_ev_per_ang, a2, -f_pairs_vec)
|
|
265
|
+
|
|
266
|
+
class EMTCore:
|
|
267
|
+
"""
|
|
268
|
+
Core calculator for EMT potential, using the standalone implementation.
|
|
269
|
+
This class acts as a wrapper and cache for the EMT calculator.
|
|
270
|
+
"""
|
|
271
|
+
def __init__(self):
|
|
272
|
+
self.UVL = UnitValueLib()
|
|
273
|
+
self._calculator_cache = {}
|
|
274
|
+
|
|
275
|
+
def _get_calculator(self, atom_symbols):
|
|
276
|
+
symbols_tuple = tuple(sorted(set(atom_symbols)))
|
|
277
|
+
if symbols_tuple not in self._calculator_cache:
|
|
278
|
+
self._calculator_cache[symbols_tuple] = EMT(symbols_tuple)
|
|
279
|
+
|
|
280
|
+
calc = self._calculator_cache[symbols_tuple]
|
|
281
|
+
calc.symbols = atom_symbols
|
|
282
|
+
calc.numbers = np.array([calc.ATOMIC_NUMBERS[s] for s in atom_symbols])
|
|
283
|
+
calc._initialize_parameters()
|
|
284
|
+
return calc
|
|
285
|
+
|
|
286
|
+
def calculate_energy_and_gradient(self, coords_bohr, atom_symbols):
|
|
287
|
+
"""Calculates EMT energy (Hartree) and gradient (Hartree/Bohr)."""
|
|
288
|
+
if coords_bohr.shape[0] == 0:
|
|
289
|
+
return {"energy": 0.0, "gradient": np.zeros_like(coords_bohr)}
|
|
290
|
+
|
|
291
|
+
coords_angstrom = coords_bohr * self.UVL.bohr2angstroms
|
|
292
|
+
calculator = self._get_calculator(atom_symbols)
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
energy_hartree, forces_hartree_per_bohr = calculator.calculate_energy_and_forces(coords_angstrom)
|
|
296
|
+
gradient_hartree_bohr = -forces_hartree_per_bohr
|
|
297
|
+
return {"energy": energy_hartree, "gradient": gradient_hartree_bohr}
|
|
298
|
+
except NotImplementedError as e:
|
|
299
|
+
print(f"Error during EMT calculation: {e}")
|
|
300
|
+
return {"energy": 0.0, "gradient": np.zeros_like(coords_bohr)}
|
|
301
|
+
|
|
302
|
+
def calculate_hessian(self, coords_bohr, atom_symbols):
|
|
303
|
+
|
|
304
|
+
"""Calculates EMT Hessian (Hartree/Bohr^2) via finite differences."""
|
|
305
|
+
print("Warning: EMT Hessian calculation is not tested well. Use with caution.")
|
|
306
|
+
if coords_bohr.shape[0] == 0:
|
|
307
|
+
return {"hessian": np.zeros((0,0))}
|
|
308
|
+
|
|
309
|
+
# FIX: Convert coordinates from Bohr to Angstrom for Hessian calculation
|
|
310
|
+
coords_angstrom = coords_bohr * self.UVL.bohr2angstroms
|
|
311
|
+
calculator = self._get_calculator(atom_symbols)
|
|
312
|
+
|
|
313
|
+
hessian_hartree_per_bohr_sq = calculator.calculate_hessian(coords_angstrom)
|
|
314
|
+
return {"hessian": hessian_hartree_per_bohr_sq}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class Calculation:
|
|
318
|
+
def __init__(self, **kwarg):
|
|
319
|
+
UVL = UnitValueLib()
|
|
320
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
321
|
+
self.atom_symbol = kwarg.get("atom_symbol", None)
|
|
322
|
+
self.FC_COUNT = kwarg.get("FC_COUNT", -1)
|
|
323
|
+
self.Model_hess = kwarg.get("Model_hess")
|
|
324
|
+
self.hessian_flag = kwarg.get("hessian_flag", False)
|
|
325
|
+
self.calculator = EMTCore()
|
|
326
|
+
self.energy = None
|
|
327
|
+
self.gradient = None
|
|
328
|
+
self.coordinate = None
|
|
329
|
+
|
|
330
|
+
def exact_hessian(self, element_list, positions_bohr):
|
|
331
|
+
"""Calculates and projects the Hessian."""
|
|
332
|
+
results = self.calculator.calculate_hessian(positions_bohr, element_list)
|
|
333
|
+
exact_hess = results['hessian']
|
|
334
|
+
element_number_list = [element_number(elem) for elem in element_list]
|
|
335
|
+
self.Model_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(
|
|
336
|
+
exact_hess, element_number_list, positions_bohr, display_eigval=False
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def single_point(self, file_directory, element_list, iter, electric_charge_and_multiplicity, geom_num_list=None):
|
|
340
|
+
"""Executes an EMT single point calculation."""
|
|
341
|
+
finish_frag = False
|
|
342
|
+
e, g, positions_bohr = None, None, None
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
346
|
+
except (OSError, TypeError):
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
if element_list is not None and len(element_list) > 0 and (type(element_list[0]) is np.int64 or type(element_list[0]) is int or type(element_list[0]) is np.int32):
|
|
350
|
+
element_list = [number_element(elem) for elem in element_list]
|
|
351
|
+
|
|
352
|
+
if file_directory is None:
|
|
353
|
+
file_list = ["dummy"]
|
|
354
|
+
else:
|
|
355
|
+
file_list = sorted(glob.glob(os.path.join(file_directory, "*_[0-9].xyz")))
|
|
356
|
+
if not file_list and geom_num_list is None:
|
|
357
|
+
raise FileNotFoundError(f"No XYZ files found in {file_directory}")
|
|
358
|
+
|
|
359
|
+
for num, input_file in enumerate(file_list):
|
|
360
|
+
try:
|
|
361
|
+
if geom_num_list is None:
|
|
362
|
+
positions_angstrom, read_elements, _ = xyz2list(input_file, electric_charge_and_multiplicity)
|
|
363
|
+
|
|
364
|
+
if element_list is None or len(element_list) == 0:
|
|
365
|
+
element_list = read_elements
|
|
366
|
+
else:
|
|
367
|
+
positions_angstrom = geom_num_list
|
|
368
|
+
|
|
369
|
+
if self.atom_symbol is None and (element_list is not None and len(element_list) > 0):
|
|
370
|
+
unique_elements = set(element_list)
|
|
371
|
+
if len(unique_elements) == 1:
|
|
372
|
+
self.atom_symbol = unique_elements.pop()
|
|
373
|
+
print(f"System type detected as homo-atomic. Atom symbol set to '{self.atom_symbol}'.")
|
|
374
|
+
else:
|
|
375
|
+
print(f"System type detected as hetero-atomic: {unique_elements}")
|
|
376
|
+
|
|
377
|
+
positions_bohr = np.array(positions_angstrom, dtype="float64") / self.bohr2angstroms
|
|
378
|
+
results = self.calculator.calculate_energy_and_gradient(positions_bohr, element_list)
|
|
379
|
+
e, g = results['energy'], results['gradient']
|
|
380
|
+
|
|
381
|
+
if self.FC_COUNT == -1 or isinstance(iter, str):
|
|
382
|
+
if self.hessian_flag: self.exact_hessian(element_list, positions_bohr)
|
|
383
|
+
elif iter % self.FC_COUNT == 0 or self.hessian_flag:
|
|
384
|
+
self.exact_hessian(element_list, positions_bohr)
|
|
385
|
+
break
|
|
386
|
+
except Exception as error:
|
|
387
|
+
print(f"Error during EMT calculation for {input_file}: {error}")
|
|
388
|
+
finish_frag = True
|
|
389
|
+
return np.array([0]), np.array([0]), np.array([0]), finish_frag
|
|
390
|
+
|
|
391
|
+
self.energy, self.gradient, self.coordinate = e, g, positions_bohr
|
|
392
|
+
return e, g, positions_bohr, finish_frag
|
|
393
|
+
|
|
394
|
+
class CalculationEngine(ABC):
|
|
395
|
+
@abstractmethod
|
|
396
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
def _get_file_list(self, file_directory):
|
|
400
|
+
return sum([sorted(glob.glob(os.path.join(file_directory, f"*_" + "[0-9]" * i + ".xyz"))) for i in range(1, 7)], [])
|
|
401
|
+
|
|
402
|
+
def _process_visualization(self, energy_list, gradient_list, num_list, optimize_num, config):
|
|
403
|
+
try:
|
|
404
|
+
if hasattr(config, 'save_pict') and config.save_pict:
|
|
405
|
+
self.UVL = UnitValueLib()
|
|
406
|
+
visualizer = NEBVisualizer(config)
|
|
407
|
+
tmp_ene_list = np.array(energy_list, dtype="float64") * self.UVL.hartree2kcalmol
|
|
408
|
+
visualizer.plot_energy(num_list, tmp_ene_list - tmp_ene_list[0], optimize_num)
|
|
409
|
+
print("energy graph plotted.")
|
|
410
|
+
gradient_norm_list = [np.linalg.norm(g) for g in gradient_list if hasattr(g, 'size') and g.size > 0]
|
|
411
|
+
visualizer.plot_gradient(num_list, gradient_norm_list, optimize_num)
|
|
412
|
+
print("gradient graph plotted.")
|
|
413
|
+
except Exception as e:
|
|
414
|
+
print(f"Visualization error: {e}")
|
|
415
|
+
|
|
416
|
+
class EMTEngine(CalculationEngine):
|
|
417
|
+
def __init__(self, **kwargs):
|
|
418
|
+
super().__init__()
|
|
419
|
+
self.calculator = EMTCore()
|
|
420
|
+
self.UVL = UnitValueLib()
|
|
421
|
+
self.bohr2angstroms = self.UVL.bohr2angstroms
|
|
422
|
+
|
|
423
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
424
|
+
gradient_list, energy_list, geometry_num_list, num_list = [], [], [], []
|
|
425
|
+
delete_pre_total_velocity = []
|
|
426
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
427
|
+
file_list = self._get_file_list(file_directory)
|
|
428
|
+
|
|
429
|
+
if not file_list:
|
|
430
|
+
print(f"No XYZ files found in directory: {file_directory}")
|
|
431
|
+
return np.array([]), np.array([]), np.array([]), pre_total_velocity
|
|
432
|
+
|
|
433
|
+
for num, input_file in enumerate(file_list):
|
|
434
|
+
try:
|
|
435
|
+
print(f"Processing file: {input_file}")
|
|
436
|
+
positions_angstrom, element_list, _ = xyz2list(input_file, None)
|
|
437
|
+
|
|
438
|
+
if element_list is None or len(element_list) == 0:
|
|
439
|
+
raise ValueError("Element list from file is empty.")
|
|
440
|
+
|
|
441
|
+
positions_bohr = np.array(positions_angstrom, dtype='float64').reshape(-1, 3) / self.bohr2angstroms
|
|
442
|
+
results = self.calculator.calculate_energy_and_gradient(positions_bohr, element_list)
|
|
443
|
+
|
|
444
|
+
energy_list.append(results['energy'])
|
|
445
|
+
gradient_list.append(results['gradient'])
|
|
446
|
+
geometry_num_list.append(positions_angstrom)
|
|
447
|
+
num_list.append(num)
|
|
448
|
+
except Exception as error:
|
|
449
|
+
print(f"Error processing {input_file}: {error}")
|
|
450
|
+
if optimize_num != 0: delete_pre_total_velocity.append(num)
|
|
451
|
+
|
|
452
|
+
self._process_visualization(energy_list, gradient_list, num_list, optimize_num, config)
|
|
453
|
+
if optimize_num != 0 and hasattr(pre_total_velocity, '__len__') and len(pre_total_velocity) > 0 and delete_pre_total_velocity:
|
|
454
|
+
pre_total_velocity = np.delete(np.array(pre_total_velocity), delete_pre_total_velocity, axis=0)
|
|
455
|
+
return (np.array(energy_list, dtype='float64'),
|
|
456
|
+
np.array(gradient_list, dtype='float64'),
|
|
457
|
+
np.array(geometry_num_list, dtype='float64'),
|
|
458
|
+
pre_total_velocity)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import numpy as np
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from ase import Atoms
|
|
8
|
+
from ase.vibrations import Vibrations
|
|
9
|
+
from gpaw import GPAW
|
|
10
|
+
except ImportError:
|
|
11
|
+
print("ASE or GPAW is not installed. Please install them to use this module.")
|
|
12
|
+
|
|
13
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
14
|
+
from multioptpy.Parameters.parameter import UnitValueLib, number_element
|
|
15
|
+
from multioptpy.fileio import xyz2list
|
|
16
|
+
from multioptpy.Visualization.visualization import NEBVisualizer
|
|
17
|
+
|
|
18
|
+
class Calculation:
|
|
19
|
+
def __init__(self, **kwarg):
|
|
20
|
+
UVL = UnitValueLib()
|
|
21
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
22
|
+
self.hartree2eV = UVL.hartree2eV
|
|
23
|
+
|
|
24
|
+
self.START_FILE = kwarg["START_FILE"]
|
|
25
|
+
self.N_THREAD = kwarg["N_THREAD"]
|
|
26
|
+
self.SET_MEMORY = kwarg["SET_MEMORY"]
|
|
27
|
+
self.FUNCTIONAL = kwarg["FUNCTIONAL"]
|
|
28
|
+
self.FC_COUNT = kwarg["FC_COUNT"]
|
|
29
|
+
self.BPA_FOLDER_DIRECTORY = kwarg["BPA_FOLDER_DIRECTORY"]
|
|
30
|
+
self.Model_hess = kwarg["Model_hess"]
|
|
31
|
+
self.hessian_flag = False
|
|
32
|
+
|
|
33
|
+
def calc_exact_hess(self, atom_obj, positions, element_list):
|
|
34
|
+
vib = Vibrations(atom_obj, delta=0.001)
|
|
35
|
+
vib.run()
|
|
36
|
+
result_vib = vib.get_vibrations()
|
|
37
|
+
exact_hess = result_vib.get_hessian_2d()
|
|
38
|
+
vib.clean()
|
|
39
|
+
exact_hess = exact_hess / self.hartree2eV * (self.bohr2angstroms ** 2)
|
|
40
|
+
if type(element_list[0]) is str:
|
|
41
|
+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, positions)
|
|
42
|
+
else:
|
|
43
|
+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, [number_element(elem_num) for elem_num in element_list], positions)
|
|
44
|
+
self.Model_hess = exact_hess
|
|
45
|
+
return exact_hess
|
|
46
|
+
|
|
47
|
+
def single_point(self, file_directory, element_list, iter, electric_charge_and_multiplicity, method, geom_num_list=None):
|
|
48
|
+
finish_frag = False
|
|
49
|
+
try:
|
|
50
|
+
os.mkdir(file_directory)
|
|
51
|
+
except:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
if file_directory is None:
|
|
55
|
+
file_list = ["dummy"]
|
|
56
|
+
else:
|
|
57
|
+
file_list = glob.glob(file_directory + "/*_[0-9].xyz")
|
|
58
|
+
|
|
59
|
+
for num, input_file in enumerate(file_list):
|
|
60
|
+
try:
|
|
61
|
+
if geom_num_list is None:
|
|
62
|
+
positions, _, electric_charge_and_multiplicity = xyz2list(input_file, electric_charge_and_multiplicity)
|
|
63
|
+
else:
|
|
64
|
+
positions = geom_num_list
|
|
65
|
+
|
|
66
|
+
positions = np.array(positions, dtype="float64")
|
|
67
|
+
atom_obj = Atoms(element_list, positions)
|
|
68
|
+
atom_obj.calc = GPAW(mode='lcao', xc=self.FUNCTIONAL, txt=None) # You can configure GPAW parameters as needed
|
|
69
|
+
|
|
70
|
+
e = atom_obj.get_potential_energy(apply_constraint=False) / self.hartree2eV
|
|
71
|
+
g = -1 * atom_obj.get_forces(apply_constraint=False) * self.bohr2angstroms / self.hartree2eV
|
|
72
|
+
|
|
73
|
+
if self.FC_COUNT == -1 or type(iter) is str:
|
|
74
|
+
if self.hessian_flag:
|
|
75
|
+
_ = self.calc_exact_hess(atom_obj, positions, element_list)
|
|
76
|
+
elif iter % self.FC_COUNT == 0 or self.hessian_flag:
|
|
77
|
+
_ = self.calc_exact_hess(atom_obj, positions, element_list)
|
|
78
|
+
except Exception as error:
|
|
79
|
+
print(error)
|
|
80
|
+
print("This molecule could not be optimized.")
|
|
81
|
+
finish_frag = True
|
|
82
|
+
return np.array([0]), np.array([0]), np.array([0]), finish_frag
|
|
83
|
+
|
|
84
|
+
positions /= self.bohr2angstroms
|
|
85
|
+
self.energy = e
|
|
86
|
+
self.gradient = g
|
|
87
|
+
self.coordinate = positions
|
|
88
|
+
|
|
89
|
+
return e, g, positions, finish_frag
|
|
90
|
+
|
|
91
|
+
class GPAWEngine(ABC):
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def _get_file_list(self, file_directory):
|
|
97
|
+
return sum([sorted(glob.glob(os.path.join(file_directory, f"*_" + "[0-9]" * i + ".xyz")))
|
|
98
|
+
for i in range(1, 7)], [])
|
|
99
|
+
|
|
100
|
+
def _process_visualization(self, energy_list, gradient_list, num_list, optimize_num, config):
|
|
101
|
+
try:
|
|
102
|
+
if config.save_pict:
|
|
103
|
+
visualizer = NEBVisualizer(config)
|
|
104
|
+
tmp_ene_list = np.array(energy_list, dtype="float64") * config.hartree2kcalmol
|
|
105
|
+
visualizer.plot_energy(num_list, tmp_ene_list - tmp_ene_list[0], optimize_num)
|
|
106
|
+
print("energy graph plotted.")
|
|
107
|
+
gradient_norm_list = [np.sqrt(np.linalg.norm(g) ** 2 / (len(g) * 3)) for g in gradient_list]
|
|
108
|
+
visualizer.plot_gradient(num_list, gradient_norm_list, optimize_num)
|
|
109
|
+
print("gradient graph plotted.")
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"Visualization error: {e}")
|
|
112
|
+
|
|
113
|
+
class GPAWASEEngine(GPAWEngine):
|
|
114
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
115
|
+
gradient_list = []
|
|
116
|
+
energy_list = []
|
|
117
|
+
geometry_num_list = []
|
|
118
|
+
num_list = []
|
|
119
|
+
delete_pre_total_velocity = []
|
|
120
|
+
|
|
121
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
122
|
+
file_list = self._get_file_list(file_directory)
|
|
123
|
+
|
|
124
|
+
if not file_list:
|
|
125
|
+
print("No input files found in directory.")
|
|
126
|
+
return (np.array([], dtype="float64"),
|
|
127
|
+
np.array([], dtype="float64"),
|
|
128
|
+
np.array([], dtype="float64"),
|
|
129
|
+
pre_total_velocity)
|
|
130
|
+
|
|
131
|
+
geometry_list_tmp, element_list, _ = xyz2list(file_list[0], None)
|
|
132
|
+
|
|
133
|
+
hess_count = 0
|
|
134
|
+
for num, input_file in enumerate(file_list):
|
|
135
|
+
try:
|
|
136
|
+
print(f"\n{input_file}\n")
|
|
137
|
+
positions, _, electric_charge_and_multiplicity = xyz2list(input_file, None)
|
|
138
|
+
|
|
139
|
+
positions = np.array(positions, dtype="float64")
|
|
140
|
+
atom_obj = Atoms(element_list, positions)
|
|
141
|
+
atom_obj.calc = GPAW(mode='lcao', xc=config.FUNCTIONAL, txt=None)
|
|
142
|
+
|
|
143
|
+
e = atom_obj.get_potential_energy(apply_constraint=False) / config.hartree2eV
|
|
144
|
+
g = -1 * atom_obj.get_forces(apply_constraint=False) * config.bohr2angstroms / config.hartree2eV
|
|
145
|
+
|
|
146
|
+
energy_list.append(e)
|
|
147
|
+
gradient_list.append(g)
|
|
148
|
+
geometry_num_list.append(positions / config.bohr2angstroms)
|
|
149
|
+
num_list.append(num)
|
|
150
|
+
|
|
151
|
+
if config.FC_COUNT == -1 or isinstance(optimize_num, str):
|
|
152
|
+
pass
|
|
153
|
+
elif optimize_num % config.FC_COUNT == 0:
|
|
154
|
+
vib = Vibrations(atom_obj, delta=0.001)
|
|
155
|
+
vib.run()
|
|
156
|
+
result_vib = vib.get_vibrations()
|
|
157
|
+
exact_hess = result_vib.get_hessian_2d()
|
|
158
|
+
vib.clean()
|
|
159
|
+
exact_hess = exact_hess / config.hartree2eV * (config.bohr2angstroms ** 2)
|
|
160
|
+
calc_tools = Calculationtools()
|
|
161
|
+
exact_hess = calc_tools.project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, positions)
|
|
162
|
+
np.save(os.path.join(config.NEB_FOLDER_DIRECTORY, f"tmp_hessian_{hess_count}.npy"), exact_hess)
|
|
163
|
+
hess_count += 1
|
|
164
|
+
|
|
165
|
+
except Exception as error:
|
|
166
|
+
print(f"Error: {error}")
|
|
167
|
+
print("This molecule could not be optimized.")
|
|
168
|
+
if optimize_num != 0:
|
|
169
|
+
delete_pre_total_velocity.append(num)
|
|
170
|
+
|
|
171
|
+
self._process_visualization(energy_list, gradient_list, num_list, optimize_num, config)
|
|
172
|
+
|
|
173
|
+
if optimize_num != 0 and len(pre_total_velocity) != 0:
|
|
174
|
+
pre_total_velocity = np.array(pre_total_velocity, dtype="float64")
|
|
175
|
+
pre_total_velocity = pre_total_velocity.tolist()
|
|
176
|
+
for i in sorted(delete_pre_total_velocity, reverse=True):
|
|
177
|
+
pre_total_velocity.pop(i)
|
|
178
|
+
pre_total_velocity = np.array(pre_total_velocity, dtype="float64")
|
|
179
|
+
|
|
180
|
+
return (np.array(energy_list, dtype="float64"),
|
|
181
|
+
np.array(gradient_list, dtype="float64"),
|
|
182
|
+
np.array(geometry_num_list, dtype="float64"),
|
|
183
|
+
pre_total_velocity)
|