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