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,74 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
NINE_ZEROS = struct.pack("d" * 9, *[0.0] * 9)
|
|
7
|
+
EYE3 = struct.pack("d" * 9, *np.eye(3).flatten())
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def send_closure(sock, hdrlen, fmts, verbose=False):
|
|
11
|
+
def send_msg(msg, fmt=None, packed=False):
|
|
12
|
+
"""Send message through socket.
|
|
13
|
+
|
|
14
|
+
If fmt is None and the message is not packed (converted to bytes),
|
|
15
|
+
it is either convert to bytes via struct with the given fmt, or it
|
|
16
|
+
is assumed to be a str and encoded as ascii. Strings are padded
|
|
17
|
+
to a length of hdrlen (header length, default=12).
|
|
18
|
+
|
|
19
|
+
If it is already packed the message is sent as is, e.g. for the
|
|
20
|
+
virial, which is already in bytes (packed).
|
|
21
|
+
"""
|
|
22
|
+
if not packed:
|
|
23
|
+
if fmt == "floats":
|
|
24
|
+
msg = struct.pack(fmts[fmt], *msg)
|
|
25
|
+
elif fmt is not None:
|
|
26
|
+
msg = struct.pack(fmts[fmt], msg)
|
|
27
|
+
else:
|
|
28
|
+
msg = f"{msg: <{hdrlen}}".encode("ascii")
|
|
29
|
+
if verbose:
|
|
30
|
+
print(f"SENDING: {msg}")
|
|
31
|
+
sock.sendall(msg)
|
|
32
|
+
|
|
33
|
+
return send_msg
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
"""Vllt. recv_msg so verändern, dass es, wenn ein fmt gegeben ist auch
|
|
37
|
+
automatisch die korrekte anzahl an bytes erwartet"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def recv_closure(sock, hdrlen, fmts, verbose=False):
|
|
41
|
+
def recv_msg(nbytes=None, fmt=None, expect=""):
|
|
42
|
+
"""Receive a message of given length from the socket.
|
|
43
|
+
|
|
44
|
+
If nbytes is not given we expect hdrlen (header length) bytes.
|
|
45
|
+
If fmt is given the message is also unpacked using struct, otherwise
|
|
46
|
+
ascii bytes are assumed and a string is returned.
|
|
47
|
+
"""
|
|
48
|
+
if nbytes is None:
|
|
49
|
+
nbytes = hdrlen
|
|
50
|
+
|
|
51
|
+
msg = sock.recv(nbytes)
|
|
52
|
+
if fmt:
|
|
53
|
+
msg = struct.unpack(fmts[fmt], msg)
|
|
54
|
+
else:
|
|
55
|
+
msg = msg.decode("ascii").strip()
|
|
56
|
+
if verbose:
|
|
57
|
+
if expect:
|
|
58
|
+
expect = f", expected '{expect}'"
|
|
59
|
+
|
|
60
|
+
print(f"RECEIVED: {msg}{expect}")
|
|
61
|
+
return msg
|
|
62
|
+
|
|
63
|
+
return recv_msg
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_fmts(cartesians):
|
|
67
|
+
fmts = {
|
|
68
|
+
"int": "i", # Atom number
|
|
69
|
+
"float": "d", # Energy
|
|
70
|
+
"nine_floats": "d" * 9, # (Inverse) cell vectors, virial, zeros
|
|
71
|
+
"floats": "d" * cartesians, # Forces
|
|
72
|
+
"floats_sq": "d" * cartesians**2, # Hessian
|
|
73
|
+
}
|
|
74
|
+
return fmts
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# See [1] 10.1002/jcc.21026
|
|
2
|
+
|
|
3
|
+
import itertools as it
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import rmsd
|
|
7
|
+
|
|
8
|
+
from pysisyphus.Geometry import Geometry
|
|
9
|
+
from pysisyphus.linalg import get_rot_mat
|
|
10
|
+
from pysisyphus.stocastic.Kick import Kick
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FragmentKick(Kick):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
geom,
|
|
17
|
+
fragments,
|
|
18
|
+
fix_fragments=None,
|
|
19
|
+
displace_from=None,
|
|
20
|
+
random_displacement=False,
|
|
21
|
+
**kwargs,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(geom, **kwargs)
|
|
24
|
+
|
|
25
|
+
self.fragments = self.get_fragments(fragments)
|
|
26
|
+
if fix_fragments is None:
|
|
27
|
+
fix_fragments = (0,)
|
|
28
|
+
self.fix_fragments = fix_fragments
|
|
29
|
+
|
|
30
|
+
if displace_from is None:
|
|
31
|
+
displace_from = list()
|
|
32
|
+
self.displace_from = displace_from
|
|
33
|
+
# True when displace_from is given
|
|
34
|
+
self.random_displacement = self.displace_from or random_displacement
|
|
35
|
+
|
|
36
|
+
frag_coords = [self.initial_geom.coords3d[frag] for frag in self.fragments]
|
|
37
|
+
# Shift centroids of the fragment into the cartesian origin
|
|
38
|
+
self.frag_coords = [fc - fc.mean(axis=0) for fc in frag_coords]
|
|
39
|
+
atoms_arr = np.array(self.initial_geom.atoms)
|
|
40
|
+
self.frag_atoms = [atoms_arr[frag] for frag in self.fragments]
|
|
41
|
+
|
|
42
|
+
new_coords3d = np.concatenate(self.frag_coords)
|
|
43
|
+
geom = Geometry(list(it.chain(*self.frag_atoms)), new_coords3d.flatten())
|
|
44
|
+
with open("fragments_in_origin.xyz", "w") as handle:
|
|
45
|
+
handle.write(geom.as_xyz())
|
|
46
|
+
|
|
47
|
+
self.frag_inds_flat = list(it.chain(*self.fragments))
|
|
48
|
+
# Given an array with fragment coordinates these indices will sort the
|
|
49
|
+
# array into the original atom order.
|
|
50
|
+
self.sort_inds = np.argsort(self.frag_inds_flat)
|
|
51
|
+
self.print_fragments()
|
|
52
|
+
|
|
53
|
+
def print_fragments(self):
|
|
54
|
+
lines = self.initial_geom.as_xyz().split("\n")[2:]
|
|
55
|
+
for i, fragment in enumerate(self.fragments):
|
|
56
|
+
self.log(f"Fragment {i}:")
|
|
57
|
+
frag_lines = [lines[j] for j in fragment]
|
|
58
|
+
self.log("\n".join(frag_lines) + "\n")
|
|
59
|
+
|
|
60
|
+
def get_fragments(self, fragments):
|
|
61
|
+
fragments = list(fragments)
|
|
62
|
+
# Compare number of atoms defined in fragments with the total
|
|
63
|
+
# number of atoms in the molecule.
|
|
64
|
+
fragment_atoms = list(it.chain(*fragments))
|
|
65
|
+
if len(fragment_atoms) != len(self.atoms):
|
|
66
|
+
self.log(
|
|
67
|
+
"Fragments were not fully specified. " "Determining remaining fragment."
|
|
68
|
+
)
|
|
69
|
+
all_indices = set(range(len(self.atoms)))
|
|
70
|
+
new_fragment = list(all_indices - set(fragment_atoms))
|
|
71
|
+
fragments.append(new_fragment)
|
|
72
|
+
|
|
73
|
+
fragments = [np.array(frag) for frag in fragments]
|
|
74
|
+
return fragments
|
|
75
|
+
|
|
76
|
+
def get_origin(self):
|
|
77
|
+
if not self.random_displacement:
|
|
78
|
+
return (0, 0, 0)
|
|
79
|
+
|
|
80
|
+
# Coordinates of fixed fragment
|
|
81
|
+
frag_coords = self.frag_coords[self.fix_fragments[0]]
|
|
82
|
+
"""Here we have two options:
|
|
83
|
+
1.) displace_from is not given, so we use all possible
|
|
84
|
+
atom indices in the fragment.
|
|
85
|
+
2.) displace_from is given, so we restrict the valid
|
|
86
|
+
atom indices. This way some degree of symmetry can
|
|
87
|
+
be considered and the generated structures should
|
|
88
|
+
be more similar. This should reduce the effort in
|
|
89
|
+
matching the geometries later on with the hungarian
|
|
90
|
+
method.
|
|
91
|
+
|
|
92
|
+
Consider a stocastic search for minima between benzene and
|
|
93
|
+
a chlorine molecule. As all carbons/hydrogens in the benzene
|
|
94
|
+
are equivalent one could greatly reduce the effort of matching
|
|
95
|
+
atoms later on, when displacing only from one carbon and one
|
|
96
|
+
hydrogen."""
|
|
97
|
+
if not self.displace_from:
|
|
98
|
+
frag_indices = np.arange(frag_coords.shape[0])
|
|
99
|
+
else:
|
|
100
|
+
frag_indices = self.displace_from
|
|
101
|
+
|
|
102
|
+
# Select random atom of fixed fragment
|
|
103
|
+
random_ind = np.random.choice(frag_indices, size=1)[0]
|
|
104
|
+
return frag_coords[random_ind]
|
|
105
|
+
|
|
106
|
+
def kick_fragment(self, frag_coords):
|
|
107
|
+
R = get_rot_mat()
|
|
108
|
+
kick = self.get_kick()[:3]
|
|
109
|
+
# Fragment rotation
|
|
110
|
+
rot_coords = R.dot(frag_coords.T).T
|
|
111
|
+
# Fragment translation
|
|
112
|
+
rot_kicked_coords = rot_coords + self.get_origin() + kick
|
|
113
|
+
return rot_kicked_coords
|
|
114
|
+
|
|
115
|
+
def get_input_geom(self, geom):
|
|
116
|
+
kicked_frags = list()
|
|
117
|
+
for i, fc in enumerate(self.frag_coords):
|
|
118
|
+
if i in self.fix_fragments:
|
|
119
|
+
kicked_frag = fc
|
|
120
|
+
else:
|
|
121
|
+
kicked_frag = self.kick_fragment(fc)
|
|
122
|
+
kicked_frags.append(kicked_frag)
|
|
123
|
+
new_coords3d = np.concatenate(kicked_frags)
|
|
124
|
+
# As the fragments are not necessarily defined in order, especially
|
|
125
|
+
# when we generate the second fragment automatically, we have to
|
|
126
|
+
# sort the coordinates, so they are in the original order. E.g. the
|
|
127
|
+
# fragments ((4, 3), (2, 1, 0)) would result 'new_coords3d' with the
|
|
128
|
+
# coordinates of atom 4 at index 0, coordinates of atom 3 at index 1, etc.
|
|
129
|
+
new_coords3d = new_coords3d[self.sort_inds]
|
|
130
|
+
new_coords = rmsd.kabsch_rotate(new_coords3d, self.initial_coords3d).flatten()
|
|
131
|
+
new_geom = Geometry(self.atoms, new_coords)
|
|
132
|
+
return new_geom
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import rmsd
|
|
3
|
+
|
|
4
|
+
from pysisyphus.stocastic.Pipeline import Pipeline
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Kick(Pipeline):
|
|
8
|
+
|
|
9
|
+
def __init__(self, geom, radius=0.5, **kwargs):
|
|
10
|
+
super().__init__(geom, **kwargs)
|
|
11
|
+
|
|
12
|
+
self.radius = radius
|
|
13
|
+
|
|
14
|
+
def get_kick(self):
|
|
15
|
+
# Interval [0, 1)
|
|
16
|
+
kick = np.random.random(4*self.coords_size)
|
|
17
|
+
# Stretch [0, 1) to [-r ... r)
|
|
18
|
+
kick = self.radius * (2*kick - 1)
|
|
19
|
+
kick = kick.reshape(-1, 3)
|
|
20
|
+
# Filter for kicks within a sphere with radius self.radius
|
|
21
|
+
kick_lengths = np.linalg.norm(kick, axis=1)
|
|
22
|
+
valid_kicks = kick[kick_lengths <= self.radius].flatten()
|
|
23
|
+
# Do a recursion when not enough valid kicks were found
|
|
24
|
+
if valid_kicks.size < self.coords_size:
|
|
25
|
+
valid_kicks = self.get_kick()
|
|
26
|
+
# Don't return more than we need
|
|
27
|
+
return valid_kicks[:self.coords_size]
|
|
28
|
+
|
|
29
|
+
def get_input_geom(self, geom):
|
|
30
|
+
kick = self.get_kick()
|
|
31
|
+
new_geom = geom.copy()
|
|
32
|
+
new_coords = new_geom.coords + kick
|
|
33
|
+
# Rotate the newly generated coordinates on the initial
|
|
34
|
+
# coordinates.
|
|
35
|
+
# TODO: this may be not needed as we align+match later on ...
|
|
36
|
+
new_coords = rmsd.kabsch_rotate(new_coords.reshape(-1, 3),
|
|
37
|
+
self.initial_coords3d
|
|
38
|
+
).flatten()
|
|
39
|
+
new_geom.coords = new_coords
|
|
40
|
+
return new_geom
|
|
41
|
+
|
|
42
|
+
def run_cycle(self, geom):
|
|
43
|
+
print(f"##### Cycle {self.cur_cycle:03d}, "
|
|
44
|
+
f"Micro Cycle {self.cur_micro_cycle:03d} #####")
|
|
45
|
+
opt_geoms = [self.run_kicked_geom(geom) for i in range(self.cycle_size)]
|
|
46
|
+
# Filter out None
|
|
47
|
+
opt_geoms = [geom for geom in opt_geoms if geom]
|
|
48
|
+
opt_num = len(opt_geoms)
|
|
49
|
+
print(f"{opt_num}/{self.cycle_size} optimizations converged.")
|
|
50
|
+
|
|
51
|
+
# Comparing to the initial geometry is only useful when the initial
|
|
52
|
+
# geometry is optimized. Otherwise all (small) kicks will converge
|
|
53
|
+
# to the same optimized structure, that is still very different from
|
|
54
|
+
# the initial one.
|
|
55
|
+
"""
|
|
56
|
+
initial_rmsds = [
|
|
57
|
+
rmsd.kabsch_rmsd(self.initial_coords3d, ogeom.coords3d)
|
|
58
|
+
for ogeom in opt_geoms
|
|
59
|
+
]
|
|
60
|
+
print("Initial RMDS")
|
|
61
|
+
print(initial_rmsds)
|
|
62
|
+
"""
|
|
63
|
+
kept_geoms = self.get_unique_geometries(opt_geoms)
|
|
64
|
+
|
|
65
|
+
# cycle_str = f"{self.cur_cycle:03d}_{self.cur_micro_cycle:03d}"
|
|
66
|
+
# fn_base = f"cycle_{cycle_str}"
|
|
67
|
+
# trj_fn = f"{fn_base}_trj.xyz"
|
|
68
|
+
# with open(trj_fn, "w") as handle:
|
|
69
|
+
# handle.write(make_trj_str_from_geoms(opt_geoms))
|
|
70
|
+
|
|
71
|
+
# trj_filtered_fn = f"{fn_base}_filtered_trj.xyz"
|
|
72
|
+
# with open(trj_filtered_fn, "w") as handle:
|
|
73
|
+
# handle.write(make_trj_str_from_geoms(kept_geoms))
|
|
74
|
+
|
|
75
|
+
# for i, ogeom in enumerate(kept_geoms):
|
|
76
|
+
# fn = f"geom_{i:02d}_{cycle_str}.xyz"
|
|
77
|
+
# with open(fn, "w") as handle:
|
|
78
|
+
# handle.write(ogeom.as_xyz())
|
|
79
|
+
self.cur_micro_cycle += 1
|
|
80
|
+
|
|
81
|
+
return kept_geoms
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
import itertools as it
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import rmsd
|
|
8
|
+
from scipy.spatial.distance import pdist
|
|
9
|
+
|
|
10
|
+
from pysisyphus.calculators.XTB import XTB
|
|
11
|
+
from pysisyphus.helpers import check_for_end_sign
|
|
12
|
+
from pysisyphus.helpers_pure import highlight_text
|
|
13
|
+
from pysisyphus.intcoords.setup import get_pair_covalent_radii
|
|
14
|
+
from pysisyphus.optimizers.RFOptimizer import RFOptimizer
|
|
15
|
+
from pysisyphus.stocastic.align import matched_rmsd
|
|
16
|
+
from pysisyphus.xyzloader import make_trj_str_from_geoms
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Pipeline:
|
|
20
|
+
|
|
21
|
+
def __init__(self, geom, calc_getter=None, seed=None, max_cycles=5, cycle_size=15,
|
|
22
|
+
rmsd_thresh=.1, energy_thresh=1e-3, energy_range=.125,
|
|
23
|
+
compare_num=25, break_after=2,
|
|
24
|
+
calc_kwargs=None):
|
|
25
|
+
self.initial_geom = geom
|
|
26
|
+
self.calc_getter = calc_getter
|
|
27
|
+
self.max_cycles = max_cycles
|
|
28
|
+
self.cycle_size = cycle_size
|
|
29
|
+
if seed is None:
|
|
30
|
+
self.seed = int(time.time())
|
|
31
|
+
else:
|
|
32
|
+
self.seed = seed
|
|
33
|
+
self.rmsd_thresh = rmsd_thresh
|
|
34
|
+
self.energy_thresh = energy_thresh
|
|
35
|
+
self.energy_range = energy_range
|
|
36
|
+
self.compare_num = compare_num
|
|
37
|
+
self.break_after = break_after
|
|
38
|
+
self.calc_kwargs = {
|
|
39
|
+
"charge": 0,
|
|
40
|
+
"mult": 1,
|
|
41
|
+
}
|
|
42
|
+
if calc_kwargs is not None:
|
|
43
|
+
self.calc_kwargs.update(calc_kwargs)
|
|
44
|
+
|
|
45
|
+
np.random.seed(self.seed)
|
|
46
|
+
self.logger = logging.getLogger("stocastic")
|
|
47
|
+
|
|
48
|
+
self.is_analytical2d = len(geom.atoms) == 1
|
|
49
|
+
self.log(f"Seed: {self.seed}")
|
|
50
|
+
self.coords_size = self.initial_geom.coords.size
|
|
51
|
+
|
|
52
|
+
self.calc_counter = 0
|
|
53
|
+
self.cur_cycle = 0
|
|
54
|
+
self.cur_micro_cycle = 0
|
|
55
|
+
# Indicates if the next cycle is the last one
|
|
56
|
+
self.break_in = self.break_after
|
|
57
|
+
|
|
58
|
+
self.new_geoms = []
|
|
59
|
+
self.new_energies = []
|
|
60
|
+
|
|
61
|
+
self.initial_coords3d = self.initial_geom.coords3d
|
|
62
|
+
self.atoms = self.initial_geom.atoms
|
|
63
|
+
|
|
64
|
+
def __str__(self):
|
|
65
|
+
return f"{self.__class__.__name__}(seed={self.seed})"
|
|
66
|
+
|
|
67
|
+
def log(self, message):
|
|
68
|
+
"""Write a log message.
|
|
69
|
+
|
|
70
|
+
Wraps the logger variable.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
message : str
|
|
75
|
+
Message to be logged.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self.logger.debug(f"{message}")
|
|
79
|
+
|
|
80
|
+
def get_valid_index_set(self, to_intersect):
|
|
81
|
+
return set(range(len(self.new_energies))) & set(to_intersect)
|
|
82
|
+
|
|
83
|
+
def atoms_are_too_close(self, geom, factor=.7):
|
|
84
|
+
"""Determine if atoms are too close."""
|
|
85
|
+
dist_mat = pdist(geom.coords3d)
|
|
86
|
+
cov_rad_mat = get_pair_covalent_radii(geom.atoms)
|
|
87
|
+
too_close = dist_mat < factor*cov_rad_mat
|
|
88
|
+
return any(too_close)
|
|
89
|
+
|
|
90
|
+
def geom_is_close_in_energy(self, geom):
|
|
91
|
+
energy = geom.energy
|
|
92
|
+
i = bisect.bisect_left(self.new_energies, energy)
|
|
93
|
+
# Determine if there are neighbours that are close in energy
|
|
94
|
+
# as we insert left/before the most similary energy the indices
|
|
95
|
+
# of the (to be) neighbours in the current self.new_energies list
|
|
96
|
+
# are i-1 and i.
|
|
97
|
+
valid_inds = self.get_valid_index_set((i-1, i))
|
|
98
|
+
diffs = [abs(self.new_energies[j] - energy) for j in valid_inds]
|
|
99
|
+
return len(diffs) > 0 and min(diffs) < self.energy_thresh
|
|
100
|
+
|
|
101
|
+
def geom_is_new(self, geom):
|
|
102
|
+
"""Determine if geometry is not already known."""
|
|
103
|
+
if len(self.new_geoms) == 0:
|
|
104
|
+
self.log("Found first geometry!")
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
i = bisect.bisect_left(self.new_energies, geom.energy)
|
|
108
|
+
to_intersect = range(i-self.compare_num, i+self.compare_num)
|
|
109
|
+
valid_inds = np.array(list(self.get_valid_index_set(to_intersect)))
|
|
110
|
+
new_energies = np.array(self.new_energies)[valid_inds]
|
|
111
|
+
# Restrict geometries for RMSD comparison to an energy range
|
|
112
|
+
# around the energy of the geometry to check.
|
|
113
|
+
in_range = np.abs(new_energies - geom.energy) < self.energy_range
|
|
114
|
+
valid_inds = valid_inds[in_range]
|
|
115
|
+
# If this evalutes to True the energy of the current in geometry is
|
|
116
|
+
# quite different and we add the geometry.
|
|
117
|
+
if valid_inds.size == 0:
|
|
118
|
+
# print("Energy of geometry is very different from the remaining "
|
|
119
|
+
# "ones. Adding geometry!")
|
|
120
|
+
reason = "different energy."
|
|
121
|
+
is_new = True
|
|
122
|
+
# Otherwise check the RMSD values for the remaining geometries that
|
|
123
|
+
# are close in energy.
|
|
124
|
+
else:
|
|
125
|
+
rmsds = [matched_rmsd(geom, self.new_geoms[i])[0] for i in valid_inds]
|
|
126
|
+
rmsds = np.array(rmsds)
|
|
127
|
+
is_new = rmsds.min() > self.rmsd_thresh
|
|
128
|
+
reason = f"different RMSD (min(RMSD) = {rmsds.min():.3f})"
|
|
129
|
+
|
|
130
|
+
if is_new:
|
|
131
|
+
self.log(f"Found new geometry based on {reason}")
|
|
132
|
+
return is_new
|
|
133
|
+
|
|
134
|
+
def geom_is_valid(self, geom):
|
|
135
|
+
"""Filter out geometries that are None, or were the atoms are too close
|
|
136
|
+
or when they are already known."""
|
|
137
|
+
|
|
138
|
+
valid = (geom is not None
|
|
139
|
+
and not self.geom_is_close_in_energy(geom)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if not self.is_analytical2d:
|
|
143
|
+
valid = (valid
|
|
144
|
+
and not self.atoms_are_too_close(geom)
|
|
145
|
+
and self.geom_is_new(geom)
|
|
146
|
+
)
|
|
147
|
+
return valid
|
|
148
|
+
|
|
149
|
+
def get_input_geom(self, geom):
|
|
150
|
+
raise Exception("Implement me!")
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
def run_new_geom(self, geom):
|
|
154
|
+
input_geom = self.get_input_geom(geom)
|
|
155
|
+
input_coords = input_geom.coords.copy()
|
|
156
|
+
|
|
157
|
+
# Check if the geometry is similar to an already known starting
|
|
158
|
+
# geometry.
|
|
159
|
+
overlaps = new_coords.dot(np.array(self.starting_coords).T)/self.coords_size
|
|
160
|
+
# print("overlaps with already known starting coordinates")
|
|
161
|
+
# print(overlaps)
|
|
162
|
+
max_overlap_ind = overlaps.argmax()
|
|
163
|
+
max_overlap = overlaps[max_overlap_ind]
|
|
164
|
+
similar_fn = f"similar_{self.similar_ind:03d}_trj.xyz"
|
|
165
|
+
# print(f"max overlap is {max_overlap:.1f}, {similar_fn}, index {max_overlap_ind}")
|
|
166
|
+
max_coords = self.starting_coords[max_overlap_ind]
|
|
167
|
+
self.similar_ind += 1
|
|
168
|
+
rmsds = list()
|
|
169
|
+
for sc in self.starting_coords:
|
|
170
|
+
sc3d = sc.reshape(-1, 3)
|
|
171
|
+
rm = rmsd.kabsch_rmsd(new_coords.reshape(-1,3), sc3d)
|
|
172
|
+
rmsds.append(rm)
|
|
173
|
+
rmsds = np.array(rmsds)
|
|
174
|
+
self.starting_coords.append(new_coords)
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def get_unique_geometries(self, geoms):
|
|
178
|
+
geom_num = len(geoms)
|
|
179
|
+
rmsds = np.full((geom_num, geom_num), np.inf)
|
|
180
|
+
for i, j in it.combinations(range(geom_num), 2):
|
|
181
|
+
coords1 = geoms[i].coords.reshape(-1, 3)
|
|
182
|
+
coords2 = geoms[j].coords.reshape(-1, 3)
|
|
183
|
+
rmsds[i, j] = rmsd.kabsch_rmsd(coords1, coords2)
|
|
184
|
+
is_, js = np.where(rmsds < self.rmsd_thresh)
|
|
185
|
+
similar_inds = np.unique(js)
|
|
186
|
+
unique_geoms = [geoms[i] for i in range(geom_num) if i not in similar_inds]
|
|
187
|
+
return unique_geoms
|
|
188
|
+
|
|
189
|
+
def run_geom_opt(self, geom):
|
|
190
|
+
if self.calc_getter is not None:
|
|
191
|
+
calc = self.calc_getter(calc_number=self.calc_counter)
|
|
192
|
+
geom.set_calculator(calc)
|
|
193
|
+
opt_kwargs = {
|
|
194
|
+
"gdiis": False,
|
|
195
|
+
"thresh": "gau_loose",
|
|
196
|
+
"overachieve_factor": 2,
|
|
197
|
+
"max_cycles": 75,
|
|
198
|
+
}
|
|
199
|
+
opt = RFOptimizer(geom, **opt_kwargs)
|
|
200
|
+
opt.run()
|
|
201
|
+
opt_geom = geom if opt.is_converged else None
|
|
202
|
+
else:
|
|
203
|
+
calc = XTB(calc_number=self.calc_counter, **self.calc_kwargs)
|
|
204
|
+
opt_result = calc.run_opt(geom.atoms, geom.coords, keep=False)
|
|
205
|
+
try:
|
|
206
|
+
opt_geom = opt_result.opt_geom
|
|
207
|
+
except AttributeError:
|
|
208
|
+
opt_geom = None
|
|
209
|
+
|
|
210
|
+
self.calc_counter += 1
|
|
211
|
+
return opt_geom
|
|
212
|
+
|
|
213
|
+
def write_geoms_to_trj(self, geoms, fn, comments=None):
|
|
214
|
+
with open(fn, "w") as handle:
|
|
215
|
+
handle.write(
|
|
216
|
+
make_trj_str_from_geoms(geoms, comments, energy_comments=True)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def run(self):
|
|
220
|
+
for self.cur_cycle in range(self.max_cycles):
|
|
221
|
+
cycle_start = time.time()
|
|
222
|
+
self.log(highlight_text(f"Cycle {self.cur_cycle}"))
|
|
223
|
+
input_geoms = [self.get_input_geom(self.initial_geom)
|
|
224
|
+
for _ in range(self.cycle_size)]
|
|
225
|
+
# Write input geometries to disk
|
|
226
|
+
self.write_geoms_to_trj(input_geoms, f"cycle_{self.cur_cycle:03d}_input_trj.xyz")
|
|
227
|
+
# Run optimizations on input geometries
|
|
228
|
+
calc_start = time.time()
|
|
229
|
+
opt_geoms = list()
|
|
230
|
+
for i, geom in enumerate(input_geoms, 1):
|
|
231
|
+
print(f"Optimizing geometry {i:03d}/{self.cycle_size:03d}", end="\r")
|
|
232
|
+
opt_geoms.append(self.run_geom_opt(geom))
|
|
233
|
+
print()
|
|
234
|
+
calc_end = time.time()
|
|
235
|
+
calc_duration = calc_end - calc_start
|
|
236
|
+
self.log(f"Optimizations took {calc_duration:.0f} s.")
|
|
237
|
+
|
|
238
|
+
kept_geoms = list()
|
|
239
|
+
for geom in opt_geoms:
|
|
240
|
+
# Do all the filtering and reject all invalid geometries
|
|
241
|
+
if not self.geom_is_valid(geom):
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
energy = geom.energy
|
|
245
|
+
i = bisect.bisect_left(self.new_energies, energy)
|
|
246
|
+
self.new_energies.insert(i, energy)
|
|
247
|
+
self.new_geoms.insert(i, geom)
|
|
248
|
+
kept_geoms.append(geom)
|
|
249
|
+
if i == 0 and len(self.new_energies) > 1:
|
|
250
|
+
last_minimum = self.new_energies[1]
|
|
251
|
+
diff = abs(energy - last_minimum)
|
|
252
|
+
self.log(f"It is a new global minimum at {energy:.4f} au! "
|
|
253
|
+
f"Last one was {diff:.4f} au higher.")
|
|
254
|
+
|
|
255
|
+
kept_num = len(kept_geoms)
|
|
256
|
+
|
|
257
|
+
trj_filtered_fn = f"cycle_{self.cur_cycle:03d}_trj.xyz"
|
|
258
|
+
# Sort by energy
|
|
259
|
+
kept_geoms = sorted(kept_geoms, key=lambda g: g.energy)
|
|
260
|
+
if kept_geoms:
|
|
261
|
+
self.write_geoms_to_trj(kept_geoms, trj_filtered_fn)
|
|
262
|
+
self.log(f"Kicks in cycle {self.cur_cycle} produced "
|
|
263
|
+
f"{kept_num} new geometries.")
|
|
264
|
+
self.break_in = self.break_after
|
|
265
|
+
elif self.break_in == 0:
|
|
266
|
+
self.log("Didn't find any new geometries in the last "
|
|
267
|
+
f"{self.break_after} cycles. Exiting!")
|
|
268
|
+
break
|
|
269
|
+
else:
|
|
270
|
+
self.log(f"Cycle {self.cur_cycle} produced no new geometries.")
|
|
271
|
+
self.break_in -= 1
|
|
272
|
+
|
|
273
|
+
cycle_end = time.time()
|
|
274
|
+
cycle_duration = cycle_end - cycle_start
|
|
275
|
+
self.log(f"Cycle {i} took {cycle_duration:.0f} s.")
|
|
276
|
+
self.log("")
|
|
277
|
+
if check_for_end_sign():
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
self.log(f"Run produced {len(self.new_energies)} geometries!")
|
|
281
|
+
# Return empty list of nothing was found
|
|
282
|
+
if not self.new_energies:
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
fn = "final_trj.xyz"
|
|
286
|
+
self.write_geoms_to_trj(self.new_geoms, fn)
|
|
287
|
+
# self.new_energies = np.array(new_energies)
|
|
288
|
+
np.savetxt("energies.dat", self.new_energies)
|
|
289
|
+
first_geom = self.new_geoms[0]
|
|
290
|
+
first_geom.standard_orientation()
|
|
291
|
+
first_geom.energy = self.new_energies[0]
|
|
292
|
+
|
|
293
|
+
if self.is_analytical2d:
|
|
294
|
+
return self.new_geoms
|
|
295
|
+
|
|
296
|
+
matched_geoms = [first_geom, ]
|
|
297
|
+
for geom, energy in zip(self.new_geoms[1:], self.new_energies):
|
|
298
|
+
rmsd, (_, matched_geom) = matched_rmsd(first_geom, geom)
|
|
299
|
+
matched_geom.energy = energy
|
|
300
|
+
matched_geoms.append(matched_geom)
|
|
301
|
+
fn_matched = "final_matched_trj.xyz"
|
|
302
|
+
self.write_geoms_to_trj(matched_geoms, fn_matched)
|
|
303
|
+
return matched_geoms
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from pysisyphus.stocastic.Kick import Kick
|
|
5
|
+
from pysisyphus.stocastic.FragmentKick import FragmentKick
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"FragmentKick",
|
|
10
|
+
"Kick",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("stocastic")
|
|
14
|
+
logger.setLevel(logging.DEBUG)
|
|
15
|
+
fh = logging.FileHandler("stocastic.log", mode="w", delay=True)
|
|
16
|
+
fh.setLevel(logging.DEBUG)
|
|
17
|
+
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
18
|
+
# fmt_str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
19
|
+
# formatter = logging.Formatter(fmt_str)
|
|
20
|
+
# fh.setFormatter(formatter)
|
|
21
|
+
logger.addHandler(fh)
|