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
@@ -0,0 +1,2 @@
1
+ class HEIIsFirstOrLastException(Exception):
2
+ pass
@@ -0,0 +1,69 @@
1
+ import argparse
2
+ import re
3
+ import sys
4
+
5
+ import numpy as np
6
+
7
+ from pysisyphus.helpers import geoms_from_trj
8
+ from pysisyphus.xyzloader import write_geoms_to_trj
9
+
10
+
11
+ def parse_args(args):
12
+ parser = argparse.ArgumentParser()
13
+
14
+ parser.add_argument("trj")
15
+ parser.add_argument("filter")
16
+ parser.add_argument("--first", type=int, metavar="N", default=None,
17
+ help="Only consider first N geometries in _trj.xyz."
18
+ )
19
+
20
+ return parser.parse_args(args)
21
+
22
+
23
+ def parse_filter(raw_filter):
24
+ split = raw_filter.strip().split()
25
+ mobjs = [re.match("(?P<not_present>!?)\((?P<atoms>[a-zA-Z-]+)\)", s) for s in split]
26
+ filters = list()
27
+ for m in mobjs:
28
+ internal = m["atoms"]
29
+ is_present = not bool(m["not_present"])
30
+ internal = tuple(sorted(internal.upper().split("-")))
31
+ filters.append((is_present, internal))
32
+ return filters
33
+
34
+
35
+ def get_unique_internals(geom):
36
+ attrs = ("bond_atom_indices", "bend_atom_indices", "dihedral_atom_indices")
37
+ atoms_arr = np.array(geom.atoms)
38
+ unique_internals = list()
39
+ for attr in attrs:
40
+ indices = getattr(geom.internal, attr)
41
+ unique = set([tuple(sorted(atoms_arr[inds])) for inds in indices])
42
+ unique_internals.extend(unique)
43
+ return unique_internals
44
+
45
+
46
+ def run():
47
+ args = parse_args(sys.argv[1:])
48
+ geoms = geoms_from_trj(args.trj, first=args.first, coord_type="redund",)
49
+ filters = parse_filter(args.filter)
50
+ print("Filters", filters)
51
+
52
+ valid_geoms = list()
53
+ invalid_geoms = list()
54
+ for i, geom in enumerate(geoms):
55
+ internals = get_unique_internals(geom)
56
+ valid = all([is_present == (internal in internals)
57
+ for is_present, internal in filters]
58
+ )
59
+ if not valid:
60
+ print(f"geom {i+1} is not valid.")
61
+ invalid_geoms.append(geom)
62
+ else:
63
+ valid_geoms.append(geom)
64
+ print()
65
+ print(f"Found {len(valid_geoms)} valid geometries.")
66
+ print(f"Found {len(invalid_geoms)} invalid geometries.")
67
+
68
+ write_geoms_to_trj(valid_geoms, "filtered_valid_trj.xyz")
69
+ write_geoms_to_trj(invalid_geoms, "filtered_invalid_trj.xyz")
pysisyphus/helpers.py ADDED
@@ -0,0 +1,623 @@
1
+ from collections import namedtuple
2
+ import getpass
3
+ import itertools as it
4
+ import logging
5
+ from math import log
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ import sys
10
+ import time
11
+
12
+ import numpy as np
13
+ import scipy as sp
14
+ from scipy.optimize import linear_sum_assignment
15
+ from scipy.spatial.distance import cdist
16
+
17
+ from pysisyphus.config import p_DEFAULT, T_DEFAULT, LIB_DIR
18
+ from pysisyphus.constants import ANG2BOHR, AU2KJPERMOL
19
+ from pysisyphus.Geometry import Geometry
20
+ from pysisyphus.helpers_pure import (
21
+ eigval_to_wavenumber,
22
+ report_isotopes,
23
+ highlight_text,
24
+ rms,
25
+ )
26
+ from pysisyphus.io import (
27
+ geom_from_cjson,
28
+ geom_from_crd,
29
+ geom_from_hessian,
30
+ geom_from_mol2,
31
+ geom_from_pdb,
32
+ geom_from_qcschema,
33
+ save_hessian as save_h5_hessian,
34
+ geom_from_zmat_fn,
35
+ geoms_from_inline_xyz,
36
+ )
37
+ from pysisyphus.thermo import (
38
+ can_thermoanalysis,
39
+ print_thermoanalysis,
40
+ )
41
+ from pysisyphus.xyzloader import parse_xyz_file, parse_trj_file, make_trj_str
42
+
43
+ import torch
44
+
45
+ def geom_from_xyz_file(xyz_fn, coord_type="cart", **coord_kwargs):
46
+ kwargs = {
47
+ "coord_type": coord_type,
48
+ }
49
+ kwargs.update(coord_kwargs)
50
+ xyz_fn = str(xyz_fn)
51
+ atoms, coords, comment = parse_xyz_file(xyz_fn, with_comment=True)
52
+ # Normally, xyz files are in Angstrom. If "IN_BOHR" is present
53
+ # in the comment, we interpret this file as in bohr.
54
+ if "IN_BOHR" not in comment.split():
55
+ coords *= ANG2BOHR
56
+ geom = Geometry(
57
+ atoms,
58
+ coords.flatten(),
59
+ comment=comment,
60
+ **kwargs,
61
+ )
62
+ return geom
63
+
64
+
65
+ def geoms_from_trj(trj_fn, first=None, coord_type="cart", **coord_kwargs):
66
+ trj_fn = str(trj_fn)
67
+ kwargs = {
68
+ "coord_type": coord_type,
69
+ }
70
+ kwargs.update(coord_kwargs)
71
+ atoms_coords_comments = parse_trj_file(trj_fn, with_comments=True)[:first]
72
+ geoms = [
73
+ Geometry(atoms, coords.flatten() * ANG2BOHR, comment=comment, **kwargs)
74
+ for atoms, coords, comment in atoms_coords_comments
75
+ ]
76
+ return geoms
77
+
78
+
79
+ def geom_loader(fn, coord_type="cart", iterable=False, **coord_kwargs):
80
+ """After introducing the pubchem functionality I don't like this
81
+ function anymore :) Too complicated."""
82
+ fn = str(fn)
83
+ org_fn = fn
84
+
85
+ split_ = re.split(r"\[(-?\d+)\]$", fn)
86
+ fn = split_.pop(0)
87
+ if split_:
88
+ index = int(split_.pop(0))
89
+ else:
90
+ index = None
91
+ ext = "" if "\n" in fn else Path(fn).suffix
92
+
93
+ funcs = {
94
+ ".cjson": geom_from_cjson,
95
+ ".crd": geom_from_crd,
96
+ ".h5": geom_from_hessian,
97
+ ".mol2": geom_from_mol2,
98
+ ".pdb": geom_from_pdb,
99
+ ".json": geom_from_qcschema,
100
+ "_trj.xyz": geoms_from_trj,
101
+ ".xyz": geom_from_xyz_file,
102
+ ".zmat": geom_from_zmat_fn,
103
+ "": geoms_from_inline_xyz,
104
+ }
105
+ assert ext in funcs, f"Unknown filetype for '{fn}'!"
106
+ func = funcs[ext]
107
+
108
+ if fn.startswith("lib:"):
109
+ fn = str(LIB_DIR / fn[4:])
110
+
111
+ kwargs = {
112
+ "coord_type": coord_type,
113
+ }
114
+ kwargs.update(coord_kwargs)
115
+ geom = func(fn, **kwargs)
116
+
117
+ if index is not None:
118
+ geom = geom[index]
119
+
120
+ if iterable and (ext in ("_trj.xyz", "")) and index is None:
121
+ geom = tuple(geom)
122
+ elif not iterable and ext == "" and len(geom) == 1:
123
+ geom = geom[0]
124
+ elif iterable:
125
+ geom = (geom,)
126
+
127
+ return geom
128
+
129
+
130
+ def _svd_align_core(coords_to_align, reference_coords):
131
+ """Compute the optimal rotation matrix aligning *coords_to_align* onto
132
+ *reference_coords* using SVD. Both arrays must be (N, 3) and already
133
+ centroid-subtracted.
134
+
135
+ Returns ``(rot_mat, rotated_coords)`` where ``rot_mat`` is a (3, 3)
136
+ rotation matrix (reflection-free) and ``rotated_coords`` is
137
+ ``coords_to_align.dot(rot_mat)``.
138
+ """
139
+ # http://nghiaho.com/?page_id=671#comment-559906
140
+ tmp_mat = coords_to_align.T.dot(reference_coords)
141
+ U, W, Vt = np.linalg.svd(tmp_mat)
142
+ rot_mat = U.dot(Vt)
143
+ # Avoid reflections
144
+ if np.linalg.det(rot_mat) < 0:
145
+ U[:, -1] *= -1
146
+ rot_mat = U.dot(Vt)
147
+ rotated_coords = coords_to_align.dot(rot_mat)
148
+ return rot_mat, rotated_coords
149
+
150
+
151
+ def align_geoms(geoms):
152
+ # http://nghiaho.com/?page_id=671#comment-559906
153
+ first_geom = geoms[0]
154
+ coords3d = first_geom.coords3d
155
+ centroid = coords3d.mean(axis=0)
156
+ last_centered = coords3d - centroid
157
+ first_geom.coords3d = last_centered
158
+ atoms_per_image = len(first_geom.atoms)
159
+
160
+ # Don't rotate the first image, so just add identity matrices
161
+ # for every atom.
162
+ rot_mats = [np.eye(3)] * atoms_per_image
163
+ for i, geom in enumerate(geoms[1:], 1):
164
+ coords3d = geom.coords3d
165
+ centroid = coords3d.mean(axis=0)
166
+ centered = coords3d - centroid
167
+ rot_mat, rotated3d = _svd_align_core(centered, last_centered)
168
+ geom.coords3d = rotated3d
169
+ last_centered = rotated3d
170
+ rot_mats.extend([rot_mat] * atoms_per_image)
171
+ return rot_mats
172
+
173
+
174
+ def procrustes(geometry, align_factor=1.0):
175
+ # http://nghiaho.com/?page_id=671#comment-559906
176
+ image0 = geometry.images[0]
177
+ coords3d = image0.coords3d
178
+ centroid = coords3d.mean(axis=0)
179
+ last_centered = coords3d - centroid
180
+ geometry.set_coords_at(0, last_centered.flatten())
181
+ atoms_per_image = len(image0.atoms)
182
+
183
+ # Don't rotate the first image, so just add identity matrices
184
+ # for every atom.
185
+ rot_mats = [np.eye(3)] * atoms_per_image
186
+ for i, image in enumerate(geometry.images[1:], 1):
187
+ coords3d = image.coords3d
188
+ centroid = coords3d.mean(axis=0)
189
+ centered = coords3d - centroid
190
+ rot_mat, _ = _svd_align_core(centered, last_centered)
191
+ # do a partial alignment if requested
192
+ if not (0.0 <= align_factor <= 1.0):
193
+ raise ValueError("align_factor must be between 0 and 1")
194
+ # mix the rotation matrix with the identity matrix
195
+ # align_factor=1 for full alignment (default); align_factor=0 for no alignment
196
+ rot_mat = align_factor * rot_mat + (1 - align_factor) * np.eye(3)
197
+ rotated3d = centered.dot(rot_mat)
198
+ geometry.set_coords_at(i, rotated3d.flatten())
199
+ last_centered = rotated3d
200
+ rot_mats.extend([rot_mat] * atoms_per_image)
201
+ return rot_mats
202
+
203
+
204
+ def align_coords(coords_list):
205
+ coords_list = np.array(coords_list)
206
+ coord_num = len(coords_list)
207
+ aligned_coords = np.empty_like(coords_list).reshape(coord_num, -1, 3)
208
+
209
+ coords0 = coords_list[0]
210
+ coords0_3d = coords0.reshape(-1, 3)
211
+ centroid = coords0_3d.mean(axis=0)
212
+ prev_centered = coords0_3d - centroid
213
+ aligned_coords[0] = prev_centered
214
+
215
+ for i, coords in enumerate(coords_list[1:], 1):
216
+ coords3d = coords.reshape(-1, 3)
217
+ centroid = coords3d.mean(axis=0)
218
+ centered = coords3d - centroid
219
+ rot_mat, rotated3d = _svd_align_core(centered, prev_centered)
220
+ aligned_coords[i] = rotated3d
221
+ prev_centered = rotated3d
222
+ aligned_coords.reshape(coord_num, -1)
223
+ return aligned_coords
224
+
225
+
226
+ def fit_rigid(
227
+ geometry, vectors=None, vector_lists=None, hessian=None, align_factor=1.0
228
+ ):
229
+ if vectors is None:
230
+ vectors = ()
231
+ if vector_lists is None:
232
+ vector_lists = ()
233
+ rotated_vector_lists = list()
234
+ rotated_hessian = None
235
+
236
+ rot_mats = procrustes(geometry, align_factor=align_factor)
237
+ G = sp.linalg.block_diag(*rot_mats)
238
+ rotated_vectors = [vec.dot(G) for vec in vectors]
239
+ for vl in vector_lists:
240
+ rvl = [vec.dot(G) for vec in vl]
241
+ rotated_vector_lists.append(rvl)
242
+
243
+ if hessian is not None:
244
+ # rotated_hessian = G.dot(hessian).dot(G.T)
245
+ # rotated_hessian = G.T.dot(hessian).dot(G)
246
+ rotated_hessian = G * hessian * G.T
247
+ return rotated_vectors, rotated_vector_lists, rotated_hessian
248
+
249
+
250
+ def slugify_worker(dask_worker):
251
+ slug = re.sub("tcp://", "host_", dask_worker)
252
+ slug = re.sub(r"\.", "_", slug)
253
+ slug = re.sub(":", "-", slug)
254
+ return slug
255
+
256
+
257
+ def match_geoms(ref_geom, geom_to_match, hydrogen=False):
258
+ """
259
+ See
260
+ [1] 10.1021/ci400534h
261
+ [2] 10.1021/acs.jcim.6b00516
262
+ """
263
+
264
+ logging.warning(
265
+ "helpers.match_geoms is deprecated!"
266
+ "Use stocastic.align.match_geom_atoms instead!"
267
+ )
268
+
269
+ assert len(ref_geom.atoms) == len(geom_to_match.atoms), "Atom numbers don't match!"
270
+
271
+ ref_coords, _ = ref_geom.coords_by_type
272
+ coords_to_match, inds_to_match = geom_to_match.coords_by_type
273
+ atoms = ref_coords.keys()
274
+ for atom in atoms:
275
+ # Only match hydrogens if explicitly requested
276
+ if atom == "H" and not hydrogen:
277
+ continue
278
+ print("atom", atom)
279
+ ref_coords_for_atom = ref_coords[atom]
280
+ coords_to_match_for_atom = coords_to_match[atom]
281
+ # Pairwise distances between two collections
282
+ # Atoms of ref_geom are along the rows, atoms of geom_to_match
283
+ # along the columns.
284
+ cd = cdist(ref_coords_for_atom, coords_to_match_for_atom)
285
+ print(cd)
286
+ # Hungarian method, row_inds are returned already sorted.
287
+ row_inds, col_inds = linear_sum_assignment(cd)
288
+ print("col_inds", col_inds)
289
+ old_inds = inds_to_match[atom]
290
+ new_inds = old_inds[col_inds]
291
+ print("old_inds", old_inds)
292
+ print("new_inds", new_inds)
293
+ new_coords_for_atom = coords_to_match_for_atom[new_inds]
294
+ # print(ref_coords_for_atom)
295
+ # print(new_coords_for_atom)
296
+ # print(ref_coords_for_atom-new_coords_for_atom)
297
+ # Update coordinates
298
+ print("old coords")
299
+ c3d = geom_to_match.coords3d
300
+ print(c3d)
301
+ # Modify the coordinates directly
302
+ c3d[old_inds] = new_coords_for_atom
303
+ # coords_to_match[atom] = coords_to_match_for_atom[new_inds]
304
+
305
+
306
+ def check_for_end_sign(check_user=True, cwd=".", add_signs=None):
307
+ if add_signs is None:
308
+ add_signs = tuple()
309
+ elif type(add_signs) == str:
310
+ add_signs = (add_signs,)
311
+ else:
312
+ add_signs = tuple(add_signs)
313
+ signs = (
314
+ "stop",
315
+ "converged",
316
+ "exit",
317
+ ) + add_signs
318
+ sign_found = False
319
+ cur_user = getpass.getuser()
320
+ cwd = Path(cwd)
321
+
322
+ def sign_owner(path):
323
+ if not check_user:
324
+ return True
325
+ else:
326
+ return path.owner() == cur_user
327
+
328
+ for sign in signs:
329
+ sign_path = cwd / sign
330
+ if sign_path.exists() and sign_owner(sign_path):
331
+ print(f"Found sign '{sign}'. Ending run.")
332
+ os.remove(sign_path)
333
+ sign_found = sign
334
+
335
+ if sign == "exit":
336
+ sys.exit()
337
+ return sign_found
338
+
339
+
340
+ def index_array_from_overlaps(overlaps, axis=1):
341
+ """It is assumed that the overlaps between two points with indices
342
+ i and j with (j > i) are computed and that i changes along the first
343
+ axis (axis=0) and j changes along the second axis (axis=1).
344
+
345
+ So the first row of the overlap matrix (overlaps[0]) should contain
346
+ the overlaps between state 0 at index i and all states at index j.
347
+
348
+ argmax along axis 1 returns the indices of the most overlapping states
349
+ at index j with the states at index i, given by the item index in the
350
+ indices array. E.g.:
351
+ [0 1 3 2] indicates a root flip in a system with four states when
352
+ going from index i to index j. Root 2 at i became root 3 at j and
353
+ vice versa.
354
+ """
355
+ # indices = np.argmax(overlaps**2, axis=1)
356
+ indices = np.argmax(np.abs(overlaps), axis=1)
357
+ return indices
358
+
359
+
360
+ def np_print(func, precision=2, suppress=True, linewidth=120):
361
+ def wrapped(*args, **kwargs):
362
+ org_print_dict = dict(np.get_printoptions())
363
+ np.set_printoptions(suppress=suppress, precision=precision, linewidth=linewidth)
364
+ result = func(*args, **kwargs)
365
+ np.set_printoptions(**org_print_dict)
366
+ return result
367
+
368
+ return wrapped
369
+
370
+
371
+ def confirm_input(message):
372
+ full_message = message + " (yes/no)\n"
373
+ inp = input(full_message)
374
+ return inp == "yes"
375
+
376
+
377
+ def get_geom_getter(ref_geom, calc_setter):
378
+ def geom_from_coords(coords):
379
+ new_geom = ref_geom.copy()
380
+ new_geom.coords = coords
381
+ new_geom.set_calculator(calc_setter())
382
+ return new_geom
383
+
384
+ return geom_from_coords
385
+
386
+
387
+ def get_coords_diffs(coords, align=False, normalize=True):
388
+ if align:
389
+ coords = align_coords(coords)
390
+ cds = [
391
+ 0,
392
+ ]
393
+ for i in range(len(coords) - 1):
394
+ diff = np.linalg.norm(coords[i + 1] - coords[i])
395
+ cds.append(diff)
396
+ cds = np.cumsum(cds)
397
+ if normalize:
398
+ cds /= cds.max()
399
+ return cds
400
+
401
+
402
+ def pick_image_inds(cart_coords, images: int):
403
+ """Pick approx. evenly distributed images from given Cartesian coordinates."""
404
+ cds = get_coords_diffs(cart_coords, align=True)
405
+ target_cds = iter(np.linspace(0, 1, num=images))
406
+ target_cd = next(target_cds)
407
+ image_inds = list()
408
+ for i, cd in enumerate(cds):
409
+ if len(image_inds) == (images - 1):
410
+ break
411
+ if cd >= target_cd:
412
+ image_inds.append(i)
413
+ target_cd = next(target_cds)
414
+
415
+ image_inds.append(len(cart_coords) - 1)
416
+ return image_inds
417
+
418
+
419
+ def shake_coords(coords, scale=0.1, seed=None):
420
+ if seed:
421
+ np.random.seed(seed)
422
+ offset = np.random.normal(scale=scale, size=coords.size)
423
+ return coords + offset
424
+
425
+
426
+ def norm_max_rms(arr):
427
+ norm = np.linalg.norm(arr)
428
+ max_ = np.max(np.abs(arr))
429
+ rms_ = rms(arr)
430
+ return norm, max_, rms_
431
+
432
+
433
+ def complete_fragments(atoms, fragments):
434
+ all_inds = set(range(len(atoms)))
435
+ frag_inds = set(it.chain(*fragments))
436
+ rest_inds = all_inds - frag_inds
437
+
438
+ assert len(frag_inds) + len(rest_inds) == len(atoms)
439
+ assert frag_inds & rest_inds == set()
440
+
441
+ if rest_inds:
442
+ fragments.append(tuple(rest_inds))
443
+ fragments = tuple([tuple(frag) for frag in fragments])
444
+
445
+ return fragments
446
+
447
+
448
+ FinalHessianResult = namedtuple(
449
+ "FinalHessianResult",
450
+ "neg_eigvals eigvals nus imag_fns thermo",
451
+ )
452
+
453
+
454
+ def do_final_hessian(
455
+ geom,
456
+ save_hessian=True,
457
+ write_imag_modes=False,
458
+ is_ts=False,
459
+ prefix="",
460
+ T=T_DEFAULT,
461
+ p=p_DEFAULT,
462
+ ev_thresh=-1e-6,
463
+ print_thermo=False,
464
+ out_dir=None,
465
+ ):
466
+ if out_dir is None:
467
+ out_dir = "."
468
+ out_dir = Path(out_dir)
469
+
470
+ print(highlight_text("Hessian at final geometry", level=1))
471
+ print()
472
+
473
+ report_isotopes(geom, "the_frequencies")
474
+
475
+ start = time.time()
476
+ print("... started Hessian calculation")
477
+ hessian = geom.cart_hessian
478
+ dur = time.time() - start
479
+ print(f"... calculation took {dur/60:.2f} min")
480
+ print("... mass-weighing cartesian hessian")
481
+ mw_hessian = geom.mass_weigh_hessian(hessian)
482
+ print("... doing Eckart-projection")
483
+ proj_hessian = geom.eckart_projection(mw_hessian)
484
+ if isinstance(proj_hessian, torch.Tensor):
485
+ eigvals, _ = torch.linalg.eigh(proj_hessian)
486
+ eigvals = eigvals.cpu().numpy()
487
+ else:
488
+ eigvals, _ = np.linalg.eigh(proj_hessian)
489
+
490
+ neg_inds = eigvals < ev_thresh
491
+ neg_eigvals = eigvals[neg_inds]
492
+ neg_num = sum(neg_inds)
493
+ eigval_str = array2string(eigvals[:10], precision=4)
494
+ print()
495
+ print("First 10 eigenvalues", eigval_str)
496
+ if neg_num > 0:
497
+ wavenumbers = eigval_to_wavenumber(neg_eigvals)
498
+ wavenum_str = array2string(wavenumbers, precision=2)
499
+ print("Imaginary frequencies:", wavenum_str, "cm⁻¹")
500
+
501
+ if prefix:
502
+ prefix = f"{prefix}_"
503
+
504
+ # Dump HDF Hessian
505
+ if save_hessian:
506
+ final_h5_hessian_fn = prefix + "final_hessian.h5"
507
+ save_h5_hessian(out_dir / final_h5_hessian_fn, geom)
508
+ print(f"Wrote Hessian data HD5 file '{final_h5_hessian_fn}'.")
509
+
510
+ imag_fns = list()
511
+ if write_imag_modes:
512
+ imag_modes = imag_modes_from_geom(geom)
513
+ for i, imag_mode in enumerate(imag_modes):
514
+ trj_fn = out_dir / (prefix + f"imaginary_mode_{i:03d}_trj.xyz")
515
+ imag_fns.append(trj_fn)
516
+ with open(trj_fn, "w") as handle:
517
+ handle.write(imag_mode.trj_str)
518
+ print(
519
+ f"Wrote imaginary mode with ṽ={imag_mode.nu: >10.2f} cm⁻¹ to '{trj_fn}'"
520
+ )
521
+ print()
522
+
523
+ thermo = None
524
+ if can_thermoanalysis:
525
+ thermo = geom.get_thermoanalysis(geom, T=T, p=p)
526
+ if print_thermo:
527
+ print_thermoanalysis(thermo, geom=geom, is_ts=is_ts)
528
+
529
+ res = FinalHessianResult(
530
+ neg_eigvals=neg_eigvals,
531
+ eigvals=eigvals,
532
+ nus=eigval_to_wavenumber(eigvals),
533
+ imag_fns=imag_fns,
534
+ thermo=thermo,
535
+ )
536
+ return res
537
+
538
+
539
+ def print_barrier(ref_energy, comp_energy, ref_str, comp_str):
540
+ barrier = (ref_energy - comp_energy) * AU2KJPERMOL
541
+ print(f"Barrier between {ref_str} and {comp_str}: {barrier:.1f} kJ mol⁻¹")
542
+ return barrier
543
+
544
+
545
+ def get_tangent_trj_str(atoms, coords, tangent, comment=None, points=10, displ=None):
546
+ if displ is None:
547
+ # Linear equation. Will give displ~3 for 30 atoms and
548
+ # displ ~ 1 for 3 atoms.
549
+ # displ = 2/27 * len(atoms) + 0.78
550
+
551
+ # Logarithmic function f(x) = a*log(x) + b
552
+ # f(3) = ~1 and (f30) = ~2 with a = 0.43429 and b = 0.52288
553
+ # I guess this works better, because only some atoms move, even in bigger
554
+ # systems and the log function converges against a certain value, whereas
555
+ # the linear function just keeps growing.
556
+ displ = 0.43429 * log(len(atoms)) + 0.52288
557
+ step_sizes = np.linspace(-displ, displ, 2 * points + 1)
558
+ steps = step_sizes[:, None] * tangent
559
+ trj_coords = coords[None, :] + steps
560
+ trj_coords = trj_coords.reshape(step_sizes.size, -1, 3) / ANG2BOHR
561
+
562
+ comments = None
563
+ if comment:
564
+ comments = [comment] * step_sizes.size
565
+ trj_str = make_trj_str(atoms, trj_coords, comments=comments)
566
+
567
+ return trj_str
568
+
569
+
570
+ def imag_modes_from_geom(geom, freq_thresh=-10, points=10, displ=None):
571
+ NormalMode = namedtuple("NormalMode", "nu mode trj_str")
572
+
573
+ # We don't want to do start any calculation here, so we directly access
574
+ # the attribute underlying the geom.hessian property.
575
+ hessian = geom._hessian
576
+ nus, _, _, cart_displs = geom.get_normal_modes(hessian)
577
+ below_thresh = nus < freq_thresh
578
+
579
+ imag_modes = list()
580
+ for nu, eigvec in zip(nus[below_thresh], cart_displs[:, below_thresh].T):
581
+ eigvec = eigvec.cpu().numpy() if isinstance(eigvec, torch.Tensor) else eigvec
582
+ comment = f"{nu:.2f} cm^-1"
583
+ trj_str = get_tangent_trj_str(
584
+ geom.atoms,
585
+ geom.cart_coords,
586
+ eigvec,
587
+ comment=comment,
588
+ points=points,
589
+ displ=displ,
590
+ )
591
+ imag_modes.append(
592
+ NormalMode(
593
+ nu=nu,
594
+ mode=eigvec,
595
+ trj_str=trj_str,
596
+ )
597
+ )
598
+
599
+ return imag_modes
600
+
601
+
602
+ def simple_peaks(data):
603
+ peaks = list()
604
+ for i, cur in enumerate(data[1:-1], 1):
605
+ prev_ = data[i - 1]
606
+ next_ = data[i + 1]
607
+ if prev_ < cur > next_:
608
+ peaks.append(i)
609
+ peaks = np.array(peaks, dtype=int)
610
+ vals = np.array(data)[peaks]
611
+ return peaks, vals
612
+
613
+
614
+ def array2string(arr, precision=None, suppress_small=None):
615
+ """Similar to np.array2string, but able to handle torch tensors.
616
+ Only implement used parameters in pysisyphus."""
617
+ if isinstance(arr, torch.Tensor):
618
+ arr = arr.cpu().numpy()
619
+ return np.array2string(
620
+ arr,
621
+ precision=precision,
622
+ suppress_small=suppress_small,
623
+ )