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,285 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
import logging
|
|
3
|
+
from math import sqrt
|
|
4
|
+
from pprint import pprint
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import sympy as sym
|
|
8
|
+
|
|
9
|
+
from pysisyphus.helpers_pure import log
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("optimizer")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gen_solutions():
|
|
17
|
+
"""
|
|
18
|
+
Given two energies (e0, e1) and corresponding gradients (g0, g1) we
|
|
19
|
+
can (try to) fit a quartic polynomial
|
|
20
|
+
f(x) = a0 + a1*x + a2*x**2 + a3*x**3 + a4*x**4
|
|
21
|
+
s.t. the constraint f''(x) >= 0, with the equality being fullfilled
|
|
22
|
+
at only one point.
|
|
23
|
+
There are five unknowns (a0 - a4) to be determined. Four equations can
|
|
24
|
+
be derived from f(x) and its first derivative
|
|
25
|
+
f'(x) = a1 + 2*a2*x + 3*a3*x**2 + 4*a4*x**3 .
|
|
26
|
+
With (e0, g0) being given at x=0 and (e1, g1) being given at x=1 we can
|
|
27
|
+
setup the following equations:
|
|
28
|
+
f (0) = a0 (1)
|
|
29
|
+
f'(0) = a1 (2)
|
|
30
|
+
using e0 and g0 at x=0, and
|
|
31
|
+
f (1) = a0 + a1 + a2 + a3 + a4 (3)
|
|
32
|
+
f'(1) = a1 + 2*a2 + 3*a3 + 4*a4 . (4)
|
|
33
|
+
The missing last equation can be derived from the constraint. The second
|
|
34
|
+
derivative of f(x) is
|
|
35
|
+
f''(x) = 2*a2 + 6*a3*x + 12*a4*x**2
|
|
36
|
+
and shall be positive except at one point where it is allowed to be 0, that
|
|
37
|
+
its two roots (f''(x) = 0) must be degenerate. This is fullfilled when the
|
|
38
|
+
discriminant D of the quadratic polynomial a*x**2 + b*x + c is zero.
|
|
39
|
+
D = b**2 – 4*a*c = 0
|
|
40
|
+
With
|
|
41
|
+
a = 12*a4
|
|
42
|
+
b = 6*a3
|
|
43
|
+
c = 2*a2
|
|
44
|
+
we get
|
|
45
|
+
0 = (6*a3)**2 - 4*12*a4*2*a2
|
|
46
|
+
0 = 36*a3**2 - 96*a4*a2
|
|
47
|
+
0 = 3*a3**2 - 8*a4*a2 (5)
|
|
48
|
+
or
|
|
49
|
+
a4 = 3/8 * a3**2 / a2
|
|
50
|
+
Using (1) - (5) we can solve the set of equations for a0 - a4.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
e0, e1, g0, g1, a0, a1, a2, a3 = sym.symbols("e0 e1 g0 g1 a:4")
|
|
54
|
+
|
|
55
|
+
a4 = sym.Rational(3, 8) * a3 ** 2 / a2
|
|
56
|
+
s0, s1 = sym.solve(
|
|
57
|
+
(
|
|
58
|
+
e0 - a0,
|
|
59
|
+
g0 - a1,
|
|
60
|
+
e1 - a0 - a1 - a2 - a3 - a4,
|
|
61
|
+
g1 - a1 - 2 * a2 - 3 * a3 - 4 * a4,
|
|
62
|
+
3 * a3 ** 2 - 8 * a2 * a4,
|
|
63
|
+
),
|
|
64
|
+
(a0, a1, a2, a3),
|
|
65
|
+
)
|
|
66
|
+
print("Solution 0")
|
|
67
|
+
print("\t", s0)
|
|
68
|
+
print()
|
|
69
|
+
print("Solution 1")
|
|
70
|
+
print("\t", s1)
|
|
71
|
+
print()
|
|
72
|
+
# There will be two solutions (s0, s1), both containing a big sqrt(...) term
|
|
73
|
+
# that can be computed once and reused.
|
|
74
|
+
s0_cse = sym.cse(s0)
|
|
75
|
+
s1_cse = sym.cse(s1)
|
|
76
|
+
print("Solution 0 after CSE")
|
|
77
|
+
pprint(s0_cse)
|
|
78
|
+
print("Solution 1 after CSE")
|
|
79
|
+
pprint(s1_cse)
|
|
80
|
+
print()
|
|
81
|
+
# The terms in the sqrt-term correspond to binomial expansions and can be further
|
|
82
|
+
# simplified.
|
|
83
|
+
ref_term = (
|
|
84
|
+
-12 * e0 ** 2
|
|
85
|
+
+ 24 * e0 * e1
|
|
86
|
+
- 12 * e0 * g0
|
|
87
|
+
- 12 * e0 * g1
|
|
88
|
+
- 12 * e1 ** 2
|
|
89
|
+
+ 12 * e1 * g0
|
|
90
|
+
+ 12 * e1 * g1
|
|
91
|
+
- 2 * g0 ** 2
|
|
92
|
+
- 8 * g0 * g1
|
|
93
|
+
- 2 * g1 ** 2
|
|
94
|
+
)
|
|
95
|
+
sqrt_term = -2 * (
|
|
96
|
+
6 * (e0 - e1) ** 2 + 6 * (e0 - e1) * (g0 + g1) + (g0 + g1) ** 2 + 2 * g0 * g1
|
|
97
|
+
)
|
|
98
|
+
assert sym.simplify(sym.expand(sqrt_term) - ref_term) == 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_minimum(poly):
|
|
102
|
+
roots = np.roots(np.polyder(poly))
|
|
103
|
+
real_roots = np.real(roots[np.isreal(roots)])
|
|
104
|
+
vals = poly(real_roots)
|
|
105
|
+
min_ind = vals.argmin()
|
|
106
|
+
min_root = real_roots[vals.argmin()]
|
|
107
|
+
min_val = vals[min_ind]
|
|
108
|
+
return min_root, min_val
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_maximum(poly):
|
|
112
|
+
roots = np.roots(np.polyder(poly))
|
|
113
|
+
real_roots = np.real(roots[np.isreal(roots)])
|
|
114
|
+
vals = poly(real_roots)
|
|
115
|
+
max_ind = vals.argmax()
|
|
116
|
+
max_root = real_roots[max_ind]
|
|
117
|
+
max_val = vals[max_ind]
|
|
118
|
+
return max_root, max_val
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
FitResult = namedtuple("FitResult", "x y polys")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def quintic_fit(e0, e1, g0, g1, H0, H1):
|
|
125
|
+
a = -H0 / 2 + H1 / 2 - 6 * e0 + 6 * e1 - 3 * g0 - 3 * g1
|
|
126
|
+
b = 3 * H0 / 2 - H1 + 15 * e0 - 15 * e1 + 8 * g0 + 7 * g1
|
|
127
|
+
c = -3 * H0 / 2 + H1 / 2 - 10 * e0 + 10 * e1 - 6 * g0 - 4 * g1
|
|
128
|
+
d = H0 / 2
|
|
129
|
+
e = g0
|
|
130
|
+
f = e0
|
|
131
|
+
|
|
132
|
+
poly = np.poly1d((a, b, c, d, e, f))
|
|
133
|
+
try:
|
|
134
|
+
mr, mv = get_minimum(poly)
|
|
135
|
+
except ValueError:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
fit_result = FitResult(mr, mv, (poly,))
|
|
139
|
+
return fit_result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def quartic_fit(e0, e1, g0, g1, maximize=False):
|
|
143
|
+
"""See gen_solutions() for derivation."""
|
|
144
|
+
a0 = e0
|
|
145
|
+
a1 = g0
|
|
146
|
+
try:
|
|
147
|
+
sqrt_term = sqrt(
|
|
148
|
+
-2
|
|
149
|
+
* (
|
|
150
|
+
6 * (e0 - e1) ** 2
|
|
151
|
+
+ 6 * (e0 - e1) * (g0 + g1)
|
|
152
|
+
+ (g0 + g1) ** 2
|
|
153
|
+
+ 2 * g0 * g1
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
except ValueError:
|
|
157
|
+
# In these cases there is no intermediate minimum between 0 and 1 and the term
|
|
158
|
+
# under the square root becomes negative.
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
a2_pre = -3 * (e0 - e1) - 5 * g0 / 2 - g1 / 2
|
|
162
|
+
a3_pre = 2 * e0 - 2 * e1 + 2 * g0
|
|
163
|
+
|
|
164
|
+
def get_poly(a3, a2, a1, a0):
|
|
165
|
+
a4 = 3 / 8 * a3 ** 2 / a2
|
|
166
|
+
return np.poly1d((a4, a3, a2, a1, a0))
|
|
167
|
+
|
|
168
|
+
a2 = a2_pre - sqrt_term / 2
|
|
169
|
+
a3 = a3_pre + sqrt_term
|
|
170
|
+
poly0 = get_poly(a3, a2, a1, a0)
|
|
171
|
+
|
|
172
|
+
a2 = a2_pre + sqrt_term / 2
|
|
173
|
+
a3 = a3_pre - sqrt_term
|
|
174
|
+
poly1 = get_poly(a3, a2, a1, a0)
|
|
175
|
+
|
|
176
|
+
get_func = get_maximum if maximize else get_minimum
|
|
177
|
+
mr0, mv0 = get_func(poly0)
|
|
178
|
+
mr1, mv1 = get_func(poly1)
|
|
179
|
+
|
|
180
|
+
if maximize:
|
|
181
|
+
mr, mv = (mr0, mv0) if mv0 > mv1 else (mr1, mv1)
|
|
182
|
+
else:
|
|
183
|
+
mr, mv = (mr0, mv0) if mv0 < mv1 else (mr1, mv1)
|
|
184
|
+
|
|
185
|
+
# Shorter sympy implementation. Probably slower? But shouldn't matter...
|
|
186
|
+
# ... of course it does ;)
|
|
187
|
+
# a0, a1, a2, a3 = sym.symbols("a:4")
|
|
188
|
+
# a4 = sym.Rational(3, 8) * a3**2 / a2
|
|
189
|
+
# s0, s1 = sym.solve((e0-a0,
|
|
190
|
+
# g0-a1,
|
|
191
|
+
# e1-a0-a1-a2-a3-a4,
|
|
192
|
+
# g1-a1-2*a2-3*a3-4*a4,
|
|
193
|
+
# 3*a3**2 - 8*a2*a4),
|
|
194
|
+
# (a0, a1, a2, a3)
|
|
195
|
+
# )
|
|
196
|
+
# N = lambda exprs: [sym.N(expr) for expr in exprs]
|
|
197
|
+
# sym_poly0 = get_poly(*N(s0[::-1]))
|
|
198
|
+
# sym_poly1 = get_poly(*N(s1[::-1]))
|
|
199
|
+
|
|
200
|
+
fit_result = FitResult(mr, mv, (poly0, poly1))
|
|
201
|
+
return fit_result
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def cubic_fit(e0, e1, g0, g1):
|
|
205
|
+
# # Shorter sympy implementation. Probably slower? But shouldn't matter...
|
|
206
|
+
# # Ok it is really slow ... and it's gone.
|
|
207
|
+
# a0, a1, a2, a3 = sym.symbols("a:4")
|
|
208
|
+
# s = sym.solve((e0-a0,
|
|
209
|
+
# g0-a1,
|
|
210
|
+
# e1-a0-a1-a2-a3,
|
|
211
|
+
# g1-a1-2*a2-3*a3),
|
|
212
|
+
# (a0, a1, a2, a3),
|
|
213
|
+
# )
|
|
214
|
+
# coeffs = [float(sym.N(expr)) for expr in (s[a3], s[a2], s[a1], s[a0])]
|
|
215
|
+
d = e0
|
|
216
|
+
c = g0
|
|
217
|
+
b = -(g1 + 2 * g0 + 3 * e0 - 3 * e1)
|
|
218
|
+
a = 2 * (e0 - e1) + g0 + g1
|
|
219
|
+
# np.testing.assert_allclose([a, b, c, d], coeffs, atol=1e-10)
|
|
220
|
+
poly = np.poly1d((a, b, c, d))
|
|
221
|
+
try:
|
|
222
|
+
mr, mv = get_minimum(poly)
|
|
223
|
+
except ValueError:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
fit_result = FitResult(mr, mv, (poly,))
|
|
227
|
+
return fit_result
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def poly_line_search(
|
|
231
|
+
cur_energy,
|
|
232
|
+
prev_energy,
|
|
233
|
+
cur_grad,
|
|
234
|
+
prev_grad,
|
|
235
|
+
prev_step,
|
|
236
|
+
cubic_max_x=2.0,
|
|
237
|
+
quartic_max_x=4.0,
|
|
238
|
+
logger=None,
|
|
239
|
+
):
|
|
240
|
+
"""Generate directional gradients by projecting them on the previous step."""
|
|
241
|
+
return_torch = False
|
|
242
|
+
if isinstance(cur_grad, torch.Tensor):
|
|
243
|
+
return_torch = True
|
|
244
|
+
device, dtype = cur_grad.device, cur_grad.dtype
|
|
245
|
+
cur_grad = cur_grad.cpu().numpy()
|
|
246
|
+
prev_grad_proj = prev_step @ prev_grad
|
|
247
|
+
cur_grad_proj = prev_step @ cur_grad
|
|
248
|
+
cubic_result = cubic_fit(prev_energy, cur_energy, prev_grad_proj, cur_grad_proj)
|
|
249
|
+
quartic_result = quartic_fit(prev_energy, cur_energy, prev_grad_proj, cur_grad_proj)
|
|
250
|
+
accept = {
|
|
251
|
+
"cubic": lambda x: (x > 0.0) and (x < cubic_max_x),
|
|
252
|
+
"quartic": lambda x: (x > 0.0) and (x <= quartic_max_x),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fit_result = None
|
|
256
|
+
if quartic_result and accept["quartic"](quartic_result.x):
|
|
257
|
+
fit_result = quartic_result
|
|
258
|
+
deg = "quartic"
|
|
259
|
+
elif cubic_result and accept["cubic"](cubic_result.x):
|
|
260
|
+
fit_result = cubic_result
|
|
261
|
+
deg = "cubic"
|
|
262
|
+
|
|
263
|
+
fit_energy = None
|
|
264
|
+
fit_grad = None
|
|
265
|
+
fit_step = None
|
|
266
|
+
if fit_result and fit_result.y < prev_energy:
|
|
267
|
+
x = fit_result.x
|
|
268
|
+
fit_energy = fit_result.y
|
|
269
|
+
log(logger, f"Did {deg} interpolation with x={x:.6f}.")
|
|
270
|
+
|
|
271
|
+
# Interpolate coordinates and gradient. 'fit_step' applied to the current
|
|
272
|
+
# coordinates yields interpolated coordinates.
|
|
273
|
+
#
|
|
274
|
+
# x == 0 would take us to the previous coordinates:
|
|
275
|
+
# (1-0) * -prev_step = -prev_step (we revert the last step)
|
|
276
|
+
# x == 1 would preserve the current coordinates:
|
|
277
|
+
# (1-1) * -prev_step = 0 (we stay at the current coordinates)
|
|
278
|
+
# x > 1 extrapolate along previous step direction:
|
|
279
|
+
# with x=2, (1-2) * -prev_step = -1*-prev_step = prev_step
|
|
280
|
+
fit_step = (1 - x) * -prev_step
|
|
281
|
+
fit_grad = (1 - x) * prev_grad + x * cur_grad
|
|
282
|
+
if return_torch:
|
|
283
|
+
fit_grad = torch.tensor(fit_grad, dtype=dtype, device=device)
|
|
284
|
+
fit_step = torch.tensor(fit_step, dtype=dtype, device=device)
|
|
285
|
+
return fit_energy, fit_grad, fit_step
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import itertools as it
|
|
2
|
+
from typing import Literal, get_args
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from scipy.spatial.distance import pdist, squareform
|
|
6
|
+
from scipy.sparse import dok_matrix
|
|
7
|
+
|
|
8
|
+
from pysisyphus.helpers_pure import log
|
|
9
|
+
from pysisyphus.intcoords.setup import get_pair_covalent_radii
|
|
10
|
+
from pysisyphus.intcoords.setup_fast import (
|
|
11
|
+
find_bonds_for_geom,
|
|
12
|
+
find_bonds_bends,
|
|
13
|
+
find_bonds_bends_dihedrals,
|
|
14
|
+
)
|
|
15
|
+
from pysisyphus.intcoords.derivatives import dq_b, dq_a, dq_d
|
|
16
|
+
from pysisyphus.intcoords import RedundantCoords
|
|
17
|
+
from pysisyphus.optimizers.guess_hessians import get_lindh_alpha
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# [1] https://www.nature.com/articles/s41598-018-32105-x
|
|
21
|
+
# Mones, 2018
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_lindh_k(atoms, coords3d, bonds=None, angles=None, torsions=None):
|
|
25
|
+
if bonds is None:
|
|
26
|
+
bonds = list()
|
|
27
|
+
if angles is None:
|
|
28
|
+
angles = list()
|
|
29
|
+
if torsions is None:
|
|
30
|
+
torsions = list()
|
|
31
|
+
|
|
32
|
+
atoms = [a.lower() for a in atoms]
|
|
33
|
+
|
|
34
|
+
alphas = [get_lindh_alpha(a1, a2) for a1, a2 in it.combinations(atoms, 2)]
|
|
35
|
+
pair_cov_radii = get_pair_covalent_radii(atoms)
|
|
36
|
+
cdm = pdist(coords3d)
|
|
37
|
+
rhos = squareform(np.exp(alphas * (pair_cov_radii ** 2 - cdm ** 2)))
|
|
38
|
+
|
|
39
|
+
k_dict = {
|
|
40
|
+
2: 0.45, # Stretches/bonds
|
|
41
|
+
3: 0.15, # Bends/angles
|
|
42
|
+
4: 0.005, # Torsions/dihedrals
|
|
43
|
+
}
|
|
44
|
+
ks = list()
|
|
45
|
+
for inds in it.chain(bonds, angles, torsions):
|
|
46
|
+
rho_product = 1
|
|
47
|
+
for i in range(len(inds) - 1):
|
|
48
|
+
i1, i2 = inds[i : i + 2]
|
|
49
|
+
rho_product *= rhos[i1, i2]
|
|
50
|
+
ks.append(k_dict[len(inds)] * rho_product)
|
|
51
|
+
return ks
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_lindh_precon(
|
|
55
|
+
atoms,
|
|
56
|
+
coords,
|
|
57
|
+
bonds=None,
|
|
58
|
+
bends=None,
|
|
59
|
+
dihedrals=None,
|
|
60
|
+
c_stab=0.0103,
|
|
61
|
+
logger=None,
|
|
62
|
+
):
|
|
63
|
+
"""c_stab = 0.00103 hartree/bohr² corresponds to 0.1 eV/Ų as
|
|
64
|
+
given in the paper."""
|
|
65
|
+
|
|
66
|
+
if bonds is None:
|
|
67
|
+
bonds = list()
|
|
68
|
+
if bends is None:
|
|
69
|
+
bends = list()
|
|
70
|
+
if dihedrals is None:
|
|
71
|
+
dihedrals = list()
|
|
72
|
+
|
|
73
|
+
dim = coords.size
|
|
74
|
+
c3d = coords.reshape(-1, 3)
|
|
75
|
+
|
|
76
|
+
# Calculate Lindh force constants
|
|
77
|
+
ks = get_lindh_k(atoms, c3d, bonds, bends)
|
|
78
|
+
|
|
79
|
+
grad_funcs = {
|
|
80
|
+
2: dq_b, # Bond
|
|
81
|
+
3: dq_a, # Bend
|
|
82
|
+
4: dq_d, # Dihedral
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
P = dok_matrix((dim, dim))
|
|
86
|
+
for inds, k in zip(it.chain(bonds, bends, dihedrals), ks):
|
|
87
|
+
# First derivatives of internal coordinates w.r.t cartesian coordinates
|
|
88
|
+
int_grad = grad_funcs[len(inds)](*c3d[inds].flatten())
|
|
89
|
+
# Assign to the correct cartesian indices
|
|
90
|
+
cart_inds = np.array(list(it.chain(*[range(3 * i, 3 * i + 3) for i in inds])))
|
|
91
|
+
P[cart_inds[:, None], cart_inds[None, :]] += abs(k) * np.outer(
|
|
92
|
+
int_grad, int_grad
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Add stabilization to diagonal
|
|
96
|
+
P[np.diag_indices(dim)] += c_stab
|
|
97
|
+
P = P.tocsc()
|
|
98
|
+
filled = P.size / dim ** 2
|
|
99
|
+
log(logger, f"Preconditioner P has {P.size} entries ({filled:.2%} filled)")
|
|
100
|
+
|
|
101
|
+
return P
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
PreconKind = Literal["full", "full_fast", "bonds", "bonds_bends"]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def precon_getter(geom, c_stab=0.0103, kind="full", logger=None):
|
|
108
|
+
valid_kinds = get_args(PreconKind)
|
|
109
|
+
assert kind in valid_kinds, f"Invalid kind='{kind}'! Valid kinds are: {valid_kinds}"
|
|
110
|
+
|
|
111
|
+
atoms = geom.moving_atoms
|
|
112
|
+
if len(geom.freeze_atoms) > 0:
|
|
113
|
+
assert (
|
|
114
|
+
kind == "full"
|
|
115
|
+
), "Preconditioning with frozen atoms is only supported for kind='full'"
|
|
116
|
+
# Default empty lists for coordinates that may be skipped
|
|
117
|
+
# for kind != "full".
|
|
118
|
+
bends = list()
|
|
119
|
+
dihedrals = list()
|
|
120
|
+
if kind == "full":
|
|
121
|
+
internal = RedundantCoords(
|
|
122
|
+
atoms,
|
|
123
|
+
geom.coords,
|
|
124
|
+
)
|
|
125
|
+
bonds = internal.bond_atom_indices
|
|
126
|
+
bends = internal.bend_atom_indices
|
|
127
|
+
dihedrals = internal.dihedral_atom_indices
|
|
128
|
+
elif kind == "full_fast":
|
|
129
|
+
bonds, bends, dihedrals = find_bonds_bends_dihedrals(geom)
|
|
130
|
+
elif kind == "bonds_bends":
|
|
131
|
+
bonds, bends = find_bonds_bends(geom)
|
|
132
|
+
elif kind == "bonds":
|
|
133
|
+
bonds = find_bonds_for_geom(geom)
|
|
134
|
+
|
|
135
|
+
msg = (
|
|
136
|
+
f"Constructing preconditioner from {len(bonds)} bonds, {len(bends)} bends "
|
|
137
|
+
f"and {len(dihedrals)} dihedrals (kind='{kind}')."
|
|
138
|
+
)
|
|
139
|
+
log(logger, msg)
|
|
140
|
+
|
|
141
|
+
def wrapper(coords):
|
|
142
|
+
P = get_lindh_precon(
|
|
143
|
+
atoms,
|
|
144
|
+
coords,
|
|
145
|
+
bonds,
|
|
146
|
+
bends,
|
|
147
|
+
dihedrals,
|
|
148
|
+
c_stab=c_stab,
|
|
149
|
+
logger=logger,
|
|
150
|
+
)
|
|
151
|
+
return P
|
|
152
|
+
|
|
153
|
+
return wrapper
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def scale_by_max_step(steps, max_step):
|
|
5
|
+
steps_max = np.abs(steps).max()
|
|
6
|
+
if steps_max > max_step:
|
|
7
|
+
steps *= max_step / steps_max
|
|
8
|
+
return steps
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_scale_max(max_element):
|
|
12
|
+
def scale_max(step):
|
|
13
|
+
step_max = np.abs(step).max()
|
|
14
|
+
if step_max > max_element:
|
|
15
|
+
step *= max_element / step_max
|
|
16
|
+
return step
|
|
17
|
+
return scale_max
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def restrict_step(steps, max_step):
|
|
21
|
+
too_big = np.abs(steps) > max_step
|
|
22
|
+
signs = np.sign(steps[too_big])
|
|
23
|
+
steps[too_big] = signs * max_step
|
|
24
|
+
return steps
|
pysisyphus/pack.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from math import pi as PI, ceil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from pysisyphus.constants import AMU2KG
|
|
7
|
+
from pysisyphus.helpers import geom_loader
|
|
8
|
+
from pysisyphus.helpers_pure import get_input, highlight_text
|
|
9
|
+
from pysisyphus.io.pdb import geom_to_pdb_str
|
|
10
|
+
from pysisyphus.wrapper.packmol import make_input, call_packmol
|
|
11
|
+
|
|
12
|
+
from pysisyphus.db import LEVELS, MOLECULES
|
|
13
|
+
from pysisyphus.db.helpers import get_path as db_get_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
AMU2G = AMU2KG * 1e3
|
|
17
|
+
CM2ANG = 1e-8
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_args(args):
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
|
|
23
|
+
# Solvent
|
|
24
|
+
solvent_group = parser.add_mutually_exclusive_group(required=True)
|
|
25
|
+
solvent_group.add_argument("--solv", help="Filename of solvent geometry.")
|
|
26
|
+
solvent_group.add_argument(
|
|
27
|
+
"--db", action="store_true", help="Choose from internal database."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--solv_num", type=int, help="Number of solvent molecules to pack."
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--solv_dens", type=float, help="Solvent density in g/cm³.")
|
|
34
|
+
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--output", default="output.pdb", help="Filename of packed molecules."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Solute
|
|
40
|
+
parser.add_argument("--solute", default=None, help="Filename of solute geometry.")
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--solute_num", type=int, default=1, help="Number of solute molecules to pack."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return parser.parse_args(args)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def as_pdb(fn):
|
|
49
|
+
if not str(fn).endswith(".pdb"):
|
|
50
|
+
geom = geom_loader(fn)
|
|
51
|
+
pdb_str = geom_to_pdb_str(geom)
|
|
52
|
+
cwd = Path(".")
|
|
53
|
+
pdb_fn = cwd / Path(fn).with_suffix(".pdb").name
|
|
54
|
+
with open(pdb_fn, "w") as handle:
|
|
55
|
+
handle.write(pdb_str)
|
|
56
|
+
print(f"Converted '{fn}' to PDB format ('{pdb_fn}')")
|
|
57
|
+
return pdb_fn
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sphere_radius_from_volume(volume):
|
|
61
|
+
radius = (3 / 4 * volume / PI) ** (1 / 3)
|
|
62
|
+
return radius
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def volume_for_density(molecule_num, mol_mass, density):
|
|
66
|
+
# Convert density from g/cm³ to amu/ų
|
|
67
|
+
density_au = density / AMU2G * CM2ANG ** 3
|
|
68
|
+
# The molar mass in g/mol is numerically equal to the value in AMU (dalton)
|
|
69
|
+
# so we can use it as it is.
|
|
70
|
+
total_mass = mol_mass * molecule_num
|
|
71
|
+
# Volume in Å
|
|
72
|
+
volume = total_mass / density_au
|
|
73
|
+
|
|
74
|
+
return volume
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def print_info(title, geom):
|
|
78
|
+
print(title)
|
|
79
|
+
print(f"\t{geom}")
|
|
80
|
+
print(f"\tMolar mass: {geom.total_mass:.2f} g mol⁻¹")
|
|
81
|
+
print()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run():
|
|
85
|
+
args = parse_args(sys.argv[1:])
|
|
86
|
+
|
|
87
|
+
solute_fn = args.solute
|
|
88
|
+
if solute_fn:
|
|
89
|
+
solute = geom_loader(solute_fn)
|
|
90
|
+
solute_num = args.solute_num
|
|
91
|
+
solute_mass = solute.total_mass
|
|
92
|
+
print_info("Solute", solute)
|
|
93
|
+
else:
|
|
94
|
+
solute = None
|
|
95
|
+
solute_mass = 0.0
|
|
96
|
+
|
|
97
|
+
solv_fn = args.solv
|
|
98
|
+
if solv_fn:
|
|
99
|
+
solv_dens = args.solv_dens
|
|
100
|
+
# Load from internal db
|
|
101
|
+
else:
|
|
102
|
+
print(highlight_text("Interactive solvent selection"))
|
|
103
|
+
level = get_input(LEVELS, "Level of theory", lbl_func=lambda lvl: lvl[0])
|
|
104
|
+
print()
|
|
105
|
+
molecule = get_input(MOLECULES, "Molecule", lbl_func=lambda mol: mol.name)
|
|
106
|
+
print()
|
|
107
|
+
|
|
108
|
+
solv_fn = db_get_path(molecule.name, level[0])
|
|
109
|
+
solv_dens = molecule.density
|
|
110
|
+
|
|
111
|
+
solv = geom_loader(solv_fn)
|
|
112
|
+
solv_num = args.solv_num
|
|
113
|
+
solv_mass = solv.total_mass
|
|
114
|
+
print_info("Solvent", solv)
|
|
115
|
+
|
|
116
|
+
solute_solv_mass = solute_mass + solv_num * solv_mass
|
|
117
|
+
print(f"Total mass of solute(s) and solvent(s): {solute_solv_mass:.2f} amu")
|
|
118
|
+
print()
|
|
119
|
+
|
|
120
|
+
# Solvent volume
|
|
121
|
+
solv_vol = volume_for_density(solv_num, solv_mass, solv_dens)
|
|
122
|
+
print(f"Solvent volume: {solv_vol:>10.2f} ų")
|
|
123
|
+
|
|
124
|
+
# Solute volume; Use the solvent density for this calculation
|
|
125
|
+
if solute:
|
|
126
|
+
solute_vol = volume_for_density(solute_num, solute_mass, solv_dens)
|
|
127
|
+
print(f" Solute volume: {solute_vol:>10.2f} ų")
|
|
128
|
+
else:
|
|
129
|
+
solute_vol = 0.0
|
|
130
|
+
|
|
131
|
+
total_vol = solv_vol + solute_vol
|
|
132
|
+
print(f" Total volume: {total_vol:>10.2f} ų")
|
|
133
|
+
print()
|
|
134
|
+
|
|
135
|
+
radius = sphere_radius_from_volume(total_vol)
|
|
136
|
+
print(f" Sphere radius: {radius:>8.2f} Å")
|
|
137
|
+
cradius = ceil(radius)
|
|
138
|
+
print(f"Using ceil(radius): {cradius:>8.2f} Å")
|
|
139
|
+
print()
|
|
140
|
+
|
|
141
|
+
# Create solute/solvent PDBs if needed
|
|
142
|
+
|
|
143
|
+
inp_kwargs = {
|
|
144
|
+
"output_fn": args.output,
|
|
145
|
+
"solvent_fn": as_pdb(solv_fn),
|
|
146
|
+
"solvent_num": solv_num,
|
|
147
|
+
"sphere_radius": cradius,
|
|
148
|
+
}
|
|
149
|
+
if solute:
|
|
150
|
+
inp_kwargs.update({"solute_fn": as_pdb(solute_fn), "solute_num": solute_num})
|
|
151
|
+
|
|
152
|
+
inp = make_input(**inp_kwargs)
|
|
153
|
+
inp_fn = "packmol.inp"
|
|
154
|
+
with open(inp_fn, "w") as handle:
|
|
155
|
+
handle.write(inp)
|
|
156
|
+
print(f"Wrote packmol input to '{inp_fn}'")
|
|
157
|
+
|
|
158
|
+
proc = call_packmol(inp)
|
|
159
|
+
|
|
160
|
+
log_fn = "packmol.log"
|
|
161
|
+
with open(log_fn, "w") as handle:
|
|
162
|
+
handle.write(proc.stdout)
|
|
163
|
+
print(f"Wrote packmol ouput to '{log_fn}'")
|
|
164
|
+
print()
|
|
165
|
+
|
|
166
|
+
return_ = proc.returncode
|
|
167
|
+
if return_ != 0:
|
|
168
|
+
print(proc.stdout)
|
|
169
|
+
else:
|
|
170
|
+
print("packmol returned successfully!")
|
|
171
|
+
|
|
172
|
+
return return_
|