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,314 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import numpy as np
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
8
|
+
from multioptpy.Parameters.parameter import UnitValueLib, number_element
|
|
9
|
+
from multioptpy.fileio import xyz2list
|
|
10
|
+
from multioptpy.Visualization.visualization import NEBVisualizer
|
|
11
|
+
|
|
12
|
+
class LennardJonesCore:
|
|
13
|
+
"""
|
|
14
|
+
Core calculator for Lennard-Jones potential using UFF parameters.
|
|
15
|
+
Handles both homo- and hetero-atomic clusters using combining rules.
|
|
16
|
+
"""
|
|
17
|
+
# UFF parameters with well depth D_i in kcal/mol.
|
|
18
|
+
# Source: Rappe, A. K., et al. J. Am. Chem. Soc. 1992, 114, 10024-10035.
|
|
19
|
+
UFF_PARAMETERS = {
|
|
20
|
+
'He': {'x_i': 2.868, 'D_i': 0.0216},
|
|
21
|
+
'Ne': {'x_i': 3.087, 'D_i': 0.0731},
|
|
22
|
+
'Ar': {'x_i': 3.817, 'D_i': 0.237},
|
|
23
|
+
'Kr': {'x_i': 4.047, 'D_i': 0.357},
|
|
24
|
+
'Xe': {'x_i': 4.363, 'D_i': 0.507},
|
|
25
|
+
'Rn': {'x_i': 4.500, 'D_i': 0.635}, # Extrapolated/common value
|
|
26
|
+
}
|
|
27
|
+
SIGMA_CONV_FACTOR = 1 / (2**(1/6))
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initializes a general Lennard-Jones calculator."""
|
|
31
|
+
self.UVL = UnitValueLib()
|
|
32
|
+
# Cache for memoizing parameters of atom types
|
|
33
|
+
self._param_cache = {}
|
|
34
|
+
|
|
35
|
+
def get_parameters(self, atom_symbols):
|
|
36
|
+
"""
|
|
37
|
+
Retrieves and converts UFF parameters for a list of atom symbols.
|
|
38
|
+
Returns arrays of sigma and epsilon values for each atom in the list.
|
|
39
|
+
"""
|
|
40
|
+
sigmas = np.zeros(len(atom_symbols))
|
|
41
|
+
epsilons = np.zeros(len(atom_symbols))
|
|
42
|
+
|
|
43
|
+
for i, symbol in enumerate(atom_symbols):
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if symbol not in self._param_cache:
|
|
47
|
+
if symbol not in self.UFF_PARAMETERS:
|
|
48
|
+
raise ValueError(f"Atom symbol '{symbol}' is not supported. "
|
|
49
|
+
f"Supported: {list(self.UFF_PARAMETERS.keys())}")
|
|
50
|
+
|
|
51
|
+
params = self.UFF_PARAMETERS[symbol]
|
|
52
|
+
sigma_angstrom = params['x_i'] * self.SIGMA_CONV_FACTOR
|
|
53
|
+
sigma = sigma_angstrom / self.UVL.bohr2angstroms
|
|
54
|
+
# Corrected conversion from kcal/mol to hartree
|
|
55
|
+
epsilon = params['D_i'] / self.UVL.hartree2kcalmol
|
|
56
|
+
self._param_cache[symbol] = (sigma, epsilon)
|
|
57
|
+
|
|
58
|
+
sigmas[i], epsilons[i] = self._param_cache[symbol]
|
|
59
|
+
|
|
60
|
+
return sigmas, epsilons
|
|
61
|
+
|
|
62
|
+
def calculate_energy_and_gradient(self, coords_bohr, atom_symbols):
|
|
63
|
+
"""Calculates the LJ energy and gradient for a list of atoms."""
|
|
64
|
+
num_atoms = coords_bohr.shape[0]
|
|
65
|
+
if num_atoms <= 1:
|
|
66
|
+
return {"energy": 0.0, "gradient": np.zeros_like(coords_bohr)}
|
|
67
|
+
|
|
68
|
+
base_sigmas, base_epsilons = self.get_parameters(atom_symbols)
|
|
69
|
+
|
|
70
|
+
a, b = np.triu_indices(num_atoms, 1)
|
|
71
|
+
diffs = coords_bohr[a] - coords_bohr[b]
|
|
72
|
+
dists_sq = np.sum(diffs**2, axis=1)
|
|
73
|
+
dists = np.sqrt(dists_sq)
|
|
74
|
+
|
|
75
|
+
# Apply Lorentz-Berthelot combining rules for each pair
|
|
76
|
+
sigmas_ab = (base_sigmas[a] + base_sigmas[b]) / 2.0
|
|
77
|
+
epsilons_ab = np.sqrt(base_epsilons[a] * base_epsilons[b])
|
|
78
|
+
|
|
79
|
+
sigma_over_r = sigmas_ab / dists
|
|
80
|
+
sigma_over_r_6 = sigma_over_r**6
|
|
81
|
+
sigma_over_r_12 = sigma_over_r_6**2
|
|
82
|
+
|
|
83
|
+
energy = np.sum(4 * epsilons_ab * (sigma_over_r_12 - sigma_over_r_6))
|
|
84
|
+
|
|
85
|
+
grad_mag_over_r = -24 * epsilons_ab / dists_sq * (2 * sigma_over_r_12 - sigma_over_r_6)
|
|
86
|
+
grad_pairs = grad_mag_over_r[:, np.newaxis] * diffs
|
|
87
|
+
gradient = np.zeros_like(coords_bohr)
|
|
88
|
+
np.add.at(gradient, a, grad_pairs)
|
|
89
|
+
np.add.at(gradient, b, -grad_pairs)
|
|
90
|
+
|
|
91
|
+
return {"energy": energy, "gradient": gradient}
|
|
92
|
+
|
|
93
|
+
def calculate_hessian(self, coords_bohr, atom_symbols):
|
|
94
|
+
"""Calculates the LJ Hessian for a list of atoms using a vectorized approach."""
|
|
95
|
+
num_atoms = coords_bohr.shape[0]
|
|
96
|
+
hessian = np.zeros((num_atoms * 3, num_atoms * 3))
|
|
97
|
+
if num_atoms <= 1:
|
|
98
|
+
return {"hessian": hessian}
|
|
99
|
+
|
|
100
|
+
base_sigmas, base_epsilons = self.get_parameters(atom_symbols)
|
|
101
|
+
|
|
102
|
+
a, b = np.triu_indices(num_atoms, 1)
|
|
103
|
+
diffs = coords_bohr[a] - coords_bohr[b]
|
|
104
|
+
dists_sq = np.sum(diffs**2, axis=1)
|
|
105
|
+
dists = np.sqrt(dists_sq)
|
|
106
|
+
|
|
107
|
+
sigmas_ab = (base_sigmas[a] + base_sigmas[b]) / 2.0
|
|
108
|
+
epsilons_ab = np.sqrt(base_epsilons[a] * base_epsilons[b])
|
|
109
|
+
|
|
110
|
+
sigma_over_r = sigmas_ab / dists
|
|
111
|
+
sigma_over_r_6 = sigma_over_r**6
|
|
112
|
+
sigma_over_r_12 = sigma_over_r_6**2
|
|
113
|
+
|
|
114
|
+
grad_mag_over_r = -24 * epsilons_ab / dists_sq * (2 * sigma_over_r_12 - sigma_over_r_6)
|
|
115
|
+
d2V_dr2 = 24 * epsilons_ab / dists_sq * (26 * sigma_over_r_12 - 7 * sigma_over_r_6)
|
|
116
|
+
dV_dr_over_r = -grad_mag_over_r
|
|
117
|
+
|
|
118
|
+
term1_mag = (d2V_dr2 - dV_dr_over_r) / dists_sq
|
|
119
|
+
term2_mag = dV_dr_over_r
|
|
120
|
+
term1 = np.einsum('p,pi,pj->pij', term1_mag, diffs, diffs)
|
|
121
|
+
term2 = np.identity(3)[np.newaxis, :, :] * term2_mag[:, np.newaxis, np.newaxis]
|
|
122
|
+
sub_hessians = term1 + term2
|
|
123
|
+
|
|
124
|
+
# **Vectorized Hessian Assembly**
|
|
125
|
+
# Create meshgrid of indices for all 3x3 sub-blocks
|
|
126
|
+
p, q = np.meshgrid(np.arange(3), np.arange(3), indexing='ij')
|
|
127
|
+
|
|
128
|
+
# Indices for off-diagonal blocks (a, b) and (b, a)
|
|
129
|
+
row_indices_ab = (a[:, None, None] * 3 + p).flatten()
|
|
130
|
+
col_indices_ab = (b[:, None, None] * 3 + q).flatten()
|
|
131
|
+
|
|
132
|
+
# Indices for diagonal blocks (a, a) and (b, b)
|
|
133
|
+
row_indices_aa = (a[:, None, None] * 3 + p).flatten()
|
|
134
|
+
col_indices_aa = (a[:, None, None] * 3 + q).flatten()
|
|
135
|
+
row_indices_bb = (b[:, None, None] * 3 + p).flatten()
|
|
136
|
+
col_indices_bb = (b[:, None, None] * 3 + q).flatten()
|
|
137
|
+
|
|
138
|
+
# Flatten the sub-hessian blocks to match the indices
|
|
139
|
+
flat_sub_hessians = sub_hessians.flatten()
|
|
140
|
+
|
|
141
|
+
# Atomically add/subtract the blocks into the Hessian matrix
|
|
142
|
+
np.subtract.at(hessian, (row_indices_ab, col_indices_ab), flat_sub_hessians)
|
|
143
|
+
np.subtract.at(hessian, (col_indices_ab, row_indices_ab), flat_sub_hessians)
|
|
144
|
+
np.add.at(hessian, (row_indices_aa, col_indices_aa), flat_sub_hessians)
|
|
145
|
+
np.add.at(hessian, (row_indices_bb, col_indices_bb), flat_sub_hessians)
|
|
146
|
+
|
|
147
|
+
return {"hessian": hessian}
|
|
148
|
+
|
|
149
|
+
class Calculation:
|
|
150
|
+
"""
|
|
151
|
+
High-level wrapper for Lennard-Jones calculations.
|
|
152
|
+
"""
|
|
153
|
+
def __init__(self, **kwarg):
|
|
154
|
+
UVL = UnitValueLib()
|
|
155
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
156
|
+
self.atom_symbol = kwarg.get("atom_symbol", None) # Can be None initially
|
|
157
|
+
self.FC_COUNT = kwarg.get("FC_COUNT", -1)
|
|
158
|
+
self.Model_hess = kwarg.get("Model_hess")
|
|
159
|
+
self.hessian_flag = kwarg.get("hessian_flag", False)
|
|
160
|
+
self.calculator = LennardJonesCore()
|
|
161
|
+
self.energy = None
|
|
162
|
+
self.gradient = None
|
|
163
|
+
self.coordinate = None
|
|
164
|
+
|
|
165
|
+
def exact_hessian(self, element_list, positions_bohr):
|
|
166
|
+
"""Calculates and projects the Hessian."""
|
|
167
|
+
results = self.calculator.calculate_hessian(positions_bohr, self.atom_symbol)
|
|
168
|
+
exact_hess = results['hessian']
|
|
169
|
+
|
|
170
|
+
self.Model_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(
|
|
171
|
+
exact_hess, element_list, positions_bohr, display_eigval=False
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def single_point(self, file_directory, element_list, iter, electric_charge_and_multiplicity, method="", geom_num_list=None):
|
|
175
|
+
"""
|
|
176
|
+
Executes a Lennard-Jones single point calculation, reading from a file
|
|
177
|
+
or using a provided geometry.
|
|
178
|
+
"""
|
|
179
|
+
finish_frag = False
|
|
180
|
+
e, g, positions_bohr = None, None, None
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
184
|
+
except (OSError, TypeError): # TypeError if file_directory is None
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
if file_directory is None:
|
|
188
|
+
file_list = ["dummy"] # To run the loop once for geom_num_list
|
|
189
|
+
else:
|
|
190
|
+
file_list = sorted(glob.glob(os.path.join(file_directory, "*_[0-9].xyz")))
|
|
191
|
+
if not file_list and geom_num_list is None:
|
|
192
|
+
raise FileNotFoundError(f"No XYZ files found in {file_directory}")
|
|
193
|
+
|
|
194
|
+
for num, input_file in enumerate(file_list):
|
|
195
|
+
try:
|
|
196
|
+
positions_angstrom = None
|
|
197
|
+
if geom_num_list is None:
|
|
198
|
+
positions_angstrom, read_elements, _ = xyz2list(input_file, electric_charge_and_multiplicity)
|
|
199
|
+
|
|
200
|
+
element_list = read_elements
|
|
201
|
+
else:
|
|
202
|
+
positions_angstrom = geom_num_list
|
|
203
|
+
|
|
204
|
+
if self.atom_symbol is None:
|
|
205
|
+
if element_list is None or len(element_list) == 0:
|
|
206
|
+
raise ValueError("Element list is empty. Cannot determine atom symbol.")
|
|
207
|
+
first_element = element_list
|
|
208
|
+
if type(element_list[0]) is not str:
|
|
209
|
+
first_element = []
|
|
210
|
+
for i in range(len(element_list)):
|
|
211
|
+
first_element.append(number_element(element_list[i]))
|
|
212
|
+
|
|
213
|
+
self.atom_symbol = first_element
|
|
214
|
+
print(f"Atom symbol set to '{self.atom_symbol}' based on the first structure.")
|
|
215
|
+
|
|
216
|
+
positions_bohr = np.array(positions_angstrom, dtype="float64") / self.bohr2angstroms
|
|
217
|
+
|
|
218
|
+
results = self.calculator.calculate_energy_and_gradient(positions_bohr, self.atom_symbol)
|
|
219
|
+
e = results['energy']
|
|
220
|
+
g = results['gradient']
|
|
221
|
+
|
|
222
|
+
if self.FC_COUNT == -1 or isinstance(iter, str):
|
|
223
|
+
if self.hessian_flag:
|
|
224
|
+
self.exact_hessian(element_list, positions_bohr)
|
|
225
|
+
elif iter % self.FC_COUNT == 0 or self.hessian_flag:
|
|
226
|
+
self.exact_hessian(element_list, positions_bohr)
|
|
227
|
+
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
except Exception as error:
|
|
231
|
+
print(f"Error during Lennard-Jones calculation for {input_file}: {error}")
|
|
232
|
+
finish_frag = True
|
|
233
|
+
return np.array([0]), np.array([0]), np.array([0]), finish_frag
|
|
234
|
+
|
|
235
|
+
self.energy = e
|
|
236
|
+
self.gradient = g
|
|
237
|
+
self.coordinate = positions_bohr
|
|
238
|
+
return e, g, positions_bohr, finish_frag
|
|
239
|
+
|
|
240
|
+
class CalculationEngine(ABC):
|
|
241
|
+
@abstractmethod
|
|
242
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
243
|
+
pass
|
|
244
|
+
def _get_file_list(self, file_directory):
|
|
245
|
+
return sum([sorted(glob.glob(os.path.join(file_directory, f"*_" + "[0-9]" * i + ".xyz"))) for i in range(1, 7)], [])
|
|
246
|
+
def _process_visualization(self, energy_list, gradient_list, num_list, optimize_num, config):
|
|
247
|
+
try:
|
|
248
|
+
if hasattr(config, 'save_pict') and config.save_pict:
|
|
249
|
+
visualizer = NEBVisualizer(config)
|
|
250
|
+
tmp_ene_list = np.array(energy_list, dtype="float64") * config.hartree2kcalmol
|
|
251
|
+
visualizer.plot_energy(num_list, tmp_ene_list - tmp_ene_list[0], optimize_num)
|
|
252
|
+
print("energy graph plotted.")
|
|
253
|
+
gradient_norm_list = [np.sqrt(np.linalg.norm(g)**2 / (len(g) * 3)) for g in gradient_list if g.size > 0]
|
|
254
|
+
visualizer.plot_gradient(num_list, gradient_norm_list, optimize_num)
|
|
255
|
+
print("gradient graph plotted.")
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"Visualization error: {e}")
|
|
258
|
+
|
|
259
|
+
class LJEngine(CalculationEngine):
|
|
260
|
+
def __init__(self, atom_symbol=None):
|
|
261
|
+
super().__init__()
|
|
262
|
+
self.atom_symbol = atom_symbol
|
|
263
|
+
self.calculator = LennardJonesCore()
|
|
264
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
265
|
+
|
|
266
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
267
|
+
gradient_list, energy_list, geometry_num_list, num_list = [], [], [], []
|
|
268
|
+
delete_pre_total_velocity = []
|
|
269
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
270
|
+
file_list = self._get_file_list(file_directory)
|
|
271
|
+
|
|
272
|
+
if not file_list:
|
|
273
|
+
print(f"No XYZ files found in directory: {file_directory}")
|
|
274
|
+
return np.array([]), np.array([]), np.array([]), pre_total_velocity
|
|
275
|
+
|
|
276
|
+
for num, input_file in enumerate(file_list):
|
|
277
|
+
try:
|
|
278
|
+
|
|
279
|
+
print(f"Processing file: {input_file}")
|
|
280
|
+
positions_angstrom, element_list, _ = xyz2list(input_file, None)
|
|
281
|
+
|
|
282
|
+
if self.atom_symbol is None:
|
|
283
|
+
if element_list is None or len(element_list) == 0:
|
|
284
|
+
raise ValueError("Element list from file is empty.")
|
|
285
|
+
first_element = element_list
|
|
286
|
+
if type(element_list[0]) is not str:
|
|
287
|
+
first_element = []
|
|
288
|
+
for i in range(len(element_list)):
|
|
289
|
+
first_element.append(number_element(element_list[i]))
|
|
290
|
+
|
|
291
|
+
self.atom_symbol = first_element
|
|
292
|
+
print(f"Engine atom symbol set to '{self.atom_symbol}' based on the first file.")
|
|
293
|
+
|
|
294
|
+
positions_angstrom = np.array(positions_angstrom, dtype='float64').reshape(-1, 3)
|
|
295
|
+
positions_bohr = positions_angstrom / self.bohr2angstroms
|
|
296
|
+
|
|
297
|
+
results = self.calculator.calculate_energy_and_gradient(positions_bohr, self.atom_symbol)
|
|
298
|
+
|
|
299
|
+
energy_list.append(results['energy'])
|
|
300
|
+
gradient_list.append(results['gradient'])
|
|
301
|
+
geometry_num_list.append(positions_bohr)
|
|
302
|
+
num_list.append(num)
|
|
303
|
+
except Exception as error:
|
|
304
|
+
print(f"Error processing {input_file}: {error}")
|
|
305
|
+
if optimize_num != 0:
|
|
306
|
+
delete_pre_total_velocity.append(num)
|
|
307
|
+
|
|
308
|
+
self._process_visualization(energy_list, gradient_list, num_list, optimize_num, config)
|
|
309
|
+
if optimize_num != 0 and len(pre_total_velocity) > 0 and delete_pre_total_velocity:
|
|
310
|
+
pre_total_velocity = np.delete(np.array(pre_total_velocity), delete_pre_total_velocity, axis=0)
|
|
311
|
+
return (np.array(energy_list, dtype='float64'),
|
|
312
|
+
np.array(gradient_list, dtype='float64'),
|
|
313
|
+
np.array(geometry_num_list, dtype='float64'),
|
|
314
|
+
pre_total_velocity)
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import psi4
|
|
9
|
+
except:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
13
|
+
from multioptpy.fileio import xyz2list
|
|
14
|
+
from multioptpy.Visualization.visualization import NEBVisualizer
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Psi4
|
|
18
|
+
D. G. A. Smith, L. A. Burns, A. C. Simmonett, R. M. Parrish, M. C. Schieber, R. Galvelis, P. Kraus, H. Kruse, R. Di Remigio, A. Alenaizan, A. M. James, S. Lehtola, J. P. Misiewicz, M. Scheurer, R. A. Shaw, J. B. Schriber, Y. Xie, Z. L. Glick, D. A. Sirianni, J. S. O'Brien, J. M. Waldrop, A. Kumar, E. G. Hohenstein, B. P. Pritchard, B. R. Brooks, H. F. Schaefer III, A. Yu. Sokolov, K. Patkowski, A. E. DePrince III, U. Bozkaya, R. A. King, F. A. Evangelista, J. M. Turney, T. D. Crawford, C. D. Sherrill, "Psi4 1.4: Open-Source Software for High-Throughput Quantum Chemistry", J. Chem. Phys. 152(18) 184108 (2020).
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
class Calculation:
|
|
22
|
+
def __init__(self, **kwarg):
|
|
23
|
+
|
|
24
|
+
self.START_FILE = kwarg["START_FILE"]
|
|
25
|
+
self.SUB_BASIS_SET = kwarg["SUB_BASIS_SET"]
|
|
26
|
+
self.BASIS_SET = kwarg["BASIS_SET"]
|
|
27
|
+
self.N_THREAD = kwarg["N_THREAD"]
|
|
28
|
+
self.SET_MEMORY = kwarg["SET_MEMORY"]
|
|
29
|
+
self.FUNCTIONAL = kwarg["FUNCTIONAL"]
|
|
30
|
+
self.FC_COUNT = kwarg["FC_COUNT"]
|
|
31
|
+
self.BPA_FOLDER_DIRECTORY = kwarg["BPA_FOLDER_DIRECTORY"]
|
|
32
|
+
self.Model_hess = kwarg["Model_hess"]
|
|
33
|
+
self.unrestrict = kwarg["unrestrict"]
|
|
34
|
+
self.dft_grid = kwarg["dft_grid"]
|
|
35
|
+
self.hessian_flag = False
|
|
36
|
+
if kwarg["excited_state"]:
|
|
37
|
+
self.excited_state = kwarg["excited_state"]
|
|
38
|
+
else:
|
|
39
|
+
self.excited_state = 0
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
def set_dft_grid(self):
|
|
43
|
+
"""set dft grid"""
|
|
44
|
+
if self.dft_grid == 0 or self.dft_grid == 1:
|
|
45
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 50, 'DFT_SPHERICAL_POINTS': 194})
|
|
46
|
+
print("DFT Grid (50, 194): SG1")
|
|
47
|
+
elif self.dft_grid == 2 or self.dft_grid == 3:
|
|
48
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 75, 'DFT_SPHERICAL_POINTS': 302})
|
|
49
|
+
print("DFT Grid (70, 302): Default")
|
|
50
|
+
elif self.dft_grid == 4 or self.dft_grid == 5:
|
|
51
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 99, 'DFT_SPHERICAL_POINTS': 590})
|
|
52
|
+
print("DFT Grid (99, 590): Fine")
|
|
53
|
+
elif self.dft_grid == 6 or self.dft_grid == 7:
|
|
54
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 150, 'DFT_SPHERICAL_POINTS': 770})
|
|
55
|
+
print("DFT Grid (150, 770): UltraFine")
|
|
56
|
+
elif self.dft_grid == 8 or self.dft_grid == 9:
|
|
57
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 250, 'DFT_SPHERICAL_POINTS': 974})
|
|
58
|
+
print("DFT Grid (250, 974): SuperFine")
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError("Invalid dft grid setting.")
|
|
61
|
+
|
|
62
|
+
def single_point(self, file_directory, element_list, iter, electric_charge_and_multiplicity, method="", geom_num_list=None):
|
|
63
|
+
"""execute QM calclation."""
|
|
64
|
+
finish_frag = False
|
|
65
|
+
input_data_for_display = None
|
|
66
|
+
try:
|
|
67
|
+
os.mkdir(file_directory)
|
|
68
|
+
except:
|
|
69
|
+
pass
|
|
70
|
+
if file_directory is None:
|
|
71
|
+
file_list = ["dummy"]
|
|
72
|
+
else:
|
|
73
|
+
file_list = glob.glob(file_directory+"/*_[0-9].xyz")
|
|
74
|
+
|
|
75
|
+
for num, input_file in enumerate(file_list):
|
|
76
|
+
try:
|
|
77
|
+
|
|
78
|
+
if int(electric_charge_and_multiplicity[1]) > 1 or self.unrestrict:
|
|
79
|
+
psi4.set_options({'reference': 'uks'})
|
|
80
|
+
logfile = file_directory+"/"+self.START_FILE[:-4]+'_'+str(num)+'.log'
|
|
81
|
+
psi4.set_options({"MAXITER": 500})
|
|
82
|
+
self.set_dft_grid()
|
|
83
|
+
if len(self.SUB_BASIS_SET) > 0:
|
|
84
|
+
psi4.basis_helper(self.SUB_BASIS_SET, name='User_Basis_Set', set_option=False)
|
|
85
|
+
psi4.set_options({"basis":'User_Basis_Set'})
|
|
86
|
+
else:
|
|
87
|
+
psi4.set_options({"basis":self.BASIS_SET})
|
|
88
|
+
|
|
89
|
+
if self.excited_state > 0:
|
|
90
|
+
psi4.set_options({'TDSCF_STATES': self.excited_state})
|
|
91
|
+
|
|
92
|
+
psi4.set_output_file(logfile)
|
|
93
|
+
psi4.set_num_threads(nthread=self.N_THREAD)
|
|
94
|
+
psi4.set_memory(self.SET_MEMORY)
|
|
95
|
+
#psi4.procrouting.response.scf_response.tdscf_excitations
|
|
96
|
+
psi4.set_options({"cubeprop_tasks": ["esp"],'cubeprop_filepath': file_directory})
|
|
97
|
+
|
|
98
|
+
if geom_num_list is None:
|
|
99
|
+
|
|
100
|
+
input_data = ""
|
|
101
|
+
position, element_list, electric_charge_and_multiplicity = xyz2list(input_file, electric_charge_and_multiplicity)
|
|
102
|
+
input_data_for_display = np.array(position, dtype="float64")/psi4.constants.bohr2angstroms
|
|
103
|
+
input_data += " ".join(list(map(str, electric_charge_and_multiplicity)))+"\n"
|
|
104
|
+
for j in range(len(position)):
|
|
105
|
+
input_data += element_list[j]+" "+" ".join(position[j])+"\n"
|
|
106
|
+
else:
|
|
107
|
+
print("Input data is given as a numpy array.")
|
|
108
|
+
input_data = ""
|
|
109
|
+
|
|
110
|
+
input_data += " ".join(list(map(str, electric_charge_and_multiplicity)))+"\n"
|
|
111
|
+
for j in range(len(geom_num_list)):
|
|
112
|
+
input_data += element_list[j]+" "+" ".join(list(map(str, geom_num_list[j].tolist())))+"\n"
|
|
113
|
+
input_data_for_display = geom_num_list / psi4.constants.bohr2angstroms
|
|
114
|
+
|
|
115
|
+
input_data = psi4.geometry(input_data)#ang.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
g, wfn = psi4.gradient(self.FUNCTIONAL, molecule=input_data, return_wfn=True)
|
|
119
|
+
|
|
120
|
+
e = float(wfn.energy())
|
|
121
|
+
g = np.array(g, dtype = "float64")
|
|
122
|
+
psi4.oeprop(wfn, 'DIPOLE')
|
|
123
|
+
psi4.oeprop(wfn, 'MULLIKEN_CHARGES')
|
|
124
|
+
psi4.oeprop(wfn, 'LOWDIN_CHARGES')
|
|
125
|
+
#psi4.oeprop(wfn, 'WIBERG_LOWDIN_INDICES')
|
|
126
|
+
lumo_alpha = wfn.nalpha()
|
|
127
|
+
lumo_beta = wfn.nbeta()
|
|
128
|
+
|
|
129
|
+
MO_levels = np.array(wfn.epsilon_a_subset("AO","ALL")).tolist()#MO energy levels
|
|
130
|
+
with open(self.BPA_FOLDER_DIRECTORY+"MO_levels.csv" ,"a") as f:
|
|
131
|
+
f.write(",".join(list(map(str,MO_levels))+[str(lumo_alpha),str(lumo_beta)])+"\n")
|
|
132
|
+
with open(self.BPA_FOLDER_DIRECTORY+"dipole.csv" ,"a") as f:
|
|
133
|
+
f.write(",".join(list(map(str,(psi4.constants.dipmom_au2debye*wfn.variable('DIPOLE')).tolist()))+[str(np.linalg.norm(psi4.constants.dipmom_au2debye*wfn.variable('DIPOLE'),ord=2))])+"\n")
|
|
134
|
+
with open(self.BPA_FOLDER_DIRECTORY+"MULLIKEN_CHARGES.csv" ,"a") as f:
|
|
135
|
+
f.write(",".join(list(map(str,wfn.variable('MULLIKEN CHARGES').tolist())))+"\n")
|
|
136
|
+
|
|
137
|
+
alpha_first_ionization_energy = -1 * MO_levels[lumo_alpha-1]
|
|
138
|
+
alpha_electron_affinity = MO_levels[lumo_alpha]
|
|
139
|
+
global_electrophilicity_index = (alpha_first_ionization_energy + alpha_electron_affinity) / (8 * (alpha_first_ionization_energy - alpha_electron_affinity + 1e-15))
|
|
140
|
+
|
|
141
|
+
print("=== global electrophilicity index ===")
|
|
142
|
+
print(global_electrophilicity_index, "hartree")
|
|
143
|
+
|
|
144
|
+
#with open(input_file[:-4]+"_WIBERG_LOWDIN_INDICES.csv" ,"a") as f:
|
|
145
|
+
# for i in range(len(np.array(wfn.variable('WIBERG LOWDIN INDICES')).tolist())):
|
|
146
|
+
# f.write(",".join(list(map(str,np.array(wfn.variable('WIBERG LOWDIN INDICES')).tolist()[i])))+"\n")
|
|
147
|
+
|
|
148
|
+
print("\n")
|
|
149
|
+
|
|
150
|
+
if self.FC_COUNT == -1 or type(iter) is str:
|
|
151
|
+
if self.hessian_flag:
|
|
152
|
+
self.exact_hessian(element_list, input_data_for_display, wfn)
|
|
153
|
+
|
|
154
|
+
elif iter % self.FC_COUNT == 0 or self.hessian_flag:
|
|
155
|
+
self.exact_hessian(element_list, input_data_for_display, wfn)
|
|
156
|
+
|
|
157
|
+
except Exception as error:
|
|
158
|
+
print(error)
|
|
159
|
+
print("This molecule could not be optimized.")
|
|
160
|
+
print("Input file: ",file_list,"\n")
|
|
161
|
+
finish_frag = True
|
|
162
|
+
return np.array([0]), np.array([0]), input_data_for_display, finish_frag
|
|
163
|
+
|
|
164
|
+
psi4.core.clean()
|
|
165
|
+
self.energy = e
|
|
166
|
+
self.gradient = g
|
|
167
|
+
if geom_num_list is None:
|
|
168
|
+
self.coordinate = input_data_for_display
|
|
169
|
+
else:
|
|
170
|
+
self.coordinate = geom_num_list / psi4.constants.bohr2angstroms
|
|
171
|
+
return e, g, self.coordinate, finish_frag
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
return e, g, input_data_for_display, finish_frag
|
|
175
|
+
|
|
176
|
+
def exact_hessian(self, element_list, input_data_for_display, wfn):
|
|
177
|
+
"""exact hessian"""
|
|
178
|
+
_, wfn = psi4.frequencies(self.FUNCTIONAL, return_wfn=True, ref_gradient=wfn.gradient())
|
|
179
|
+
exact_hess = np.array(wfn.hessian())
|
|
180
|
+
|
|
181
|
+
freqs = np.array(wfn.frequencies())
|
|
182
|
+
|
|
183
|
+
print("frequencies: \n",freqs)
|
|
184
|
+
#eigenvalues, _ = np.linalg.eigh(exact_hess)
|
|
185
|
+
#print("=== hessian (before add bias potential) ===")
|
|
186
|
+
#print("eigenvalues: ", eigenvalues)
|
|
187
|
+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, input_data_for_display, display_eigval=False)
|
|
188
|
+
self.Model_hess = exact_hess
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class CalculationEngine(ABC):
|
|
193
|
+
"""Base class for calculation engines"""
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
197
|
+
"""Calculate energy and gradients"""
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
def _get_file_list(self, file_directory):
|
|
201
|
+
"""Get list of input files"""
|
|
202
|
+
return sum([sorted(glob.glob(os.path.join(file_directory, f"*_" + "[0-9]" * i + ".xyz")))
|
|
203
|
+
for i in range(1, 7)], [])
|
|
204
|
+
|
|
205
|
+
def _process_visualization(self, energy_list, gradient_list, num_list, optimize_num, config):
|
|
206
|
+
"""Process common visualization tasks"""
|
|
207
|
+
try:
|
|
208
|
+
if config.save_pict:
|
|
209
|
+
visualizer = NEBVisualizer(config)
|
|
210
|
+
tmp_ene_list = np.array(energy_list, dtype="float64") * config.hartree2kcalmol
|
|
211
|
+
visualizer.plot_energy(num_list, tmp_ene_list - tmp_ene_list[0], optimize_num)
|
|
212
|
+
print("energy graph plotted.")
|
|
213
|
+
|
|
214
|
+
gradient_norm_list = [np.sqrt(np.linalg.norm(g)**2/(len(g)*3)) for g in gradient_list]
|
|
215
|
+
visualizer.plot_gradient(num_list, gradient_norm_list, optimize_num)
|
|
216
|
+
print("gradient graph plotted.")
|
|
217
|
+
except Exception as e:
|
|
218
|
+
print(f"Visualization error: {e}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class Psi4Engine(CalculationEngine):
|
|
222
|
+
"""Psi4 calculation engine"""
|
|
223
|
+
|
|
224
|
+
def calculate(self, file_directory, optimize_num, pre_total_velocity, config):
|
|
225
|
+
if psi4 is None:
|
|
226
|
+
raise ImportError("Psi4 is not available")
|
|
227
|
+
|
|
228
|
+
psi4.core.clean()
|
|
229
|
+
gradient_list = []
|
|
230
|
+
gradient_norm_list = []
|
|
231
|
+
energy_list = []
|
|
232
|
+
geometry_num_list = []
|
|
233
|
+
num_list = []
|
|
234
|
+
delete_pre_total_velocity = []
|
|
235
|
+
|
|
236
|
+
os.makedirs(file_directory, exist_ok=True)
|
|
237
|
+
file_list = self._get_file_list(file_directory)
|
|
238
|
+
|
|
239
|
+
hess_count = 0
|
|
240
|
+
|
|
241
|
+
for num, input_file in enumerate(file_list):
|
|
242
|
+
try:
|
|
243
|
+
print(input_file)
|
|
244
|
+
|
|
245
|
+
logfile = file_directory + "/" + config.init_input + '_' + str(num) + '.log'
|
|
246
|
+
psi4.set_output_file(logfile)
|
|
247
|
+
psi4.set_num_threads(nthread=config.N_THREAD)
|
|
248
|
+
psi4.set_memory(config.SET_MEMORY)
|
|
249
|
+
self._set_psi4_dft_grid(config)
|
|
250
|
+
|
|
251
|
+
if config.unrestrict:
|
|
252
|
+
psi4.set_options({'reference': 'uks'})
|
|
253
|
+
|
|
254
|
+
geometry_list, element_list, electric_charge_and_multiplicity = xyz2list(input_file, None)
|
|
255
|
+
|
|
256
|
+
input_data = str(electric_charge_and_multiplicity[0]) + " " + str(electric_charge_and_multiplicity[1]) + "\n"
|
|
257
|
+
for j in range(len(geometry_list)):
|
|
258
|
+
input_data += element_list[j] + " " + geometry_list[j][0] + " " + geometry_list[j][1] + " " + geometry_list[j][2] + "\n"
|
|
259
|
+
|
|
260
|
+
input_data = psi4.geometry(input_data)
|
|
261
|
+
input_data_for_display = np.array(input_data.geometry(), dtype="float64")
|
|
262
|
+
|
|
263
|
+
g, wfn = psi4.gradient(config.basic_set_and_function, molecule=input_data, return_wfn=True)
|
|
264
|
+
g = np.array(g, dtype="float64")
|
|
265
|
+
e = float(wfn.energy())
|
|
266
|
+
|
|
267
|
+
print('energy:' + str(e) + " a.u.")
|
|
268
|
+
|
|
269
|
+
gradient_list.append(g)
|
|
270
|
+
gradient_norm_list.append(np.sqrt(np.linalg.norm(g)**2/(len(g)*3))) # RMS
|
|
271
|
+
energy_list.append(e)
|
|
272
|
+
num_list.append(num)
|
|
273
|
+
geometry_num_list.append(input_data_for_display)
|
|
274
|
+
|
|
275
|
+
if config.FC_COUNT == -1 or type(optimize_num) is str:
|
|
276
|
+
pass
|
|
277
|
+
elif optimize_num % config.FC_COUNT == 0:
|
|
278
|
+
"""exact hessian"""
|
|
279
|
+
_, wfn = psi4.frequencies(config.basic_set_and_function, return_wfn=True, ref_gradient=wfn.gradient())
|
|
280
|
+
exact_hess = np.array(wfn.hessian())
|
|
281
|
+
freqs = np.array(wfn.frequencies())
|
|
282
|
+
print("frequencies: \n", freqs)
|
|
283
|
+
eigenvalues, _ = np.linalg.eigh(exact_hess)
|
|
284
|
+
print("=== hessian (before add bias potential) ===")
|
|
285
|
+
print("eigenvalues: ", eigenvalues)
|
|
286
|
+
exact_hess = Calculationtools().project_out_hess_tr_and_rot_for_coord(exact_hess, element_list, input_data_for_display)
|
|
287
|
+
np.save(config.NEB_FOLDER_DIRECTORY + "tmp_hessian_" + str(hess_count) + ".npy", exact_hess)
|
|
288
|
+
with open(config.NEB_FOLDER_DIRECTORY + "tmp_hessian_" + str(hess_count) + ".csv", "a") as f:
|
|
289
|
+
f.write("frequency," + ",".join(map(str, freqs)) + "\n")
|
|
290
|
+
|
|
291
|
+
hess_count += 1
|
|
292
|
+
|
|
293
|
+
except Exception as error:
|
|
294
|
+
print(error)
|
|
295
|
+
print("This molecule could not be optimized.")
|
|
296
|
+
if optimize_num != 0:
|
|
297
|
+
delete_pre_total_velocity.append(num)
|
|
298
|
+
|
|
299
|
+
psi4.core.clean()
|
|
300
|
+
|
|
301
|
+
print("data sampling was completed...")
|
|
302
|
+
|
|
303
|
+
self._process_visualization(energy_list, gradient_list, num_list, optimize_num, config)
|
|
304
|
+
|
|
305
|
+
if optimize_num != 0 and len(pre_total_velocity) != 0:
|
|
306
|
+
pre_total_velocity = pre_total_velocity.tolist()
|
|
307
|
+
for i in sorted(delete_pre_total_velocity, reverse=True):
|
|
308
|
+
pre_total_velocity.pop(i)
|
|
309
|
+
pre_total_velocity = np.array(pre_total_velocity, dtype="float64")
|
|
310
|
+
|
|
311
|
+
return (np.array(energy_list, dtype="float64"),
|
|
312
|
+
np.array(gradient_list, dtype="float64"),
|
|
313
|
+
np.array(geometry_num_list, dtype="float64"),
|
|
314
|
+
pre_total_velocity)
|
|
315
|
+
|
|
316
|
+
def _set_psi4_dft_grid(self, config):
|
|
317
|
+
"""Set DFT grid for Psi4"""
|
|
318
|
+
if config.dft_grid == 0 or config.dft_grid == 1:
|
|
319
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 50, 'DFT_SPHERICAL_POINTS': 194})
|
|
320
|
+
print("DFT Grid (50, 194): SG1")
|
|
321
|
+
elif config.dft_grid == 2 or config.dft_grid == 3:
|
|
322
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 75, 'DFT_SPHERICAL_POINTS': 302})
|
|
323
|
+
print("DFT Grid (70, 302): Default")
|
|
324
|
+
elif config.dft_grid == 4 or config.dft_grid == 5:
|
|
325
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 99, 'DFT_SPHERICAL_POINTS': 590})
|
|
326
|
+
print("DFT Grid (99, 590): Fine")
|
|
327
|
+
elif config.dft_grid == 6 or config.dft_grid == 7:
|
|
328
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 150, 'DFT_SPHERICAL_POINTS': 770})
|
|
329
|
+
print("DFT Grid (150, 770): UltraFine")
|
|
330
|
+
elif config.dft_grid == 8 or config.dft_grid == 9:
|
|
331
|
+
psi4.set_options({'DFT_RADIAL_POINTS': 250, 'DFT_SPHERICAL_POINTS': 974})
|
|
332
|
+
print("DFT Grid (250, 974): SuperFine")
|
|
333
|
+
else:
|
|
334
|
+
raise ValueError("Invalid dft grid setting.")
|