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,69 @@
1
+ from math import sin
2
+
3
+ import numpy as np
4
+
5
+ from pysisyphus.intcoords.Primitive import Primitive
6
+ from pysisyphus.intcoords.derivatives import d2q_a
7
+ from pysisyphus.linalg import cross3, norm3
8
+
9
+
10
+ class Bend(Primitive):
11
+
12
+ @staticmethod
13
+ def _weight(atoms, coords3d, indices, f_damping):
14
+ m, o, n = indices
15
+ rho_mo = Bend.rho(atoms, coords3d, (m, o))
16
+ rho_on = Bend.rho(atoms, coords3d, (o, n))
17
+ rad = Bend._calculate(coords3d, indices)
18
+ return (rho_mo * rho_on)**0.5 * (f_damping + (1-f_damping)*sin(rad))
19
+
20
+ @staticmethod
21
+ def _calculate(coords3d, indices, gradient=False):
22
+ m, o, n = indices
23
+ u_dash = coords3d[m] - coords3d[o]
24
+ v_dash = coords3d[n] - coords3d[o]
25
+ u_norm = norm3(u_dash)
26
+ v_norm = norm3(v_dash)
27
+ u = u_dash / u_norm
28
+ v = v_dash / v_norm
29
+
30
+ udv = max(-1.0, min(1.0, u.dot(v)))
31
+ angle_rad = np.arccos(udv)
32
+
33
+ if gradient:
34
+ cross_vec1 = ( 1, -1, 1)
35
+ cross_vec2 = (-1, 1, 1)
36
+
37
+ # Determine second vector for the cross product, to get an
38
+ # orthogonal direction. Eq. (24) in [1]
39
+ uv_parallel = Bend.parallel(u, v)
40
+ if not uv_parallel:
41
+ cross_vec = v
42
+ elif not Bend.parallel(u, cross_vec1):
43
+ cross_vec = cross_vec1
44
+ else:
45
+ cross_vec = cross_vec2
46
+
47
+ w_dash = cross3(u, cross_vec)
48
+ w = w_dash / norm3(w_dash)
49
+
50
+ uxw = cross3(u, w)
51
+ wxv = cross3(w, v)
52
+
53
+ row = np.zeros_like(coords3d)
54
+ # | m | n | o |
55
+ # -----------------------------------
56
+ # sign_factor(amo) | 1 | 0 | -1 | first_term
57
+ # sign_factor(ano) | 0 | 1 | -1 | second_term
58
+ first_term = uxw / u_norm
59
+ second_term = wxv / v_norm
60
+ row[m,:] = first_term
61
+ row[o,:] = -first_term - second_term
62
+ row[n,:] = second_term
63
+ row = row.flatten()
64
+ return angle_rad, row
65
+ return angle_rad
66
+
67
+ @staticmethod
68
+ def _jacobian(coords3d, indices):
69
+ return d2q_a(*coords3d[indices].flatten())
@@ -0,0 +1,25 @@
1
+ # See
2
+ # https://www.jwwalker.com/pages/angle-between-vectors.html
3
+
4
+ import numpy as np
5
+
6
+ from pysisyphus.intcoords.Bend import Bend
7
+ from pysisyphus.intcoords.derivatives import q_a2, dq_a2, d2q_a2
8
+
9
+
10
+ class Bend2(Bend):
11
+
12
+ @staticmethod
13
+ def _calculate(coords3d, indices, gradient=False):
14
+ args = coords3d[indices].flatten()
15
+ val = q_a2(*args)
16
+ if gradient:
17
+ grad = dq_a2(*args).reshape(-1, 3)
18
+ row = np.zeros_like(coords3d)
19
+ row[indices] = grad
20
+ return val, row.flatten()
21
+ return val
22
+
23
+ @staticmethod
24
+ def _jacobian(coords3d, indices):
25
+ return d2q_a2(*coords3d[indices].flatten())
@@ -0,0 +1,32 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.intcoords.Primitive import Primitive
4
+ from pysisyphus.linalg import norm3
5
+
6
+
7
+ class BondedFragment(Primitive):
8
+ def __init__(self, indices, bond_indices, **kwargs):
9
+ from_frag, to_ = bond_indices
10
+ assert from_frag in indices
11
+ assert to_ not in indices
12
+ self.bond_indices = list(bond_indices)
13
+ kwargs["calc_kwargs"] = ("bond_indices",)
14
+ super().__init__(indices, **kwargs)
15
+
16
+ @staticmethod
17
+ def _weight(atoms, coords3d, indices, f_damping):
18
+ return 1
19
+
20
+ @staticmethod
21
+ def _calculate(coords3d, indices, gradient=False, bond_indices=None):
22
+ from_frag, to_ = bond_indices
23
+ bond = coords3d[to_] - coords3d[from_frag]
24
+ value = norm3(bond)
25
+
26
+ if gradient:
27
+ bond_normed = bond / value
28
+ row = np.zeros_like(coords3d)
29
+ row[indices, :] = -bond_normed
30
+ row = row.flatten()
31
+ return value, row
32
+ return value
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.intcoords.Primitive import Primitive
4
+
5
+
6
+ class Cartesian(Primitive):
7
+
8
+ def __init__(self, *args, **kwargs):
9
+ kwargs["calc_kwargs"] = ("cart_axis",)
10
+ super().__init__(*args, **kwargs)
11
+
12
+ @staticmethod
13
+ def _weight(atoms, coords3d, indices, f_damping):
14
+ return 1
15
+
16
+ @staticmethod
17
+ def _calculate(coords3d, indices, gradient=False, cart_axis=0):
18
+ m, = indices
19
+ value = coords3d[m, cart_axis]
20
+ if gradient:
21
+ row = np.zeros_like(coords3d)
22
+ row[m, cart_axis] = 1
23
+ row = row.flatten()
24
+ return value, row
25
+ return value
26
+
27
+ @staticmethod
28
+ def _jacobian(coords3d, indices, cart_axis):
29
+ return np.zeros(1)
30
+
31
+
32
+ class CartesianX(Cartesian):
33
+ cart_axis = 0
34
+
35
+
36
+ class CartesianY(Cartesian):
37
+ cart_axis = 1
38
+
39
+
40
+ class CartesianZ(Cartesian):
41
+ cart_axis = 2
@@ -0,0 +1,140 @@
1
+ from typing import List, Optional
2
+ import warnings
3
+
4
+ import numpy as np
5
+ import torch
6
+ from numpy.typing import NDArray
7
+
8
+ from pysisyphus.intcoords.Coords import CoordSys
9
+
10
+
11
+ class CartesianCoords(CoordSys):
12
+ def __init__(
13
+ self,
14
+ atoms,
15
+ coords3d: NDArray,
16
+ masses,
17
+ freeze_atoms=None,
18
+ *,
19
+ mass_weighted=False,
20
+ **kwargs,
21
+ ):
22
+ for key, val in kwargs.items():
23
+ # f-string don't seem to work when pytest reports the warnings after a test
24
+ # run, so we construct to warning before it is issued.
25
+ msg = (
26
+ # Also printing the value is too much
27
+ # f"Keyword '{key}': '{val}' is not supported by this coordinate system!"
28
+ f"Keyword '{key}' is not supported by this coordinate system!"
29
+ )
30
+ warnings.warn(msg)
31
+ self.atoms = atoms
32
+ self._coords3d = coords3d
33
+ self.atom_num = len(self.atoms)
34
+ self.masses = masses
35
+ if freeze_atoms is None:
36
+ freeze_atoms = list()
37
+ self.freeze_atoms = freeze_atoms
38
+ self.mass_weighted = mass_weighted
39
+
40
+ self.move_mask = np.full(self.atom_num, True, dtype=bool)
41
+ self.move_mask[self.freeze_atoms] = False
42
+ self.move_mask_rep = np.repeat(self.move_mask, 3)
43
+ self.zero_vec = np.zeros_like(coords3d)
44
+
45
+ @property
46
+ def masses(self) -> NDArray:
47
+ return self._masses
48
+
49
+ @masses.setter
50
+ def masses(self, masses: NDArray):
51
+ assert len(masses) == self.atom_num
52
+ masses = np.array(masses, dtype=float)
53
+ self._masses = masses
54
+ # Also precalculate masses' square roots for mass-weighting
55
+ self._masses_sqrt = np.sqrt(self.masses)
56
+ # and its inverse
57
+ self._inv_masses_rep_sqrt = 1 / np.repeat(self.masses_sqrt, 3)
58
+
59
+ @property
60
+ def masses_sqrt(self) -> NDArray:
61
+ return self._masses_sqrt
62
+
63
+ @property
64
+ def inv_masses_rep_sqrt(self) -> NDArray:
65
+ return self._inv_masses_rep_sqrt
66
+
67
+ @property
68
+ def coords(self) -> NDArray:
69
+ coords3d = self.coords3d.copy()
70
+ if self.mass_weighted:
71
+ coords3d *= self.masses_sqrt[:, None]
72
+ coords3d = coords3d[self.move_mask]
73
+ return coords3d.flatten()
74
+
75
+ def transform_forces(self, cart_forces: NDArray) -> NDArray:
76
+ forces = cart_forces.reshape(-1, 3)
77
+ if self.mass_weighted:
78
+ forces /= self.masses_sqrt[:, None]
79
+ forces = forces[self.move_mask]
80
+ return forces.flatten()
81
+
82
+ def transform_hessian(
83
+ self, cart_hessian: NDArray, int_gradient: Optional[NDArray] = None
84
+ ):
85
+ if isinstance(cart_hessian, torch.Tensor):
86
+ if self.mass_weighted:
87
+ inv_masses = torch.tensor(
88
+ self.inv_masses_rep_sqrt,
89
+ dtype=cart_hessian.dtype,
90
+ device=cart_hessian.device,
91
+ )
92
+ M_inv = torch.diag(inv_masses)
93
+ cart_hessian = M_inv @ cart_hessian @ M_inv
94
+ mask = torch.tensor(
95
+ self.move_mask_rep, dtype=torch.bool, device=cart_hessian.device
96
+ )
97
+ cart_hessian = cart_hessian[mask][:, mask]
98
+ return cart_hessian
99
+ if self.mass_weighted:
100
+ M_inv = np.diag(self.inv_masses_rep_sqrt)
101
+ cart_hessian = M_inv @ cart_hessian @ M_inv
102
+ cart_hessian = cart_hessian[self.move_mask_rep][:, self.move_mask_rep]
103
+ return cart_hessian
104
+
105
+ def transform_int_step(
106
+ self,
107
+ step: NDArray,
108
+ update_constraints: Optional[bool] = False,
109
+ pure: Optional[bool] = False,
110
+ ) -> NDArray:
111
+ if update_constraints:
112
+ raise Exception("update_constraints is currently ignored!")
113
+ full_step = self.zero_vec.copy()
114
+ full_step[self.move_mask] = step.reshape(-1, 3)
115
+ if self.mass_weighted:
116
+ full_step /= self.masses_sqrt[:, None]
117
+ if not pure:
118
+ self.coords3d += full_step
119
+ return full_step.flatten()
120
+
121
+ def project_hessian(self, hessian):
122
+ return hessian
123
+
124
+ @property
125
+ def coords3d(self) -> NDArray:
126
+ return self._coords3d
127
+
128
+ @coords3d.setter
129
+ def coords3d(self, coords3d: NDArray):
130
+ self._coords3d = coords3d.reshape(-1, 3)
131
+
132
+ @property
133
+ def typed_prims(self) -> List:
134
+ return list()
135
+
136
+
137
+ class MWCartesianCoords(CartesianCoords):
138
+ def __init__(self, *args, **kwargs):
139
+ kwargs["mass_weighted"] = True
140
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,56 @@
1
+ from abc import abstractmethod
2
+ from typing import List, Optional, Protocol
3
+
4
+ from numpy.typing import NDArray
5
+
6
+
7
+ class CoordSys(Protocol):
8
+ @abstractmethod
9
+ def transform_forces(self, cart_forces: NDArray) -> NDArray:
10
+ """Transform Cartesian forces to this coordinate system."""
11
+ pass
12
+
13
+ @abstractmethod
14
+ def transform_hessian(
15
+ self, cart_hessian: NDArray, int_gradient: Optional[NDArray]
16
+ ) -> NDArray:
17
+ """Transform Cartesian Hessian to this coordinate system."""
18
+ pass
19
+
20
+ @abstractmethod
21
+ def transform_int_step(
22
+ self, step: NDArray, update_constraints: bool = False, pure: bool = False
23
+ ) -> NDArray:
24
+ """Transform step in this coordinate system to Cartesians."""
25
+ pass
26
+
27
+ @abstractmethod
28
+ def project_hessian(self, hessian: NDArray) -> NDArray:
29
+ """Project Hessian in the current coordinate system."""
30
+ pass
31
+
32
+ @property
33
+ @abstractmethod
34
+ def coords(self) -> NDArray:
35
+ """Getter for coordinates in this coordinate system."""
36
+ pass
37
+
38
+ @property
39
+ @abstractmethod
40
+ def coords3d(self) -> NDArray:
41
+ """Getter for 3d Cartesian coordinates."""
42
+ pass
43
+
44
+ @coords3d.setter
45
+ @abstractmethod
46
+ def coords3d(self, coords3d: NDArray):
47
+ """Setter for 3d Cartesian coordinates."""
48
+ pass
49
+
50
+ @property
51
+ @abstractmethod
52
+ def typed_prims(self) -> List:
53
+ """List of (primitive) internal coordinates.
54
+
55
+ May be empty, e.g., when the coordinate system is Cartesian."""
56
+ pass
@@ -0,0 +1,197 @@
1
+ # [1] https://aip.scitation.org/doi/10.1063/1.471864
2
+ # The generation and use of delocalized internal coordinates in geometry
3
+ # optimization.
4
+ # Baker, 1996
5
+
6
+ import numpy as np
7
+ import torch
8
+
9
+ from pysisyphus.intcoords import RedundantCoords
10
+ from pysisyphus.linalg import gram_schmidt
11
+
12
+
13
+ class DLC(RedundantCoords):
14
+ def __init__(self, *args, full_set=True, **kwargs):
15
+ super().__init__(*args, **kwargs)
16
+
17
+ self.full_set = full_set
18
+ self.set_active_set()
19
+
20
+ @property
21
+ def U(self):
22
+ return self._U
23
+
24
+ @U.setter
25
+ def U(self, U):
26
+ self._U = U
27
+
28
+ @property
29
+ def constraints(self):
30
+ return self._constraints
31
+
32
+ @constraints.setter
33
+ def constraints(self, constraints):
34
+ self.U = self.U_unconstrained.copy()
35
+ constraints = np.array(constraints)
36
+ constraints.flags.writeable = False
37
+ # Constraint columns should be same length as columns of U.
38
+ assert constraints.shape[0] == self.U.shape[0]
39
+ self._constraints = constraints
40
+ U_constrained = self.get_constrained_U(self._constraints)
41
+
42
+ # # Replace the constraint-columns with zeros vectors, so the total
43
+ # # number of coords doesn't change. This also zeros any force components
44
+ # # that belong to the constraints.
45
+ # zero_arr = np.zeros_like(constraints)
46
+ # self.U = np.concatenate((zero_arr, U_constrained), axis=1)
47
+ U_constrained = np.concatenate((constraints, U_constrained), axis=1)
48
+ self.U = U_constrained
49
+
50
+ def reset_constraints(self):
51
+ self._constraints = tuple()
52
+ self.U = self.U_unconstrained
53
+
54
+ @property
55
+ def coords(self):
56
+ return self.U.T.dot(self.prim_coords)
57
+
58
+ @property
59
+ def B(self):
60
+ """Wilson B-Matrix in the non-redundant subspace."""
61
+ return self.U.T.dot(self.B_prim)
62
+
63
+ def project_hessian(self, H):
64
+ """As we already work in the non-redundant subspace we don't have
65
+ to project/shift the hessian as we do it for simple redundant
66
+ internal coordinates."""
67
+ return H
68
+
69
+ def transform_hessian(self, cart_hessian, int_gradient=None):
70
+ """Transform Cartesian Hessian to DLC."""
71
+ # Transform the DLC gradient to primitive coordinates
72
+ if isinstance(cart_hessian, torch.Tensor):
73
+ U = self._as_torch_like(self.U, cart_hessian)
74
+ if int_gradient is not None:
75
+ int_gradient = self._as_torch_like(int_gradient, cart_hessian)
76
+ prim_gradient = (U * int_gradient).sum(dim=1)
77
+ else:
78
+ prim_gradient = None
79
+ H = super().transform_hessian(cart_hessian, prim_gradient)
80
+ return U.T @ H @ U
81
+ if int_gradient is not None:
82
+ prim_gradient = (self.U * int_gradient).sum(axis=1)
83
+ else:
84
+ prim_gradient = None
85
+ H = super().transform_hessian(cart_hessian, prim_gradient)
86
+ return self.U.T.dot(H).dot(self.U)
87
+
88
+ def backtransform_hessian(self, *args, **kwargs):
89
+ raise Exception("Check if we can just use the parents method.")
90
+
91
+ def transform_int_step(self, step, *args, **kwargs):
92
+ """As the transformation is done in primitive internal coordinates
93
+ we convert the DLC back to primitive coordinates."""
94
+ prim_step = (step * self.U).sum(axis=1)
95
+ return super().transform_int_step(prim_step, *args, **kwargs)
96
+
97
+ def get_active_set(self, B, inv_thresh=None):
98
+ """See [5] between Eq. (7) and Eq. (8) for advice regarding
99
+ the threshold."""
100
+ if self.weighted:
101
+ weights = np.array(
102
+ [prim.weight(self.atoms, self.coords3d) for prim in self.primitives]
103
+ )
104
+ self.log(
105
+ "Weighting B-matrix:\n"
106
+ f"\tWeights: {np.array2string(weights, precision=4)}\n"
107
+ f"\tmax(weights)={weights.max():.4f}, "
108
+ f"min(weights)={weights.min():.4f}, ({len(weights)} primitives)"
109
+ )
110
+ B = np.diag(weights).dot(B)
111
+
112
+ G = B.dot(B.T)
113
+ eigvals, eigvectors = np.linalg.eigh(G)
114
+
115
+ if inv_thresh is None:
116
+ # The singular values of G=B B^T correspond to the square roots of the
117
+ # eigenvalues of G.
118
+ #
119
+ # S = sqrt(w)
120
+ # w = S**2
121
+ #
122
+ # To stay consistent with the SVD, we derive the eigenvalue threshold from
123
+ # the SVD threshold.
124
+ inv_thresh = self.svd_inv_thresh ** 2
125
+
126
+ if self.full_set:
127
+ use_inds = np.full_like(eigvals, False, dtype=bool)
128
+ dof = 3 * len(self.atoms) - 6
129
+ use_inds[-dof:] = True
130
+ else:
131
+ use_inds = np.abs(eigvals) > inv_thresh
132
+ use_eigvals = eigvals[use_inds]
133
+ min_eigval = use_eigvals.min()
134
+ self.log(
135
+ f"Diagonalizing G yielded {use_inds.sum()} DLCs. Smallest eigenvalue "
136
+ f"is {min_eigval:.4e}"
137
+ )
138
+ return eigvectors[:, use_inds]
139
+
140
+ def set_active_set(self):
141
+ self.U = self.get_active_set(self.B_prim)
142
+ # Keep a copy of the original active set, in case self.U gets
143
+ # modified by constraint application.
144
+ self.U_unconstrained = self.U.copy()
145
+ self.original_U_shape = self.U_unconstrained.shape
146
+ self._constraints = tuple()
147
+
148
+ def project_primitive_on_active_set(self, prim_ind):
149
+ prim_vec = np.zeros(self.U.shape[0])
150
+ prim_vec[prim_ind] = 1
151
+ c_proj = (np.einsum("i,ij->j", prim_vec, self.U) * self.U).sum(axis=1)
152
+ c_proj /= np.linalg.norm(c_proj)
153
+ return c_proj
154
+
155
+ def get_constrained_U(self, constraint_vecs):
156
+ # Constraints are organized in columns
157
+ constr_num = constraint_vecs.shape[1]
158
+ V = np.concatenate((constraint_vecs, self.U), axis=1)
159
+ orthonormalized = gram_schmidt(V.T).T
160
+ # During Gram-Schmidt a number of columns of U should have
161
+ # dropped out. They are replaced by the constraint_vecs, so
162
+ # the total shape of should not change.
163
+ assert orthonormalized.shape[1] == self.original_U_shape[1]
164
+ # Remove constraint vectors
165
+ # [1] states that we somehow have to keep the constraint vectors
166
+ # (or the corresponding unit vectors) for the iterative
167
+ # back-transformation. Right now I don't understand why we would
168
+ # have to do this ([1], p. 10 (200), right column).
169
+ U_proj = orthonormalized[:, constr_num:]
170
+ return U_proj
171
+
172
+ def freeze_primitives(self, typed_prims):
173
+ """Freeze primitive internal coordinates.
174
+
175
+ Parameters
176
+ ----------
177
+ typed_prims : iterable of typed primitives
178
+ Iterable containing typed_primitives, starting with a PrimType and
179
+ followed by atom indices.
180
+ """
181
+ prim_indices = [self.get_index_of_typed_prim(tp) for tp in typed_prims]
182
+ not_defined = [
183
+ tp for tp, prim_ind in zip(typed_prims, prim_indices) if prim_ind is None
184
+ ]
185
+ assert (
186
+ None not in prim_indices
187
+ ), f"Some primitive internals are not defined ({not_defined})!"
188
+ projected_primitives = np.array(
189
+ [self.project_primitive_on_active_set(pi) for pi in prim_indices]
190
+ ).T
191
+ self.constraints = projected_primitives
192
+
193
+
194
+ class HDLC(DLC):
195
+ def __init__(self, *args, **kwargs):
196
+ kwargs["hybrid"] = True
197
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,34 @@
1
+ from pysisyphus.intcoords.Primitive import Primitive
2
+ from pysisyphus.intcoords.Stretch import Stretch
3
+
4
+
5
+ class DistanceFunction(Primitive):
6
+ def __init__(self, indices, coeff=-1, **kwargs):
7
+ self.coeff = float(coeff)
8
+ kwargs["calc_kwargs"] = ("coeff",)
9
+ super().__init__(indices, **kwargs)
10
+
11
+ @staticmethod
12
+ def _weight(atoms, coords3d, indices, f_damping):
13
+ return 1
14
+
15
+ @staticmethod
16
+ def _calculate(coords3d, indices, gradient=False, coeff=-1):
17
+ """
18
+ m--n o--p
19
+ """
20
+ m, n, o, p = indices
21
+ val1, grad1 = Stretch._calculate(coords3d, (m, n), gradient=True)
22
+ val2, grad2 = Stretch._calculate(coords3d, (o, p), gradient=True)
23
+ val = val1 + coeff * val2
24
+ grad = grad1 + coeff * grad2
25
+ if gradient:
26
+ return val, grad
27
+ return val
28
+
29
+ @staticmethod
30
+ def _jacobian(coords3d, indices, coeff):
31
+ m, n, o, p = indices
32
+ jac1 = Stretch._jacobian(coords3d, [m, n])
33
+ jac2 = Stretch._jacobian(coords3d, [o, p])
34
+ return jac1 + coeff * jac2
@@ -0,0 +1,70 @@
1
+ import numpy as np
2
+
3
+ from pysisyphus.intcoords.Torsion import Torsion
4
+
5
+
6
+ class DummyImproper(Torsion):
7
+ def __init__(self, indices, *args, fix_inner=True, **kwargs):
8
+ self.fix_inner = fix_inner
9
+ kwargs["calc_kwargs"] = ("fix_inner",)
10
+ super().__init__(indices, *args, **kwargs)
11
+ self.log("DummyImproper is never checked for collinear atoms!")
12
+
13
+ @staticmethod
14
+ def _get_dummy_coords(coords3d, indices, r=1.889):
15
+ r"""
16
+ dummy -> D\
17
+ atomy | \
18
+ | \
19
+ A---B---C
20
+ """
21
+ a, b, c = indices
22
+ # Center
23
+ B = coords3d[b]
24
+ # Bond, pointing away from a to c
25
+ AC_ = coords3d[c] - coords3d[a]
26
+ AC = AC_ / np.linalg.norm(AC_)
27
+
28
+ w = DummyImproper._get_cross_vec(coords3d, indices).flatten()
29
+ dummy_vec = (np.eye(3) - np.outer(AC, AC)) @ w
30
+ dummy_coords = B + r * dummy_vec
31
+ return dummy_coords
32
+
33
+ @staticmethod
34
+ def get_coords3d_and_indices_ext(coords3d, indices):
35
+ dummy_coords = DummyImproper._get_dummy_coords(coords3d, indices)
36
+ dummy_ind = len(coords3d)
37
+ coords3d_ext = np.zeros((len(coords3d) + 1, 3))
38
+ coords3d_ext[:dummy_ind] = coords3d
39
+ coords3d_ext[dummy_ind] = dummy_coords
40
+ a, b, c = indices
41
+ indices_ext = a, b, dummy_ind, c
42
+ return coords3d_ext, indices_ext
43
+
44
+ @staticmethod
45
+ def _weight(*args, **kwargs):
46
+ return 1
47
+
48
+ @staticmethod
49
+ def _calculate(coords3d, indices, gradient=False, fix_inner=False):
50
+ coords3d_ext, indices_ext = DummyImproper.get_coords3d_and_indices_ext(
51
+ coords3d, indices
52
+ )
53
+
54
+ results = Torsion._calculate(coords3d_ext, indices_ext, gradient=gradient)
55
+ if gradient:
56
+ val, grad = results
57
+
58
+ # import pdb; pdb.set_trace() # fmt: skip
59
+ # Remove entries that belong to the dummy atom.
60
+ grad = grad[:-3] # .reshape[](-1, 3)[[0, 1, 3]]
61
+
62
+ # Zero out contributions of the inner two atoms in the torsion.
63
+ # So basically only the atom at the first index moves.
64
+ #
65
+ # This usually degrades optimization convergence.
66
+ # if fix_inner:
67
+ # grad.reshape(-1, 3)[indices_ext[1:3]] = 0.0
68
+ return val, grad.flatten()
69
+ else:
70
+ return results