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
pysisyphus/io/pdb.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
import re
|
|
3
|
+
import struct
|
|
4
|
+
import textwrap
|
|
5
|
+
|
|
6
|
+
from jinja2 import Template
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from pysisyphus.elem_data import KNOWN_ATOMS
|
|
10
|
+
from pysisyphus.constants import ANG2BOHR
|
|
11
|
+
from pysisyphus.Geometry import Geometry
|
|
12
|
+
from pysisyphus.helpers_pure import chunks, file_or_str
|
|
13
|
+
from pysisyphus.intcoords.setup_fast import find_bonds
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
AMINOS = (
|
|
17
|
+
"ALA CYS ASP GLU PHE GLY HIS ILE LYS LEU"
|
|
18
|
+
"MET ASN PRO GLN ARG SER THR VAL TRP TYR".split()
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_parser(widths):
|
|
23
|
+
fmt = " ".join("{}{}".format(width, "s") for width in widths)
|
|
24
|
+
fieldstruct = struct.Struct(fmt)
|
|
25
|
+
parse = lambda line: tuple(s.decode() for s in fieldstruct.unpack(line.encode()))
|
|
26
|
+
return parse
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
STRIP_RE = re.compile(r"[\d\s]*") # To remove numbers and whitespace
|
|
30
|
+
NAME_MAP = {
|
|
31
|
+
"hb": "H",
|
|
32
|
+
"he": "H",
|
|
33
|
+
"hh": "H",
|
|
34
|
+
"hd": "H",
|
|
35
|
+
"hg": "H",
|
|
36
|
+
"so": "Na",
|
|
37
|
+
}
|
|
38
|
+
FULL_NAME = {
|
|
39
|
+
" sod",
|
|
40
|
+
" cla",
|
|
41
|
+
" cal",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def parse_atom_name(name):
|
|
46
|
+
org_name = name
|
|
47
|
+
assert len(name) == 4
|
|
48
|
+
name = name.lower()
|
|
49
|
+
# Cases like " SOD" require special handling. Sticking to the PDB specification (!)
|
|
50
|
+
# and using only the first two characters would result in S (sulphur).
|
|
51
|
+
use_full_name = name in FULL_NAME
|
|
52
|
+
if not use_full_name:
|
|
53
|
+
name = name[:2]
|
|
54
|
+
stripped = STRIP_RE.sub("", name).lower()
|
|
55
|
+
if use_full_name:
|
|
56
|
+
stripped = stripped[:2]
|
|
57
|
+
try:
|
|
58
|
+
mapped = NAME_MAP[stripped]
|
|
59
|
+
except KeyError:
|
|
60
|
+
assert stripped in KNOWN_ATOMS, f"Could not parse atom name '{org_name}'"
|
|
61
|
+
mapped = stripped
|
|
62
|
+
return mapped.capitalize()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@file_or_str(".pdb")
|
|
66
|
+
def parse_pdb(text):
|
|
67
|
+
atm_lines = list()
|
|
68
|
+
for line in text.split("\n"):
|
|
69
|
+
if line.startswith("HETATM") or line.startswith("ATOM"):
|
|
70
|
+
atm_lines.append(line.strip())
|
|
71
|
+
continue
|
|
72
|
+
elif line.startswith("MASTER"):
|
|
73
|
+
master_line = line.strip()
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
0 Record name
|
|
77
|
+
1 serial
|
|
78
|
+
2 name
|
|
79
|
+
3 altLoc
|
|
80
|
+
4 ResName
|
|
81
|
+
5 chainID
|
|
82
|
+
6 resSeq
|
|
83
|
+
7 iCode
|
|
84
|
+
8 x
|
|
85
|
+
9 y
|
|
86
|
+
10 z
|
|
87
|
+
11 occupancy
|
|
88
|
+
12 tempFactor
|
|
89
|
+
13 element
|
|
90
|
+
(14 charge), not parsed
|
|
91
|
+
|
|
92
|
+
See https://stackoverflow.com/questions/4914008
|
|
93
|
+
"""
|
|
94
|
+
atm_widths = (6, 6, 4, 1, 4, 1, 4, 1, 11, 8, 8, 6, 6, 12)
|
|
95
|
+
atm_parse = get_parser(atm_widths)
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
0 Record name
|
|
99
|
+
1 Num. of REMARK
|
|
100
|
+
2 "0"
|
|
101
|
+
3 Num. of HET
|
|
102
|
+
4 Num. of HELIX
|
|
103
|
+
5 Num. of SHEET
|
|
104
|
+
6 deprecated
|
|
105
|
+
7 Num. of SITE
|
|
106
|
+
8 numXform
|
|
107
|
+
9 Num. of atomic coordinates (HETATM + ATOMS)
|
|
108
|
+
10 Num. of CONECT
|
|
109
|
+
11 Num. of SEQRES
|
|
110
|
+
"""
|
|
111
|
+
master_widths = (6, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5)
|
|
112
|
+
master_parse = get_parser(master_widths)
|
|
113
|
+
try:
|
|
114
|
+
master_fields = master_parse(master_line)
|
|
115
|
+
except UnboundLocalError:
|
|
116
|
+
master_fields = None
|
|
117
|
+
|
|
118
|
+
atoms = list()
|
|
119
|
+
coords = list()
|
|
120
|
+
fragments = {}
|
|
121
|
+
atom_map = dict()
|
|
122
|
+
|
|
123
|
+
for i, line in enumerate(atm_lines):
|
|
124
|
+
# Charge is not considered right now ...
|
|
125
|
+
fields = atm_parse(f"{line[:78]: <78}")
|
|
126
|
+
res_name = fields[4].strip()
|
|
127
|
+
chain = fields[5].strip()
|
|
128
|
+
res_seq = int(fields[6])
|
|
129
|
+
frag = f"{chain}_{res_name}{res_seq}"
|
|
130
|
+
|
|
131
|
+
xyz = np.array(fields[8:11], dtype=float)
|
|
132
|
+
atom = fields[13].strip()
|
|
133
|
+
if not atom.lower() in KNOWN_ATOMS:
|
|
134
|
+
name = fields[2]
|
|
135
|
+
atom = parse_atom_name(name)
|
|
136
|
+
atoms.append(atom)
|
|
137
|
+
coords.append(xyz)
|
|
138
|
+
id_ = int(fields[1])
|
|
139
|
+
atom_map[id_] = i
|
|
140
|
+
|
|
141
|
+
frag = fragments.setdefault(frag, list())
|
|
142
|
+
frag.append(i)
|
|
143
|
+
|
|
144
|
+
# Verification using MASTER record, if present.
|
|
145
|
+
if master_fields is not None:
|
|
146
|
+
num_coord = int(master_fields[9])
|
|
147
|
+
try:
|
|
148
|
+
assert len(atoms) == num_coord
|
|
149
|
+
except AssertionError as err:
|
|
150
|
+
if len(atoms) < 99_999: # See, e.g., 1HTQ
|
|
151
|
+
raise err
|
|
152
|
+
|
|
153
|
+
coords = np.array(coords, dtype=float) * ANG2BOHR
|
|
154
|
+
return atoms, coords.flatten(), fragments, atom_map
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def geom_from_pdb(fn, **kwargs):
|
|
158
|
+
atoms, coords, fragments, _ = parse_pdb(fn)
|
|
159
|
+
kwargs["fragments"] = fragments
|
|
160
|
+
geom = Geometry(atoms, coords.flatten(), **kwargs)
|
|
161
|
+
return geom
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def get_conect_lines(atoms, coords):
|
|
165
|
+
bonds = find_bonds(atoms, coords)
|
|
166
|
+
# PDB indexing is 1-based
|
|
167
|
+
bonds += 1
|
|
168
|
+
# Bring in a suitable order for CONECT entries
|
|
169
|
+
bonds.sort(axis=1)
|
|
170
|
+
bonds = sorted(bonds, key=lambda row: row[0])
|
|
171
|
+
conect = OrderedDict()
|
|
172
|
+
for from_, to_ in bonds:
|
|
173
|
+
conect.setdefault(from_, list()).append(to_)
|
|
174
|
+
|
|
175
|
+
conect_lines = list()
|
|
176
|
+
for from_, to_ in conect.items():
|
|
177
|
+
to_iter = chunks(sorted(to_), 4)
|
|
178
|
+
for to_chunk in to_iter:
|
|
179
|
+
conect_lines.append([from_] + to_chunk)
|
|
180
|
+
return conect_lines
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def atoms_coords_to_pdb_str(atoms, coords, fragments=None, resname="", conect=True):
|
|
184
|
+
coords3d = coords.reshape(-1, 3)
|
|
185
|
+
coords3d_ang = coords3d / ANG2BOHR
|
|
186
|
+
|
|
187
|
+
coord_fmt = "{: >8.3f}" * 3
|
|
188
|
+
# serial name altLoc chainID iCode
|
|
189
|
+
# resName resSeq
|
|
190
|
+
hetatm_fmt = "HETATM{: >5d} {: >4}{: >1}{: >3}{: >1} {: >4d}{: >1} "
|
|
191
|
+
# xyz
|
|
192
|
+
hetatm_fmt += coord_fmt
|
|
193
|
+
# occupancy tempFactor atom
|
|
194
|
+
hetatm_fmt += "{: >6.2f}{: >6.2f}" + 10 * " " + "{: >2s}"
|
|
195
|
+
ter_fmt = "TER {: >5d} {: >3s} {:1s}{: >4d}"
|
|
196
|
+
|
|
197
|
+
if fragments is None:
|
|
198
|
+
fragments = [
|
|
199
|
+
range(len(atoms)),
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
# Fixed for now
|
|
203
|
+
altLoc = ""
|
|
204
|
+
resName = resname
|
|
205
|
+
chainID = ""
|
|
206
|
+
iCode = ""
|
|
207
|
+
occupancy = 1.0
|
|
208
|
+
tempFactor = 0.0
|
|
209
|
+
|
|
210
|
+
lines = list()
|
|
211
|
+
serial = 1
|
|
212
|
+
for resSeq, fragment in enumerate(fragments, 1):
|
|
213
|
+
for id_ in fragment:
|
|
214
|
+
name = atoms[id_]
|
|
215
|
+
xyz = coords3d_ang[id_]
|
|
216
|
+
line = hetatm_fmt.format(
|
|
217
|
+
serial,
|
|
218
|
+
name,
|
|
219
|
+
altLoc,
|
|
220
|
+
resName,
|
|
221
|
+
chainID,
|
|
222
|
+
resSeq,
|
|
223
|
+
iCode,
|
|
224
|
+
*xyz,
|
|
225
|
+
occupancy,
|
|
226
|
+
tempFactor,
|
|
227
|
+
name,
|
|
228
|
+
)
|
|
229
|
+
lines.append(line)
|
|
230
|
+
serial += 1
|
|
231
|
+
lines.append(ter_fmt.format(serial, resName, chainID, resSeq))
|
|
232
|
+
|
|
233
|
+
def fmt_conect_line(conect_line):
|
|
234
|
+
return "CONECT" + ("{: >5d}" * len(conect_line)).format(*conect_line)
|
|
235
|
+
|
|
236
|
+
if conect:
|
|
237
|
+
conect_lines = get_conect_lines(atoms, coords)
|
|
238
|
+
else:
|
|
239
|
+
conect_lines = []
|
|
240
|
+
conect_str = "\n".join([fmt_conect_line(cl) for cl in conect_lines])
|
|
241
|
+
|
|
242
|
+
pdb_tpl = Template(
|
|
243
|
+
textwrap.dedent(
|
|
244
|
+
"""\
|
|
245
|
+
REMARK 1 Created by pysisyphus
|
|
246
|
+
{%+ for line in lines -%}
|
|
247
|
+
{{ line }}
|
|
248
|
+
{%+ endfor -%}
|
|
249
|
+
{{- conect_str }}
|
|
250
|
+
END"""
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
pdb_str = pdb_tpl.render(lines=lines, conect_str=conect_str)
|
|
255
|
+
return pdb_str
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def geom_to_pdb_str(geom, detect_fragments=False, **kwargs):
|
|
259
|
+
fragments = None
|
|
260
|
+
if detect_fragments:
|
|
261
|
+
try:
|
|
262
|
+
fragments = geom.internal.fragments
|
|
263
|
+
except AttributeError:
|
|
264
|
+
geom_ = geom.copy(coord_type="redund")
|
|
265
|
+
fragments = geom_.internal.fragments
|
|
266
|
+
pdb_str = atoms_coords_to_pdb_str(
|
|
267
|
+
geom.atoms, geom.coords3d, fragments=fragments, **kwargs
|
|
268
|
+
)
|
|
269
|
+
return pdb_str
|
pysisyphus/io/psf.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import pyparsing as pp
|
|
2
|
+
|
|
3
|
+
from pysisyphus.helpers_pure import file_or_str
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@file_or_str(".psf")
|
|
7
|
+
def parse_psf(text):
|
|
8
|
+
int_ = pp.common.integer
|
|
9
|
+
real = pp.common.sci_real
|
|
10
|
+
psf_style_comment = pp.Regex(r"\*.*")
|
|
11
|
+
|
|
12
|
+
psf = pp.CaselessLiteral("PSF")
|
|
13
|
+
token = pp.ZeroOrMore(
|
|
14
|
+
pp.CaselessLiteral("EXT")
|
|
15
|
+
| pp.CaselessLiteral("CMAP")
|
|
16
|
+
| pp.CaselessLiteral("XPLOR")
|
|
17
|
+
| pp.CaselessLiteral("AUTOG")
|
|
18
|
+
).set_results_name("token")
|
|
19
|
+
ntitle = int_ + pp.CaselessLiteral("!NTITLE")
|
|
20
|
+
natom = int_.set_results_name("natom") + pp.CaselessLiteral("!NATOM")
|
|
21
|
+
|
|
22
|
+
# Atom records
|
|
23
|
+
atom_name = pp.Word(pp.alphanums)
|
|
24
|
+
zero = pp.Literal("0")
|
|
25
|
+
atom_record = pp.Group(
|
|
26
|
+
int_.set_results_name("id")
|
|
27
|
+
+ pp.Word(pp.alphas).set_results_name("segment")
|
|
28
|
+
+ int_.set_results_name("resid")
|
|
29
|
+
+ pp.Word(pp.alphanums).set_results_name("resname") # VAL, ALA, TIP3, etc.
|
|
30
|
+
+ atom_name.set_results_name("atom_name")
|
|
31
|
+
+ atom_name.set_results_name("atom_type")
|
|
32
|
+
+ real.set_results_name("charge")
|
|
33
|
+
+ real.set_results_name("mass")
|
|
34
|
+
+ zero
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
new_section = int_ + pp.Literal("!")
|
|
38
|
+
|
|
39
|
+
def get_section(lhs, rhs=None):
|
|
40
|
+
parser = int_.set_results_name("nterm") + pp.CaselessLiteral(lhs)
|
|
41
|
+
|
|
42
|
+
if rhs is not None:
|
|
43
|
+
parser += pp.Literal(":") + pp.CaselessLiteral(rhs)
|
|
44
|
+
# Negative lookahead prevents matching 'nterm' of the next section
|
|
45
|
+
parser += pp.Group(pp.ZeroOrMore(~new_section + int_)).set_results_name("inds")
|
|
46
|
+
parser = pp.Group(parser)
|
|
47
|
+
return parser
|
|
48
|
+
|
|
49
|
+
nbond = get_section("!NBOND", "bonds").set_results_name("nbond")
|
|
50
|
+
ntheta = get_section("!NTHETA", "angles").set_results_name("ntheta")
|
|
51
|
+
nphi = get_section("!NPHI", "dihedrals").set_results_name("nphi")
|
|
52
|
+
nimphi = get_section("!NIMPHI", "impropers").set_results_name("nimphi")
|
|
53
|
+
ndon = get_section("!NDON", "donors").set_results_name("ndon")
|
|
54
|
+
nacc = get_section("!NACC", "acceptors").set_results_name("nacc")
|
|
55
|
+
nnb = get_section("!NNB").set_results_name("nnb")
|
|
56
|
+
ncrterm = get_section("!NCRTERM", "cross-terms").set_results_name("ncrterm")
|
|
57
|
+
|
|
58
|
+
parser = (
|
|
59
|
+
psf
|
|
60
|
+
+ token
|
|
61
|
+
+ ntitle
|
|
62
|
+
+ natom
|
|
63
|
+
+ pp.OneOrMore(atom_record).set_results_name("atoms")
|
|
64
|
+
+ nbond # bonds
|
|
65
|
+
+ ntheta # bends
|
|
66
|
+
+ nphi # dihedrals
|
|
67
|
+
+ nimphi # impropers
|
|
68
|
+
+ ndon # donors
|
|
69
|
+
+ nacc # acceptors
|
|
70
|
+
+ nnb # Non bonding something?
|
|
71
|
+
# 0 0 !NGRP NST2
|
|
72
|
+
# 0 0 !NUMLPH NUMLPH
|
|
73
|
+
# + ncrterm
|
|
74
|
+
)
|
|
75
|
+
parser.ignore(psf_style_comment)
|
|
76
|
+
|
|
77
|
+
result = parser.parseString(text)
|
|
78
|
+
as_dict = result.asDict()
|
|
79
|
+
return as_dict
|
pysisyphus/io/pubchem.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
from urllib.error import HTTPError
|
|
4
|
+
|
|
5
|
+
from pysisyphus.io.sdf import geom_from_sdf
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def cid_from_name(name):
|
|
9
|
+
url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{name}/property/IUPACName/JSON"
|
|
10
|
+
with urllib.request.urlopen(url) as handle:
|
|
11
|
+
text = handle.read()
|
|
12
|
+
json_ = json.loads(text)
|
|
13
|
+
cid = json_["PropertyTable"]["Properties"][0]["CID"]
|
|
14
|
+
return cid
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sdf_from_cid(cid):
|
|
18
|
+
url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/SDF?record_type=3d"
|
|
19
|
+
with urllib.request.urlopen(url) as handle:
|
|
20
|
+
sdf = handle.read().decode("utf-8")
|
|
21
|
+
return sdf
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def geom_from_pubchem_name(name, **kwargs):
|
|
25
|
+
try:
|
|
26
|
+
cid = cid_from_name(name)
|
|
27
|
+
sdf = sdf_from_cid(cid)
|
|
28
|
+
geom = geom_from_sdf(sdf, **kwargs)
|
|
29
|
+
except HTTPError:
|
|
30
|
+
geom = None
|
|
31
|
+
return geom
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from pysisyphus.Geometry import Geometry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@functools.singledispatch
|
|
12
|
+
def geom_from_qcschema(qcschema: Dict, **geom_kwargs):
|
|
13
|
+
mol = qcschema["molecule"]
|
|
14
|
+
coords = np.array((mol["geometry"]))
|
|
15
|
+
atoms = mol["symbols"]
|
|
16
|
+
return Geometry(atoms, coords, **geom_kwargs)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# These definitions may seem a bit wild, but I was not able to make
|
|
20
|
+
# singledispatch work with 'file_or_str'.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@geom_from_qcschema.register
|
|
24
|
+
def _(text: str, **geom_kwargs):
|
|
25
|
+
if (p := Path(text).exists()):
|
|
26
|
+
with open(p, "r") as handle:
|
|
27
|
+
text = handle.read()
|
|
28
|
+
qcschema = json.loads(text)
|
|
29
|
+
return geom_from_qcschema(qcschema, **geom_kwargs)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@geom_from_qcschema.register
|
|
33
|
+
def _(path: Path, **geom_kwargs):
|
|
34
|
+
return geom_from_qcschema(str(path), **geom_kwargs)
|
pysisyphus/io/sdf.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.constants import BOHR2ANG
|
|
4
|
+
from pysisyphus.Geometry import Geometry
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_coord_line(line):
|
|
8
|
+
x, y, z, atom, *_ = line.strip().split()
|
|
9
|
+
return (x, y, z), atom
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_sdf(text):
|
|
13
|
+
lines = text.split("\n")
|
|
14
|
+
# title, program, comment = lines[:3]
|
|
15
|
+
count = lines[3]
|
|
16
|
+
atoms, *_ = count.split()
|
|
17
|
+
atoms = int(atoms)
|
|
18
|
+
|
|
19
|
+
coord_lines = lines[4:4+int(atoms)]
|
|
20
|
+
coords, atoms = zip(*[parse_coord_line(cl) for cl in coord_lines])
|
|
21
|
+
coords = np.array(coords, dtype=float)
|
|
22
|
+
coords /= BOHR2ANG
|
|
23
|
+
return atoms, coords
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def geom_from_sdf(text, **kwargs):
|
|
27
|
+
atoms, coords = parse_sdf(text)
|
|
28
|
+
geom = Geometry(atoms, coords, **kwargs)
|
|
29
|
+
return geom
|
pysisyphus/io/xyz.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from pysisyphus.constants import ANG2BOHR
|
|
4
|
+
from pysisyphus.Geometry import Geometry
|
|
5
|
+
from pysisyphus.helpers_pure import file_or_str
|
|
6
|
+
from pysisyphus.xyzloader import split_xyz_str
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@file_or_str(".xyz", "_trj.xyz")
|
|
10
|
+
def parse_xyz(xyz_str, with_comment=False):
|
|
11
|
+
lines = iter(xyz_str.strip().split("\n"))
|
|
12
|
+
atoms_coords = list()
|
|
13
|
+
comments = list()
|
|
14
|
+
for line in lines:
|
|
15
|
+
line = line.strip()
|
|
16
|
+
# Skip emtpy lines
|
|
17
|
+
if not line:
|
|
18
|
+
continue
|
|
19
|
+
# A new xyz block starts with an integer
|
|
20
|
+
try:
|
|
21
|
+
expect = int(line)
|
|
22
|
+
# We only skip empty lines, otherwise we leave the loop.
|
|
23
|
+
except ValueError:
|
|
24
|
+
break
|
|
25
|
+
atoms = list()
|
|
26
|
+
coords3d = np.zeros((expect, 3), dtype=float)
|
|
27
|
+
# Advance iterator over xyz definition
|
|
28
|
+
comment = next(lines)
|
|
29
|
+
comments.append(comment if with_comment else None)
|
|
30
|
+
for i in range(expect):
|
|
31
|
+
atom, *xyz_ = next(lines).split()
|
|
32
|
+
atoms.append(atom)
|
|
33
|
+
coords3d[i] = xyz_
|
|
34
|
+
assert i == expect - 1
|
|
35
|
+
assert len(atoms) == expect
|
|
36
|
+
coords3d *= ANG2BOHR
|
|
37
|
+
atoms_coords.append((atoms, coords3d.flatten()))
|
|
38
|
+
return atoms_coords, comments
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def geoms_from_xyz(fn, **kwargs):
|
|
43
|
+
atoms_coords, comments = parse_xyz(fn, with_comment=True)
|
|
44
|
+
geoms = [
|
|
45
|
+
Geometry(atoms, coords.flatten(), comment=comment, **kwargs)
|
|
46
|
+
for (atoms, coords), comment in zip(atoms_coords, comments)
|
|
47
|
+
]
|
|
48
|
+
return geoms
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def geom_from_xyz(fn, **kwargs):
|
|
52
|
+
return geoms_from_xyz(fn, **kwargs)[0]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def geoms_from_inline_xyz(inline_xyz, **kwargs):
|
|
56
|
+
atoms_coords = split_xyz_str(inline_xyz)
|
|
57
|
+
# We excpect the coordinates to be given in Angstrom
|
|
58
|
+
geoms = [
|
|
59
|
+
Geometry(atoms, coords * ANG2BOHR, **kwargs) for atoms, coords in atoms_coords
|
|
60
|
+
]
|
|
61
|
+
return geoms
|
pysisyphus/io/zmat.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from pysisyphus.constants import ANG2BOHR as ANG2BOHR
|
|
6
|
+
from pysisyphus.Geometry import Geometry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ZLine = namedtuple(
|
|
10
|
+
"ZLine", "atom rind r aind a dind d", defaults=(None, None, None, None, None, None)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def geom_from_zmat(
|
|
15
|
+
zmat,
|
|
16
|
+
atoms=None,
|
|
17
|
+
coords3d=None,
|
|
18
|
+
geom=None,
|
|
19
|
+
start_at=None,
|
|
20
|
+
drop_dummy=True,
|
|
21
|
+
**geom_kwargs
|
|
22
|
+
):
|
|
23
|
+
"""Adapted from https://github.com/robashaw/geomConvert by Robert Shaw."""
|
|
24
|
+
|
|
25
|
+
if isinstance(zmat, str):
|
|
26
|
+
zmat = zmat_from_str(zmat)
|
|
27
|
+
|
|
28
|
+
zmat_atoms = [zline.atom for zline in zmat]
|
|
29
|
+
# Extend supplied geometry by zmat
|
|
30
|
+
if geom is not None:
|
|
31
|
+
atoms = geom.atoms
|
|
32
|
+
coords3d = geom.coords3d
|
|
33
|
+
start_at = len(geom.atoms)
|
|
34
|
+
|
|
35
|
+
if atoms is not None:
|
|
36
|
+
atoms = list(atoms) + zmat_atoms
|
|
37
|
+
else:
|
|
38
|
+
atoms = zmat_atoms
|
|
39
|
+
|
|
40
|
+
# Grow coordindate array and assign old coordinates
|
|
41
|
+
if coords3d is not None:
|
|
42
|
+
_coords3d = np.zeros((len(coords3d) + len(zmat), 3))
|
|
43
|
+
_coords3d[: len(coords3d)] = coords3d
|
|
44
|
+
coords3d = _coords3d
|
|
45
|
+
else:
|
|
46
|
+
coords3d = np.zeros((len(zmat), 3), dtype=float)
|
|
47
|
+
|
|
48
|
+
if atoms or coords3d:
|
|
49
|
+
assert len(coords3d) == len(atoms)
|
|
50
|
+
|
|
51
|
+
if start_at is None:
|
|
52
|
+
start_at = 0
|
|
53
|
+
|
|
54
|
+
for i, zline in enumerate(zmat, start_at):
|
|
55
|
+
assert all(
|
|
56
|
+
[
|
|
57
|
+
(ind is None) or (ind >= 0)
|
|
58
|
+
for ind in (zline.rind, zline.aind, zline.dind)
|
|
59
|
+
]
|
|
60
|
+
), "Found invalid atom index. Atom indices start with 1, not 0!"
|
|
61
|
+
|
|
62
|
+
r = zline.r
|
|
63
|
+
# First atom is placed at the origin
|
|
64
|
+
if i == 0:
|
|
65
|
+
continue
|
|
66
|
+
# Bond along x-axis
|
|
67
|
+
elif i == 1:
|
|
68
|
+
coords3d[i, 0] = r
|
|
69
|
+
# Angle in xy-plane from polar coordinates
|
|
70
|
+
elif i == 2:
|
|
71
|
+
r"""
|
|
72
|
+
M P <- add
|
|
73
|
+
\ /
|
|
74
|
+
u v
|
|
75
|
+
\ /
|
|
76
|
+
O
|
|
77
|
+
"""
|
|
78
|
+
theta = np.deg2rad(zline.a)
|
|
79
|
+
# Center
|
|
80
|
+
O = coords3d[zline.rind]
|
|
81
|
+
# Bond, pointing away from O to M
|
|
82
|
+
u = coords3d[zline.aind] - O
|
|
83
|
+
# Direction of u along x axis (left/right)
|
|
84
|
+
sign = np.sign(u[0])
|
|
85
|
+
# Polar coordinates
|
|
86
|
+
x = r * np.cos(theta)
|
|
87
|
+
y = r * np.sin(theta)
|
|
88
|
+
# Translate from center with correct orientation
|
|
89
|
+
coords3d[i] = O + (sign * x, sign * y, 0.0)
|
|
90
|
+
# Dihedral in xyz-space from spherical coordinates
|
|
91
|
+
else:
|
|
92
|
+
theta, phi = np.deg2rad((zline.a, zline.d))
|
|
93
|
+
|
|
94
|
+
sin_theta = np.sin(theta)
|
|
95
|
+
cos_theta = np.cos(theta)
|
|
96
|
+
sin_phi = np.sin(phi)
|
|
97
|
+
cos_phi = np.cos(phi)
|
|
98
|
+
|
|
99
|
+
x = r * cos_theta
|
|
100
|
+
y = r * sin_theta * cos_phi
|
|
101
|
+
z = r * sin_theta * sin_phi
|
|
102
|
+
|
|
103
|
+
r"""
|
|
104
|
+
M <- add N
|
|
105
|
+
\ /
|
|
106
|
+
u v
|
|
107
|
+
\ /
|
|
108
|
+
O--w--P
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
O = coords3d[zline.rind]
|
|
112
|
+
P = coords3d[zline.aind]
|
|
113
|
+
N = coords3d[zline.dind]
|
|
114
|
+
|
|
115
|
+
# Local axis system
|
|
116
|
+
v_ = P - N
|
|
117
|
+
v = v_ / np.linalg.norm(v_)
|
|
118
|
+
w_ = O - P
|
|
119
|
+
w = w_ / np.linalg.norm(w_)
|
|
120
|
+
a = np.cross(v, w)
|
|
121
|
+
a /= np.linalg.norm(a)
|
|
122
|
+
b = np.cross(a, w)
|
|
123
|
+
b /= np.linalg.norm(b)
|
|
124
|
+
coords3d[i] = O - w * x + b * y + a * z
|
|
125
|
+
|
|
126
|
+
if drop_dummy:
|
|
127
|
+
atoms_ = list()
|
|
128
|
+
coords3d_ = list()
|
|
129
|
+
for atom, xyz in zip(atoms, coords3d):
|
|
130
|
+
if atom.lower() == "x":
|
|
131
|
+
continue
|
|
132
|
+
atoms_.append(atom)
|
|
133
|
+
coords3d_.append(xyz)
|
|
134
|
+
atoms = atoms_
|
|
135
|
+
coords3d = np.array(coords3d_)
|
|
136
|
+
|
|
137
|
+
geom = Geometry(atoms, coords3d, **geom_kwargs)
|
|
138
|
+
return geom
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def geom_from_zmat_str(text, coord_type="cart", coord_kwargs=None):
|
|
142
|
+
zmat = zmat_from_str(text)
|
|
143
|
+
return geom_from_zmat(zmat, coord_type=coord_type, coord_kwargs=coord_kwargs)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def zmat_from_str(text):
|
|
147
|
+
def dec(str_):
|
|
148
|
+
return int(str_) - 1
|
|
149
|
+
|
|
150
|
+
def to_bohr(str_):
|
|
151
|
+
return float(str_) * ANG2BOHR
|
|
152
|
+
|
|
153
|
+
def convert(items):
|
|
154
|
+
funcs = (str, dec, to_bohr, dec, float, dec, float)
|
|
155
|
+
return [f(item) for f, item in zip(funcs, items)]
|
|
156
|
+
|
|
157
|
+
zmat = list()
|
|
158
|
+
for line in text.strip().split("\n"):
|
|
159
|
+
line = line.strip()
|
|
160
|
+
if line.startswith("#"):
|
|
161
|
+
continue
|
|
162
|
+
zmat.append(ZLine(*convert(line.split())))
|
|
163
|
+
return zmat
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def zmat_from_fn(fn):
|
|
167
|
+
with open(fn) as handle:
|
|
168
|
+
text = handle.read()
|
|
169
|
+
return zmat_from_str(text)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def geom_from_zmat_fn(fn, **geom_kwargs):
|
|
173
|
+
zmat = zmat_from_fn(fn)
|
|
174
|
+
geom = geom_from_zmat(zmat, **geom_kwargs)
|
|
175
|
+
return geom
|