mlmm-toolkit 0.2.2.dev0__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.
- hessian_ff/__init__.py +50 -0
- hessian_ff/analytical_hessian.py +609 -0
- hessian_ff/constants.py +46 -0
- hessian_ff/forcefield.py +339 -0
- hessian_ff/loaders.py +608 -0
- hessian_ff/native/Makefile +8 -0
- hessian_ff/native/__init__.py +28 -0
- hessian_ff/native/analytical_hessian.py +88 -0
- hessian_ff/native/analytical_hessian_ext.cpp +258 -0
- hessian_ff/native/bonded.py +82 -0
- hessian_ff/native/bonded_ext.cpp +640 -0
- hessian_ff/native/loader.py +349 -0
- hessian_ff/native/nonbonded.py +118 -0
- hessian_ff/native/nonbonded_ext.cpp +1150 -0
- hessian_ff/prmtop_parmed.py +23 -0
- hessian_ff/system.py +107 -0
- hessian_ff/terms/__init__.py +14 -0
- hessian_ff/terms/angle.py +73 -0
- hessian_ff/terms/bond.py +44 -0
- hessian_ff/terms/cmap.py +406 -0
- hessian_ff/terms/dihedral.py +141 -0
- hessian_ff/terms/nonbonded.py +209 -0
- hessian_ff/tests/__init__.py +0 -0
- hessian_ff/tests/conftest.py +75 -0
- hessian_ff/tests/data/small/complex.parm7 +1346 -0
- hessian_ff/tests/data/small/complex.pdb +125 -0
- hessian_ff/tests/data/small/complex.rst7 +63 -0
- hessian_ff/tests/test_coords_input.py +44 -0
- hessian_ff/tests/test_energy_force.py +49 -0
- hessian_ff/tests/test_hessian.py +137 -0
- hessian_ff/tests/test_smoke.py +18 -0
- hessian_ff/tests/test_validation.py +40 -0
- hessian_ff/workflows.py +889 -0
- mlmm/__init__.py +36 -0
- mlmm/__main__.py +7 -0
- mlmm/_version.py +34 -0
- mlmm/add_elem_info.py +374 -0
- mlmm/advanced_help.py +91 -0
- mlmm/align_freeze_atoms.py +601 -0
- mlmm/all.py +3535 -0
- mlmm/bond_changes.py +231 -0
- mlmm/bool_compat.py +223 -0
- mlmm/cli.py +574 -0
- mlmm/cli_utils.py +166 -0
- mlmm/default_group.py +337 -0
- mlmm/defaults.py +467 -0
- mlmm/define_layer.py +526 -0
- mlmm/dft.py +1041 -0
- mlmm/energy_diagram.py +253 -0
- mlmm/extract.py +2213 -0
- mlmm/fix_altloc.py +464 -0
- mlmm/freq.py +1406 -0
- mlmm/harmonic_constraints.py +140 -0
- mlmm/hessian_cache.py +44 -0
- mlmm/hessian_calc.py +174 -0
- mlmm/irc.py +638 -0
- mlmm/mlmm_calc.py +2262 -0
- mlmm/mm_parm.py +945 -0
- mlmm/oniom_export.py +1983 -0
- mlmm/oniom_import.py +457 -0
- mlmm/opt.py +1742 -0
- mlmm/path_opt.py +1353 -0
- mlmm/path_search.py +2299 -0
- mlmm/preflight.py +88 -0
- mlmm/py.typed +1 -0
- mlmm/pysis_runner.py +45 -0
- mlmm/scan.py +1047 -0
- mlmm/scan2d.py +1226 -0
- mlmm/scan3d.py +1265 -0
- mlmm/scan_common.py +184 -0
- mlmm/summary_log.py +736 -0
- mlmm/trj2fig.py +448 -0
- mlmm/tsopt.py +2871 -0
- mlmm/utils.py +2309 -0
- mlmm/xtb_embedcharge_correction.py +475 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
- pysisyphus/Geometry.py +1667 -0
- pysisyphus/LICENSE +674 -0
- pysisyphus/TableFormatter.py +63 -0
- pysisyphus/TablePrinter.py +74 -0
- pysisyphus/__init__.py +12 -0
- pysisyphus/calculators/AFIR.py +452 -0
- pysisyphus/calculators/AnaPot.py +20 -0
- pysisyphus/calculators/AnaPot2.py +48 -0
- pysisyphus/calculators/AnaPot3.py +12 -0
- pysisyphus/calculators/AnaPot4.py +20 -0
- pysisyphus/calculators/AnaPotBase.py +337 -0
- pysisyphus/calculators/AnaPotCBM.py +25 -0
- pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
- pysisyphus/calculators/CFOUR.py +250 -0
- pysisyphus/calculators/Calculator.py +844 -0
- pysisyphus/calculators/CerjanMiller.py +24 -0
- pysisyphus/calculators/Composite.py +123 -0
- pysisyphus/calculators/ConicalIntersection.py +171 -0
- pysisyphus/calculators/DFTBp.py +430 -0
- pysisyphus/calculators/DFTD3.py +66 -0
- pysisyphus/calculators/DFTD4.py +84 -0
- pysisyphus/calculators/Dalton.py +61 -0
- pysisyphus/calculators/Dimer.py +681 -0
- pysisyphus/calculators/Dummy.py +20 -0
- pysisyphus/calculators/EGO.py +76 -0
- pysisyphus/calculators/EnergyMin.py +224 -0
- pysisyphus/calculators/ExternalPotential.py +264 -0
- pysisyphus/calculators/FakeASE.py +35 -0
- pysisyphus/calculators/FourWellAnaPot.py +28 -0
- pysisyphus/calculators/FreeEndNEBPot.py +39 -0
- pysisyphus/calculators/Gaussian09.py +18 -0
- pysisyphus/calculators/Gaussian16.py +726 -0
- pysisyphus/calculators/HardSphere.py +159 -0
- pysisyphus/calculators/IDPPCalculator.py +49 -0
- pysisyphus/calculators/IPIClient.py +133 -0
- pysisyphus/calculators/IPIServer.py +234 -0
- pysisyphus/calculators/LEPSBase.py +24 -0
- pysisyphus/calculators/LEPSExpr.py +139 -0
- pysisyphus/calculators/LennardJones.py +80 -0
- pysisyphus/calculators/MOPAC.py +219 -0
- pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
- pysisyphus/calculators/MultiCalc.py +85 -0
- pysisyphus/calculators/NFK.py +45 -0
- pysisyphus/calculators/OBabel.py +87 -0
- pysisyphus/calculators/ONIOMv2.py +1129 -0
- pysisyphus/calculators/ORCA.py +893 -0
- pysisyphus/calculators/ORCA5.py +6 -0
- pysisyphus/calculators/OpenMM.py +88 -0
- pysisyphus/calculators/OpenMolcas.py +281 -0
- pysisyphus/calculators/OverlapCalculator.py +908 -0
- pysisyphus/calculators/Psi4.py +218 -0
- pysisyphus/calculators/PyPsi4.py +37 -0
- pysisyphus/calculators/PySCF.py +341 -0
- pysisyphus/calculators/PyXTB.py +73 -0
- pysisyphus/calculators/QCEngine.py +106 -0
- pysisyphus/calculators/Rastrigin.py +22 -0
- pysisyphus/calculators/Remote.py +76 -0
- pysisyphus/calculators/Rosenbrock.py +15 -0
- pysisyphus/calculators/SocketCalc.py +97 -0
- pysisyphus/calculators/TIP3P.py +111 -0
- pysisyphus/calculators/TransTorque.py +161 -0
- pysisyphus/calculators/Turbomole.py +965 -0
- pysisyphus/calculators/VRIPot.py +37 -0
- pysisyphus/calculators/WFOWrapper.py +333 -0
- pysisyphus/calculators/WFOWrapper2.py +341 -0
- pysisyphus/calculators/XTB.py +418 -0
- pysisyphus/calculators/__init__.py +81 -0
- pysisyphus/calculators/cosmo_data.py +139 -0
- pysisyphus/calculators/parser.py +150 -0
- pysisyphus/color.py +19 -0
- pysisyphus/config.py +133 -0
- pysisyphus/constants.py +65 -0
- pysisyphus/cos/AdaptiveNEB.py +230 -0
- pysisyphus/cos/ChainOfStates.py +725 -0
- pysisyphus/cos/FreeEndNEB.py +25 -0
- pysisyphus/cos/FreezingString.py +103 -0
- pysisyphus/cos/GrowingChainOfStates.py +71 -0
- pysisyphus/cos/GrowingNT.py +309 -0
- pysisyphus/cos/GrowingString.py +508 -0
- pysisyphus/cos/NEB.py +189 -0
- pysisyphus/cos/SimpleZTS.py +64 -0
- pysisyphus/cos/__init__.py +22 -0
- pysisyphus/cos/stiffness.py +199 -0
- pysisyphus/drivers/__init__.py +17 -0
- pysisyphus/drivers/afir.py +855 -0
- pysisyphus/drivers/barriers.py +271 -0
- pysisyphus/drivers/birkholz.py +138 -0
- pysisyphus/drivers/cluster.py +318 -0
- pysisyphus/drivers/diabatization.py +133 -0
- pysisyphus/drivers/merge.py +368 -0
- pysisyphus/drivers/merge_mol2.py +322 -0
- pysisyphus/drivers/opt.py +375 -0
- pysisyphus/drivers/perf.py +91 -0
- pysisyphus/drivers/pka.py +52 -0
- pysisyphus/drivers/precon_pos_rot.py +669 -0
- pysisyphus/drivers/rates.py +480 -0
- pysisyphus/drivers/replace.py +219 -0
- pysisyphus/drivers/scan.py +212 -0
- pysisyphus/drivers/spectrum.py +166 -0
- pysisyphus/drivers/thermo.py +31 -0
- pysisyphus/dynamics/Gaussian.py +103 -0
- pysisyphus/dynamics/__init__.py +20 -0
- pysisyphus/dynamics/colvars.py +136 -0
- pysisyphus/dynamics/driver.py +297 -0
- pysisyphus/dynamics/helpers.py +256 -0
- pysisyphus/dynamics/lincs.py +105 -0
- pysisyphus/dynamics/mdp.py +364 -0
- pysisyphus/dynamics/rattle.py +121 -0
- pysisyphus/dynamics/thermostats.py +128 -0
- pysisyphus/dynamics/wigner.py +266 -0
- pysisyphus/elem_data.py +3473 -0
- pysisyphus/exceptions.py +2 -0
- pysisyphus/filtertrj.py +69 -0
- pysisyphus/helpers.py +623 -0
- pysisyphus/helpers_pure.py +649 -0
- pysisyphus/init_logging.py +50 -0
- pysisyphus/intcoords/Bend.py +69 -0
- pysisyphus/intcoords/Bend2.py +25 -0
- pysisyphus/intcoords/BondedFragment.py +32 -0
- pysisyphus/intcoords/Cartesian.py +41 -0
- pysisyphus/intcoords/CartesianCoords.py +140 -0
- pysisyphus/intcoords/Coords.py +56 -0
- pysisyphus/intcoords/DLC.py +197 -0
- pysisyphus/intcoords/DistanceFunction.py +34 -0
- pysisyphus/intcoords/DummyImproper.py +70 -0
- pysisyphus/intcoords/DummyTorsion.py +72 -0
- pysisyphus/intcoords/LinearBend.py +105 -0
- pysisyphus/intcoords/LinearDisplacement.py +80 -0
- pysisyphus/intcoords/OutOfPlane.py +59 -0
- pysisyphus/intcoords/PrimTypes.py +286 -0
- pysisyphus/intcoords/Primitive.py +137 -0
- pysisyphus/intcoords/RedundantCoords.py +659 -0
- pysisyphus/intcoords/RobustTorsion.py +59 -0
- pysisyphus/intcoords/Rotation.py +147 -0
- pysisyphus/intcoords/Stretch.py +31 -0
- pysisyphus/intcoords/Torsion.py +101 -0
- pysisyphus/intcoords/Torsion2.py +25 -0
- pysisyphus/intcoords/Translation.py +45 -0
- pysisyphus/intcoords/__init__.py +61 -0
- pysisyphus/intcoords/augment_bonds.py +126 -0
- pysisyphus/intcoords/derivatives.py +10512 -0
- pysisyphus/intcoords/eval.py +80 -0
- pysisyphus/intcoords/exceptions.py +37 -0
- pysisyphus/intcoords/findiffs.py +48 -0
- pysisyphus/intcoords/generate_derivatives.py +414 -0
- pysisyphus/intcoords/helpers.py +235 -0
- pysisyphus/intcoords/logging_conf.py +10 -0
- pysisyphus/intcoords/mp_derivatives.py +10836 -0
- pysisyphus/intcoords/setup.py +962 -0
- pysisyphus/intcoords/setup_fast.py +176 -0
- pysisyphus/intcoords/update.py +272 -0
- pysisyphus/intcoords/valid.py +89 -0
- pysisyphus/interpolate/Geodesic.py +93 -0
- pysisyphus/interpolate/IDPP.py +55 -0
- pysisyphus/interpolate/Interpolator.py +116 -0
- pysisyphus/interpolate/LST.py +70 -0
- pysisyphus/interpolate/Redund.py +152 -0
- pysisyphus/interpolate/__init__.py +9 -0
- pysisyphus/interpolate/helpers.py +34 -0
- pysisyphus/io/__init__.py +22 -0
- pysisyphus/io/aomix.py +178 -0
- pysisyphus/io/cjson.py +24 -0
- pysisyphus/io/crd.py +101 -0
- pysisyphus/io/cube.py +220 -0
- pysisyphus/io/fchk.py +184 -0
- pysisyphus/io/hdf5.py +49 -0
- pysisyphus/io/hessian.py +72 -0
- pysisyphus/io/mol2.py +146 -0
- pysisyphus/io/molden.py +293 -0
- pysisyphus/io/orca.py +189 -0
- pysisyphus/io/pdb.py +269 -0
- pysisyphus/io/psf.py +79 -0
- pysisyphus/io/pubchem.py +31 -0
- pysisyphus/io/qcschema.py +34 -0
- pysisyphus/io/sdf.py +29 -0
- pysisyphus/io/xyz.py +61 -0
- pysisyphus/io/zmat.py +175 -0
- pysisyphus/irc/DWI.py +108 -0
- pysisyphus/irc/DampedVelocityVerlet.py +134 -0
- pysisyphus/irc/Euler.py +22 -0
- pysisyphus/irc/EulerPC.py +345 -0
- pysisyphus/irc/GonzalezSchlegel.py +187 -0
- pysisyphus/irc/IMKMod.py +164 -0
- pysisyphus/irc/IRC.py +878 -0
- pysisyphus/irc/IRCDummy.py +10 -0
- pysisyphus/irc/Instanton.py +307 -0
- pysisyphus/irc/LQA.py +53 -0
- pysisyphus/irc/ModeKill.py +136 -0
- pysisyphus/irc/ParamPlot.py +53 -0
- pysisyphus/irc/RK4.py +36 -0
- pysisyphus/irc/__init__.py +31 -0
- pysisyphus/irc/initial_displ.py +219 -0
- pysisyphus/linalg.py +411 -0
- pysisyphus/line_searches/Backtracking.py +88 -0
- pysisyphus/line_searches/HagerZhang.py +184 -0
- pysisyphus/line_searches/LineSearch.py +232 -0
- pysisyphus/line_searches/StrongWolfe.py +108 -0
- pysisyphus/line_searches/__init__.py +9 -0
- pysisyphus/line_searches/interpol.py +15 -0
- pysisyphus/modefollow/NormalMode.py +40 -0
- pysisyphus/modefollow/__init__.py +10 -0
- pysisyphus/modefollow/davidson.py +199 -0
- pysisyphus/modefollow/lanczos.py +95 -0
- pysisyphus/optimizers/BFGS.py +99 -0
- pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
- pysisyphus/optimizers/ConjugateGradient.py +98 -0
- pysisyphus/optimizers/CubicNewton.py +75 -0
- pysisyphus/optimizers/FIRE.py +113 -0
- pysisyphus/optimizers/HessianOptimizer.py +1176 -0
- pysisyphus/optimizers/LBFGS.py +228 -0
- pysisyphus/optimizers/LayerOpt.py +411 -0
- pysisyphus/optimizers/MicroOptimizer.py +169 -0
- pysisyphus/optimizers/NCOptimizer.py +90 -0
- pysisyphus/optimizers/Optimizer.py +1084 -0
- pysisyphus/optimizers/PreconLBFGS.py +260 -0
- pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
- pysisyphus/optimizers/QuickMin.py +74 -0
- pysisyphus/optimizers/RFOptimizer.py +181 -0
- pysisyphus/optimizers/RSA.py +99 -0
- pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
- pysisyphus/optimizers/SteepestDescent.py +23 -0
- pysisyphus/optimizers/StringOptimizer.py +173 -0
- pysisyphus/optimizers/__init__.py +41 -0
- pysisyphus/optimizers/closures.py +301 -0
- pysisyphus/optimizers/cls_map.py +58 -0
- pysisyphus/optimizers/exceptions.py +6 -0
- pysisyphus/optimizers/gdiis.py +280 -0
- pysisyphus/optimizers/guess_hessians.py +311 -0
- pysisyphus/optimizers/hessian_updates.py +355 -0
- pysisyphus/optimizers/poly_fit.py +285 -0
- pysisyphus/optimizers/precon.py +153 -0
- pysisyphus/optimizers/restrict_step.py +24 -0
- pysisyphus/pack.py +172 -0
- pysisyphus/peakdetect.py +948 -0
- pysisyphus/plot.py +1031 -0
- pysisyphus/run.py +2106 -0
- pysisyphus/socket_helper.py +74 -0
- pysisyphus/stocastic/FragmentKick.py +132 -0
- pysisyphus/stocastic/Kick.py +81 -0
- pysisyphus/stocastic/Pipeline.py +303 -0
- pysisyphus/stocastic/__init__.py +21 -0
- pysisyphus/stocastic/align.py +127 -0
- pysisyphus/testing.py +96 -0
- pysisyphus/thermo.py +156 -0
- pysisyphus/trj.py +824 -0
- pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
- pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
- pysisyphus/tsoptimizers/TRIM.py +59 -0
- pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
- pysisyphus/tsoptimizers/__init__.py +23 -0
- pysisyphus/wavefunction/Basis.py +239 -0
- pysisyphus/wavefunction/DIIS.py +76 -0
- pysisyphus/wavefunction/__init__.py +25 -0
- pysisyphus/wavefunction/build_ext.py +42 -0
- pysisyphus/wavefunction/cart2sph.py +190 -0
- pysisyphus/wavefunction/diabatization.py +304 -0
- pysisyphus/wavefunction/excited_states.py +435 -0
- pysisyphus/wavefunction/gen_ints.py +1811 -0
- pysisyphus/wavefunction/helpers.py +104 -0
- pysisyphus/wavefunction/ints/__init__.py +0 -0
- pysisyphus/wavefunction/ints/boys.py +193 -0
- pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
- pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
- pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
- pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
- pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
- pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
- pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
- pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
- pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
- pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
- pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
- pysisyphus/wavefunction/localization.py +458 -0
- pysisyphus/wavefunction/multipole.py +159 -0
- pysisyphus/wavefunction/normalization.py +36 -0
- pysisyphus/wavefunction/pop_analysis.py +134 -0
- pysisyphus/wavefunction/shells.py +1171 -0
- pysisyphus/wavefunction/wavefunction.py +504 -0
- pysisyphus/wrapper/__init__.py +11 -0
- pysisyphus/wrapper/exceptions.py +2 -0
- pysisyphus/wrapper/jmol.py +120 -0
- pysisyphus/wrapper/mwfn.py +169 -0
- pysisyphus/wrapper/packmol.py +71 -0
- pysisyphus/xyzloader.py +168 -0
- pysisyphus/yaml_mods.py +45 -0
- thermoanalysis/LICENSE +674 -0
- thermoanalysis/QCData.py +244 -0
- thermoanalysis/__init__.py +0 -0
- thermoanalysis/config.py +3 -0
- thermoanalysis/constants.py +20 -0
- thermoanalysis/thermo.py +1011 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# [1] http://dx.doi.org/10.1021/acs.jctc.8b00885
|
|
2
|
+
# Exploring Potential Energy Surface with External Forces
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from pysisyphus.calculators.Calculator import Calculator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EGO(Calculator):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
calculator,
|
|
13
|
+
ref_geom,
|
|
14
|
+
max_force=0.175,
|
|
15
|
+
**kwargs,
|
|
16
|
+
):
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
|
|
19
|
+
self.calculator = calculator
|
|
20
|
+
self.ref_geom = ref_geom
|
|
21
|
+
self.max_force = max_force
|
|
22
|
+
assert (
|
|
23
|
+
self.max_force > 0.0
|
|
24
|
+
), f"Maximum force must be positve bug to max_force={self.max_force:.4f} au"
|
|
25
|
+
|
|
26
|
+
self._ref_hessian = None
|
|
27
|
+
self._s = None
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def ref_hessian(self):
|
|
31
|
+
if self._ref_hessian is None:
|
|
32
|
+
geom = self.ref_geom
|
|
33
|
+
results = self.calculator.get_hessian(geom.atoms, geom.cart_coords)
|
|
34
|
+
self._ref_hessian = results["hessian"]
|
|
35
|
+
Hr0 = self._ref_hessian @ self.ref_geom.cart_coords[:, None]
|
|
36
|
+
self._s = self.max_force / np.abs(Hr0).max()
|
|
37
|
+
self.log(f"Set EGO s={self._s:.6f}")
|
|
38
|
+
return self._ref_hessian
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def s(self):
|
|
42
|
+
return self._s
|
|
43
|
+
|
|
44
|
+
def get_mods(self, atoms, coords):
|
|
45
|
+
assert atoms == self.ref_geom.atoms
|
|
46
|
+
Hr = self.ref_hessian @ coords[:, None]
|
|
47
|
+
s = self.s
|
|
48
|
+
energy_mod = float(coords[None, :] @ Hr)
|
|
49
|
+
forces_mod = -s * Hr.flatten()
|
|
50
|
+
return energy_mod, forces_mod
|
|
51
|
+
|
|
52
|
+
def get_energy(self, atoms, coords, **prepare_kwargs):
|
|
53
|
+
true_energy = self.calculator.get_energy(atoms, coords)["energy"]
|
|
54
|
+
energy_mod, _ = self.get_mods(atoms, coords)
|
|
55
|
+
|
|
56
|
+
results = {
|
|
57
|
+
"energy": true_energy + energy_mod,
|
|
58
|
+
"true_energy": true_energy,
|
|
59
|
+
}
|
|
60
|
+
self.calc_counter += 1
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
def get_forces(self, atoms, coords, **prepare_kwargs):
|
|
64
|
+
true_results = self.calculator.get_forces(atoms, coords, **prepare_kwargs)
|
|
65
|
+
true_energy = true_results["energy"]
|
|
66
|
+
true_forces = true_results["forces"]
|
|
67
|
+
energy_mod, forces_mod = self.get_mods(atoms, coords)
|
|
68
|
+
|
|
69
|
+
results = {
|
|
70
|
+
"energy": true_energy + energy_mod,
|
|
71
|
+
"forces": true_forces + forces_mod,
|
|
72
|
+
"true_forces": true_forces,
|
|
73
|
+
"true_energy": true_energy,
|
|
74
|
+
}
|
|
75
|
+
self.calc_counter += 1
|
|
76
|
+
return results
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# [1] https://doi.org/10.1063/5.0021923
|
|
2
|
+
# Extending NEB method to reaction pathways involving multiple spin states
|
|
3
|
+
# Zhao et al, 2020
|
|
4
|
+
# [2] https://doi.org/10.1021/jp0761618
|
|
5
|
+
# Optimizing Conical Intersections without Derivative Coupling Vectors:
|
|
6
|
+
# Application to Multistate Multireference Second-Order Perturbation Theory
|
|
7
|
+
# Levine, Coe, Martinez 2008
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from pysisyphus.calculators.Calculator import Calculator
|
|
12
|
+
from pysisyphus.constants import AU2KJPERMOL
|
|
13
|
+
from pysisyphus.helpers import norm_max_rms
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EnergyMin(Calculator):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
calculator1: Calculator,
|
|
20
|
+
calculator2: Calculator,
|
|
21
|
+
mix: bool = False,
|
|
22
|
+
alpha: float = 0.02, # Hartree
|
|
23
|
+
sigma: float = 3.5, # Unitless; default for ethene case in [1]
|
|
24
|
+
min_energy_diff: float = 0.0,
|
|
25
|
+
check_after: int = 0,
|
|
26
|
+
**kwargs,
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Use energy and derivatives of the calculator with lower energy.
|
|
30
|
+
|
|
31
|
+
This calculators carries out two calculations with different settings
|
|
32
|
+
and returns the results of the lower energy one. This can be used
|
|
33
|
+
to consider flips between a singlet and a triplet PES etc.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
calculator1
|
|
38
|
+
Wrapped QC calculator that provides energies and its derivatives.
|
|
39
|
+
calculator2
|
|
40
|
+
Wrapped QC calculator that provides energies and its derivatives.
|
|
41
|
+
mix
|
|
42
|
+
Enable mixing of both forces, according to the approach outlined
|
|
43
|
+
in [2]. Can be used to optimize guesses for MECPs.
|
|
44
|
+
Pass
|
|
45
|
+
alpha
|
|
46
|
+
Smoothing parameter in Hartree. See [2] for a discussion.
|
|
47
|
+
sigma
|
|
48
|
+
Unitless gap size parameter. The final gap becomes
|
|
49
|
+
smaller for bigga sigmas. Has to be adapted for each case. See
|
|
50
|
+
[2] for a discussion (p. 407 right column and p. 408 left column.)
|
|
51
|
+
min_energy_diff
|
|
52
|
+
Energy difference in Hartree. When set to a value != 0 and the
|
|
53
|
+
energy difference between both
|
|
54
|
+
calculators drops below this value, execution of both calculations
|
|
55
|
+
is diabled for 'check_after' cycles. In these cycles the calculator choice
|
|
56
|
+
remains fixed. After 'check_after' cycles, both energies
|
|
57
|
+
will be calculated and it is checked, if the previous calculator
|
|
58
|
+
choice remains valid.
|
|
59
|
+
In conjunction with 'check_after' both arguments can be used to
|
|
60
|
+
save computational ressources.
|
|
61
|
+
check_after
|
|
62
|
+
Amount of cycles in which the calculator choice remains fixed.
|
|
63
|
+
|
|
64
|
+
Other Parameters
|
|
65
|
+
----------------
|
|
66
|
+
**kwargs
|
|
67
|
+
Keyword arguments passed to the Calculator baseclass.
|
|
68
|
+
"""
|
|
69
|
+
super().__init__(**kwargs)
|
|
70
|
+
self.calc1 = calculator1
|
|
71
|
+
self.calc2 = calculator2
|
|
72
|
+
self.alpha = alpha
|
|
73
|
+
self.sigma = sigma
|
|
74
|
+
self.min_energy_diff = float(min_energy_diff)
|
|
75
|
+
self.check_after = int(check_after)
|
|
76
|
+
assert self.check_after >= 0, "'check_after' must not be negative!"
|
|
77
|
+
|
|
78
|
+
self.mix = mix
|
|
79
|
+
self.recalc_in = self.check_after
|
|
80
|
+
self.fixed_calc = None
|
|
81
|
+
|
|
82
|
+
def do_calculations(self, name, atoms, coords, **prepare_kwargs):
|
|
83
|
+
def run_calculation(calc, name=name):
|
|
84
|
+
results = getattr(calc, name)(atoms, coords, **prepare_kwargs)
|
|
85
|
+
return results
|
|
86
|
+
|
|
87
|
+
if (self.fixed_calc is not None) and self.recalc_in > 0:
|
|
88
|
+
self.log(
|
|
89
|
+
f"Used fixed calculator '{self.fixed_calc}'. Re-checking both "
|
|
90
|
+
f"calculators in {self.recalc_in} cycles."
|
|
91
|
+
)
|
|
92
|
+
results = run_calculation(self.fixed_calc)
|
|
93
|
+
self.recalc_in -= 1
|
|
94
|
+
self.calc_counter += 1
|
|
95
|
+
return results
|
|
96
|
+
elif (self.fixed_calc is not None) and self.recalc_in == 0:
|
|
97
|
+
self.log(f"Unset fixed calculator {self.calc1}.")
|
|
98
|
+
self.fixed_calc = None
|
|
99
|
+
|
|
100
|
+
# Avoid unnecessary costly Hessian calculations; decide which Hessian
|
|
101
|
+
# to calculate from previous energy calculation.
|
|
102
|
+
if name == "get_hessian":
|
|
103
|
+
tmp_energy1 = run_calculation(self.calc1, "get_energy")["energy"]
|
|
104
|
+
tmp_energy2 = run_calculation(self.calc2, "get_energy")["energy"]
|
|
105
|
+
if tmp_energy1 <= tmp_energy2:
|
|
106
|
+
results1 = run_calculation(self.calc1)
|
|
107
|
+
results2 = {"energy": tmp_energy2}
|
|
108
|
+
self.log("Calculated Hessian for calc1, skipped it for calc2.")
|
|
109
|
+
else:
|
|
110
|
+
results1 = {"energy": tmp_energy1}
|
|
111
|
+
results2 = run_calculation(self.calc2)
|
|
112
|
+
self.log("Calculated Hessian for calc2, skipped it for calc1.")
|
|
113
|
+
# Do both full calculation otherwise
|
|
114
|
+
else:
|
|
115
|
+
results1 = run_calculation(self.calc1, name)
|
|
116
|
+
results2 = run_calculation(self.calc2, name)
|
|
117
|
+
energy1 = results1["energy"]
|
|
118
|
+
energy2 = results2["energy"]
|
|
119
|
+
all_energies = np.array((energy1, energy2))
|
|
120
|
+
|
|
121
|
+
# Mixed forces to optimize crossing points
|
|
122
|
+
if self.mix:
|
|
123
|
+
# Must be positive, so substract lower energy from higher energy.
|
|
124
|
+
# I is the higher state, j the lower one.
|
|
125
|
+
en_i, en_j = (
|
|
126
|
+
(energy2, energy1) if (energy1 < energy2) else (energy1, energy2)
|
|
127
|
+
)
|
|
128
|
+
energy_diff = en_i - en_j
|
|
129
|
+
energy_diff_kJ = energy_diff * AU2KJPERMOL
|
|
130
|
+
assert energy_diff > 0.0
|
|
131
|
+
self.log(
|
|
132
|
+
f"Mix mode, ΔE={energy_diff:.6f} au ({energy_diff_kJ:.2f} kJ mol⁻¹)"
|
|
133
|
+
)
|
|
134
|
+
energy_diff_sq = energy_diff**2
|
|
135
|
+
denom = energy_diff + self.alpha
|
|
136
|
+
energy = (en_i + en_j) / 2 + self.sigma * energy_diff_sq / denom
|
|
137
|
+
results = {
|
|
138
|
+
"energy": energy,
|
|
139
|
+
"all_energies": all_energies,
|
|
140
|
+
}
|
|
141
|
+
self.log(f"Mixed energy: {energy:.6f} au")
|
|
142
|
+
if name == "get_forces":
|
|
143
|
+
forces1 = results1["forces"]
|
|
144
|
+
forces2 = results2["forces"]
|
|
145
|
+
forces_i, forces_j = (
|
|
146
|
+
(forces2, forces1) if (energy1 < energy2) else (forces1, forces2)
|
|
147
|
+
)
|
|
148
|
+
# There seems to be a typo in Eq. (8) in [1]; the final term
|
|
149
|
+
# should be (dV_J - dV_I) instead of the (dV_I - dV_J).
|
|
150
|
+
# The formula is correct though, in the original publication
|
|
151
|
+
# (Eq. (7) in [2]).
|
|
152
|
+
forces = (forces_i + forces_j) / 2 + self.sigma * (
|
|
153
|
+
energy_diff_sq + 2 * self.alpha * energy_diff
|
|
154
|
+
) / denom**2 * (forces_i - forces_j)
|
|
155
|
+
results["forces"] = forces
|
|
156
|
+
for key, forces in (
|
|
157
|
+
("mixed forces", forces),
|
|
158
|
+
("forces1", forces1),
|
|
159
|
+
("forces2", forces2),
|
|
160
|
+
):
|
|
161
|
+
norm, max_, rms_ = norm_max_rms(forces)
|
|
162
|
+
self.log(
|
|
163
|
+
f"{key: >14s}: (norm={norm:.4f}, max(|{key: >14s}|)={max_:.4f}, "
|
|
164
|
+
f"rms={rms_:.4f}) au a0⁻¹"
|
|
165
|
+
)
|
|
166
|
+
self.calc_counter += 1
|
|
167
|
+
return results
|
|
168
|
+
# Mixed forces end
|
|
169
|
+
|
|
170
|
+
min_ind = [1, 0][int(energy1 < energy2)]
|
|
171
|
+
en1_or_en2 = ("calc1", "calc2")[min_ind]
|
|
172
|
+
energy_diff = energy1 - energy2
|
|
173
|
+
# Try to fix calculator, if requested
|
|
174
|
+
if (self.min_energy_diff and self.check_after) and (
|
|
175
|
+
# When the actual difference is above to minimum differences
|
|
176
|
+
# or
|
|
177
|
+
# no calculator is fixed yet
|
|
178
|
+
(energy_diff > self.min_energy_diff)
|
|
179
|
+
or (self.fixed_calc is None)
|
|
180
|
+
):
|
|
181
|
+
self.fixed_calc = (self.calc1, self.calc2)[min_ind]
|
|
182
|
+
self.recalc_in = self.check_after
|
|
183
|
+
self.log(
|
|
184
|
+
f"Fixed calculator choice ({en1_or_en2}) for {self.recalc_in} cycles."
|
|
185
|
+
)
|
|
186
|
+
results = (results1, results2)[min_ind]
|
|
187
|
+
results["all_energies"] = all_energies
|
|
188
|
+
energy_diff_kJ = abs(energy_diff) * AU2KJPERMOL
|
|
189
|
+
|
|
190
|
+
self.log(
|
|
191
|
+
f"energy_calc1={energy1:.6f} au, energy_calc2={energy2:.6f} au, returning "
|
|
192
|
+
f"results for {en1_or_en2}, {energy_diff_kJ: >10.2f} kJ mol⁻¹ lower."
|
|
193
|
+
)
|
|
194
|
+
self.calc_counter += 1
|
|
195
|
+
return results
|
|
196
|
+
|
|
197
|
+
def get_energy(self, atoms, coords, **prepare_kwargs):
|
|
198
|
+
return self.do_calculations("get_energy", atoms, coords, **prepare_kwargs)
|
|
199
|
+
|
|
200
|
+
def get_forces(self, atoms, coords, **prepare_kwargs):
|
|
201
|
+
return self.do_calculations("get_forces", atoms, coords, **prepare_kwargs)
|
|
202
|
+
|
|
203
|
+
def get_hessian(self, atoms, coords, **prepare_kwargs):
|
|
204
|
+
return self.do_calculations("get_hessian", atoms, coords, **prepare_kwargs)
|
|
205
|
+
|
|
206
|
+
def get_chkfiles(self) -> dict:
|
|
207
|
+
chkfiles = {}
|
|
208
|
+
for key in ("calc1", "calc2"):
|
|
209
|
+
try:
|
|
210
|
+
chkfiles[key] = getattr(self, key).get_chkfiles()
|
|
211
|
+
except AttributeError:
|
|
212
|
+
pass
|
|
213
|
+
return chkfiles
|
|
214
|
+
|
|
215
|
+
def set_chkfiles(self, chkfiles: dict):
|
|
216
|
+
for key in ("calc1", "calc2"):
|
|
217
|
+
try:
|
|
218
|
+
getattr(self, key).set_chkfiles(chkfiles[key])
|
|
219
|
+
msg = f"Set chkfile on '{key}'"
|
|
220
|
+
except KeyError:
|
|
221
|
+
msg = f"Found no information for '{key}' in chkfiles!"
|
|
222
|
+
except AttributeError:
|
|
223
|
+
msg = f"Setting chkfiles is not supported by '{key}'"
|
|
224
|
+
self.log(msg)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from math import pi
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import ArrayLike
|
|
6
|
+
|
|
7
|
+
from pysisyphus.calculators.Calculator import Calculator
|
|
8
|
+
from pysisyphus.constants import KB, AU2J
|
|
9
|
+
from pysisyphus.Geometry import Geometry
|
|
10
|
+
from pysisyphus.intcoords.PrimTypes import prims_from_prim_inputs
|
|
11
|
+
from pysisyphus.intcoords.update import correct_dihedrals
|
|
12
|
+
from pysisyphus.intcoords import Torsion
|
|
13
|
+
from pysisyphus.linalg import rmsd_grad
|
|
14
|
+
from pysisyphus.calculators.DFTD3 import DFTD3
|
|
15
|
+
|
|
16
|
+
class LogFermi:
|
|
17
|
+
def __init__(self, beta, radius, T=300, origin=(0.0, 0.0, 0.0), geom=None):
|
|
18
|
+
"""As described in the XTB docs.
|
|
19
|
+
|
|
20
|
+
https://xtb-docs.readthedocs.io/en/latest/xcontrol.html#confining-in-a-cavity
|
|
21
|
+
"""
|
|
22
|
+
self.beta = beta
|
|
23
|
+
self.radius = radius
|
|
24
|
+
self.T = T
|
|
25
|
+
self.origin = np.array(origin)
|
|
26
|
+
|
|
27
|
+
# In Hartree
|
|
28
|
+
self.kT = KB * self.T / AU2J
|
|
29
|
+
|
|
30
|
+
def calc(self, coords3d, gradient=False):
|
|
31
|
+
t0 = coords3d - self.origin[None, :]
|
|
32
|
+
t1 = np.linalg.norm(t0, axis=1)
|
|
33
|
+
t2 = np.exp(self.beta * (t1 - self.radius))
|
|
34
|
+
|
|
35
|
+
energy = (self.kT * np.log(1 + t2)).sum()
|
|
36
|
+
if not gradient:
|
|
37
|
+
return energy
|
|
38
|
+
|
|
39
|
+
grad = self.kT * ((self.beta * t2) / ((1 + t2) * t1))[:, None] * t0
|
|
40
|
+
return energy, grad.flatten()
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return (
|
|
44
|
+
f"LogFermi(beta={self.beta:.6f}, radius={self.radius:.6f}, "
|
|
45
|
+
f"T={self.T:.6f}, origin={self.origin})"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HarmonicSphere:
|
|
50
|
+
def __init__(self, k, radius, origin=(0.0, 0.0, 0.0), geom=None):
|
|
51
|
+
self.k = k
|
|
52
|
+
self.radius = radius
|
|
53
|
+
self.origin = np.array(origin)
|
|
54
|
+
|
|
55
|
+
def calc(self, coords3d, gradient=False):
|
|
56
|
+
c3d_wrt_origin = coords3d - self.origin
|
|
57
|
+
distances = np.linalg.norm(c3d_wrt_origin, axis=1)
|
|
58
|
+
energies = np.where(distances > self.radius, self.k * distances**2, 0.0)
|
|
59
|
+
energy = energies.sum()
|
|
60
|
+
|
|
61
|
+
if not gradient:
|
|
62
|
+
return energy
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
E(r(x)) = k*r**2
|
|
66
|
+
dE(r(x))/dx = dE/dr * dr/dx
|
|
67
|
+
dE/dr = 2*k*r
|
|
68
|
+
dr/dx = x/r
|
|
69
|
+
dE/dr * dr/dx = 2*k*x
|
|
70
|
+
"""
|
|
71
|
+
grad = np.where(distances > self.radius, 2 * self.k * c3d_wrt_origin, 0.0)
|
|
72
|
+
|
|
73
|
+
return energy, grad.flatten()
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def surface_area(self):
|
|
77
|
+
"""In Bohr**2"""
|
|
78
|
+
return 4 * pi * self.radius**2
|
|
79
|
+
|
|
80
|
+
def instant_pressure(self, coords3d):
|
|
81
|
+
_, gradient = self.calc(coords3d, gradient=True)
|
|
82
|
+
norm = np.linalg.norm(gradient)
|
|
83
|
+
p = norm / self.surface_area
|
|
84
|
+
return p
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Restraint:
|
|
88
|
+
def __init__(self, restraints, geom=None):
|
|
89
|
+
self.restraints = list()
|
|
90
|
+
|
|
91
|
+
for prim_inp, *rest in restraints:
|
|
92
|
+
prims = prims_from_prim_inputs((prim_inp,))
|
|
93
|
+
assert len(prims) == 1
|
|
94
|
+
prim = prims[0]
|
|
95
|
+
force_const = rest.pop(0)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
ref_val = rest.pop(0)
|
|
99
|
+
except IndexError:
|
|
100
|
+
assert (
|
|
101
|
+
geom is not None
|
|
102
|
+
), "Need initial coordinates when no reference value is specified!"
|
|
103
|
+
ref_val = prim.calculate(geom.coords3d)
|
|
104
|
+
|
|
105
|
+
self.restraints.append((prim, force_const, ref_val))
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def calc_prim_restraint(prim, coords3d, force_const, ref_val):
|
|
109
|
+
val, grad = prim.calculate(coords3d, gradient=True)
|
|
110
|
+
if isinstance(prim, Torsion):
|
|
111
|
+
# correct_dihedrals always returns a 1d array, even for scalar inputs
|
|
112
|
+
val = correct_dihedrals(val, ref_val)[0]
|
|
113
|
+
diff = val - ref_val
|
|
114
|
+
pot = force_const * diff**2
|
|
115
|
+
pot_grad = 2 * force_const * diff * grad
|
|
116
|
+
return pot, pot_grad
|
|
117
|
+
|
|
118
|
+
def calc(self, coords3d, gradient=False):
|
|
119
|
+
energy = 0.0
|
|
120
|
+
grad = np.zeros(coords3d.size)
|
|
121
|
+
|
|
122
|
+
for prim, force_const, ref_val in self.restraints:
|
|
123
|
+
penergy, pgrad = self.calc_prim_restraint(
|
|
124
|
+
prim, coords3d, force_const, ref_val
|
|
125
|
+
)
|
|
126
|
+
energy += penergy
|
|
127
|
+
grad += pgrad
|
|
128
|
+
|
|
129
|
+
if not gradient:
|
|
130
|
+
return energy
|
|
131
|
+
|
|
132
|
+
return energy, grad.flatten()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class RMSD:
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
geom: Geometry,
|
|
139
|
+
k: float,
|
|
140
|
+
beta: float = 0.5,
|
|
141
|
+
atom_indices: Optional[ArrayLike] = None,
|
|
142
|
+
):
|
|
143
|
+
"""Restrain based on RMSD with a reference geometry.
|
|
144
|
+
|
|
145
|
+
As described in https://doi.org/10.1021/acs.jctc.0c01306, Eq. (5).
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
|
|
150
|
+
geom:
|
|
151
|
+
Reference geometry for RMSD calculation.
|
|
152
|
+
k:
|
|
153
|
+
Gaussian height in units of energy. Should be a negative number if the system
|
|
154
|
+
under study should stay close to the reference geometry (pulling k).
|
|
155
|
+
A positive Gaussian height k results in forces that push the system under study
|
|
156
|
+
away from the reference geometry (pushing k).
|
|
157
|
+
b:
|
|
158
|
+
Gaussian width in inverse units of lengths.
|
|
159
|
+
atom_indices
|
|
160
|
+
Optional, numpy array or iterable of integer atom indices. Restricts the RMSD
|
|
161
|
+
calculation to these atoms. If omitted, all atoms are used.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
self.k = k
|
|
165
|
+
self.beta = beta
|
|
166
|
+
natoms = len(geom.atoms)
|
|
167
|
+
if atom_indices is None:
|
|
168
|
+
atom_indices = np.arange(natoms)
|
|
169
|
+
self.atom_indices = np.array(atom_indices, dtype=int)
|
|
170
|
+
assert (self.atom_indices.min() >= 0) and (
|
|
171
|
+
self.atom_indices.max() <= (natoms - 1)
|
|
172
|
+
), "Got atom_indices outside the valid range!"
|
|
173
|
+
self.ref_coords3d = geom.coords3d.copy()
|
|
174
|
+
# print("Using {self.atom_indices.size} of {natoms} atoms for RMSD calculations.")
|
|
175
|
+
|
|
176
|
+
def calc(self, coords3d, gradient=False):
|
|
177
|
+
rmsd, grad_ = rmsd_grad(
|
|
178
|
+
coords3d[self.atom_indices], self.ref_coords3d[self.atom_indices]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
k = self.k
|
|
182
|
+
beta = self.beta
|
|
183
|
+
energy = k * np.exp(-beta * rmsd**2)
|
|
184
|
+
|
|
185
|
+
if not gradient:
|
|
186
|
+
return energy
|
|
187
|
+
|
|
188
|
+
grad = np.zeros_like(self.ref_coords3d)
|
|
189
|
+
grad[self.atom_indices] = -beta * 2 * grad_ * energy
|
|
190
|
+
return energy, grad.flatten()
|
|
191
|
+
|
|
192
|
+
def __repr__(self):
|
|
193
|
+
return f"RMSD(k={self.k:.4f}, beta={self.beta:.6f}, {self.atom_indices.size} atoms)"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ExternalPotential(Calculator):
|
|
197
|
+
|
|
198
|
+
available_potentials = {
|
|
199
|
+
"logfermi": LogFermi,
|
|
200
|
+
"harmonic_sphere": HarmonicSphere,
|
|
201
|
+
"restraint": Restraint,
|
|
202
|
+
"rmsd": RMSD,
|
|
203
|
+
"d3": DFTD3,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def __init__(self, calculator=None, potentials=None, geom=None, **kwargs):
|
|
207
|
+
super().__init__(**kwargs)
|
|
208
|
+
|
|
209
|
+
self.calculator = calculator
|
|
210
|
+
|
|
211
|
+
self.potentials = list()
|
|
212
|
+
self.log("Creating external potentials")
|
|
213
|
+
for i, pot_kwargs in enumerate(potentials):
|
|
214
|
+
pot_kwargs.update({"geom": geom})
|
|
215
|
+
pot_key = pot_kwargs.pop("type")
|
|
216
|
+
pot_cls = self.available_potentials[pot_key]
|
|
217
|
+
pot = pot_cls(**pot_kwargs)
|
|
218
|
+
self.potentials.append(pot)
|
|
219
|
+
self.log(f"\t{i:02d}: {pot}")
|
|
220
|
+
|
|
221
|
+
def get_potential_energy(self, coords):
|
|
222
|
+
coords3d = coords.reshape(-1, 3)
|
|
223
|
+
potential_energies = [pot.calc(coords3d) for pot in self.potentials]
|
|
224
|
+
potential_energy = sum(potential_energies)
|
|
225
|
+
self.log(f"Energies from external potential: {potential_energies}")
|
|
226
|
+
return potential_energy
|
|
227
|
+
|
|
228
|
+
def get_energy(self, atoms, coords):
|
|
229
|
+
potential_energy = self.get_potential_energy(coords)
|
|
230
|
+
if self.calculator is not None:
|
|
231
|
+
results = self.calculator.get_energy(atoms, coords)
|
|
232
|
+
else:
|
|
233
|
+
results = {"energy": 0.0}
|
|
234
|
+
results["energy"] += potential_energy
|
|
235
|
+
return results
|
|
236
|
+
|
|
237
|
+
def get_potential_forces(self, coords):
|
|
238
|
+
coords3d = coords.reshape(-1, 3)
|
|
239
|
+
energies_gradients = [
|
|
240
|
+
pot.calc(coords3d, gradient=True) for pot in self.potentials
|
|
241
|
+
]
|
|
242
|
+
energies, gradients = zip(*energies_gradients)
|
|
243
|
+
self.log(f"Energies from external potential: {energies}")
|
|
244
|
+
energy = sum(energies)
|
|
245
|
+
forces = -np.sum(gradients, axis=0)
|
|
246
|
+
self.log(f"Forces from external potential: {forces}")
|
|
247
|
+
return energy, forces
|
|
248
|
+
|
|
249
|
+
def get_forces(self, atoms, coords):
|
|
250
|
+
potential_energy, potential_forces = self.get_potential_forces(coords)
|
|
251
|
+
if self.calculator is not None:
|
|
252
|
+
results = self.calculator.get_forces(atoms, coords)
|
|
253
|
+
else:
|
|
254
|
+
results = {"energy": 0.0, "forces": np.zeros_like(coords)}
|
|
255
|
+
results["energy"] += potential_energy
|
|
256
|
+
results["forces"] += potential_forces
|
|
257
|
+
return results
|
|
258
|
+
|
|
259
|
+
def get_hessian(self, atoms, coords):
|
|
260
|
+
raise Exception("Hessian is not implemented for ExternalPotential!")
|
|
261
|
+
|
|
262
|
+
def __str__(self):
|
|
263
|
+
pots = ", ".join([str(pot) for pot in self.potentials])
|
|
264
|
+
return f"ExternalPotential({pots})"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from pysisyphus.constants import BOHR2ANG
|
|
2
|
+
|
|
3
|
+
class FakeASE:
|
|
4
|
+
|
|
5
|
+
def __init__(self, calc):
|
|
6
|
+
self.calc = calc
|
|
7
|
+
|
|
8
|
+
self.results = dict()
|
|
9
|
+
|
|
10
|
+
def get_atoms_coords(self, atoms):
|
|
11
|
+
return (atoms.get_chemical_symbols(),
|
|
12
|
+
# Convert ASE Angstrom to Bohr for pysisyphus
|
|
13
|
+
atoms.get_positions().flatten() / BOHR2ANG
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def get_potential_energy(self, atoms=None):
|
|
17
|
+
atoms, coords = self.get_atoms_coords(atoms)
|
|
18
|
+
results = self.calc.get_energy(atoms, coords)
|
|
19
|
+
|
|
20
|
+
energy = results["energy"]
|
|
21
|
+
# self.results["energy"] = results["energy"]
|
|
22
|
+
|
|
23
|
+
return energy
|
|
24
|
+
|
|
25
|
+
def get_forces(self, atoms=None):
|
|
26
|
+
atoms, coords = self.get_atoms_coords(atoms)
|
|
27
|
+
results = self.calc.get_forces(atoms, coords)
|
|
28
|
+
|
|
29
|
+
# Convert back to Angstrom for ASE
|
|
30
|
+
forces = results["forces"].reshape(-1, 3) / BOHR2ANG
|
|
31
|
+
|
|
32
|
+
# self.results["energy"] = results["energy"]
|
|
33
|
+
# self.results["forces"] = forces
|
|
34
|
+
|
|
35
|
+
return forces
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from pysisyphus.calculators.AnaPotBase import AnaPotBase
|
|
2
|
+
|
|
3
|
+
# See J. Chem. Phys. 122 174106 (2005)
|
|
4
|
+
# https://doi.org/10.1063/1.1885467
|
|
5
|
+
# Eq. (11)
|
|
6
|
+
|
|
7
|
+
class FourWellAnaPot(AnaPotBase):
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
V_str = "x**4 + y**4 - 2*x**2 - 4*y**2 + x*y + 0.3*x + 0.1*y"
|
|
11
|
+
xlim = (-1.75, 1.75)
|
|
12
|
+
ylim = (-1.75, 1.75)
|
|
13
|
+
minima = (
|
|
14
|
+
(1.12410175, -1.48527428, 0.0),
|
|
15
|
+
(-0.82190767, -1.36672971, 0.0),
|
|
16
|
+
(-1.17405609, 1.47708706, 0.0),
|
|
17
|
+
)
|
|
18
|
+
super().__init__(V_str=V_str, xlim=xlim, ylim=ylim, minima=minima)
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return "FourWellAnaPot calculator"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
fw = FourWellAnaPot()
|
|
26
|
+
fw.plot()
|
|
27
|
+
import matplotlib.pyplot as plt
|
|
28
|
+
plt.show()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# import numpy as np
|
|
2
|
+
from sympy import atan, symbols
|
|
3
|
+
|
|
4
|
+
from pysisyphus.calculators.AnaPotBase import AnaPotBase
|
|
5
|
+
from pysisyphus.calculators.LEPSExpr import LEPSExpr
|
|
6
|
+
|
|
7
|
+
# [1] 10.1063/1.4962019 Free-end adaptive NEB
|
|
8
|
+
|
|
9
|
+
class FreeEndNEBPot(AnaPotBase):
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Analyitcal potential as described in [1] Appendix A"""
|
|
13
|
+
leps_expr = LEPSExpr()
|
|
14
|
+
V_expr, xlim, ylim, levels = leps_expr.get_expr("harmonic")
|
|
15
|
+
|
|
16
|
+
self.x0 = 1.93
|
|
17
|
+
x = symbols("x")
|
|
18
|
+
|
|
19
|
+
V_expr = V_expr - 2*atan(5*(x-self.x0)) - 2*x
|
|
20
|
+
V_str = str(V_expr)
|
|
21
|
+
# xlim = (0.5, 2.25)
|
|
22
|
+
# ylim = (0.0, 1.6)
|
|
23
|
+
# levels = np.linspace(-10, 0, 125)
|
|
24
|
+
xlim = (0.5, 4)
|
|
25
|
+
ylim = (-4, 2)
|
|
26
|
+
minima = ((0.828758, 1.2027524, 0), (3.0894413, -1.4060824, 0))
|
|
27
|
+
|
|
28
|
+
super().__init__(V_str=V_str, xlim=xlim, ylim=ylim, levels=levels,
|
|
29
|
+
minima=minima)
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return "FreeEndNEBPot calculator"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
import matplotlib.pyplot as plt
|
|
37
|
+
fep = FreeEndNEBPot()
|
|
38
|
+
fep.plot()
|
|
39
|
+
plt.show()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pysisyphus.calculators.Gaussian16 import Gaussian16
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Gaussian09(Gaussian16):
|
|
5
|
+
|
|
6
|
+
conf_key = "gaussian09"
|
|
7
|
+
|
|
8
|
+
def __init__(self, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
self.fn_base = "gaussian09"
|
|
11
|
+
self.inp_fn = f"{self.fn_base}.com"
|
|
12
|
+
self.out_fn = f"{self.fn_base}.log"
|
|
13
|
+
self.chk_fn = f"{self.fn_base}.chk"
|
|
14
|
+
self.base_cmd = self.get_cmd()
|
|
15
|
+
self.formchk_cmd = self.get_cmd("formchk")
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return "Gaussian09 calculator"
|