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.
Files changed (372) hide show
  1. hessian_ff/__init__.py +50 -0
  2. hessian_ff/analytical_hessian.py +609 -0
  3. hessian_ff/constants.py +46 -0
  4. hessian_ff/forcefield.py +339 -0
  5. hessian_ff/loaders.py +608 -0
  6. hessian_ff/native/Makefile +8 -0
  7. hessian_ff/native/__init__.py +28 -0
  8. hessian_ff/native/analytical_hessian.py +88 -0
  9. hessian_ff/native/analytical_hessian_ext.cpp +258 -0
  10. hessian_ff/native/bonded.py +82 -0
  11. hessian_ff/native/bonded_ext.cpp +640 -0
  12. hessian_ff/native/loader.py +349 -0
  13. hessian_ff/native/nonbonded.py +118 -0
  14. hessian_ff/native/nonbonded_ext.cpp +1150 -0
  15. hessian_ff/prmtop_parmed.py +23 -0
  16. hessian_ff/system.py +107 -0
  17. hessian_ff/terms/__init__.py +14 -0
  18. hessian_ff/terms/angle.py +73 -0
  19. hessian_ff/terms/bond.py +44 -0
  20. hessian_ff/terms/cmap.py +406 -0
  21. hessian_ff/terms/dihedral.py +141 -0
  22. hessian_ff/terms/nonbonded.py +209 -0
  23. hessian_ff/tests/__init__.py +0 -0
  24. hessian_ff/tests/conftest.py +75 -0
  25. hessian_ff/tests/data/small/complex.parm7 +1346 -0
  26. hessian_ff/tests/data/small/complex.pdb +125 -0
  27. hessian_ff/tests/data/small/complex.rst7 +63 -0
  28. hessian_ff/tests/test_coords_input.py +44 -0
  29. hessian_ff/tests/test_energy_force.py +49 -0
  30. hessian_ff/tests/test_hessian.py +137 -0
  31. hessian_ff/tests/test_smoke.py +18 -0
  32. hessian_ff/tests/test_validation.py +40 -0
  33. hessian_ff/workflows.py +889 -0
  34. mlmm/__init__.py +36 -0
  35. mlmm/__main__.py +7 -0
  36. mlmm/_version.py +34 -0
  37. mlmm/add_elem_info.py +374 -0
  38. mlmm/advanced_help.py +91 -0
  39. mlmm/align_freeze_atoms.py +601 -0
  40. mlmm/all.py +3535 -0
  41. mlmm/bond_changes.py +231 -0
  42. mlmm/bool_compat.py +223 -0
  43. mlmm/cli.py +574 -0
  44. mlmm/cli_utils.py +166 -0
  45. mlmm/default_group.py +337 -0
  46. mlmm/defaults.py +467 -0
  47. mlmm/define_layer.py +526 -0
  48. mlmm/dft.py +1041 -0
  49. mlmm/energy_diagram.py +253 -0
  50. mlmm/extract.py +2213 -0
  51. mlmm/fix_altloc.py +464 -0
  52. mlmm/freq.py +1406 -0
  53. mlmm/harmonic_constraints.py +140 -0
  54. mlmm/hessian_cache.py +44 -0
  55. mlmm/hessian_calc.py +174 -0
  56. mlmm/irc.py +638 -0
  57. mlmm/mlmm_calc.py +2262 -0
  58. mlmm/mm_parm.py +945 -0
  59. mlmm/oniom_export.py +1983 -0
  60. mlmm/oniom_import.py +457 -0
  61. mlmm/opt.py +1742 -0
  62. mlmm/path_opt.py +1353 -0
  63. mlmm/path_search.py +2299 -0
  64. mlmm/preflight.py +88 -0
  65. mlmm/py.typed +1 -0
  66. mlmm/pysis_runner.py +45 -0
  67. mlmm/scan.py +1047 -0
  68. mlmm/scan2d.py +1226 -0
  69. mlmm/scan3d.py +1265 -0
  70. mlmm/scan_common.py +184 -0
  71. mlmm/summary_log.py +736 -0
  72. mlmm/trj2fig.py +448 -0
  73. mlmm/tsopt.py +2871 -0
  74. mlmm/utils.py +2309 -0
  75. mlmm/xtb_embedcharge_correction.py +475 -0
  76. mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
  77. mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
  78. mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
  79. mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
  80. mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
  81. mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
  82. pysisyphus/Geometry.py +1667 -0
  83. pysisyphus/LICENSE +674 -0
  84. pysisyphus/TableFormatter.py +63 -0
  85. pysisyphus/TablePrinter.py +74 -0
  86. pysisyphus/__init__.py +12 -0
  87. pysisyphus/calculators/AFIR.py +452 -0
  88. pysisyphus/calculators/AnaPot.py +20 -0
  89. pysisyphus/calculators/AnaPot2.py +48 -0
  90. pysisyphus/calculators/AnaPot3.py +12 -0
  91. pysisyphus/calculators/AnaPot4.py +20 -0
  92. pysisyphus/calculators/AnaPotBase.py +337 -0
  93. pysisyphus/calculators/AnaPotCBM.py +25 -0
  94. pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
  95. pysisyphus/calculators/CFOUR.py +250 -0
  96. pysisyphus/calculators/Calculator.py +844 -0
  97. pysisyphus/calculators/CerjanMiller.py +24 -0
  98. pysisyphus/calculators/Composite.py +123 -0
  99. pysisyphus/calculators/ConicalIntersection.py +171 -0
  100. pysisyphus/calculators/DFTBp.py +430 -0
  101. pysisyphus/calculators/DFTD3.py +66 -0
  102. pysisyphus/calculators/DFTD4.py +84 -0
  103. pysisyphus/calculators/Dalton.py +61 -0
  104. pysisyphus/calculators/Dimer.py +681 -0
  105. pysisyphus/calculators/Dummy.py +20 -0
  106. pysisyphus/calculators/EGO.py +76 -0
  107. pysisyphus/calculators/EnergyMin.py +224 -0
  108. pysisyphus/calculators/ExternalPotential.py +264 -0
  109. pysisyphus/calculators/FakeASE.py +35 -0
  110. pysisyphus/calculators/FourWellAnaPot.py +28 -0
  111. pysisyphus/calculators/FreeEndNEBPot.py +39 -0
  112. pysisyphus/calculators/Gaussian09.py +18 -0
  113. pysisyphus/calculators/Gaussian16.py +726 -0
  114. pysisyphus/calculators/HardSphere.py +159 -0
  115. pysisyphus/calculators/IDPPCalculator.py +49 -0
  116. pysisyphus/calculators/IPIClient.py +133 -0
  117. pysisyphus/calculators/IPIServer.py +234 -0
  118. pysisyphus/calculators/LEPSBase.py +24 -0
  119. pysisyphus/calculators/LEPSExpr.py +139 -0
  120. pysisyphus/calculators/LennardJones.py +80 -0
  121. pysisyphus/calculators/MOPAC.py +219 -0
  122. pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
  123. pysisyphus/calculators/MultiCalc.py +85 -0
  124. pysisyphus/calculators/NFK.py +45 -0
  125. pysisyphus/calculators/OBabel.py +87 -0
  126. pysisyphus/calculators/ONIOMv2.py +1129 -0
  127. pysisyphus/calculators/ORCA.py +893 -0
  128. pysisyphus/calculators/ORCA5.py +6 -0
  129. pysisyphus/calculators/OpenMM.py +88 -0
  130. pysisyphus/calculators/OpenMolcas.py +281 -0
  131. pysisyphus/calculators/OverlapCalculator.py +908 -0
  132. pysisyphus/calculators/Psi4.py +218 -0
  133. pysisyphus/calculators/PyPsi4.py +37 -0
  134. pysisyphus/calculators/PySCF.py +341 -0
  135. pysisyphus/calculators/PyXTB.py +73 -0
  136. pysisyphus/calculators/QCEngine.py +106 -0
  137. pysisyphus/calculators/Rastrigin.py +22 -0
  138. pysisyphus/calculators/Remote.py +76 -0
  139. pysisyphus/calculators/Rosenbrock.py +15 -0
  140. pysisyphus/calculators/SocketCalc.py +97 -0
  141. pysisyphus/calculators/TIP3P.py +111 -0
  142. pysisyphus/calculators/TransTorque.py +161 -0
  143. pysisyphus/calculators/Turbomole.py +965 -0
  144. pysisyphus/calculators/VRIPot.py +37 -0
  145. pysisyphus/calculators/WFOWrapper.py +333 -0
  146. pysisyphus/calculators/WFOWrapper2.py +341 -0
  147. pysisyphus/calculators/XTB.py +418 -0
  148. pysisyphus/calculators/__init__.py +81 -0
  149. pysisyphus/calculators/cosmo_data.py +139 -0
  150. pysisyphus/calculators/parser.py +150 -0
  151. pysisyphus/color.py +19 -0
  152. pysisyphus/config.py +133 -0
  153. pysisyphus/constants.py +65 -0
  154. pysisyphus/cos/AdaptiveNEB.py +230 -0
  155. pysisyphus/cos/ChainOfStates.py +725 -0
  156. pysisyphus/cos/FreeEndNEB.py +25 -0
  157. pysisyphus/cos/FreezingString.py +103 -0
  158. pysisyphus/cos/GrowingChainOfStates.py +71 -0
  159. pysisyphus/cos/GrowingNT.py +309 -0
  160. pysisyphus/cos/GrowingString.py +508 -0
  161. pysisyphus/cos/NEB.py +189 -0
  162. pysisyphus/cos/SimpleZTS.py +64 -0
  163. pysisyphus/cos/__init__.py +22 -0
  164. pysisyphus/cos/stiffness.py +199 -0
  165. pysisyphus/drivers/__init__.py +17 -0
  166. pysisyphus/drivers/afir.py +855 -0
  167. pysisyphus/drivers/barriers.py +271 -0
  168. pysisyphus/drivers/birkholz.py +138 -0
  169. pysisyphus/drivers/cluster.py +318 -0
  170. pysisyphus/drivers/diabatization.py +133 -0
  171. pysisyphus/drivers/merge.py +368 -0
  172. pysisyphus/drivers/merge_mol2.py +322 -0
  173. pysisyphus/drivers/opt.py +375 -0
  174. pysisyphus/drivers/perf.py +91 -0
  175. pysisyphus/drivers/pka.py +52 -0
  176. pysisyphus/drivers/precon_pos_rot.py +669 -0
  177. pysisyphus/drivers/rates.py +480 -0
  178. pysisyphus/drivers/replace.py +219 -0
  179. pysisyphus/drivers/scan.py +212 -0
  180. pysisyphus/drivers/spectrum.py +166 -0
  181. pysisyphus/drivers/thermo.py +31 -0
  182. pysisyphus/dynamics/Gaussian.py +103 -0
  183. pysisyphus/dynamics/__init__.py +20 -0
  184. pysisyphus/dynamics/colvars.py +136 -0
  185. pysisyphus/dynamics/driver.py +297 -0
  186. pysisyphus/dynamics/helpers.py +256 -0
  187. pysisyphus/dynamics/lincs.py +105 -0
  188. pysisyphus/dynamics/mdp.py +364 -0
  189. pysisyphus/dynamics/rattle.py +121 -0
  190. pysisyphus/dynamics/thermostats.py +128 -0
  191. pysisyphus/dynamics/wigner.py +266 -0
  192. pysisyphus/elem_data.py +3473 -0
  193. pysisyphus/exceptions.py +2 -0
  194. pysisyphus/filtertrj.py +69 -0
  195. pysisyphus/helpers.py +623 -0
  196. pysisyphus/helpers_pure.py +649 -0
  197. pysisyphus/init_logging.py +50 -0
  198. pysisyphus/intcoords/Bend.py +69 -0
  199. pysisyphus/intcoords/Bend2.py +25 -0
  200. pysisyphus/intcoords/BondedFragment.py +32 -0
  201. pysisyphus/intcoords/Cartesian.py +41 -0
  202. pysisyphus/intcoords/CartesianCoords.py +140 -0
  203. pysisyphus/intcoords/Coords.py +56 -0
  204. pysisyphus/intcoords/DLC.py +197 -0
  205. pysisyphus/intcoords/DistanceFunction.py +34 -0
  206. pysisyphus/intcoords/DummyImproper.py +70 -0
  207. pysisyphus/intcoords/DummyTorsion.py +72 -0
  208. pysisyphus/intcoords/LinearBend.py +105 -0
  209. pysisyphus/intcoords/LinearDisplacement.py +80 -0
  210. pysisyphus/intcoords/OutOfPlane.py +59 -0
  211. pysisyphus/intcoords/PrimTypes.py +286 -0
  212. pysisyphus/intcoords/Primitive.py +137 -0
  213. pysisyphus/intcoords/RedundantCoords.py +659 -0
  214. pysisyphus/intcoords/RobustTorsion.py +59 -0
  215. pysisyphus/intcoords/Rotation.py +147 -0
  216. pysisyphus/intcoords/Stretch.py +31 -0
  217. pysisyphus/intcoords/Torsion.py +101 -0
  218. pysisyphus/intcoords/Torsion2.py +25 -0
  219. pysisyphus/intcoords/Translation.py +45 -0
  220. pysisyphus/intcoords/__init__.py +61 -0
  221. pysisyphus/intcoords/augment_bonds.py +126 -0
  222. pysisyphus/intcoords/derivatives.py +10512 -0
  223. pysisyphus/intcoords/eval.py +80 -0
  224. pysisyphus/intcoords/exceptions.py +37 -0
  225. pysisyphus/intcoords/findiffs.py +48 -0
  226. pysisyphus/intcoords/generate_derivatives.py +414 -0
  227. pysisyphus/intcoords/helpers.py +235 -0
  228. pysisyphus/intcoords/logging_conf.py +10 -0
  229. pysisyphus/intcoords/mp_derivatives.py +10836 -0
  230. pysisyphus/intcoords/setup.py +962 -0
  231. pysisyphus/intcoords/setup_fast.py +176 -0
  232. pysisyphus/intcoords/update.py +272 -0
  233. pysisyphus/intcoords/valid.py +89 -0
  234. pysisyphus/interpolate/Geodesic.py +93 -0
  235. pysisyphus/interpolate/IDPP.py +55 -0
  236. pysisyphus/interpolate/Interpolator.py +116 -0
  237. pysisyphus/interpolate/LST.py +70 -0
  238. pysisyphus/interpolate/Redund.py +152 -0
  239. pysisyphus/interpolate/__init__.py +9 -0
  240. pysisyphus/interpolate/helpers.py +34 -0
  241. pysisyphus/io/__init__.py +22 -0
  242. pysisyphus/io/aomix.py +178 -0
  243. pysisyphus/io/cjson.py +24 -0
  244. pysisyphus/io/crd.py +101 -0
  245. pysisyphus/io/cube.py +220 -0
  246. pysisyphus/io/fchk.py +184 -0
  247. pysisyphus/io/hdf5.py +49 -0
  248. pysisyphus/io/hessian.py +72 -0
  249. pysisyphus/io/mol2.py +146 -0
  250. pysisyphus/io/molden.py +293 -0
  251. pysisyphus/io/orca.py +189 -0
  252. pysisyphus/io/pdb.py +269 -0
  253. pysisyphus/io/psf.py +79 -0
  254. pysisyphus/io/pubchem.py +31 -0
  255. pysisyphus/io/qcschema.py +34 -0
  256. pysisyphus/io/sdf.py +29 -0
  257. pysisyphus/io/xyz.py +61 -0
  258. pysisyphus/io/zmat.py +175 -0
  259. pysisyphus/irc/DWI.py +108 -0
  260. pysisyphus/irc/DampedVelocityVerlet.py +134 -0
  261. pysisyphus/irc/Euler.py +22 -0
  262. pysisyphus/irc/EulerPC.py +345 -0
  263. pysisyphus/irc/GonzalezSchlegel.py +187 -0
  264. pysisyphus/irc/IMKMod.py +164 -0
  265. pysisyphus/irc/IRC.py +878 -0
  266. pysisyphus/irc/IRCDummy.py +10 -0
  267. pysisyphus/irc/Instanton.py +307 -0
  268. pysisyphus/irc/LQA.py +53 -0
  269. pysisyphus/irc/ModeKill.py +136 -0
  270. pysisyphus/irc/ParamPlot.py +53 -0
  271. pysisyphus/irc/RK4.py +36 -0
  272. pysisyphus/irc/__init__.py +31 -0
  273. pysisyphus/irc/initial_displ.py +219 -0
  274. pysisyphus/linalg.py +411 -0
  275. pysisyphus/line_searches/Backtracking.py +88 -0
  276. pysisyphus/line_searches/HagerZhang.py +184 -0
  277. pysisyphus/line_searches/LineSearch.py +232 -0
  278. pysisyphus/line_searches/StrongWolfe.py +108 -0
  279. pysisyphus/line_searches/__init__.py +9 -0
  280. pysisyphus/line_searches/interpol.py +15 -0
  281. pysisyphus/modefollow/NormalMode.py +40 -0
  282. pysisyphus/modefollow/__init__.py +10 -0
  283. pysisyphus/modefollow/davidson.py +199 -0
  284. pysisyphus/modefollow/lanczos.py +95 -0
  285. pysisyphus/optimizers/BFGS.py +99 -0
  286. pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
  287. pysisyphus/optimizers/ConjugateGradient.py +98 -0
  288. pysisyphus/optimizers/CubicNewton.py +75 -0
  289. pysisyphus/optimizers/FIRE.py +113 -0
  290. pysisyphus/optimizers/HessianOptimizer.py +1176 -0
  291. pysisyphus/optimizers/LBFGS.py +228 -0
  292. pysisyphus/optimizers/LayerOpt.py +411 -0
  293. pysisyphus/optimizers/MicroOptimizer.py +169 -0
  294. pysisyphus/optimizers/NCOptimizer.py +90 -0
  295. pysisyphus/optimizers/Optimizer.py +1084 -0
  296. pysisyphus/optimizers/PreconLBFGS.py +260 -0
  297. pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
  298. pysisyphus/optimizers/QuickMin.py +74 -0
  299. pysisyphus/optimizers/RFOptimizer.py +181 -0
  300. pysisyphus/optimizers/RSA.py +99 -0
  301. pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
  302. pysisyphus/optimizers/SteepestDescent.py +23 -0
  303. pysisyphus/optimizers/StringOptimizer.py +173 -0
  304. pysisyphus/optimizers/__init__.py +41 -0
  305. pysisyphus/optimizers/closures.py +301 -0
  306. pysisyphus/optimizers/cls_map.py +58 -0
  307. pysisyphus/optimizers/exceptions.py +6 -0
  308. pysisyphus/optimizers/gdiis.py +280 -0
  309. pysisyphus/optimizers/guess_hessians.py +311 -0
  310. pysisyphus/optimizers/hessian_updates.py +355 -0
  311. pysisyphus/optimizers/poly_fit.py +285 -0
  312. pysisyphus/optimizers/precon.py +153 -0
  313. pysisyphus/optimizers/restrict_step.py +24 -0
  314. pysisyphus/pack.py +172 -0
  315. pysisyphus/peakdetect.py +948 -0
  316. pysisyphus/plot.py +1031 -0
  317. pysisyphus/run.py +2106 -0
  318. pysisyphus/socket_helper.py +74 -0
  319. pysisyphus/stocastic/FragmentKick.py +132 -0
  320. pysisyphus/stocastic/Kick.py +81 -0
  321. pysisyphus/stocastic/Pipeline.py +303 -0
  322. pysisyphus/stocastic/__init__.py +21 -0
  323. pysisyphus/stocastic/align.py +127 -0
  324. pysisyphus/testing.py +96 -0
  325. pysisyphus/thermo.py +156 -0
  326. pysisyphus/trj.py +824 -0
  327. pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
  328. pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
  329. pysisyphus/tsoptimizers/TRIM.py +59 -0
  330. pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
  331. pysisyphus/tsoptimizers/__init__.py +23 -0
  332. pysisyphus/wavefunction/Basis.py +239 -0
  333. pysisyphus/wavefunction/DIIS.py +76 -0
  334. pysisyphus/wavefunction/__init__.py +25 -0
  335. pysisyphus/wavefunction/build_ext.py +42 -0
  336. pysisyphus/wavefunction/cart2sph.py +190 -0
  337. pysisyphus/wavefunction/diabatization.py +304 -0
  338. pysisyphus/wavefunction/excited_states.py +435 -0
  339. pysisyphus/wavefunction/gen_ints.py +1811 -0
  340. pysisyphus/wavefunction/helpers.py +104 -0
  341. pysisyphus/wavefunction/ints/__init__.py +0 -0
  342. pysisyphus/wavefunction/ints/boys.py +193 -0
  343. pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
  344. pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
  345. pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
  346. pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
  347. pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
  348. pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
  349. pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
  350. pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
  351. pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
  352. pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
  353. pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
  354. pysisyphus/wavefunction/localization.py +458 -0
  355. pysisyphus/wavefunction/multipole.py +159 -0
  356. pysisyphus/wavefunction/normalization.py +36 -0
  357. pysisyphus/wavefunction/pop_analysis.py +134 -0
  358. pysisyphus/wavefunction/shells.py +1171 -0
  359. pysisyphus/wavefunction/wavefunction.py +504 -0
  360. pysisyphus/wrapper/__init__.py +11 -0
  361. pysisyphus/wrapper/exceptions.py +2 -0
  362. pysisyphus/wrapper/jmol.py +120 -0
  363. pysisyphus/wrapper/mwfn.py +169 -0
  364. pysisyphus/wrapper/packmol.py +71 -0
  365. pysisyphus/xyzloader.py +168 -0
  366. pysisyphus/yaml_mods.py +45 -0
  367. thermoanalysis/LICENSE +674 -0
  368. thermoanalysis/QCData.py +244 -0
  369. thermoanalysis/__init__.py +0 -0
  370. thermoanalysis/config.py +3 -0
  371. thermoanalysis/constants.py +20 -0
  372. 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
@@ -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