molSimplify 1.7.4__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 (651) hide show
  1. docs/source/conf.py +224 -0
  2. molSimplify/Classes/__init__.py +6 -0
  3. molSimplify/Classes/atom3D.py +235 -0
  4. molSimplify/Classes/dft_obs.py +130 -0
  5. molSimplify/Classes/globalvars.py +827 -0
  6. molSimplify/Classes/helpers.py +161 -0
  7. molSimplify/Classes/ligand.py +2330 -0
  8. molSimplify/Classes/mGUI.py +2493 -0
  9. molSimplify/Classes/mWidgets.py +438 -0
  10. molSimplify/Classes/miniGUI.py +41 -0
  11. molSimplify/Classes/mol2D.py +260 -0
  12. molSimplify/Classes/mol3D.py +5846 -0
  13. molSimplify/Classes/monomer3D.py +253 -0
  14. molSimplify/Classes/partialcharges.py +226 -0
  15. molSimplify/Classes/protein3D.py +1178 -0
  16. molSimplify/Classes/rundiag.py +151 -0
  17. molSimplify/Data/ML.dat +212 -0
  18. molSimplify/Data/MLS_FSR_for_inter.dat +23 -0
  19. molSimplify/Data/MLS_FSR_for_inter2.dat +23 -0
  20. molSimplify/Data/MLS_angle_for_click.dat +8 -0
  21. molSimplify/Data/MLS_angle_for_inter.dat +23 -0
  22. molSimplify/Data/MLS_angle_for_inter2.dat +48 -0
  23. molSimplify/Data/MLS_angle_for_intra.dat +10 -0
  24. molSimplify/Data/MLS_angle_for_intra2.dat +6 -0
  25. molSimplify/Data/MLS_angle_for_oa.dat +18 -0
  26. molSimplify/Data/ML_FSR_for_inter.dat +112 -0
  27. molSimplify/Data/ML_FSR_for_inter2.dat +110 -0
  28. molSimplify/Data/ML_bond_for_cat.dat +8 -0
  29. molSimplify/Data/ML_bond_for_click.dat +8 -0
  30. molSimplify/Data/ML_bond_for_inter.dat +48 -0
  31. molSimplify/Data/ML_bond_for_inter2.dat +48 -0
  32. molSimplify/Data/ML_bond_for_intra.dat +10 -0
  33. molSimplify/Data/ML_bond_for_intra2.dat +6 -0
  34. molSimplify/Data/ML_bond_for_oa.dat +18 -0
  35. molSimplify/Data/bp1.dat +21 -0
  36. molSimplify/Data/li.dat +3 -0
  37. molSimplify/Data/no.dat +2 -0
  38. molSimplify/Data/oct.dat +7 -0
  39. molSimplify/Data/pbp.dat +8 -0
  40. molSimplify/Data/spy.dat +6 -0
  41. molSimplify/Data/sqap.dat +9 -0
  42. molSimplify/Data/sqp.dat +5 -0
  43. molSimplify/Data/tbp.dat +6 -0
  44. molSimplify/Data/tdhd.dat +9 -0
  45. molSimplify/Data/thd.dat +5 -0
  46. molSimplify/Data/tpl.dat +4 -0
  47. molSimplify/Data/tpr.dat +7 -0
  48. molSimplify/Informatics/HFXsensitivity/__init__.py +0 -0
  49. molSimplify/Informatics/HFXsensitivity/measure_HFX_sensitivity_oxo_hat_reb_rel.py +443 -0
  50. molSimplify/Informatics/HFXsensitivity/measure_HFX_stable.py +346 -0
  51. molSimplify/Informatics/MOF/Linker_rotation.py +179 -0
  52. molSimplify/Informatics/MOF/MOF_descriptors.py +1299 -0
  53. molSimplify/Informatics/MOF/MOF_descriptors_alternate_functional.py +589 -0
  54. molSimplify/Informatics/MOF/MOF_functionalizer.py +1648 -0
  55. molSimplify/Informatics/MOF/PBC_functions.py +1347 -0
  56. molSimplify/Informatics/MOF/__init__.py +0 -0
  57. molSimplify/Informatics/MOF/atomic.py +267 -0
  58. molSimplify/Informatics/MOF/cluster_extraction.py +388 -0
  59. molSimplify/Informatics/MOF/fragment_MOFs_for_pormake.py +895 -0
  60. molSimplify/Informatics/MOF/monofunctionalized_BDC/index_information.py +10 -0
  61. molSimplify/Informatics/Mol2Parser.py +46 -0
  62. molSimplify/Informatics/RACassemble.py +408 -0
  63. molSimplify/Informatics/__init__.py +0 -0
  64. molSimplify/Informatics/active_learning/__init__.py +0 -0
  65. molSimplify/Informatics/active_learning/expected_improvement.py +269 -0
  66. molSimplify/Informatics/autocorrelation.py +1930 -0
  67. molSimplify/Informatics/clean_autocorrelation.py +778 -0
  68. molSimplify/Informatics/coulomb_analyze.py +67 -0
  69. molSimplify/Informatics/decoration_manager.py +193 -0
  70. molSimplify/Informatics/geo_analyze.py +88 -0
  71. molSimplify/Informatics/geometrics.py +56 -0
  72. molSimplify/Informatics/graph_analyze.py +163 -0
  73. molSimplify/Informatics/graph_racs.py +288 -0
  74. molSimplify/Informatics/jupyter_vis.py +172 -0
  75. molSimplify/Informatics/lacRACAssemble.py +2192 -0
  76. molSimplify/Informatics/lacRACAssemble_bisdithiolenes.py +236 -0
  77. molSimplify/Informatics/misc_descriptors.py +198 -0
  78. molSimplify/Informatics/organic_fingerprints.py +61 -0
  79. molSimplify/Informatics/partialcharges.py +345 -0
  80. molSimplify/Informatics/protein/activesite.py +53 -0
  81. molSimplify/Informatics/protein/pymol_add_hs.py +33 -0
  82. molSimplify/Informatics/rac155_geo.py +48 -0
  83. molSimplify/Ligands/(1_methylbenzimidazol_2_yl)pyridine.xyz +45 -0
  84. molSimplify/Ligands/1-4-dimethyl-1-2-3-triazole.xyz +15 -0
  85. molSimplify/Ligands/12crown4.mol +62 -0
  86. molSimplify/Ligands/Antipyrine.mol +58 -0
  87. molSimplify/Ligands/BPAbipy.mol +106 -0
  88. molSimplify/Ligands/Hpyrrole.mol +26 -0
  89. molSimplify/Ligands/N-quinolinylbutyramidate.xyz +31 -0
  90. molSimplify/Ligands/N-quinolinylmethylmethinylacetamidate.xyz +30 -0
  91. molSimplify/Ligands/NMe2_-1.xyz +11 -0
  92. molSimplify/Ligands/PCy3.mol +111 -0
  93. molSimplify/Ligands/PMe3.xyz +15 -0
  94. molSimplify/Ligands/PPh3.mol +76 -0
  95. molSimplify/Ligands/Propyphenazone.mol +77 -0
  96. molSimplify/Ligands/acac.mol +33 -0
  97. molSimplify/Ligands/acacen.mol +76 -0
  98. molSimplify/Ligands/acetate.smi +1 -0
  99. molSimplify/Ligands/acetate.xyz +9 -0
  100. molSimplify/Ligands/aceticacidbipyridine.mol +70 -0
  101. molSimplify/Ligands/acetonitrile.mol +17 -0
  102. molSimplify/Ligands/alanine.mol +30 -0
  103. molSimplify/Ligands/alphabetizer.py +21 -0
  104. molSimplify/Ligands/amine.mol +11 -0
  105. molSimplify/Ligands/ammonia.mol +12 -0
  106. molSimplify/Ligands/arginine.mol +58 -0
  107. molSimplify/Ligands/asparagine.mol +38 -0
  108. molSimplify/Ligands/aspartic_acid.mol +35 -0
  109. molSimplify/Ligands/azide.mol +11 -0
  110. molSimplify/Ligands/benzene.mol +28 -0
  111. molSimplify/Ligands/benzene_pi.mol +30 -0
  112. molSimplify/Ligands/benzenedithiol.mol +30 -0
  113. molSimplify/Ligands/benzenethiol.mol +30 -0
  114. molSimplify/Ligands/benzylisocy.mol +38 -0
  115. molSimplify/Ligands/bidiazine.mol +42 -0
  116. molSimplify/Ligands/bidiazole.mol +38 -0
  117. molSimplify/Ligands/bifuran.mol +38 -0
  118. molSimplify/Ligands/bihydrodiazine.mol +58 -0
  119. molSimplify/Ligands/bihydrodiazole.mol +46 -0
  120. molSimplify/Ligands/bihydrooxazine.mol +54 -0
  121. molSimplify/Ligands/bihydrooxazole.mol +42 -0
  122. molSimplify/Ligands/bihydrothiazine.mol +54 -0
  123. molSimplify/Ligands/bihydrothiazole.mol +42 -0
  124. molSimplify/Ligands/biimidazole.mol +38 -0
  125. molSimplify/Ligands/bioxazole.mol +34 -0
  126. molSimplify/Ligands/bipy.mol +46 -0
  127. molSimplify/Ligands/bipyrazine.xyz +20 -0
  128. molSimplify/Ligands/bipyrimidine.mol +42 -0
  129. molSimplify/Ligands/bipyrrole.mol +42 -0
  130. molSimplify/Ligands/bisnapthyridylpyridine.mol +111 -0
  131. molSimplify/Ligands/bithiazole.mol +34 -0
  132. molSimplify/Ligands/bromide.mol +7 -0
  133. molSimplify/Ligands/bromide.smi +1 -0
  134. molSimplify/Ligands/c2.mol +9 -0
  135. molSimplify/Ligands/caprolactone.mol +41 -0
  136. molSimplify/Ligands/carbonyl.mol +8 -0
  137. molSimplify/Ligands/carboxyl.mol +13 -0
  138. molSimplify/Ligands/cat.mol +30 -0
  139. molSimplify/Ligands/chloride.mol +7 -0
  140. molSimplify/Ligands/chloride.smi +1 -0
  141. molSimplify/Ligands/chloropyridine.mol +27 -0
  142. molSimplify/Ligands/co2.mol +10 -0
  143. molSimplify/Ligands/corrolazine.mol +72 -0
  144. molSimplify/Ligands/cs.mol +8 -0
  145. molSimplify/Ligands/cyanate.xyz +5 -0
  146. molSimplify/Ligands/cyanide.mol +9 -0
  147. molSimplify/Ligands/cyanoaceticporphyrin.mol +114 -0
  148. molSimplify/Ligands/cyanopyridine.mol +29 -0
  149. molSimplify/Ligands/cyclam.mol +81 -0
  150. molSimplify/Ligands/cyclen.mol +69 -0
  151. molSimplify/Ligands/cyclopentadienyl.mol +26 -0
  152. molSimplify/Ligands/cysteine.mol +32 -0
  153. molSimplify/Ligands/diaminomethyl.mol +19 -0
  154. molSimplify/Ligands/diazine.mol +25 -0
  155. molSimplify/Ligands/diazole.mol +23 -0
  156. molSimplify/Ligands/dicyanamide.mol +15 -0
  157. molSimplify/Ligands/dihydrofuran.mol +27 -0
  158. molSimplify/Ligands/dmap.xyz +35 -0
  159. molSimplify/Ligands/dmf.mol +28 -0
  160. molSimplify/Ligands/dmi.mol +41 -0
  161. molSimplify/Ligands/dmpe.mol +52 -0
  162. molSimplify/Ligands/dpmu.mol +47 -0
  163. molSimplify/Ligands/dppe.mol +112 -0
  164. molSimplify/Ligands/edta.mol +69 -0
  165. molSimplify/Ligands/en.mol +28 -0
  166. molSimplify/Ligands/ethanethiol.mol +21 -0
  167. molSimplify/Ligands/ethanolamine.mol +26 -0
  168. molSimplify/Ligands/ethbipy.mol +70 -0
  169. molSimplify/Ligands/ethyl.mol +19 -0
  170. molSimplify/Ligands/ethylamine.mol +24 -0
  171. molSimplify/Ligands/ethylene.mol +16 -0
  172. molSimplify/Ligands/ethylesteracac.mol +57 -0
  173. molSimplify/Ligands/fluoride.mol +7 -0
  174. molSimplify/Ligands/fluoride.smi +1 -0
  175. molSimplify/Ligands/formaldehyde.mol +12 -0
  176. molSimplify/Ligands/formamidate.xyz +8 -0
  177. molSimplify/Ligands/formate.xyz +6 -0
  178. molSimplify/Ligands/furan.mol +23 -0
  179. molSimplify/Ligands/glutamic_acid.mol +42 -0
  180. molSimplify/Ligands/glutamine.mol +44 -0
  181. molSimplify/Ligands/glycinate.mol +23 -0
  182. molSimplify/Ligands/glycine.mol +24 -0
  183. molSimplify/Ligands/h2s.mol +10 -0
  184. molSimplify/Ligands/helium.mol +6 -0
  185. molSimplify/Ligands/histidine.mol +45 -0
  186. molSimplify/Ligands/hmpa.mol +62 -0
  187. molSimplify/Ligands/hs-.mol +9 -0
  188. molSimplify/Ligands/hydride.mol +7 -0
  189. molSimplify/Ligands/hydrocarboxyacetylide.xyz +8 -0
  190. molSimplify/Ligands/hydrocyanide.mol +10 -0
  191. molSimplify/Ligands/hydrodiazine.mol +33 -0
  192. molSimplify/Ligands/hydrodiazole.mol +27 -0
  193. molSimplify/Ligands/hydrogensulfide.mol +10 -0
  194. molSimplify/Ligands/hydroisocyanide.mol +11 -0
  195. molSimplify/Ligands/hydrooxazine.mol +31 -0
  196. molSimplify/Ligands/hydrooxazole.mol +25 -0
  197. molSimplify/Ligands/hydrothiazine.mol +31 -0
  198. molSimplify/Ligands/hydrothiazole.mol +25 -0
  199. molSimplify/Ligands/hydroxyl.mol +9 -0
  200. molSimplify/Ligands/imidazole.mol +23 -0
  201. molSimplify/Ligands/imidazolidinone.mol +29 -0
  202. molSimplify/Ligands/imine.mol +13 -0
  203. molSimplify/Ligands/iminodiacetic.mol +33 -0
  204. molSimplify/Ligands/iodide.mol +7 -0
  205. molSimplify/Ligands/iodobenzene.xyz +14 -0
  206. molSimplify/Ligands/isoleucine.mol +48 -0
  207. molSimplify/Ligands/isothiocyanate.mol +11 -0
  208. molSimplify/Ligands/leucine.mol +48 -0
  209. molSimplify/Ligands/ligands.dict +257 -0
  210. molSimplify/Ligands/lysine.mol +54 -0
  211. molSimplify/Ligands/mebenzenedithiol.mol +36 -0
  212. molSimplify/Ligands/mebim_py.xyz +29 -0
  213. molSimplify/Ligands/mebim_pz.xyz +28 -0
  214. molSimplify/Ligands/mebipy.mol +58 -0
  215. molSimplify/Ligands/mecat.mol +36 -0
  216. molSimplify/Ligands/methanal.mol +11 -0
  217. molSimplify/Ligands/methanethiol.mol +15 -0
  218. molSimplify/Ligands/methanol.mol +16 -0
  219. molSimplify/Ligands/methionine.mol +44 -0
  220. molSimplify/Ligands/methyl.mol +13 -0
  221. molSimplify/Ligands/methylacetylide.xyz +8 -0
  222. molSimplify/Ligands/methylamine.mol +19 -0
  223. molSimplify/Ligands/methylazide.xyz +9 -0
  224. molSimplify/Ligands/methylisocy.mol +17 -0
  225. molSimplify/Ligands/methylpyridine.mol +33 -0
  226. molSimplify/Ligands/n2.mol +8 -0
  227. molSimplify/Ligands/n4py.xyz +51 -0
  228. molSimplify/Ligands/nch.mol +10 -0
  229. molSimplify/Ligands/nco-.mol +11 -0
  230. molSimplify/Ligands/nethanolamine.mol +26 -0
  231. molSimplify/Ligands/nitrate.mol +14 -0
  232. molSimplify/Ligands/nitrite.mol +11 -0
  233. molSimplify/Ligands/nitro.mol +11 -0
  234. molSimplify/Ligands/nitrobipy.mol +54 -0
  235. molSimplify/Ligands/nitroso.mol +8 -0
  236. molSimplify/Ligands/nme3.mol +30 -0
  237. molSimplify/Ligands/no-.mol +10 -0
  238. molSimplify/Ligands/no2-.mol +11 -0
  239. molSimplify/Ligands/noxygen.mol +8 -0
  240. molSimplify/Ligands/ns-.mol +10 -0
  241. molSimplify/Ligands/o-pyridylbenzene.xyz +23 -0
  242. molSimplify/Ligands/o-pyridylphenylanion.xyz +22 -0
  243. molSimplify/Ligands/o2-.mol +9 -0
  244. molSimplify/Ligands/o2.xyz +4 -0
  245. molSimplify/Ligands/och2.mol +12 -0
  246. molSimplify/Ligands/oethanolamine.mol +26 -0
  247. molSimplify/Ligands/ome2.mol +22 -0
  248. molSimplify/Ligands/ooh.xyz +5 -0
  249. molSimplify/Ligands/oxalate.mol +17 -0
  250. molSimplify/Ligands/oxalate.smi +1 -0
  251. molSimplify/Ligands/oxygen.mol +7 -0
  252. molSimplify/Ligands/pentacyanocyclopentadienide.mol +36 -0
  253. molSimplify/Ligands/ph2-.mol +11 -0
  254. molSimplify/Ligands/ph3.mol +12 -0
  255. molSimplify/Ligands/phen.mol +51 -0
  256. molSimplify/Ligands/phenacac.mol +63 -0
  257. molSimplify/Ligands/phenalalanine.mol +51 -0
  258. molSimplify/Ligands/phendione.mol +51 -0
  259. molSimplify/Ligands/phenphen.mol +75 -0
  260. molSimplify/Ligands/phenylbenzoxazole.mol +54 -0
  261. molSimplify/Ligands/phenylcyc.mol +99 -0
  262. molSimplify/Ligands/phenylenediamine.mol +37 -0
  263. molSimplify/Ligands/phenylisocy.mol +32 -0
  264. molSimplify/Ligands/phosacidbipy.mol +66 -0
  265. molSimplify/Ligands/phosphine.mol +13 -0
  266. molSimplify/Ligands/phosphorine.mol +27 -0
  267. molSimplify/Ligands/phosphorustrifluoride.mol +12 -0
  268. molSimplify/Ligands/phthalocyanine.mol +126 -0
  269. molSimplify/Ligands/pme3o.mol +32 -0
  270. molSimplify/Ligands/porphyrin.mol +82 -0
  271. molSimplify/Ligands/pph3o.mol +77 -0
  272. molSimplify/Ligands/proline.mol +39 -0
  273. molSimplify/Ligands/propdiol.mol +21 -0
  274. molSimplify/Ligands/propylene.mol +23 -0
  275. molSimplify/Ligands/pyridine.mol +27 -0
  276. molSimplify/Ligands/pyrimidone.mol +27 -0
  277. molSimplify/Ligands/pyrrole.mol +24 -0
  278. molSimplify/Ligands/quinoxalinedithiol.mol +39 -0
  279. molSimplify/Ligands/s2-.mol +9 -0
  280. molSimplify/Ligands/salen.mol +75 -0
  281. molSimplify/Ligands/salphen.mol +84 -0
  282. molSimplify/Ligands/serine.mol +32 -0
  283. molSimplify/Ligands/simple_ligands.dict +14 -0
  284. molSimplify/Ligands/sulfacidbipy.mol +63 -0
  285. molSimplify/Ligands/tbucat.mol +54 -0
  286. molSimplify/Ligands/tbuphisocy.mol +56 -0
  287. molSimplify/Ligands/tbutylcyclen.mol +166 -0
  288. molSimplify/Ligands/tbutylisocy.mol +35 -0
  289. molSimplify/Ligands/tbutylthiol.mol +33 -0
  290. molSimplify/Ligands/tcnoet.mol +43 -0
  291. molSimplify/Ligands/tcnoetOH.mol +45 -0
  292. molSimplify/Ligands/terpy.mol +65 -0
  293. molSimplify/Ligands/tetrahydrofuran.mol +31 -0
  294. molSimplify/Ligands/thiane.mol +37 -0
  295. molSimplify/Ligands/thiazole.mol +21 -0
  296. molSimplify/Ligands/thiocyanate.mol +11 -0
  297. molSimplify/Ligands/thiol.mol +9 -0
  298. molSimplify/Ligands/thiophene.mol +23 -0
  299. molSimplify/Ligands/thiopyridine.mol +29 -0
  300. molSimplify/Ligands/threonine.mol +38 -0
  301. molSimplify/Ligands/tpp.mol +165 -0
  302. molSimplify/Ligands/tricyanomethyl.mol +19 -0
  303. molSimplify/Ligands/trifluoromethyl.mol +13 -0
  304. molSimplify/Ligands/tryptophan.mol +60 -0
  305. molSimplify/Ligands/tyrosine.mol +53 -0
  306. molSimplify/Ligands/uthiol.mol +11 -0
  307. molSimplify/Ligands/uthiolme2.mol +23 -0
  308. molSimplify/Ligands/valine.mol +42 -0
  309. molSimplify/Ligands/water.mol +10 -0
  310. molSimplify/Ligands/x.mol +6 -0
  311. molSimplify/Scripts/__init__.py +0 -0
  312. molSimplify/Scripts/addtodb.py +308 -0
  313. molSimplify/Scripts/cellbuilder.py +1592 -0
  314. molSimplify/Scripts/cellbuilder_tools.py +701 -0
  315. molSimplify/Scripts/chains.py +342 -0
  316. molSimplify/Scripts/convert_2to3.py +23 -0
  317. molSimplify/Scripts/dbinteract.py +631 -0
  318. molSimplify/Scripts/distgeom.py +617 -0
  319. molSimplify/Scripts/findcorrelations.py +287 -0
  320. molSimplify/Scripts/generator.py +267 -0
  321. molSimplify/Scripts/geometry.py +1224 -0
  322. molSimplify/Scripts/grabguivars.py +845 -0
  323. molSimplify/Scripts/in_b3lyp_usetc.py +141 -0
  324. molSimplify/Scripts/inparse.py +1673 -0
  325. molSimplify/Scripts/io.py +1149 -0
  326. molSimplify/Scripts/isomers.py +415 -0
  327. molSimplify/Scripts/jobgen.py +247 -0
  328. molSimplify/Scripts/krr_prep.py +1262 -0
  329. molSimplify/Scripts/molSimplify_io.py +18 -0
  330. molSimplify/Scripts/molden2psi4wfn.py +166 -0
  331. molSimplify/Scripts/namegen.py +32 -0
  332. molSimplify/Scripts/nn_prep.py +561 -0
  333. molSimplify/Scripts/oct_check_mols.py +782 -0
  334. molSimplify/Scripts/periodic_QE.py +97 -0
  335. molSimplify/Scripts/postmold.py +304 -0
  336. molSimplify/Scripts/postmwfn.py +709 -0
  337. molSimplify/Scripts/postparse.py +488 -0
  338. molSimplify/Scripts/postproc.py +139 -0
  339. molSimplify/Scripts/qcgen.py +1450 -0
  340. molSimplify/Scripts/rmsd.py +489 -0
  341. molSimplify/Scripts/rungen.py +670 -0
  342. molSimplify/Scripts/structgen.py +3040 -0
  343. molSimplify/Scripts/tf_nn_prep.py +894 -0
  344. molSimplify/Scripts/tsgen.py +295 -0
  345. molSimplify/Scripts/uq_calibration.py +69 -0
  346. molSimplify/__init__.py +0 -0
  347. molSimplify/__main__.py +197 -0
  348. molSimplify/icons/chemdb.png +0 -0
  349. molSimplify/icons/hjklogo.png +0 -0
  350. molSimplify/icons/icon.png +0 -0
  351. molSimplify/icons/logo.png +0 -0
  352. molSimplify/icons/logo_old.png +0 -0
  353. molSimplify/icons/petachem.png +0 -0
  354. molSimplify/icons/petachem2.png +0 -0
  355. molSimplify/icons/petachem_full.png +0 -0
  356. molSimplify/icons/pythonlogo.png +0 -0
  357. molSimplify/icons/sge copy.png +0 -0
  358. molSimplify/icons/sge.png +0 -0
  359. molSimplify/icons/slurm.png +0 -0
  360. molSimplify/icons/wft1.png +0 -0
  361. molSimplify/icons/wft2.png +0 -0
  362. molSimplify/icons/wft3.png +0 -0
  363. molSimplify/ml/__init__.py +0 -0
  364. molSimplify/ml/kernels.py +36 -0
  365. molSimplify/ml/layers.py +29 -0
  366. molSimplify/molscontrol/__init__.py +14 -0
  367. molSimplify/molscontrol/_version.py +521 -0
  368. molSimplify/molscontrol/clf_tools.py +144 -0
  369. molSimplify/molscontrol/data/README.md +21 -0
  370. molSimplify/molscontrol/data/look_and_say.dat +15 -0
  371. molSimplify/molscontrol/dynamic_classifier.py +514 -0
  372. molSimplify/molscontrol/io_tools.py +363 -0
  373. molSimplify/molscontrol/molscontrol.py +49 -0
  374. molSimplify/molscontrol/terachem/jobscript_control.sh +31 -0
  375. molSimplify/molscontrol/terachem/terachem_input +22 -0
  376. molSimplify/python_krr/X_train_TS.csv +535 -0
  377. molSimplify/python_krr/__init__.py +0 -0
  378. molSimplify/python_krr/hat2_X_mean_std.csv +3 -0
  379. molSimplify/python_krr/hat2_feature_names.csv +1 -0
  380. molSimplify/python_krr/hat2_y_mean_std.csv +2 -0
  381. molSimplify/python_krr/hat_X_mean_std.csv +6 -0
  382. molSimplify/python_krr/hat_feature_names.csv +1 -0
  383. molSimplify/python_krr/hat_krr_X_train.csv +5205 -0
  384. molSimplify/python_krr/hat_krr_dual_coef.csv +1 -0
  385. molSimplify/python_krr/hat_y_mean_std.csv +2 -0
  386. molSimplify/python_krr/sklearn_models.py +34 -0
  387. molSimplify/python_krr/y_train_TS.csv +535 -0
  388. molSimplify/python_nn/ANN.py +198 -0
  389. molSimplify/python_nn/__init__.py +0 -0
  390. molSimplify/python_nn/clf_analysis_tool.py +125 -0
  391. molSimplify/python_nn/dictionary_toolbox.py +49 -0
  392. molSimplify/python_nn/ensemble_test.py +309 -0
  393. molSimplify/python_nn/hs_center.csv +26 -0
  394. molSimplify/python_nn/hs_scale.csv +26 -0
  395. molSimplify/python_nn/ls_center.csv +26 -0
  396. molSimplify/python_nn/ls_scale.csv +26 -0
  397. molSimplify/python_nn/ms_hs_b1.csv +50 -0
  398. molSimplify/python_nn/ms_hs_b2.csv +50 -0
  399. molSimplify/python_nn/ms_hs_b3.csv +1 -0
  400. molSimplify/python_nn/ms_hs_w1.csv +50 -0
  401. molSimplify/python_nn/ms_hs_w2.csv +50 -0
  402. molSimplify/python_nn/ms_hs_w3.csv +1 -0
  403. molSimplify/python_nn/ms_ls_b1.csv +50 -0
  404. molSimplify/python_nn/ms_ls_b2.csv +50 -0
  405. molSimplify/python_nn/ms_ls_b3.csv +1 -0
  406. molSimplify/python_nn/ms_ls_w1.csv +50 -0
  407. molSimplify/python_nn/ms_ls_w2.csv +50 -0
  408. molSimplify/python_nn/ms_ls_w3.csv +1 -0
  409. molSimplify/python_nn/ms_slope_b1.csv +50 -0
  410. molSimplify/python_nn/ms_slope_b2.csv +50 -0
  411. molSimplify/python_nn/ms_slope_b3.csv +1 -0
  412. molSimplify/python_nn/ms_slope_w1.csv +50 -0
  413. molSimplify/python_nn/ms_slope_w2.csv +50 -0
  414. molSimplify/python_nn/ms_slope_w3.csv +1 -0
  415. molSimplify/python_nn/ms_split_b1.csv +50 -0
  416. molSimplify/python_nn/ms_split_b2.csv +50 -0
  417. molSimplify/python_nn/ms_split_b3.csv +1 -0
  418. molSimplify/python_nn/ms_split_w1.csv +50 -0
  419. molSimplify/python_nn/ms_split_w2.csv +50 -0
  420. molSimplify/python_nn/ms_split_w3.csv +1 -0
  421. molSimplify/python_nn/slope_center.csv +25 -0
  422. molSimplify/python_nn/slope_scale.csv +25 -0
  423. molSimplify/python_nn/split_center.csv +26 -0
  424. molSimplify/python_nn/split_scale.csv +26 -0
  425. molSimplify/python_nn/tf_ANN.py +762 -0
  426. molSimplify/python_nn/train_data.csv +1211 -0
  427. molSimplify/tf_nn/__init__.py +0 -0
  428. molSimplify/tf_nn/geo_static_clf/geo_static_clf_model.h5 +0 -0
  429. molSimplify/tf_nn/geo_static_clf/geo_static_clf_train_name.csv +1591 -0
  430. molSimplify/tf_nn/geo_static_clf/geo_static_clf_train_x.csv +2790 -0
  431. molSimplify/tf_nn/geo_static_clf/geo_static_clf_train_y.csv +2790 -0
  432. molSimplify/tf_nn/geo_static_clf/geo_static_clf_vars.csv +154 -0
  433. molSimplify/tf_nn/geos/hs_ii_bl_x.csv +1577 -0
  434. molSimplify/tf_nn/geos/hs_ii_bl_y.csv +1577 -0
  435. molSimplify/tf_nn/geos/hs_ii_model.h5 +0 -0
  436. molSimplify/tf_nn/geos/hs_ii_model.json +1 -0
  437. molSimplify/tf_nn/geos/hs_ii_vars.csv +154 -0
  438. molSimplify/tf_nn/geos/hs_iii_bl_x.csv +1659 -0
  439. molSimplify/tf_nn/geos/hs_iii_bl_y.csv +1659 -0
  440. molSimplify/tf_nn/geos/hs_iii_model.h5 +0 -0
  441. molSimplify/tf_nn/geos/hs_iii_model.json +1 -0
  442. molSimplify/tf_nn/geos/hs_iii_vars.csv +154 -0
  443. molSimplify/tf_nn/geos/ls_ii_bl_x.csv +1374 -0
  444. molSimplify/tf_nn/geos/ls_ii_bl_y.csv +1374 -0
  445. molSimplify/tf_nn/geos/ls_ii_model.h5 +0 -0
  446. molSimplify/tf_nn/geos/ls_ii_model.json +1 -0
  447. molSimplify/tf_nn/geos/ls_ii_vars.csv +154 -0
  448. molSimplify/tf_nn/geos/ls_iii_bl_x.csv +1364 -0
  449. molSimplify/tf_nn/geos/ls_iii_bl_y.csv +1364 -0
  450. molSimplify/tf_nn/geos/ls_iii_model.h5 +0 -0
  451. molSimplify/tf_nn/geos/ls_iii_model.json +1 -0
  452. molSimplify/tf_nn/geos/ls_iii_vars.csv +154 -0
  453. molSimplify/tf_nn/homolumo/gap_model.h5 +0 -0
  454. molSimplify/tf_nn/homolumo/gap_model.json +1 -0
  455. molSimplify/tf_nn/homolumo/gap_test_names.csv +175 -0
  456. molSimplify/tf_nn/homolumo/gap_test_x.csv +176 -0
  457. molSimplify/tf_nn/homolumo/gap_test_y.csv +176 -0
  458. molSimplify/tf_nn/homolumo/gap_train_names.csv +699 -0
  459. molSimplify/tf_nn/homolumo/gap_train_x.csv +700 -0
  460. molSimplify/tf_nn/homolumo/gap_train_y.csv +700 -0
  461. molSimplify/tf_nn/homolumo/gap_vars.csv +153 -0
  462. molSimplify/tf_nn/homolumo/homo_model.h5 +0 -0
  463. molSimplify/tf_nn/homolumo/homo_model.json +126 -0
  464. molSimplify/tf_nn/homolumo/homo_test_names.csv +175 -0
  465. molSimplify/tf_nn/homolumo/homo_test_x.csv +176 -0
  466. molSimplify/tf_nn/homolumo/homo_test_y.csv +176 -0
  467. molSimplify/tf_nn/homolumo/homo_train_names.csv +699 -0
  468. molSimplify/tf_nn/homolumo/homo_train_x.csv +700 -0
  469. molSimplify/tf_nn/homolumo/homo_train_y.csv +700 -0
  470. molSimplify/tf_nn/homolumo/homo_vars.csv +153 -0
  471. molSimplify/tf_nn/oxoandhomo/homo_empty_info.json +7 -0
  472. molSimplify/tf_nn/oxoandhomo/homo_empty_model.h5 +0 -0
  473. molSimplify/tf_nn/oxoandhomo/homo_empty_model.json +1 -0
  474. molSimplify/tf_nn/oxoandhomo/homo_empty_test_names.csv +143 -0
  475. molSimplify/tf_nn/oxoandhomo/homo_empty_test_x.csv +144 -0
  476. molSimplify/tf_nn/oxoandhomo/homo_empty_test_y.csv +144 -0
  477. molSimplify/tf_nn/oxoandhomo/homo_empty_train_names.csv +513 -0
  478. molSimplify/tf_nn/oxoandhomo/homo_empty_train_x.csv +514 -0
  479. molSimplify/tf_nn/oxoandhomo/homo_empty_train_y.csv +514 -0
  480. molSimplify/tf_nn/oxoandhomo/homo_empty_val_names.csv +143 -0
  481. molSimplify/tf_nn/oxoandhomo/homo_empty_val_x.csv +58 -0
  482. molSimplify/tf_nn/oxoandhomo/homo_empty_val_y.csv +58 -0
  483. molSimplify/tf_nn/oxoandhomo/homo_empty_vars.csv +155 -0
  484. molSimplify/tf_nn/oxoandhomo/oxo20_info.json +7 -0
  485. molSimplify/tf_nn/oxoandhomo/oxo20_model.h5 +0 -0
  486. molSimplify/tf_nn/oxoandhomo/oxo20_model.json +1 -0
  487. molSimplify/tf_nn/oxoandhomo/oxo20_test_names.csv +143 -0
  488. molSimplify/tf_nn/oxoandhomo/oxo20_test_x.csv +144 -0
  489. molSimplify/tf_nn/oxoandhomo/oxo20_test_y.csv +144 -0
  490. molSimplify/tf_nn/oxoandhomo/oxo20_train_names.csv +513 -0
  491. molSimplify/tf_nn/oxoandhomo/oxo20_train_x.csv +514 -0
  492. molSimplify/tf_nn/oxoandhomo/oxo20_train_y.csv +514 -0
  493. molSimplify/tf_nn/oxoandhomo/oxo20_val_names.csv +143 -0
  494. molSimplify/tf_nn/oxoandhomo/oxo20_val_x.csv +58 -0
  495. molSimplify/tf_nn/oxoandhomo/oxo20_val_y.csv +58 -0
  496. molSimplify/tf_nn/oxoandhomo/oxo20_vars.csv +154 -0
  497. molSimplify/tf_nn/oxocatalysis/hat_model.h5 +0 -0
  498. molSimplify/tf_nn/oxocatalysis/hat_model.json +1 -0
  499. molSimplify/tf_nn/oxocatalysis/hat_test_names.csv +419 -0
  500. molSimplify/tf_nn/oxocatalysis/hat_test_x.csv +420 -0
  501. molSimplify/tf_nn/oxocatalysis/hat_test_y.csv +420 -0
  502. molSimplify/tf_nn/oxocatalysis/hat_train_names.csv +1507 -0
  503. molSimplify/tf_nn/oxocatalysis/hat_train_x.csv +1508 -0
  504. molSimplify/tf_nn/oxocatalysis/hat_train_y.csv +1508 -0
  505. molSimplify/tf_nn/oxocatalysis/hat_val_x.csv +169 -0
  506. molSimplify/tf_nn/oxocatalysis/hat_val_y.csv +169 -0
  507. molSimplify/tf_nn/oxocatalysis/hat_vars.csv +162 -0
  508. molSimplify/tf_nn/oxocatalysis/oxo_model.h5 +0 -0
  509. molSimplify/tf_nn/oxocatalysis/oxo_model.json +1 -0
  510. molSimplify/tf_nn/oxocatalysis/oxo_test_names.csv +527 -0
  511. molSimplify/tf_nn/oxocatalysis/oxo_test_x.csv +528 -0
  512. molSimplify/tf_nn/oxocatalysis/oxo_test_y.csv +528 -0
  513. molSimplify/tf_nn/oxocatalysis/oxo_train_names.csv +1897 -0
  514. molSimplify/tf_nn/oxocatalysis/oxo_train_x.csv +1898 -0
  515. molSimplify/tf_nn/oxocatalysis/oxo_train_y.csv +1898 -0
  516. molSimplify/tf_nn/oxocatalysis/oxo_val_x.csv +212 -0
  517. molSimplify/tf_nn/oxocatalysis/oxo_val_y.csv +212 -0
  518. molSimplify/tf_nn/oxocatalysis/oxo_vars.csv +162 -0
  519. molSimplify/tf_nn/rescaling_data/gap_mean_x.csv +153 -0
  520. molSimplify/tf_nn/rescaling_data/gap_mean_y.csv +1 -0
  521. molSimplify/tf_nn/rescaling_data/gap_var_x.csv +153 -0
  522. molSimplify/tf_nn/rescaling_data/gap_var_y.csv +1 -0
  523. molSimplify/tf_nn/rescaling_data/geo_static_clf_mean_x.csv +154 -0
  524. molSimplify/tf_nn/rescaling_data/geo_static_clf_mean_y.csv +1 -0
  525. molSimplify/tf_nn/rescaling_data/geo_static_clf_var_x.csv +154 -0
  526. molSimplify/tf_nn/rescaling_data/geo_static_clf_var_y.csv +1 -0
  527. molSimplify/tf_nn/rescaling_data/hat_mean_x.csv +162 -0
  528. molSimplify/tf_nn/rescaling_data/hat_mean_y.csv +1 -0
  529. molSimplify/tf_nn/rescaling_data/hat_var_x.csv +162 -0
  530. molSimplify/tf_nn/rescaling_data/hat_var_y.csv +1 -0
  531. molSimplify/tf_nn/rescaling_data/homo_empty_mean_x.csv +155 -0
  532. molSimplify/tf_nn/rescaling_data/homo_empty_mean_y.csv +1 -0
  533. molSimplify/tf_nn/rescaling_data/homo_empty_var_x.csv +155 -0
  534. molSimplify/tf_nn/rescaling_data/homo_empty_var_y.csv +1 -0
  535. molSimplify/tf_nn/rescaling_data/homo_mean_x.csv +153 -0
  536. molSimplify/tf_nn/rescaling_data/homo_mean_y.csv +1 -0
  537. molSimplify/tf_nn/rescaling_data/homo_var_x.csv +153 -0
  538. molSimplify/tf_nn/rescaling_data/homo_var_y.csv +1 -0
  539. molSimplify/tf_nn/rescaling_data/hs_ii_mean_x.csv +154 -0
  540. molSimplify/tf_nn/rescaling_data/hs_ii_mean_y.csv +3 -0
  541. molSimplify/tf_nn/rescaling_data/hs_ii_var_x.csv +154 -0
  542. molSimplify/tf_nn/rescaling_data/hs_ii_var_y.csv +3 -0
  543. molSimplify/tf_nn/rescaling_data/hs_iii_mean_x.csv +154 -0
  544. molSimplify/tf_nn/rescaling_data/hs_iii_mean_y.csv +3 -0
  545. molSimplify/tf_nn/rescaling_data/hs_iii_var_x.csv +154 -0
  546. molSimplify/tf_nn/rescaling_data/hs_iii_var_y.csv +3 -0
  547. molSimplify/tf_nn/rescaling_data/ls_ii_mean_x.csv +154 -0
  548. molSimplify/tf_nn/rescaling_data/ls_ii_mean_y.csv +3 -0
  549. molSimplify/tf_nn/rescaling_data/ls_ii_var_x.csv +154 -0
  550. molSimplify/tf_nn/rescaling_data/ls_ii_var_y.csv +3 -0
  551. molSimplify/tf_nn/rescaling_data/ls_iii_mean_x.csv +154 -0
  552. molSimplify/tf_nn/rescaling_data/ls_iii_mean_y.csv +3 -0
  553. molSimplify/tf_nn/rescaling_data/ls_iii_var_x.csv +154 -0
  554. molSimplify/tf_nn/rescaling_data/ls_iii_var_y.csv +3 -0
  555. molSimplify/tf_nn/rescaling_data/oxo20_mean_x.csv +154 -0
  556. molSimplify/tf_nn/rescaling_data/oxo20_mean_y.csv +1 -0
  557. molSimplify/tf_nn/rescaling_data/oxo20_var_x.csv +154 -0
  558. molSimplify/tf_nn/rescaling_data/oxo20_var_y.csv +1 -0
  559. molSimplify/tf_nn/rescaling_data/oxo_mean_x.csv +162 -0
  560. molSimplify/tf_nn/rescaling_data/oxo_mean_y.csv +1 -0
  561. molSimplify/tf_nn/rescaling_data/oxo_var_x.csv +162 -0
  562. molSimplify/tf_nn/rescaling_data/oxo_var_y.csv +1 -0
  563. molSimplify/tf_nn/rescaling_data/sc_static_clf_mean_x.csv +154 -0
  564. molSimplify/tf_nn/rescaling_data/sc_static_clf_mean_y.csv +1 -0
  565. molSimplify/tf_nn/rescaling_data/sc_static_clf_var_x.csv +154 -0
  566. molSimplify/tf_nn/rescaling_data/sc_static_clf_var_y.csv +1 -0
  567. molSimplify/tf_nn/rescaling_data/split_mean_x.csv +155 -0
  568. molSimplify/tf_nn/rescaling_data/split_mean_y.csv +1 -0
  569. molSimplify/tf_nn/rescaling_data/split_var_x.csv +155 -0
  570. molSimplify/tf_nn/rescaling_data/split_var_y.csv +1 -0
  571. molSimplify/tf_nn/sc_static_clf/sc_static_clf_model.h5 +0 -0
  572. molSimplify/tf_nn/sc_static_clf/sc_static_clf_train_name.csv +1591 -0
  573. molSimplify/tf_nn/sc_static_clf/sc_static_clf_train_x.csv +1592 -0
  574. molSimplify/tf_nn/sc_static_clf/sc_static_clf_train_y.csv +1592 -0
  575. molSimplify/tf_nn/sc_static_clf/sc_static_clf_vars.csv +154 -0
  576. molSimplify/tf_nn/split/split_model.h5 +0 -0
  577. molSimplify/tf_nn/split/split_model.json +1 -0
  578. molSimplify/tf_nn/split/split_vars.csv +155 -0
  579. molSimplify/tf_nn/split/split_x.csv +1902 -0
  580. molSimplify/tf_nn/split/split_y.csv +1902 -0
  581. molSimplify/tf_nn/split/train_names.csv +1901 -0
  582. molSimplify/utils/__init__.py +0 -0
  583. molSimplify/utils/decorators.py +16 -0
  584. molSimplify/utils/metaclasses.py +12 -0
  585. molSimplify/utils/tensorflow.py +23 -0
  586. molSimplify/utils/timer.py +16 -0
  587. molSimplify-1.7.4.dist-info/LICENSE +674 -0
  588. molSimplify-1.7.4.dist-info/METADATA +821 -0
  589. molSimplify-1.7.4.dist-info/RECORD +651 -0
  590. molSimplify-1.7.4.dist-info/WHEEL +5 -0
  591. molSimplify-1.7.4.dist-info/entry_points.txt +3 -0
  592. molSimplify-1.7.4.dist-info/top_level.txt +4 -0
  593. tests/generateTests.py +122 -0
  594. tests/helperFuncs.py +658 -0
  595. tests/informatics/test_MOF_descriptors.py +128 -0
  596. tests/informatics/test_active_learning.py +113 -0
  597. tests/informatics/test_coulomb_analyze.py +24 -0
  598. tests/informatics/test_graph_racs.py +193 -0
  599. tests/ml/test_kernels.py +20 -0
  600. tests/ml/test_layers.py +47 -0
  601. tests/runtest.py +10 -0
  602. tests/test_Mol2D.py +128 -0
  603. tests/test_basic_imports.py +62 -0
  604. tests/test_bidentate.py +25 -0
  605. tests/test_cli.py +20 -0
  606. tests/test_distgeom.py +106 -0
  607. tests/test_example_1.py +29 -0
  608. tests/test_example_3.py +31 -0
  609. tests/test_example_5.py +43 -0
  610. tests/test_example_7.py +28 -0
  611. tests/test_example_8.py +15 -0
  612. tests/test_example_tbp.py +15 -0
  613. tests/test_ff_xtb.py +111 -0
  614. tests/test_geocheck_oct.py +26 -0
  615. tests/test_geocheck_one_empty.py +15 -0
  616. tests/test_geometry.py +44 -0
  617. tests/test_inparse.py +76 -0
  618. tests/test_io.py +84 -0
  619. tests/test_jobgen.py +84 -0
  620. tests/test_joption_pythonic.py +27 -0
  621. tests/test_ligand_assign.py +58 -0
  622. tests/test_ligand_assign_consistent.py +60 -0
  623. tests/test_ligand_class.py +26 -0
  624. tests/test_ligand_from_mol_file.py +35 -0
  625. tests/test_ligands.py +86 -0
  626. tests/test_mol3D.py +337 -0
  627. tests/test_molcas_caspt2.py +15 -0
  628. tests/test_molcas_casscf.py +15 -0
  629. tests/test_old_ANNs.py +68 -0
  630. tests/test_orca_ccsdt.py +15 -0
  631. tests/test_orca_dft.py +15 -0
  632. tests/test_qcgen.py +50 -0
  633. tests/test_racs.py +124 -0
  634. tests/test_rmsd.py +68 -0
  635. tests/test_structgen_functions.py +198 -0
  636. tests/test_tetrahedral.py +29 -0
  637. tests/test_tutorial_10_part_one.py +16 -0
  638. tests/test_tutorial_10_part_two.py +15 -0
  639. tests/test_tutorial_2.py +11 -0
  640. tests/test_tutorial_3.py +15 -0
  641. tests/test_tutorial_4.py +57 -0
  642. tests/test_tutorial_6.py +10 -0
  643. tests/test_tutorial_8.py +29 -0
  644. tests/test_tutorial_9_part_one.py +15 -0
  645. tests/test_tutorial_9_part_two.py +15 -0
  646. tests/test_tutorial_qm9_part_one.py +6 -0
  647. tests/testresources/refs/racs/generate_references.py +85 -0
  648. workflows/NandyJACSAu2022/bridge_functionalizer.py +253 -0
  649. workflows/NandyJACSAu2022/frag_functionalizer.py +242 -0
  650. workflows/NandyJACSAu2022/fragment_classes.py +586 -0
  651. workflows/NandyJACSAu2022/macrocycle_synthesis.py +179 -0
@@ -0,0 +1,3040 @@
1
+ # file structgen.py
2
+ # Main structure generation routine
3
+ #
4
+ # Written by Kulik Group
5
+ #
6
+ # Department of Chemical Engineering, MIT
7
+
8
+ import os
9
+ import subprocess
10
+ import tempfile
11
+ try:
12
+ from openbabel import openbabel # version 3 style import
13
+ except ImportError:
14
+ import openbabel # fallback to version 2
15
+ import random
16
+ import itertools
17
+ import numpy as np
18
+ from typing import Any, List, Tuple, Dict, Union, Optional
19
+ from argparse import Namespace
20
+ from molSimplify.Scripts.distgeom import GetConf
21
+ from molSimplify.Scripts.geometry import (aligntoaxis2,
22
+ best_fit_plane,
23
+ checkcolinear,
24
+ distance,
25
+ getPointu,
26
+ kabsch,
27
+ midpt,
28
+ norm,
29
+ PointTranslateSph,
30
+ reflect_through_plane,
31
+ rotate_around_axis,
32
+ rotate_mat,
33
+ rotation_params,
34
+ setPdistance,
35
+ vecangle,
36
+ vecdiff)
37
+ from molSimplify.Scripts.io import (core_load,
38
+ getgeoms,
39
+ getinputargs,
40
+ getlicores,
41
+ lig_load,
42
+ loadcoord,
43
+ loaddata,
44
+ name_complex)
45
+ from molSimplify.Classes.atom3D import atom3D
46
+ from molSimplify.Classes.mol3D import mol3D
47
+ from molSimplify.Classes.rundiag import run_diag
48
+ from molSimplify.Classes.globalvars import (elementsbynum,
49
+ globalvars,
50
+ romans,
51
+ )
52
+ from molSimplify.Informatics.decoration_manager import (decorate_ligand)
53
+ from molSimplify.Classes.ligand import ligand as ligand_class
54
+ import logging
55
+
56
+ logger = logging.getLogger(__name__)
57
+ np.seterr(all='raise')
58
+
59
+
60
+ def getbackbcombsall(nums):
61
+ """Gets all possible combinations for connection atoms in geometry in the
62
+ case of forced order or unknown geometry.
63
+
64
+ Parameters
65
+ ----------
66
+ nums : list
67
+ List of connection atoms.
68
+
69
+ Returns
70
+ -------
71
+ bbcombs : list
72
+ List of possible backbone atom combinations.
73
+
74
+ """
75
+ bbcombs = []
76
+ for i in range(1, len(nums)+1):
77
+ bbcombs += list(itertools.combinations(nums, i))
78
+ for i, tup in enumerate(bbcombs):
79
+ bbcombs[i] = list(tup)
80
+ return bbcombs
81
+
82
+
83
+ def getnupdateb(backbatoms: List[List[int]], denticity: int) -> Tuple[List[int], List[List[int]]]:
84
+ """Gets a combination of backbone points that satisfies denticity and updates possible combinations.
85
+
86
+ Parameters
87
+ ----------
88
+ backbatoms : list
89
+ List of possible backbone atom combinations.
90
+ denticity : int
91
+ Denticity of ligand.
92
+
93
+ Returns
94
+ -------
95
+ batoms : list
96
+ Selected combination of backbone atoms.
97
+ backbatoms : list
98
+ Updated list of possible backbone atom combinations.
99
+
100
+ """
101
+ dlist = []
102
+ batoms = []
103
+ # find matching combination
104
+ for bba in backbatoms:
105
+ if len(bba) == denticity:
106
+ batoms = bba
107
+ break
108
+ # loop and find elements to delete
109
+ for ba in batoms:
110
+ for i, bcomb in enumerate(backbatoms):
111
+ if ba in bcomb and i not in dlist:
112
+ dlist.append(i)
113
+ dlist.sort(reverse=True) # sort
114
+ # delete used points
115
+ for i in dlist:
116
+ del backbatoms[i]
117
+ if len(batoms) < 1:
118
+ print('No more connecting points available..')
119
+ return batoms, backbatoms
120
+
121
+
122
+ def init_ANN(args, ligands: List[str], occs: List[int], dents: List[int],
123
+ batslist: List[List[int]], tcats: List[List[Union[int, str]]],
124
+ licores: dict) -> Tuple[bool, List[Any], str, Dict[str, Any], bool]:
125
+ """Initializes ANN.
126
+
127
+ Parameters
128
+ ----------
129
+ args : Namespace
130
+ Namespace of arguments.
131
+ ligands : list
132
+ List of ligands, given as names.
133
+ occs : list
134
+ List of ligand occupations (frequencies of each ligand).
135
+ dents : list
136
+ List of ligand denticities.
137
+ batslist : list
138
+ List of backbond points.
139
+ tcats : list
140
+ List of SMILES ligand connecting atoms.
141
+ licores : dict
142
+ Ligand dictionary within molSimplify.
143
+
144
+ Returns
145
+ -------
146
+ ANN_flag : bool
147
+ Whether an ANN call was successful.
148
+ ANN_bondl : float
149
+ ANN predicted bond length.
150
+ ANN_reason : str
151
+ Reason for ANN failure, if failed.
152
+ ANN_attributes : dict
153
+ Dictionary of predicted attributes of complex.
154
+ catalysis_flag : bool
155
+ Whether or not complex is compatible for catalytic ANNs.
156
+
157
+ """
158
+ # initialize ANN
159
+ globs = globalvars()
160
+ catalysis_flag = False
161
+ if args.skipANN:
162
+ print('Skipping ANN')
163
+ ANN_flag = False
164
+ # there needs to be 1 length per possible lig
165
+ ANN_bondl = len([item for items in batslist for item in items])*[False]
166
+ ANN_attributes: Dict[str, Any] = {}
167
+ ANN_reason = 'ANN skipped by user'
168
+ return ANN_flag, ANN_bondl, ANN_reason, ANN_attributes, catalysis_flag
169
+
170
+ if args.oldANN:
171
+ print('using old ANN by request')
172
+ from molSimplify.Scripts.nn_prep import ANN_preproc
173
+ ANN_flag, ANN_reason, ANN_attributes = ANN_preproc(
174
+ args, ligands, occs, dents, batslist, tcats, licores)
175
+ else:
176
+ if globs.testTF():
177
+ # new RACs-ANN
178
+ from molSimplify.Scripts.tf_nn_prep import tf_ANN_preproc
179
+ if args.debug:
180
+ print('Using tf_ANN_preproc')
181
+ # Set default value [] in case decoration is not used
182
+ decoration_index = [] if not args.decoration else args.decoration_index
183
+
184
+ ANN_flag, ANN_reason, ANN_attributes, catalysis_flag = tf_ANN_preproc(
185
+ args.core, args.oxstate, args.spin, ligands, occs, dents, batslist,
186
+ tcats, licores, args.decoration, decoration_index, args.exchange,
187
+ args.geometry, args.debug)
188
+ else:
189
+ # old MCDL-25
190
+ print('using old ANN because tensorflow/keras import failed')
191
+ from molSimplify.Scripts.nn_prep import ANN_preproc
192
+ ANN_flag, ANN_reason, ANN_attributes = ANN_preproc(
193
+ args, ligands, occs, dents, batslist, tcats, licores)
194
+ if ANN_flag:
195
+ ANN_bondl = ANN_attributes['ANN_bondl']
196
+ if args.debug:
197
+ print(('ANN bond length is ' + str(ANN_bondl) +
198
+ ' type ' + str(type(ANN_bondl))))
199
+
200
+ else:
201
+ # there needs to be 1 length per possible lig
202
+ ANN_bondl = len(
203
+ [item for items in batslist for item in items])*[False]
204
+ if args.debug:
205
+ if ANN_reason == 'found incorrect ligand symmetry':
206
+ # This is a workaround so as to not have to change
207
+ # report files checked by GitHub CI when running test
208
+ # cases, which would require everyone using molSimplify
209
+ # from source to have to git pull the new files before
210
+ # any new commits
211
+ print("ANN call failed with reason: either found "
212
+ "incorrect ligand symmetry, or see ANN "
213
+ "messages above")
214
+ else:
215
+ print(("ANN call failed with reason: " + ANN_reason))
216
+ return ANN_flag, ANN_bondl, ANN_reason, ANN_attributes, catalysis_flag
217
+
218
+
219
+ def init_template(args: Namespace, cpoints_required: int) -> Tuple[mol3D, mol3D, str, list, int, mol3D]:
220
+ """Initializes core and template mol3Ds and properties.
221
+
222
+ Parameters
223
+ ----------
224
+ args : Namespace
225
+ Namespace of arguments.
226
+ cpoints_required : int
227
+ Number of connecting points required.
228
+
229
+ Returns
230
+ -------
231
+ m3D : mol3D
232
+ Template complex mol3D instance.
233
+ core3D : mol3D
234
+ Core mol3D instance.
235
+ geom : str
236
+ Geometry used.
237
+ backbatoms : list
238
+ List of backbone atoms.
239
+ coord : int
240
+ Coordination number.
241
+ corerefatoms : mol3D
242
+ Core reference atom index, mol3D instance.
243
+
244
+ """
245
+ globs = globalvars()
246
+ # initialize core and template
247
+ core3D = mol3D()
248
+ m3D = mol3D()
249
+ # container for ordered list of core reference atoms
250
+ corerefatoms = mol3D()
251
+ # geometry load flag
252
+ geom = 'unknown'
253
+ backbatoms = []
254
+ coord = 0
255
+ # build mode
256
+ if args.geometry and not args.ccatoms:
257
+ # determine geometry
258
+ coord = int(args.coord)
259
+ # get available geometries
260
+ coords, geomnames, geomshorts, geomgroups = getgeoms()
261
+ # get list of possible combinations for connecting points
262
+ bbcombsdict = globs.bbcombs_mononuc()
263
+ # get a default geometry
264
+ geom = geomgroups[coord-1][0]
265
+ # check if geometry is defined and overwrite
266
+ if args.geometry in geomshorts:
267
+ geom = args.geometry
268
+ elif args.geometry in geomnames:
269
+ geom = geomshorts[geomnames.index(args.geometry)]
270
+ else:
271
+ emsg = "Requested geometry not available." + \
272
+ "Defaulting to "+geomgroups[coord-1][0]
273
+ if args.gui:
274
+ from Classes.mWidgets import mQDialogWarn
275
+ qqb = mQDialogWarn('Warning', emsg)
276
+ qqb.setParent(args.gui.wmain)
277
+ print(emsg)
278
+ # load predefined backbone coordinates
279
+ corexyz = loadcoord(geom)
280
+ # load backbone atom combinations
281
+ if geom in list(bbcombsdict.keys()) and not args.ligloc:
282
+ backbatoms = bbcombsdict[geom]
283
+ else:
284
+ nums = list(range(1, len(corexyz)))
285
+ backbatoms = getbackbcombsall(nums)
286
+ # distort if requested
287
+ if args.pangles:
288
+ corexyz = modifybackbonep(
289
+ corexyz, args.pangles) # point distortion
290
+ if args.distort:
291
+ corexyz = distortbackbone(
292
+ corexyz, args.distort) # random distortion
293
+ # add center atom
294
+ if args.core[0].upper()+args.core[1:] in elementsbynum:
295
+ centeratom = args.core[0].upper()+args.core[1:]
296
+ else:
297
+ print('WARNING: Core is not an element. Defaulting to Fe')
298
+ centeratom = 'Fe'
299
+ core3D.addAtom(atom3D(centeratom, corexyz[0]))
300
+ m3D.copymol3D(core3D)
301
+ # add connecting points to template
302
+ for m in range(1, coord+1):
303
+ m3D.addAtom(atom3D('X', corexyz[m]))
304
+ corerefatoms.addAtom(core3D.getAtom(0))
305
+ # corerefatoms.append(0)
306
+
307
+ # functionalize mode
308
+ else:
309
+ # check ccatoms
310
+ if not args.ccatoms:
311
+ emsg = 'Connection atoms for custom core not specified. Defaulting to 1!\n'
312
+ print(emsg)
313
+ if args.gui:
314
+ from Classes.mWidgets import mQDialogWarn
315
+ qqb = mQDialogWarn('Warning', emsg)
316
+ qqb.setParent(args.gui.wmain)
317
+ ccatoms = args.ccatoms if args.ccatoms else [0]
318
+ coord = len(ccatoms)
319
+ if args.debug:
320
+ print(('setting ccatoms ' + str(ccatoms)))
321
+
322
+ # load core
323
+ core, emsg = core_load(args.core)
324
+ if core is None or emsg:
325
+ raise ValueError(emsg)
326
+ core.convert2mol3D()
327
+ core3D.copymol3D(core)
328
+ m3D.copymol3D(core3D)
329
+ for i in range(cpoints_required):
330
+ if not args.replig:
331
+ # not replacing ligands: add Xs to ccatoms
332
+ # NOTE: ccatoms should be a list with # elements = cpoints_required
333
+ cpoint = getconnection(m3D, ccatoms[i], 2)
334
+ # store core reference atom
335
+ conatom3D = atom3D(core3D.getAtom(
336
+ ccatoms[i]).sym, core3D.getAtom(ccatoms[i]).coords())
337
+ corerefatoms.addAtom(conatom3D)
338
+ # corerefatoms.append(ccatoms[i])
339
+ # add connecting points to template
340
+ m3D.addAtom(atom3D(Sym='X', xyz=cpoint))
341
+ else:
342
+ try:
343
+ # replacing ligands
344
+ cpoint = core3D.getAtom(ccatoms[i]).coords()
345
+ conatoms = core3D.getBondedAtoms(ccatoms[i])
346
+ # find smaller submolecule, i.e., ligand to remove
347
+ minmol = 10000
348
+ mindelats = []
349
+ atclose = 0
350
+ # loop over different connected atoms
351
+ for cat in conatoms:
352
+ # find submolecule
353
+ delatoms = core3D.findsubMol(ccatoms[i], cat)
354
+ if len(delatoms) < minmol: # check for smallest
355
+ mindelats = delatoms
356
+ minmol = len(delatoms) # size
357
+ atclose = cat # connection atom
358
+ # if same atoms in ligand get shortest distance
359
+ elif len(delatoms) == minmol:
360
+ d0 = core3D.getAtom(ccatoms[i]).distance(
361
+ core3D.getAtom(cat))
362
+ d1 = core3D.getAtom(ccatoms[i]).distance(
363
+ core3D.getAtom(mindelats[0]))
364
+ if d0 < d1:
365
+ mindelats = delatoms
366
+ atclose = cat
367
+ # store core reference atom
368
+ conatom3D = atom3D(core3D.getAtom(
369
+ atclose).sym, core3D.getAtom(atclose).coords())
370
+ corerefatoms.addAtom(conatom3D)
371
+ # corerefatoms.append(atclose)
372
+ delatoms = mindelats
373
+ # add connecting points to template
374
+ m3D.addAtom(atom3D(Sym='X', xyz=cpoint))
375
+ # for multidentate ligands: if a submolecule contains multiple ccatoms, add all of them to the template
376
+ for atomidx in delatoms:
377
+ if atomidx in ccatoms[i+1:]:
378
+ # add connecting points to template
379
+ m3D.addAtom(
380
+ atom3D(Sym='X', xyz=core3D.getAtom(atomidx).coords()))
381
+ ccatoms.remove(atomidx)
382
+ corerefatoms.addAtom(conatom3D)
383
+ # update remaining ccatoms according to deleted atoms
384
+ if len(ccatoms) > i+1:
385
+ for cccat in range(i+1, len(ccatoms)):
386
+ lshift = len(
387
+ [a for a in delatoms if a < ccatoms[cccat]])
388
+ ccatoms[cccat] -= lshift
389
+ # delete submolecule
390
+ core3D.deleteatoms(delatoms)
391
+ m3D.deleteatoms(delatoms)
392
+
393
+ except IndexError:
394
+ pass
395
+ nums = m3D.findAtomsbySymbol('X')
396
+ backbatoms = getbackbcombsall(nums)
397
+ # set charge from oxidation state if desired
398
+ if args.calccharge:
399
+ if args.oxstate:
400
+ if args.oxstate in list(romans.keys()):
401
+ core3D.charge = int(romans[args.oxstate])
402
+ else:
403
+ core3D.charge = int(args.oxstate)
404
+ return m3D, core3D, geom, backbatoms, coord, corerefatoms
405
+
406
+
407
+ def init_ligand(args: Namespace, lig: mol3D, tcats,
408
+ keepHs: List[List[Union[bool, str]]], i: int):
409
+ """Initializes ligand 3D geometry and properties.
410
+
411
+ Parameters
412
+ ----------
413
+ args : Namespace
414
+ Namespace of arguments.
415
+ lig : mol3D
416
+ mol3D instance of the ligand.
417
+ tcats : list
418
+ List of SMILES ligand connecting atoms.
419
+ keepHs : bool
420
+ Flag for keeping H atoms on connecting atoms.
421
+ i : int
422
+ Ligand index.
423
+
424
+ Returns
425
+ -------
426
+ lig3D : mol3D
427
+ Ligand mol3D instance.
428
+ rempi : bool
429
+ Flag for pi coordination.
430
+ ligpiatoms : list
431
+ List of pi coordinating atoms.
432
+
433
+ """
434
+ globs = globalvars()
435
+ rempi = False
436
+ # if SMILES string, copy connecting atoms list to mol3D properties
437
+ if not lig.cat and tcats[i]:
438
+ if 'c' in tcats[i]:
439
+ lig.cat = [lig.natoms]
440
+ else:
441
+ lig.cat = tcats[i]
442
+ # change name
443
+ lig3D = mol3D()
444
+ lig3D.copymol3D(lig)
445
+ # check for pi-coordinating ligand
446
+ ligpiatoms = []
447
+ if 'pi' in lig.cat:
448
+ lig3Dpiatoms = mol3D()
449
+ for k in lig.cat[:-1]:
450
+ lig3Dpiatoms.addAtom(lig3D.getAtom(k))
451
+ lig3Dpiatoms.addAtom(lig3D.getAtom(k))
452
+ ligpiatoms = lig.cat[:-1]
453
+ lig3D.addAtom(atom3D('C', lig3Dpiatoms.centermass()))
454
+ lig.cat = [lig3D.natoms-1]
455
+ rempi = True
456
+ # perform FF optimization if requested (not supported for pi-coordinating ligands)
457
+ if args.ff and 'b' in args.ffoption and not rempi:
458
+ if 'b' in lig.ffopt.lower():
459
+ if args.debug:
460
+ print('FF optimizing ligand')
461
+ lig3D.convert2mol3D()
462
+ lig3D, enl = ffopt(args.ff, lig3D, lig3D.cat, 0,
463
+ [], False, [], 100, debug=args.debug)
464
+ # skip hydrogen removal for pi-coordinating ligands
465
+ if not rempi:
466
+ # check smarts match
467
+ if 'auto' in keepHs[i]:
468
+ for j, catom in enumerate(lig.cat):
469
+ match = findsmarts(lig3D.OBMol, globs.remHsmarts, catom)
470
+ if match:
471
+ keepHs[i][j] = False
472
+ else:
473
+ keepHs[i][j] = True
474
+ # remove one hydrogen from each connecting atom with keepH false
475
+ for j, cat in enumerate(lig.cat): # lig.cat are the connecting atoms
476
+ Hs = lig3D.getHsbyIndex(cat)
477
+ if len(Hs) > 0 and not keepHs[i][j]:
478
+ if args.debug:
479
+ print(f'modifying charge down from {lig3D.charge}')
480
+ try:
481
+ print('Debug keepHs check\n'
482
+ f'Removing? {keepHs} \n'
483
+ f'i = {i}, j = {j}\n'
484
+ f'lig = \n{lig.coords()}\n'
485
+ f'keepHs[i]: {keepHs[i]}\n'
486
+ f'length of keepHs list : {len(keepHs)}')
487
+ except (AttributeError, IndexError):
488
+ # Could fail because lig has no Attribute coords
489
+ # or because keepHs has no element with Index i
490
+ pass
491
+ # Need to shift all connecting atom indices if they are greater
492
+ # than Hs[0], i.e. the index of the hydrogen atom that is
493
+ # connected to the current connecting atom and is to be removed.
494
+ # Note that only one hydrogen atom is removed at the most under
495
+ # the current implementation.
496
+ for _i, connecting_index in enumerate(lig.cat):
497
+ if connecting_index > Hs[0]:
498
+ lig.cat[_i] -= 1
499
+ lig3D.deleteatom(Hs[0])
500
+ lig3D.charge = lig3D.charge - 1
501
+ # Conformer search for multidentate SMILES ligands
502
+ lig3D.convert2OBMol()
503
+
504
+ if lig.needsconformer:
505
+ tcats[i] = True
506
+ print(('getting conformers for ' + str(lig.ident)))
507
+
508
+ if len(lig.cat) > 1 and tcats[i]:
509
+ lig3D = GetConf(lig3D, args, lig.cat)
510
+ return lig3D, rempi, ligpiatoms
511
+
512
+
513
+ def modifybackbonep(backb, pangles):
514
+ """Distorts backbone according to user specified angles.
515
+
516
+ Parameters
517
+ ----------
518
+ backb : List
519
+ List with points comprising the backbone.
520
+ pangles : List
521
+ Pairs of theta/phi angles in DEGREES. Should be list of tuples.
522
+
523
+ Returns
524
+ -------
525
+ backb : list
526
+ List of distorted backbone points.
527
+
528
+ """
529
+ for i, ll in enumerate(pangles):
530
+ if ll:
531
+ theta = np.pi*float(ll.split('/')[0])/180.0
532
+ phi = np.pi*float(ll.split('/')[-1])/180.0
533
+ backb[i+1] = PointTranslateSph(backb[0], backb[i+1],
534
+ [distance(backb[0], backb[i+1]), theta, phi])
535
+ return backb
536
+
537
+
538
+ def distortbackbone(backb, distort):
539
+ """Randomly distorts backbone.
540
+
541
+ Parameters
542
+ ----------
543
+ backb : List
544
+ List with points comprising the backbone.
545
+ distort : float
546
+ Percentage of backbone to be distorted.
547
+
548
+ Returns
549
+ -------
550
+ backb : list
551
+ List of distorted backbone points.
552
+
553
+ """
554
+ for i in range(1, len(backb)):
555
+ theta = random.uniform(0.0, 0.01*int(distort)) # *0.5
556
+ phi = random.uniform(0.0, 0.01*int(distort)*0.5) # *0.5
557
+ backb[i] = PointTranslateSph(
558
+ backb[0], backb[i], [distance(backb[0], backb[i]), theta, phi])
559
+ return backb
560
+
561
+
562
+ def smartreorderligs(ligs: List[str], dentl: List[int],
563
+ ligalign: bool = True) -> List[int]:
564
+ """Smart reorder ligands by denticity (-ligalign True)
565
+
566
+ Parameters
567
+ ----------
568
+ args : Namespace
569
+ Namespace of arguments.
570
+ ligs : list
571
+ List of ligands as ligand names.
572
+ dentl : list
573
+ List of ligand denticities.
574
+
575
+ Returns
576
+ -------
577
+ indices : list
578
+ Reordered ligand indices.
579
+
580
+ """
581
+
582
+ # reorder ligands
583
+ if not ligalign:
584
+ indices = list(range(0, len(ligs)))
585
+ return indices
586
+ lsizes = []
587
+ for ligand in ligs:
588
+ lig, _ = lig_load(ligand) # load ligand
589
+ lig.convert2mol3D()
590
+ lsizes.append(lig.natoms)
591
+ # sort ligands into subsets by denticity, since set() sort the items
592
+ # this list goes from lowest to highest denticity, e.g. first list entry
593
+ # contains all monodentate indices, second all bidentates...
594
+ ligdentsidcs = [[i for i, dent in enumerate(dentl) if dent == unique_dent]
595
+ for unique_dent in set(dentl)]
596
+ # sort by highest denticity first
597
+ ligdentsidcs = list(reversed(ligdentsidcs))
598
+ indices = []
599
+ # within each group sort by size (smaller first)
600
+ for ii, dd in enumerate(ligdentsidcs):
601
+ locs = [lsizes[i] for i in dd]
602
+ locind = [i[0] for i in sorted(enumerate(locs), key=lambda x:x[1])]
603
+ for li in locind:
604
+ indices.append(ligdentsidcs[ii][li])
605
+ return indices
606
+
607
+
608
+ def ffopt(ff: str, mol: mol3D, connected: List[int], constopt: int,
609
+ frozenats: List[int], frozenangles: bool,
610
+ mlbonds: List[float], nsteps: Union[int, str],
611
+ spin: int = 1, debug: bool = False) -> Tuple[mol3D, float]:
612
+ """Main constrained FF opt routine.
613
+
614
+ Parameters
615
+ ----------
616
+ ff : str
617
+ Name force field to use. Available options are MMFF94, UFF,
618
+ Ghemical, GAFF, XTB.
619
+ (XTB only works if the xtb command-line program is installed.)
620
+ mol : mol3D
621
+ mol3D instance of molecule to be optimized.
622
+ connected : list
623
+ List of indices of connection atoms to metal.
624
+ constopt : int
625
+ Flag for constrained optimization -
626
+ 0: unconstrained,
627
+ 1: fixed connecting atom positions,
628
+ 2: fixed connecting atom distances.
629
+ frozenats : list
630
+ List of frozen atom indices.
631
+ frozenangles : bool
632
+ Flag for frozen angles, equivalent to constopt==1.
633
+ mlbonds : list
634
+ List of M-L bonds for distance constraints.
635
+ nsteps : int
636
+ Number of steps to take.
637
+ spin: int
638
+ Spin multiplicity
639
+ debug : bool
640
+ Flag to print extra info to debug.
641
+
642
+ Returns
643
+ -------
644
+ mol : mol3D
645
+ Optimized molecule mol3D instance.
646
+ en : float
647
+ Forcefield energy of optimized molecule.
648
+
649
+ """
650
+ # check requested force field
651
+ ffav = 'mmff94, uff, ghemical, gaff, mmff94s, xtb, gfnff' # force fields
652
+
653
+ if ff.lower() not in ffav:
654
+ print('Requested force field not available. Defaulting to UFF')
655
+ ff = 'uff'
656
+ if debug:
657
+ print(('using ff: ' + ff))
658
+ if ff.lower() in ['xtb', 'gfnff']:
659
+ return xtb_opt(ff, mol, connected, constopt, frozenats,
660
+ frozenangles, mlbonds, nsteps, spin=spin, debug=debug)
661
+ return openbabel_ffopt(ff, mol, connected, constopt, frozenats,
662
+ frozenangles, mlbonds, nsteps, debug=debug)
663
+
664
+
665
+ def openbabel_ffopt(ff: str, mol: mol3D, connected: List[int], constopt: int,
666
+ frozenats: List[int], frozenangles: bool,
667
+ mlbonds: List[float], nsteps: Union[int, str],
668
+ debug: bool = False) -> Tuple[mol3D, float]:
669
+ """ OpenBabel constraint optimization. To optimize metal-containing
670
+ complexes with MMFF94, an intricate procedure of masking the metal
671
+ atoms and manually editing their valences is applied. OpenBabel's
672
+ implementation of MMFF94 may run extremely slowly on some systems.
673
+ If so, consider switching to UFF.
674
+
675
+ Parameters
676
+ ----------
677
+ ff : str
678
+ Name force field to use. Available options are MMFF94, UFF, Ghemical, GAFF.
679
+ mol : mol3D
680
+ mol3D instance of molecule to be optimized.
681
+ connected : list
682
+ List of indices of connection atoms to metal.
683
+ constopt : int
684
+ Flag for constrained optimization
685
+ 0: unconstrained,
686
+ 1: fixed connecting atom positions,
687
+ 2: fixed connecting atom distances.
688
+ frozenats : list
689
+ List of frozen atom indices.
690
+ frozenangles : bool
691
+ Flag for frozen angles, equivalent to constopt==1.
692
+ mlbonds : list
693
+ List of M-L bonds for distance constraints.
694
+ nsteps : int
695
+ Number of steps to take.
696
+ debug : bool
697
+ Flag to print extra info to debug.
698
+
699
+ Returns
700
+ -------
701
+ mol : mol3D
702
+ Optimized molecule mol3D instance.
703
+ en : float
704
+ Forcefield energy of optimized molecule.
705
+
706
+ """
707
+ metals = list(range(21, 31))+list(range(39, 49))+list(range(72, 81))
708
+ # perform constrained ff optimization if requested after #
709
+ if (constopt > 0):
710
+ # get metal
711
+ midx = mol.findMetal()
712
+ # convert mol3D to OBMol
713
+ mol.convert2OBMol()
714
+ OBMol = mol.OBMol
715
+ # initialize force field
716
+ forcefield = openbabel.OBForceField.FindForceField(ff)
717
+ # initialize constraints
718
+ constr = openbabel.OBFFConstraints()
719
+ # openbabel indexing starts at 1 !!!
720
+ # convert metals to carbons for FF
721
+ indmtls = []
722
+ mtlsnums = []
723
+ for iiat, atom in enumerate(openbabel.OBMolAtomIter(OBMol)):
724
+ if atom.GetAtomicNum() in metals:
725
+ indmtls.append(iiat)
726
+ mtlsnums.append(atom.GetAtomicNum())
727
+ atom.SetAtomicNum(6)
728
+ # freeze and ignore metals
729
+ for midxm in indmtls:
730
+ constr.AddAtomConstraint(midxm+1) # indexing babel
731
+ # add coordinating atom constraints
732
+ for ii, catom in enumerate(connected):
733
+
734
+ if constopt == 1 or frozenangles:
735
+ constr.AddAtomConstraint(catom+1) # indexing babel
736
+ if debug:
737
+ print('using connnected opt to freeze atom number: '
738
+ + str(catom))
739
+ else:
740
+ constr.AddDistanceConstraint(
741
+ midx[0]+1, catom+1, mlbonds[ii]) # indexing babel
742
+ # print('ff is '+ str(ff))
743
+ if not ff.lower() == "uff":
744
+ bridgingatoms = []
745
+ # identify bridging atoms in the case of bimetallic cores,
746
+ # as well as single-atom ligands (oxo, nitrido)
747
+ # these are immune to deletion
748
+ for i in range(mol.natoms):
749
+ nbondedmetals = len([idx for idx in range(len(mol.getBondedAtoms(
750
+ i))) if mol.getAtom(mol.getBondedAtoms(i)[idx]).ismetal()])
751
+ if nbondedmetals > 1 or (nbondedmetals == 1 and len(mol.getBondedAtoms(i)) == 1):
752
+ bridgingatoms.append(i)
753
+ # ensure correct valences for FF setup
754
+ deleted_bonds = 0
755
+
756
+ for m in indmtls:
757
+ # first delete all metal-ligand bonds excluding bridging atoms
758
+ for i in range(len(mol.getBondedAtoms(m))):
759
+ if (OBMol.GetBond(m+1, mol.getBondedAtoms(m)[i]+1) is not None
760
+ and mol.getBondedAtoms(m)[i] not in bridgingatoms):
761
+ OBMol.DeleteBond(OBMol.GetBond(
762
+ m+1, mol.getBondedAtoms(m)[i]+1))
763
+ # print('FFopt deleting bond')
764
+ deleted_bonds += 1
765
+ print(('FFopt deleted ' + str(deleted_bonds) + ' bonds'))
766
+ # then add back one metal-ligand bond for FF
767
+ try:
768
+ numNeighbors = OBMol.GetAtom(m+1).GetValence()
769
+ except AttributeError:
770
+ # quick workaround for openbabel 3.1.0 compatibility
771
+ numNeighbors = OBMol.GetAtom(m + 1).GetExplicitDegree()
772
+ if numNeighbors == 0:
773
+ # getBondedAtomsOct(m,deleted_bonds+len(bridgingatoms)):
774
+ for i in mol.getBondedAtoms(m):
775
+ # quick workaround for openbabel 3.1.0 compatibility
776
+ try:
777
+ _numNeighbors = OBMol.GetAtom(m+1).GetValence()
778
+ except AttributeError:
779
+ _numNeighbors = OBMol.GetAtom(m + 1).GetExplicitDegree()
780
+ if _numNeighbors < 1 and i not in bridgingatoms:
781
+ OBMol.AddBond(m+1, i+1, 1)
782
+ # freeze small ligands
783
+ for cat in frozenats:
784
+ if debug:
785
+ print(('using frozenats to freeze atom number: ' + str(cat)))
786
+ constr.AddAtomConstraint(cat+1) # indexing babel
787
+ if debug:
788
+ # for iiat, atom in enumerate(openbabel.OBMolAtomIter(OBMol)):
789
+ # print((' atom '+str(iiat)+' atomic num '+str(atom.GetAtomicNum())+' valence ' +
790
+ # str(atom.GetValence()) + ' is fixed ' + str(constr.IsFixed(iiat+1))))
791
+ # Note: Commented out the preceding for loop because it was
792
+ # throwing the following error:
793
+ # AttributeError: 'OBAtom' object has no attribute 'GetValence'
794
+ print('Commented out')
795
+ # set up forcefield
796
+ s = forcefield.Setup(OBMol, constr)
797
+ if not s:
798
+ print('FF setup failed')
799
+ # force field optimize structure
800
+ elif nsteps == 'Adaptive':
801
+ i = 0
802
+ while i < 20:
803
+ forcefield.ConjugateGradients(50)
804
+ forcefield.GetCoordinates(OBMol)
805
+ mol.OBMol = OBMol
806
+ mol.convert2mol3D()
807
+ overlap, mind = mol.sanitycheck(True)
808
+ if not overlap:
809
+ break
810
+ i += 1
811
+ elif nsteps != 0:
812
+ n = nsteps
813
+ if debug:
814
+ print(('running ' + str(n) + ' steps'))
815
+ forcefield.ConjugateGradients(n)
816
+ forcefield.GetCoordinates(OBMol)
817
+ mol.OBMol = OBMol
818
+ mol.convert2mol3D()
819
+ else:
820
+ forcefield.GetCoordinates(OBMol)
821
+ en = forcefield.Energy()
822
+ mol.OBMol = OBMol
823
+ # reset atomic number to metal
824
+ for i, iiat in enumerate(indmtls):
825
+ mol.OBMol.GetAtomById(iiat).SetAtomicNum(mtlsnums[i])
826
+ mol.convert2mol3D()
827
+ del forcefield, constr, OBMol
828
+ else:
829
+ # initialize constraints
830
+ constr = openbabel.OBFFConstraints()
831
+ # add atom constraints
832
+ for catom in connected:
833
+ constr.AddAtomConstraint(catom+1) # indexing babel
834
+ # set up forcefield
835
+ forcefield = openbabel.OBForceField.FindForceField(ff)
836
+ # if len(connected) < 2:
837
+ # mol.OBMol.localopt('mmff94',100) # add hydrogens and coordinates
838
+ OBMol = mol.OBMol # convert to OBMol
839
+ _ = forcefield.Setup(OBMol, constr)
840
+ # force field optimize structure
841
+ if OBMol.NumHvyAtoms() > 10:
842
+ if debug:
843
+ print('doing 50 steps')
844
+ forcefield.ConjugateGradients(50)
845
+ else:
846
+ if debug:
847
+ print('doing 200 steps')
848
+ forcefield.ConjugateGradients(200)
849
+ forcefield.GetCoordinates(OBMol)
850
+ en = forcefield.Energy()
851
+ mol.OBMol = OBMol
852
+ mol.convert2mol3D()
853
+ del forcefield, constr, OBMol
854
+ return mol, en
855
+
856
+
857
+ def xtb_opt(ff: str, mol: mol3D, connected: List[int], constopt: int,
858
+ frozenats: List[int], frozenangles: bool,
859
+ mlbonds: List[float], nsteps: Union[int, str], spin: int = 1,
860
+ inertial: bool = False, debug: bool = False) -> Tuple[mol3D, float]:
861
+ """ XTB optimization. Writes an input file (xtb.in) containing
862
+ all the constraints and parameters to a temporary folder,
863
+ executes the XTB program using the subprocess module and parses
864
+ the output.
865
+
866
+ Parameters
867
+ ----------
868
+ ff : str
869
+ Name force field to use. Only option for now is XTB.
870
+ mol : mol3D
871
+ mol3D instance of molecule to be optimized.
872
+ connected : list
873
+ List of indices of connection atoms to metal.
874
+ constopt : int
875
+ Flag for constrained optimization -
876
+ 0: unconstrained,
877
+ 1: fixed connecting atom positions,
878
+ 2: fixed connecting atom distances.
879
+ frozenats : list
880
+ List of frozen atom indices.
881
+ frozenangles : bool
882
+ Flag for frozen angles, equivalent to constopt==1.
883
+ mlbonds : list
884
+ List of M-L bonds for distance constraints.
885
+ nsteps : int
886
+ Number of steps to take.
887
+ spin: int
888
+ Spin multiplicity
889
+ inertial: bool
890
+ Flag for the fast inertial relaxation engine (FIRE)
891
+ debug : bool
892
+ Flag to print extra info to debug.
893
+
894
+ Returns
895
+ -------
896
+ mol : mol3D
897
+ Optimized molecule mol3D instance.
898
+ en : float
899
+ Forcefield energy of optimized molecule.
900
+
901
+ """
902
+ logger.debug(f'xtbopt() called with {mol.natoms} atoms '
903
+ f'constopt: {constopt}, frozenats: {frozenats}, '
904
+ f'frozenangles: {frozenangles}, nsteps: {nsteps}, '
905
+ f'spin {spin}, inertial {inertial}')
906
+ if nsteps == 'Adaptive':
907
+ # While a similar concept to adaptive would be to set nsteps = 0
908
+ # which corresponds to "automatic" mode in xtb, here the maximum
909
+ # number of steps is just restricted to the same maximum used in
910
+ # adaptive mode: 20*50 = 1000
911
+ nsteps = 1000
912
+ # Initialize defailed input file with optimization parameters.
913
+ input_lines = ['$opt\n', f'maxcycle={nsteps}\n']
914
+ if inertial:
915
+ # engine=inertial is selected in cases if the generation of approximate
916
+ # Hessian coordinates (AHC) fails e.g.: for highly symmetric systems.
917
+ input_lines.append('engine=inertial\n')
918
+ # Arguments for the commandline call of the xtb program
919
+ cmdl_args = ['--opt', 'normal', '--input', 'xtb.inp']
920
+ if ff.lower() == 'gfnff':
921
+ cmdl_args.append('--gfnff')
922
+
923
+ # Extract charge (and spin)
924
+ if mol.charge != 0:
925
+ input_lines.append(f'$chrg {mol.charge}\n')
926
+ # xtb uses number of unpaired electrons (Nalpha - Nbeta) instead
927
+ # of multiplicity to define the spin state.
928
+ input_lines.append(f'$spin {spin-1}\n')
929
+
930
+ if constopt > 0: # constrained optimization:
931
+ # List of user selected frozen atoms
932
+ frozen_atoms = frozenats
933
+ # Add all metal atoms
934
+ for i, atom in enumerate(mol.getAtoms()):
935
+ if atom.ismetal():
936
+ frozen_atoms.append(i)
937
+
938
+ if constopt == 1 or frozenangles: # Freeze connecting atoms
939
+ frozen_atoms += connected
940
+ else: # Contrain bond lengths
941
+ raise NotImplementedError(
942
+ 'Bond length constraint XTB optimization '
943
+ 'not yet implemented')
944
+ input_lines.append('$fix\n')
945
+ # xtb uses indices starting from 1
946
+ ids = ','.join([str(i+1) for i in frozen_atoms])
947
+ input_lines.append(f'atoms: {ids}\n')
948
+
949
+ with tempfile.TemporaryDirectory() as tmpdir:
950
+ # Write detailed input file
951
+ with open(os.path.join(tmpdir, 'xtb.inp'), 'w') as fout:
952
+ fout.writelines(input_lines)
953
+ fout.write('$end\n')
954
+ # Write .xyz file
955
+ mol.writexyz(os.path.join(tmpdir, 'tmp.xyz'))
956
+ # Run xtb using the cmdl args and capture the stdout
957
+ try:
958
+ output = subprocess.run(
959
+ ['xtb'] + cmdl_args + ['tmp.xyz'],
960
+ cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
961
+ except FileNotFoundError:
962
+ raise ChildProcessError('Could not find subprocess xtb. Ensure xtb'
963
+ ' is installed and properly configured.')
964
+ if output.returncode != 0:
965
+ if b'ANC generation failed!' in output.stdout:
966
+ print('Switching xtb_opt to inertial engine.')
967
+ return xtb_opt(ff, mol, connected, constopt, frozenats,
968
+ frozenangles, mlbonds, nsteps, spin=spin,
969
+ inertial=True, debug=debug)
970
+ else:
971
+ print(output)
972
+ raise ChildProcessError('XTB calculation failed')
973
+ # Parse geometry, inspired by mol3D.convert2mol3D()
974
+ original_graph = mol.graph
975
+ mol.initialize()
976
+ mol.graph = original_graph
977
+ mol.readfromxyz(os.path.join(tmpdir, 'xtbopt.xyz'))
978
+ # Parse energy from .xyz file comment line
979
+ with open(os.path.join(tmpdir, 'xtbopt.xyz'), 'r') as fout:
980
+ output_lines = fout.readlines()
981
+ en = float(output_lines[1].split()[1])
982
+ return mol, en
983
+
984
+
985
+ def getconnection(core: mol3D, cidx: int, BL: float) -> List[float]:
986
+ """Finds the optimum attachment point for an atom/group to a central atom given the desired bond length.
987
+ Objective function maximizes the minimum distance between attachment point and other groups bonded to the central atom.
988
+
989
+ Parameters
990
+ ----------
991
+ core : mol3D
992
+ mol3D class instance of the core.
993
+ cidx : int
994
+ Core connecting atom index.
995
+ BL : float
996
+ Optimal core-ligand bond length.
997
+
998
+ Returns
999
+ -------
1000
+ cpoint : list
1001
+ Coordinates of attachment point.
1002
+
1003
+ """
1004
+ groups = core.getBondedAtoms(cidx)
1005
+ ccoords = core.getAtom(cidx).coords()
1006
+ # brute force search
1007
+ cpoint = []
1008
+ objopt = 0
1009
+ for itheta in range(1, 359, 1):
1010
+ for iphi in range(1, 179, 1):
1011
+ P = PointTranslateSph(ccoords, ccoords, [BL, itheta, iphi])
1012
+ dists = []
1013
+ for ig in groups:
1014
+ dists.append(distance(core.getAtomCoords(ig), P))
1015
+ obj = min(dists)
1016
+ if obj > objopt:
1017
+ objopt = obj
1018
+ cpoint = P
1019
+ return cpoint
1020
+
1021
+
1022
+ def findsmarts(lig3D: mol3D, smarts: List[str], catom: int) -> bool:
1023
+ """Checks if connecting atom of lig3D is part of SMARTS pattern.
1024
+
1025
+ Parameters
1026
+ ----------
1027
+ lig3D : OBMol
1028
+ OBMol class instance of ligand. Use convert2OBMol mol3D bound method to obtain it.
1029
+ smarts : list
1030
+ List of SMARTS patterns (strings).
1031
+ catom : int
1032
+ connecting atom of lig3D (zero based numbering).
1033
+
1034
+ Returns
1035
+ -------
1036
+ SMARTS_flag : bool
1037
+ SMARTS match flag. True if found, False if not.
1038
+
1039
+ """
1040
+ mall = []
1041
+ for smart in smarts:
1042
+ # initialize SMARTS matcher
1043
+ sm = openbabel.OBSmartsPattern()
1044
+ sm.Init(smart)
1045
+ sm.Match(lig3D)
1046
+ matches = list(sm.GetUMapList())
1047
+ # unpack tuple
1048
+ matches = [i for sub in matches for i in sub]
1049
+ for m in matches:
1050
+ if m not in mall:
1051
+ mall.append(m)
1052
+ if catom+1 in mall:
1053
+ return True
1054
+ else:
1055
+ return False
1056
+
1057
+
1058
+ def align_lig_centersym(corerefcoords, lig3D, atom0, core3D, EnableAutoLinearBend):
1059
+ """Aligns a ligand's center of symmetry along the metal-connecting atom axis
1060
+
1061
+ Parameters
1062
+ ----------
1063
+ corerefcoords : list
1064
+ Core reference coordinates.
1065
+ lig3D : mol3D
1066
+ mol3D class instance of the ligand.
1067
+ atom0 : int
1068
+ Ligand connecting atom index.
1069
+ core3D : mol3D
1070
+ mol3D instance of partially built complex.
1071
+ EnableAutoLinearBend : bool
1072
+ Flag for enabling automatic bending of linear ligands (e.g. superoxo).
1073
+
1074
+ Returns
1075
+ -------
1076
+ lig3D_aligned : mol3D
1077
+ mol3D class instance of aligned ligand.
1078
+
1079
+ """
1080
+ # rotate to align center of symmetry
1081
+ r0 = corerefcoords
1082
+ r1 = lig3D.getAtom(atom0).coords()
1083
+ lig3Db = mol3D()
1084
+ lig3Db.copymol3D(lig3D)
1085
+ auxmol = mol3D()
1086
+ for at in lig3D.getBondedAtoms(atom0):
1087
+ auxmol.addAtom(lig3D.getAtom(at))
1088
+ r2 = auxmol.centersym()
1089
+ theta, u = rotation_params(r0, r1, r2)
1090
+ # rotate around axis and get both images
1091
+ lig3D = rotate_around_axis(lig3D, r1, u, theta)
1092
+ lig3Db = rotate_around_axis(lig3Db, r1, u, theta-180)
1093
+ # compare shortest distances to core reference coordinates
1094
+ d2 = distance(r0, lig3D.centersym())
1095
+ d1 = distance(r0, lig3Db.centersym())
1096
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
1097
+ # additional rotation for bent terminal connecting atom:
1098
+ if auxmol.natoms == 1:
1099
+ if (distance(auxmol.getAtomCoords(0), lig3D.getAtomCoords(atom0))
1100
+ > 0.8*(auxmol.getAtom(0).rad + lig3D.getAtom(atom0).rad)
1101
+ and EnableAutoLinearBend):
1102
+ print('bending of linear terminal ligand')
1103
+ # warning: force field might overwrite this
1104
+ # warning: skipping this part because
1105
+ # we no longer understand it
1106
+ if False:
1107
+ globs = globalvars()
1108
+ r1 = lig3D.getAtom(atom0).coords()
1109
+ r2 = auxmol.getAtom(0).coords()
1110
+ theta, u = rotation_params([1, 1, 1], r1, r2)
1111
+ lig3D = rotate_around_axis(
1112
+ lig3D, r1, u, -1*globs.linearbentang)
1113
+ lig3D_aligned = mol3D()
1114
+ lig3D_aligned.copymol3D(lig3D)
1115
+ return lig3D_aligned
1116
+
1117
+
1118
+ def align_linear_pi_lig(corerefcoords, lig3D, atom0, ligpiatoms):
1119
+ """Aligns a linear pi ligand's connecting point to the metal-ligand axis.
1120
+
1121
+ Parameters
1122
+ ----------
1123
+ corerefcoords : list
1124
+ Core reference coordinates.
1125
+ lig3D : mol3D
1126
+ mol3D class instance of the ligand.
1127
+ atom0 : int
1128
+ Ligand connecting atom index.
1129
+ ligpiatoms : list
1130
+ List of ligand pi-connecting atom indices.
1131
+
1132
+ Returns
1133
+ -------
1134
+ lig3D_aligned : mol3D
1135
+ mol3D class instance of aligned ligand.
1136
+
1137
+ """
1138
+ # first rotate in the metal plane to ensure perpendicularity
1139
+ r0 = corerefcoords
1140
+ r1 = lig3D.getAtom(ligpiatoms[0]).coords()
1141
+ r2 = lig3D.getAtom(ligpiatoms[1]).coords()
1142
+ theta, u = rotation_params(r0, r1, r2)
1143
+ objfuncopt = 90
1144
+ # thetaopt = 0
1145
+ for theta in range(0, 360, 1):
1146
+ lig3D_tmp = mol3D()
1147
+ lig3D_tmp.copymol3D(lig3D)
1148
+ lig3D_tmp = rotate_around_axis(
1149
+ lig3D_tmp, lig3D_tmp.getAtom(atom0).coords(), u, theta)
1150
+ # objfunc = abs(vecangle(vecdiff(lig3D_tmp.getAtom(atom0).coords(),corerefcoords),
1151
+ # vecdiff(lig3D_tmp.getAtom(ligpiatoms[0]).coords(),
1152
+ # lig3D_tmp.getAtom(ligpiatoms[1]).coords())) - 90)
1153
+ objfunc = abs(distance(lig3D_tmp.getAtom(ligpiatoms[0]).coords(
1154
+ ), corerefcoords) - distance(lig3D_tmp.getAtom(ligpiatoms[1]).coords(), corerefcoords))
1155
+ if objfunc < objfuncopt:
1156
+ # thetaopt = theta
1157
+ objfuncopt = objfunc
1158
+ lig3Dopt = mol3D() # lig3Dopt = lig3D_tmp DOES NOT WORK!!!
1159
+ lig3Dopt.copymol3D(lig3D_tmp)
1160
+ lig3D = lig3Dopt
1161
+ # then rotate 90 degrees about the bond axis to further reduce steric repulsion
1162
+ r1 = lig3D.getAtom(ligpiatoms[0]).coords()
1163
+ r2 = lig3D.getAtom(ligpiatoms[1]).coords()
1164
+ u = vecdiff(r1, r2)
1165
+ lig3D_tmpa = mol3D()
1166
+ lig3D_tmpa.copymol3D(lig3D)
1167
+ lig3D_tmpa = rotate_around_axis(
1168
+ lig3D_tmpa, lig3D_tmpa.getAtom(atom0).coords(), u, 90)
1169
+ lig3D_tmpb = mol3D()
1170
+ lig3D_tmpb.copymol3D(lig3D)
1171
+ lig3D_tmpb = rotate_around_axis(
1172
+ lig3D_tmpb, lig3D_tmpb.getAtom(atom0).coords(), u, -90)
1173
+ d1 = distance(corerefcoords, lig3D_tmpa.centermass())
1174
+ d2 = distance(corerefcoords, lig3D_tmpb.centermass())
1175
+ # lig3D = lig3D if (d1 < d2) else lig3Db
1176
+ # pick the better structure
1177
+ lig3D = lig3D_tmpa if (d1 > d2) else lig3D_tmpb
1178
+ lig3D_aligned = mol3D()
1179
+ lig3D_aligned.copymol3D(lig3D)
1180
+ return lig3D_aligned
1181
+
1182
+
1183
+ def rotation_objective_func(rotations, lig3D, atom0, ligpiatoms, metal_lig_vec, directional_vectors):
1184
+ """Objective function for finding rotations that make an aromatic ring perpendicular to the metal-ligand vector.
1185
+
1186
+ Parameters
1187
+ ----------
1188
+ rotations : list
1189
+ Floats that indicate angles by which to rotate the ligand. Length is 3.
1190
+ lig3D : mol3D
1191
+ mol3D class instance of the ligand.
1192
+ atom0 : int
1193
+ Ligand connecting atom index. Here, refers to the fictitious atom in the center of the aromatic ring.
1194
+ ligpiatoms : list
1195
+ List of ligand pi-connecting atom indices.
1196
+ metal_lig_vec : np.array
1197
+ Vector from the metal to the fictitious atom in the center of the aromatic ring. Shape is (3,)
1198
+ directional_vectors : list
1199
+ Numpy arrays of the x-axis vector, y-axis vector, and z-axis vector. Length is 3.
1200
+
1201
+ Returns
1202
+ -------
1203
+ lig3D_aligned : mol3D
1204
+ mol3D class instance of aligned ligand.
1205
+
1206
+ """
1207
+ lig3D_tmp = mol3D()
1208
+ lig3D_tmp.copymol3D(lig3D)
1209
+
1210
+ for _i in range(3): # 3 axes of rotation
1211
+ # Three rotations
1212
+ rotate_around_axis(lig3D_tmp, lig3D_tmp.getAtom(atom0).coords(), directional_vectors[_i], rotations[_i])
1213
+
1214
+ # Get the best fit plane for the aromatic atoms.
1215
+ aromatic_coordinates = np.zeros((3, len(ligpiatoms)))
1216
+ for idx, _i in enumerate(ligpiatoms): # Iterate over the aromatic atoms
1217
+ current_coordinates = lig3D_tmp.getAtom(_i).coords()
1218
+
1219
+ for _j in range(3): # Iterate over the three dimensions of space
1220
+ aromatic_coordinates[_j, _i] = current_coordinates[_j]
1221
+
1222
+ normal_vector_plane = best_fit_plane(aromatic_coordinates) # plane formed by the aromatic ring atoms
1223
+
1224
+ # The roots for this objective function are to be found
1225
+ normalized_metal_lig_vec = metal_lig_vec / np.linalg.norm(metal_lig_vec)
1226
+ normalized_normal_vector_plane = normal_vector_plane / np.linalg.norm(normal_vector_plane)
1227
+ return normalized_metal_lig_vec - normalized_normal_vector_plane
1228
+
1229
+
1230
+ def align_pi_ring_lig(corerefcoords, lig3D, atom0, ligpiatoms, u):
1231
+ """Rotates the ligand such that the aromatic ring that bonds to the central metal
1232
+ is perpendicular to the vector from the metal to the fictitous atom in the center
1233
+ of the ring.
1234
+
1235
+ Parameters
1236
+ ----------
1237
+ corerefcoords : list
1238
+ Core reference coordinates. These are the coordinates of the central metal.
1239
+ lig3D : mol3D
1240
+ mol3D class instance of the ligand.
1241
+ atom0 : int
1242
+ Ligand connecting atom index. Here, refers to the fictitious atom in the
1243
+ center of the aromatic ring, since we have a ligand that coordinates
1244
+ through an aromatic ring.
1245
+ ligpiatoms : list
1246
+ List of ligand pi-connecting atom indices.
1247
+ u : list
1248
+ Vector from the metal to the fictitious atom in the center of the aromatic
1249
+ ring. Length is 3.
1250
+
1251
+ Returns
1252
+ -------
1253
+ lig3D : mol3D
1254
+ mol3D class instance of aligned ligand.
1255
+
1256
+ """
1257
+ x_vec = np.array([1., 0., 0.])
1258
+ y_vec = np.array([0., 1., 0.])
1259
+ z_vec = np.array([0., 0., 1.])
1260
+ directional_vectors = [x_vec, y_vec, z_vec]
1261
+
1262
+ # use a solver to find the rotations around the three vectors (x, y, z) required such that
1263
+ # the plane formed by the aromatic ring atoms is perpendicular to u
1264
+ from scipy.optimize import fsolve
1265
+ initial_guess = [0., 0., 0.]
1266
+ rotations = fsolve(rotation_objective_func, initial_guess,
1267
+ args=(lig3D, atom0, ligpiatoms, np.array(u), directional_vectors))
1268
+
1269
+ # rotate lig3D by the three rotations found
1270
+ for _i in range(3): # 3 axes of rotation
1271
+ rotate_around_axis(lig3D, lig3D.getAtom(atom0).coords(), directional_vectors[_i], rotations[_i])
1272
+
1273
+ return lig3D
1274
+
1275
+
1276
+ def check_rotate_linear_lig(corerefcoords, lig3D, atom0):
1277
+ """Checks if ligand has a linear coordination environment (e.g., OCO) and ensures perpendicularity to M-L axis
1278
+
1279
+ Parameters
1280
+ ----------
1281
+ corerefcoords : list
1282
+ Core reference coordinates.
1283
+ lig3D : mol3D
1284
+ mol3D class instance of the ligand.
1285
+ atom0 : int
1286
+ Ligand connecting atom index.
1287
+
1288
+ Returns
1289
+ -------
1290
+ lig3D_aligned : mol3D
1291
+ mol3D class instance of rotated ligand.
1292
+
1293
+ """
1294
+ auxm = mol3D()
1295
+ lig3D_aligned = mol3D()
1296
+ for at in lig3D.getBondedAtoms(atom0):
1297
+ auxm.addAtom(lig3D.getAtom(at))
1298
+ if auxm.natoms > 1:
1299
+ r0 = lig3D.getAtom(atom0).coords()
1300
+ r1 = auxm.getAtom(0).coords()
1301
+ r2 = auxm.getAtom(1).coords()
1302
+ if checkcolinear(r1, r0, r2):
1303
+ # rotate so that O-C-O bond is perpendicular to M-L axis
1304
+ theta, urot = rotation_params(r1, corerefcoords, r2)
1305
+ theta = vecangle(vecdiff(r0, corerefcoords), urot)
1306
+ lig3D = rotate_around_axis(lig3D, r0, urot, theta)
1307
+ lig3D_aligned.copymol3D(lig3D)
1308
+ return lig3D_aligned
1309
+
1310
+
1311
+ def check_rotate_symm_lig(corerefcoords, lig3D, atom0, core3D):
1312
+ """Aligns a ligand's center of symmetry along the metal-connecting atom axis
1313
+
1314
+ Parameters
1315
+ ----------
1316
+ corerefcoords : list
1317
+ Core reference coordinates.
1318
+ lig3D : mol3D
1319
+ mol3D class instance of the ligand.
1320
+ atom0 : int
1321
+ Ligand connecting atom index.
1322
+ core3D : mol3D
1323
+ mol3D instance of partially built complex.
1324
+
1325
+ Returns
1326
+ -------
1327
+ lig3D_aligned : mol3D
1328
+ mol3D class instance of rotated ligand.
1329
+
1330
+ """
1331
+ if distance(lig3D.getAtom(atom0).coords(), lig3D.centersym()) < 8.0e-2:
1332
+ at = lig3D.getBondedAtoms(atom0)
1333
+ r0 = lig3D.getAtom(atom0).coords()
1334
+ r1 = lig3D.getAtom(at[0]).coords()
1335
+ r2 = lig3D.getAtom(at[1]).coords()
1336
+ theta, u = rotation_params(r0, r1, r2)
1337
+ theta = vecangle(u, vecdiff(r0, corerefcoords))
1338
+ urot = np.cross(u, vecdiff(r0, corerefcoords))
1339
+ # rotate around axis and get both images
1340
+ lig3Db = mol3D()
1341
+ lig3Db.copymol3D(lig3D)
1342
+ lig3D = rotate_around_axis(lig3D, r0, urot, theta)
1343
+ lig3Db = rotate_around_axis(lig3Db, r0, urot, -theta)
1344
+ # compute shortest distances to core
1345
+ d2 = lig3D.mindist(core3D)
1346
+ d1 = lig3Db.mindist(core3D)
1347
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
1348
+ lig3D_aligned = mol3D()
1349
+ lig3D_aligned.copymol3D(lig3D)
1350
+ return lig3D_aligned
1351
+
1352
+
1353
+ def rotate_MLaxis_minimize_steric(corerefcoords, lig3D, atom0, core3D):
1354
+ """Rotates aligned ligand about M-L axis to minimize steric clashes with rest of complex
1355
+
1356
+ Parameters
1357
+ ----------
1358
+ corerefcoords : list
1359
+ Core reference coordinates.
1360
+ lig3D : mol3D
1361
+ mol3D class instance of the ligand.
1362
+ atom0 : int
1363
+ Ligand connecting atom index.
1364
+ core3D : mol3D
1365
+ mol3D instance of partially built complex.
1366
+
1367
+ Returns
1368
+ -------
1369
+ lig3D_aligned : mol3D
1370
+ mol3D class instance of rotated ligand.
1371
+
1372
+ """
1373
+ r1 = lig3D.getAtom(atom0).coords()
1374
+ u = vecdiff(r1, corerefcoords)
1375
+ dtheta = 2
1376
+ optmax = -9999
1377
+ totiters = 0
1378
+ lig3Db = mol3D()
1379
+ lig3Db.copymol3D(lig3D)
1380
+ # maximize a combination of minimum distance between atoms and center of mass distance
1381
+ while totiters < 180:
1382
+ lig3D = rotate_around_axis(lig3D, r1, u, dtheta)
1383
+ d0 = lig3D.mindist(core3D) # shortest distance
1384
+ d0cm = lig3D.distance(core3D) # center of mass distance
1385
+ iteropt = d0cm+10*np.log(d0)
1386
+ if (iteropt > optmax): # if better conformation, keep
1387
+ lig3Db = mol3D()
1388
+ lig3Db.copymol3D(lig3D)
1389
+ optmax = iteropt
1390
+ totiters += 1
1391
+ lig3D = lig3Db
1392
+ lig3D_aligned = mol3D()
1393
+ lig3D_aligned.copymol3D(lig3D)
1394
+ return lig3D_aligned
1395
+
1396
+
1397
+ def rotate_catom_fix_Hs(lig3D, catoms, n, mcoords, core3D):
1398
+ """Rotates a connecting atom of a multidentate ligand to improve H atom placement.
1399
+ There are separate routines for terminal connecting atoms and intermediate connecting atoms.
1400
+
1401
+ Parameters
1402
+ ----------
1403
+ lig3D : mol3D
1404
+ mol3D class instance of the ligand.
1405
+ catoms : list
1406
+ List of ligand connecting atom indices.
1407
+ n : int
1408
+ Index of connecting atom.
1409
+ mcoords : list
1410
+ Coordinates of a core reference (usually a metal).
1411
+ core3D : mol3D
1412
+ mol3D of partially built complex.
1413
+
1414
+ Returns
1415
+ -------
1416
+ lig3D_aligned : mol3D
1417
+ mol3D class instance of rotated ligand.
1418
+
1419
+ """
1420
+ # isolate fragment to be rotated
1421
+ confrag3D = mol3D()
1422
+ confragatomlist = []
1423
+ danglinggroup = []
1424
+ catoms_other = catoms[:]
1425
+ catoms_other.pop(n)
1426
+ # add connecting atom
1427
+ confrag3D.addAtom(lig3D.getAtom(catoms[n]))
1428
+ confragatomlist.append(catoms[n])
1429
+ # add all Hs bound to connecting atom
1430
+ for ii in lig3D.getHsbyIndex(catoms[n]):
1431
+ confrag3D.addAtom(lig3D.getAtom(ii))
1432
+ confragatomlist.append(ii)
1433
+ # add dangling groups
1434
+ anchoratoms = []
1435
+ for atom in lig3D.getBondedAtomsnotH(catoms[n]):
1436
+ subm = lig3D.findsubMol(atom, catoms[n])
1437
+ if len(list(set(subm).intersection(catoms_other))) == 0:
1438
+ danglinggroup = subm
1439
+ else:
1440
+ # bridginggroup = subm
1441
+ if list(set(subm).intersection(lig3D.getBondedAtoms(catoms[n])))[0] not in anchoratoms:
1442
+ anchoratoms.append(list(set(subm).intersection(
1443
+ lig3D.getBondedAtoms(catoms[n])))[0])
1444
+
1445
+ if not len(anchoratoms) == 1:
1446
+ for atom in danglinggroup:
1447
+ confrag3D.addAtom(lig3D.getAtom(atom))
1448
+ confragatomlist.append(atom)
1449
+ if confrag3D.natoms > 1:
1450
+ # terminal connecting atom
1451
+ confrag3Dtmp = mol3D()
1452
+ confrag3Dtmp.copymol3D(confrag3D)
1453
+ if len(anchoratoms) == 1:
1454
+ anchoratom = anchoratoms[0]
1455
+ anchor = lig3D.getAtomCoords(anchoratom)
1456
+ if not checkcolinear(anchor, confrag3D.getAtomCoords(0), confrag3D.getAtomCoords(1)):
1457
+ refpt = confrag3D.getAtomCoords(0)
1458
+ u = vecdiff(refpt, anchor)
1459
+ dtheta = 5
1460
+ # objs = []
1461
+ objopt = 0
1462
+ thetaopt = 0
1463
+ # localmaxs = []
1464
+ thetas = list(range(0, 360, dtheta))
1465
+ for theta in thetas:
1466
+ confrag3Dtmp = rotate_around_axis(
1467
+ confrag3Dtmp, refpt, u, dtheta)
1468
+ auxmol1 = mol3D()
1469
+ auxmol1.addAtom(confrag3Dtmp.getAtom(0))
1470
+ for at in confrag3Dtmp.getBondedAtoms(0):
1471
+ auxmol1.addAtom(confrag3Dtmp.getAtom(at))
1472
+ auxmol1.addAtom(lig3D.getAtom(anchoratom))
1473
+ auxmol2 = mol3D()
1474
+ auxmol2.copymol3D(confrag3Dtmp)
1475
+ # objs.append(distance(mcoords,auxmol.centersym()))
1476
+ if auxmol2.natoms > 3:
1477
+ obj = auxmol2.mindisttopoint(mcoords)
1478
+ else:
1479
+ obj = distance(mcoords, auxmol1.centersym())
1480
+ if obj > objopt:
1481
+ objopt = obj
1482
+ thetaopt = theta
1483
+ # for i,obj in enumerate(objs):
1484
+ # try:
1485
+ # if objs[i] > objs[i-1] and objs[i] > objs[i+1]:
1486
+ # localmaxs.append(thetas[i])
1487
+ # except IndexError:
1488
+ # pass
1489
+ # in future, compare multiple local maxima
1490
+ # if localmaxs == []:
1491
+ # localmaxs = [0]
1492
+ confrag3D = rotate_around_axis(confrag3D, refpt, u, thetaopt)
1493
+ # confrag3D = rotate_around_axis(confrag3D,refpt,u,localmaxs[0])
1494
+ # non-terminal connecting atom
1495
+ elif len(anchoratoms) == 2:
1496
+ refpt = confrag3D.getAtomCoords(0)
1497
+ anchorcoords1 = lig3D.getAtomCoords(anchoratoms[0])
1498
+ anchorcoords2 = lig3D.getAtomCoords(anchoratoms[1])
1499
+ u = vecdiff(anchorcoords1, anchorcoords2)
1500
+ dtheta = 5
1501
+ objs = []
1502
+ localmaxs = []
1503
+ thetas = list(range(0, 360, dtheta))
1504
+ for _ in thetas:
1505
+ confrag3Dtmp = rotate_around_axis(
1506
+ confrag3Dtmp, refpt, u, dtheta)
1507
+ newHcoords = confrag3Dtmp.getAtomCoords(1)
1508
+ objs.append(distance(newHcoords, anchorcoords1)+distance(
1509
+ newHcoords, anchorcoords2)+distance(newHcoords, mcoords))
1510
+ for i, obj in enumerate(objs):
1511
+ try:
1512
+ if objs[i] > objs[i-1] and objs[i] > objs[i+1]:
1513
+ localmaxs.append(thetas[i])
1514
+ except IndexError:
1515
+ pass
1516
+ if localmaxs == []:
1517
+ localmaxs = [0]
1518
+ confrag3D = rotate_around_axis(
1519
+ confrag3D, refpt, u, localmaxs[0])
1520
+ for i, atom in enumerate(confragatomlist):
1521
+ lig3D.getAtom(atom).setcoords(confrag3D.getAtomCoords(i))
1522
+ lig3D_aligned = mol3D()
1523
+ lig3D_aligned.copymol3D(lig3D)
1524
+ return lig3D_aligned
1525
+
1526
+
1527
+ def rotate_catoms_fix_Hs(lig3D: mol3D, catoms: List[int], mcoords, core3D: mol3D) -> mol3D:
1528
+ """Rotates connecting atoms of multidentate ligands to improve H atom placement.
1529
+ Loops over rotate_catom_fix_Hs().
1530
+
1531
+ Parameters
1532
+ ----------
1533
+ lig3D : mol3D
1534
+ mol3D class instance of the ligand.
1535
+ catoms : list
1536
+ List of ligand connecting atom indices.
1537
+ mcoords : list
1538
+ Coordinates of a core reference (usually a metal).
1539
+ core3D : mol3D
1540
+ mol3D of partially built complex.
1541
+
1542
+ Returns
1543
+ -------
1544
+ lig3D_aligned : mol3D
1545
+ mol3D class instance of rotated ligand.
1546
+
1547
+ """
1548
+ for i, n in enumerate(catoms):
1549
+ # if len(lig3D.getHsbyIndex(n)) > 0:
1550
+ lig3D = rotate_catom_fix_Hs(lig3D, catoms, i, mcoords, core3D)
1551
+ lig3D_aligned = mol3D()
1552
+ lig3D_aligned.copymol3D(lig3D)
1553
+ return lig3D_aligned
1554
+
1555
+
1556
+ def get_MLdist(metal: atom3D, oxstate: str, spin: str, lig3D: mol3D,
1557
+ atom0: int, ligand: str, MLb: List[str], i: int,
1558
+ ANN_flag: bool, ANN_bondl: float, this_diag: run_diag,
1559
+ MLbonds: dict, debug: bool = False) -> float:
1560
+ """Gets target M-L distance from desired source (custom, sum cov rad or ANN).
1561
+ Aligns a monodentate ligand to core connecting atom coordinates.
1562
+
1563
+ Parameters
1564
+ ----------
1565
+ args : Namespace
1566
+ Namespace of arguments.
1567
+ lig3D : mol3D
1568
+ mol3D class instance of the ligand.
1569
+ atom0 : int
1570
+ Ligand connecting atom index.
1571
+ ligand : str
1572
+ Name of ligand for dictionary lookup.
1573
+ metal : atom3D
1574
+ atom3D class instance of the first atom (usually a metal).
1575
+ MLb : float
1576
+ Custom M-L bond length (if any).
1577
+ i : int
1578
+ Ligand index number.
1579
+ ANN_flag : bool
1580
+ Flag for ANN activation.
1581
+ ANN_bondl : float
1582
+ ANN predicted M-L bond length.
1583
+ this_diag : run_diag
1584
+ run_diag instance for ANN diagnostic object.
1585
+ MLbonds : dict
1586
+ M-L bond dictionary.
1587
+
1588
+ Returns
1589
+ -------
1590
+ bondl : float
1591
+ M-L bond length in angstroms.
1592
+
1593
+ """
1594
+ # first check for user-specified distances and use them
1595
+ # print(MLb, MLb[i])
1596
+ if (MLb and MLb[i]) and ("F" not in MLb[i]):
1597
+ print('using user-specified M-L distances')
1598
+ if 'c' in MLb[i].lower():
1599
+ bondl = metal.rad + lig3D.getAtom(atom0).rad
1600
+ else:
1601
+ bondl = float(MLb[i])
1602
+ else:
1603
+ # otherwise, check for exact DB match
1604
+ bondl, exact_match = get_MLdist_database(
1605
+ metal, oxstate, spin, lig3D, atom0, ligand, MLbonds, debug)
1606
+ try:
1607
+ this_diag.set_dict_bl(bondl)
1608
+ except AttributeError:
1609
+ pass
1610
+ if not exact_match and ANN_flag:
1611
+ # if no exact match found and ANN enabled, use it
1612
+ if debug:
1613
+ print('no exact M-L match in DB, using ANN')
1614
+ bondl = ANN_bondl
1615
+ elif exact_match:
1616
+ print('using exact M-L match from DB')
1617
+ else:
1618
+ print('Warning: ANN not active and exact M-L match not found in '
1619
+ 'DB, distance may not be accurate')
1620
+ print(f'using partial DB match distance of {bondl}')
1621
+ return bondl
1622
+
1623
+
1624
+ def get_MLdist_database(metal: atom3D, oxstate: str, spin: str, lig3D: mol3D,
1625
+ atom0: int, ligand: str, MLbonds: dict,
1626
+ debug=False) -> Tuple[float, bool]:
1627
+ """Gets target M-L distance from desired source (custom, sum cov rad or ANN).
1628
+ Aligns a monodentate ligand to core connecting atom coordinates.
1629
+
1630
+ Parameters
1631
+ ----------
1632
+ metal : atom3D
1633
+ atom3D class instance of the first atom (usually a metal).
1634
+ oxstate: str:
1635
+ oxidation state
1636
+ spin : str
1637
+ spin state
1638
+ lig3D : mol3D
1639
+ mol3D class instance of the ligand.
1640
+ atom0 : int
1641
+ Ligand connecting atom index.
1642
+ ligand : str
1643
+ Name of ligand for dictionary lookup.
1644
+ MLbonds : dict
1645
+ M-L bond dictionary.
1646
+
1647
+ Returns
1648
+ -------
1649
+ bondl : float
1650
+ M-L bond length in angstroms.
1651
+ exact_match : bool
1652
+ Flag for database match.
1653
+ """
1654
+ # check for roman letters in oxstate
1655
+ if oxstate: # if defined put oxstate in keys
1656
+ if oxstate in romans.keys():
1657
+ oxs = romans[oxstate]
1658
+ else:
1659
+ oxs = oxstate
1660
+ else:
1661
+ oxs = '-'
1662
+ # check for spin multiplicity
1663
+ spin = spin if spin else '-'
1664
+ # Build possible keys in descending order of specificity
1665
+ key = []
1666
+ key.append((metal.sym, oxs, spin, lig3D.getAtom(atom0).sym, ligand))
1667
+ # disregard exact ligand
1668
+ key.append((metal.sym, oxs, spin, lig3D.getAtom(atom0).sym, '-'))
1669
+ # disregard oxstate/spin
1670
+ key.append((metal.sym, '-', '-', lig3D.getAtom(atom0).sym, ligand))
1671
+ # else just consider bonding atom
1672
+ key.append((metal.sym, '-', '-', lig3D.getAtom(atom0).sym, '-'))
1673
+ exact_match = False
1674
+ # search for data
1675
+ for kk in key:
1676
+ if kk in MLbonds.keys(): # if exact key in dictionary
1677
+ bondl = float(MLbonds[kk])
1678
+ if (kk == ((metal.sym, oxs, spin, lig3D.getAtom(atom0).sym, ligand))): # exact match
1679
+ exact_match = True
1680
+ break
1681
+ else: # If no match in dict (no break encountered):
1682
+ # last resort sum of covalent radii
1683
+ bondl = metal.rad + lig3D.getAtom(atom0).rad
1684
+ if debug:
1685
+ print(f'ms default distance is {bondl}')
1686
+ return bondl, exact_match
1687
+
1688
+
1689
+ def get_batoms(args, batslist, ligsused):
1690
+ """Get backbone atoms from template.
1691
+
1692
+ Parameters
1693
+ ----------
1694
+ args : Namespace
1695
+ Namespace of arguments.
1696
+ batslist : list
1697
+ List of backbone connecting atoms for each ligand.
1698
+ ligsused : int
1699
+ Number of ligands placed.
1700
+
1701
+ Returns
1702
+ -------
1703
+ batoms : list
1704
+ Backbone connecting atoms for ligand.
1705
+
1706
+ """
1707
+ batoms = batslist[ligsused]
1708
+ if len(batoms) < 1:
1709
+ emsg = 'Connecting all ligands is not possible. Check your input!'
1710
+ if args.gui:
1711
+ from Classes.mWidgets import mQDialogWarn
1712
+ qqb = mQDialogWarn('Warning', emsg)
1713
+ qqb.setParent(args.gui.wmain)
1714
+ return batoms
1715
+
1716
+
1717
+ def align_dent2_catom2_coarse(args, lig3D, core3D, catoms, r1, r0, m3D, batoms, corerefcoords):
1718
+ """Crude rotations to improve alignment of the 2nd connecting atom of a bidentate substrate.
1719
+
1720
+ Parameters
1721
+ ----------
1722
+ args : Namespace
1723
+ Namespace of arguments.
1724
+ lig3D : mol3D
1725
+ mol3D class instance of the ligand.
1726
+ core3D : mol3D
1727
+ mol3D class instance of partially built complex.
1728
+ catoms : list
1729
+ List of ligand connecting atom indices.
1730
+ r1 : list
1731
+ Coordinates of ligand first connecting atom.
1732
+ r0 : list
1733
+ Coordinates of core reference point.
1734
+ m3D : mol3D
1735
+ mol3D class instance of backbone template.
1736
+ batoms : list
1737
+ List of backbone atom indices.
1738
+ corerefcoords : list
1739
+ Coordinates of core reference atom.
1740
+
1741
+ Returns
1742
+ -------
1743
+ lig3D_aligned : mol3D
1744
+ mol3D class instance of aligned ligand.
1745
+ r1b : list
1746
+ Coordinates of second backbone point.
1747
+
1748
+ """
1749
+ r21 = [a-b for a, b in zip(lig3D.getAtom(catoms[1]).coords(), r1)]
1750
+ r21n = [a-b for a, b in zip(m3D.getAtom(batoms[1]).coords(), r1)]
1751
+ if (norm(r21)*norm(r21n)) > 1e-8:
1752
+ theta = 180*np.arccos(np.dot(r21, r21n)/(norm(r21)*norm(r21n)))/np.pi
1753
+ else:
1754
+ theta = 0.0
1755
+ u = np.cross(r21, r21n)
1756
+ lig3Db = mol3D()
1757
+ lig3Db.copymol3D(lig3D)
1758
+ # rotate around axis and get both images
1759
+ lig3D = rotate_around_axis(lig3D, r1, u, theta)
1760
+ lig3Db = rotate_around_axis(lig3Db, r1, u, theta-180)
1761
+ d1 = distance(lig3D.getAtom(
1762
+ catoms[1]).coords(), m3D.getAtom(batoms[1]).coords())
1763
+ d2 = distance(lig3Db.getAtom(
1764
+ catoms[1]).coords(), m3D.getAtom(batoms[1]).coords())
1765
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
1766
+ # flip if overlap
1767
+ r0l = lig3D.getAtom(catoms[0]).coords()
1768
+ r1l = lig3D.getAtom(catoms[1]).coords()
1769
+ md = min(distance(r0l, corerefcoords), distance(r1l, corerefcoords))
1770
+ if lig3D.mindist(core3D) < md:
1771
+ lig3D = rotate_around_axis(lig3D, r0l, vecdiff(r1l, r0l), 180.0)
1772
+ # correct plane
1773
+ # r0b = m3D.getAtom(batoms[0]).coords()
1774
+ r1b = m3D.getAtom(batoms[1]).coords()
1775
+ r0l = lig3D.getAtom(catoms[0]).coords()
1776
+ r1l = lig3D.getAtom(catoms[1]).coords()
1777
+ # rm = lig3D.centermass()
1778
+ urot = vecdiff(r1l, r0l)
1779
+ # theta,ub = rotation_params(corerefcoords,r0b,r1b)
1780
+ # theta,ul = rotation_params(rm,r0l,r1l)
1781
+ # if (norm(ub)*norm(ul)) > 1e-8:
1782
+ # theta = 180*np.arccos(np.dot(ub,ul)/(norm(ub)*norm(ul)))/pi-180.0
1783
+ # else:
1784
+ # theta = 0.0
1785
+ # rotate around axis
1786
+ objopt = 0
1787
+ for theta in range(0, 360, 5):
1788
+ lig3D_tmp = mol3D()
1789
+ lig3D_tmp.copymol3D(lig3D)
1790
+ lig3D_tmp = rotate_around_axis(lig3D_tmp, r1, urot, theta)
1791
+ lig3D_tmp2 = mol3D()
1792
+ lig3D_tmp2.copymol3D(lig3D_tmp)
1793
+ H1 = lig3D_tmp2.getBondedAtomsH(catoms[1])
1794
+ H2 = lig3D_tmp2.getBondedAtomsH(catoms[0])
1795
+ lig3D_tmp2.deleteatoms([catoms[1]]+[catoms[0]]+H1+H2)
1796
+ obj = lig3D_tmp2.mindisttopoint(corerefcoords)
1797
+ if obj > objopt:
1798
+ objopt = obj
1799
+ lig3Dopt = mol3D()
1800
+ lig3Dopt.copymol3D(lig3D_tmp)
1801
+ lig3D = mol3D()
1802
+ lig3D.copymol3D(lig3Dopt)
1803
+ tmp3D = mol3D()
1804
+ tmp3D.copymol3D(m3D)
1805
+ tmp3D.combine(lig3D)
1806
+ # tmp3D.writexyz('new')
1807
+ # lig3Db = mol3D()
1808
+ # lig3Db.copymol3D(lig3D)
1809
+ # lig3D = rotate_around_axis(lig3D,r1,urot,theta)
1810
+ # lig3Db = rotate_around_axis(lig3Db,r1,urot,-theta)
1811
+ # select best
1812
+
1813
+ # The following block was commented because ub is undefinded after
1814
+ # someone previously commented out other parts of this function.
1815
+ # Note: this is not a "fix" of the problem and just the simplest
1816
+ # solution to stay consistent with previous behavior.
1817
+ # try:
1818
+ # rm0, rm1 = lig3D.centermass(), lig3Db.centermass()
1819
+ # theta, ul0 = rotation_params(rm0, r0l, r1l)
1820
+ # theta, ul1 = rotation_params(rm1, r0l, r1l)
1821
+ # th0 = 180*np.arccos(np.dot(ub, ul0)/(norm(ub)*norm(ul0)))/pi
1822
+ # th0 = min(abs(th0), abs(180-th0))
1823
+ # th1 = 180*np.arccos(np.dot(ub, ul1)/(norm(ub)*norm(ul1)))/pi
1824
+ # th1 = min(abs(th1), abs(180-th1))
1825
+ # lig3D = lig3D if th0 < th1 else lig3Db
1826
+ # except:
1827
+ # pass
1828
+ lig3D_aligned = mol3D()
1829
+ lig3D_aligned.copymol3D(lig3D)
1830
+ return lig3D_aligned, r1b
1831
+
1832
+
1833
+ def align_dent2_catom2_refined(args, lig3D, catoms, bondl, r1, r0, core3D, rtarget, coreref, MLoptbds):
1834
+ """Aligns second connecting atom of a bidentate ligand to balance ligand strain and the desired coordination environment.
1835
+
1836
+ Parameters
1837
+ ----------
1838
+ args : Namespace
1839
+ Namespace of arguments.
1840
+ lig3D : mol3D
1841
+ mol3D class instance of the ligand.
1842
+ catoms : list
1843
+ List of ligand connecting atom indices.
1844
+ bondl : float
1845
+ Target M-L bond length.
1846
+ r1 : list
1847
+ Coordinates of ligand first connecting atom.
1848
+ r0 : list
1849
+ Coordinates of core reference point.
1850
+ core3D : mol3D
1851
+ mol3D class instance of partially built complex.
1852
+ rtarget : list
1853
+ Coordinates of target point for second connecting atom.
1854
+ coreref : atom3D
1855
+ atom3D of core reference atom.
1856
+ MLoptbds : list
1857
+ List of final M-L bond lengths.
1858
+
1859
+ Returns
1860
+ -------
1861
+ lig3D_aligned : mol3D
1862
+ mol3D class instance of aligned ligand.
1863
+
1864
+ """
1865
+ # compute starting ligand FF energy for later comparison
1866
+ corerefcoords = coreref.coords()
1867
+ dr = vecdiff(rtarget, lig3D.getAtom(catoms[1]).coords())
1868
+ cutoff = 5 # energy threshold for ligand strain, kcal/mol
1869
+ lig3Dtmp = mol3D()
1870
+ lig3Dtmp.copymol3D(lig3D)
1871
+ lig3Dtmp, en_start = ffopt(
1872
+ args.ff, lig3Dtmp, [], 1, [], False, [], 200, debug=args.debug)
1873
+ # take steps between current ligand position and ideal position on backbone
1874
+ nsteps = 20
1875
+ ddr = [di/nsteps for di in dr]
1876
+ ens = []
1877
+ finished = False
1878
+ relax = False
1879
+ while True:
1880
+ lig3Dtmp = mol3D()
1881
+ lig3Dtmp.copymol3D(lig3D)
1882
+ for ii in range(0, nsteps):
1883
+ lig3Dtmp, enl = ffopt(args.ff, lig3Dtmp, [], 1, [
1884
+ catoms[0], catoms[1]], False, [], 'Adaptive', debug=args.debug)
1885
+ ens.append(enl)
1886
+ lig3Dtmp.getAtom(catoms[1]).translate(ddr)
1887
+ # once the ligand strain energy becomes too high, stop and accept ligand position
1888
+ # or if the ideal coordinating point is reached without reaching the strain energy cutoff, stop
1889
+ if (ens[-1] - ens[0] > cutoff) or (ii == nsteps-1):
1890
+ r0, r1 = lig3Dtmp.getAtomCoords(
1891
+ catoms[0]), lig3Dtmp.getAtomCoords(catoms[1])
1892
+ r01 = distance(r0, r1)
1893
+ try:
1894
+ # but if ligand still cannot be aligned, instead force
1895
+ # alignment with a huge cutoff and then relax later
1896
+ theta1 = 180*np.arccos(0.5*r01/bondl)/np.pi
1897
+ except AssertionError:
1898
+ # To whoever encounters this: Please replace AssertionError
1899
+ # with whatever we are actually trying to except. I am
1900
+ # pretty sure that np.arccos does not raise Exceptions.
1901
+ # RM 2022/02/17
1902
+ print('Forcing alignment...')
1903
+ cutoff += 5000000
1904
+ relax = True
1905
+ break
1906
+ theta2 = vecangle(vecdiff(r1, r0), vecdiff(corerefcoords, r0))
1907
+ dtheta = theta2-theta1
1908
+ theta, urot = rotation_params(corerefcoords, r0, r1)
1909
+ # rotate so that it matches bond
1910
+ lig3Dtmp = rotate_around_axis(lig3Dtmp, r0, urot, -dtheta)
1911
+ finished = True
1912
+ break
1913
+ if finished:
1914
+ break
1915
+ # for long linear ligand chains, this procedure might produce the wrong ligand curvature. If so, reflect about M-L plane
1916
+ lig3Dtmpb = mol3D()
1917
+ lig3Dtmpb.copymol3D(lig3Dtmp)
1918
+ lig3Dtmpb = reflect_through_plane(lig3Dtmpb, vecdiff(midpt(lig3Dtmpb.getAtom(catoms[0]).coords(
1919
+ ), lig3Dtmpb.getAtom(catoms[1]).coords()), corerefcoords), lig3Dtmpb.getAtom(catoms[0]).coords())
1920
+ lig3Dtmp = lig3Dtmpb if lig3Dtmp.mindist(
1921
+ core3D) < lig3Dtmpb.mindist(core3D) else lig3Dtmp
1922
+ if relax:
1923
+ # Relax the ligand
1924
+ lig3Dtmp, enl = ffopt(args.ff, lig3Dtmp, [catoms[1]], 2, [
1925
+ catoms[0]], False, MLoptbds[-2:-1], 200, debug=args.debug)
1926
+ lig3Dtmp.deleteatom(lig3Dtmp.natoms-1)
1927
+ lig3Dtmp, en_final = ffopt(
1928
+ args.ff, lig3Dtmp, [], 1, [], False, [], 0, debug=args.debug)
1929
+ if en_final - en_start > 20:
1930
+ print(('Warning: Complex may be strained. Ligand strain energy (kcal/mol) = ' +
1931
+ str(en_final - en_start)))
1932
+ lig3D_aligned = mol3D()
1933
+ lig3D_aligned.copymol3D(lig3Dtmp)
1934
+ return lig3D_aligned
1935
+
1936
+
1937
+ def align_dent1_lig(args, cpoint, core3D, coreref, ligand, lig3D, catoms,
1938
+ rempi=False, ligpiatoms=[], MLb=[], ANN_flag=False,
1939
+ ANN_bondl: float = np.nan, this_diag=0, MLbonds=dict(),
1940
+ MLoptbds: Optional[List[float]] = None, i: int = 0,
1941
+ EnableAutoLinearBend=True) -> Tuple[mol3D, List[float]]:
1942
+ """Aligns a monodentate ligand to core connecting atom coordinates.
1943
+
1944
+ Parameters
1945
+ ----------
1946
+ args : Namespace
1947
+ Namespace of arguments.
1948
+ cpoint : atom3D
1949
+ atom3D class instance containing backbone connecting point.
1950
+ core3D : mol3D
1951
+ mol3D class instance of partially built complex.
1952
+ coreref : atom3D
1953
+ atom3D of core reference atom.
1954
+ ligand : str
1955
+ Name of ligand for dictionary lookup.
1956
+ lig3D : mol3D
1957
+ mol3D class instance of the ligand.
1958
+ catoms : list
1959
+ List of ligand connecting atom indices.
1960
+ rempi : bool, optional
1961
+ Flag for pi-coordinating ligand. Default is False.
1962
+ ligpiatoms : list, optional
1963
+ List of pi-coordinating atom indices in ligand. Default is empty.
1964
+ MLb : list, optional
1965
+ Custom M-L bond length (if any). Default is empty.
1966
+ ANN_flag : bool, optional
1967
+ Flag for ANN activation. Default is False.
1968
+ this_diag : run_diag, optional
1969
+ ANN run_diag class instance. Default is 0.
1970
+ MLbonds : dict, optional
1971
+ M-L bond dictionary. Default is empty.
1972
+ MLoptbds : list, optional
1973
+ List of final M-L bond lengths. Default is None.
1974
+ i : int, optional
1975
+ Ligand serial number. Default is 0.
1976
+ EnableAutoLinearBend : bool, optional
1977
+ Flag for enabling automatic bending of linear ligands (e.g. superoxo).
1978
+
1979
+ Returns
1980
+ -------
1981
+ lig3D_aligned : mol3D
1982
+ mol3D class instance of aligned ligand.
1983
+ MLoptbds : list
1984
+ Updated list of metal ligand bonds.
1985
+
1986
+ """
1987
+ if MLoptbds is None:
1988
+ MLoptbds = []
1989
+
1990
+ corerefcoords = coreref.coords()
1991
+ # connection atom in lig3D
1992
+ atom0 = catoms[0]
1993
+ # translate ligand to overlap with backbone connecting point
1994
+ lig3D.alignmol(lig3D.getAtom(atom0), cpoint)
1995
+ # determine bond length (database/cov rad/ANN)
1996
+ bondl = get_MLdist(coreref, args.oxstate, args.spin, lig3D, atom0, ligand,
1997
+ MLb, i, ANN_flag, ANN_bondl, this_diag, MLbonds, args.debug)
1998
+ MLoptbds.append(bondl)
1999
+ # align ligand to correct M-L distance
2000
+ u = vecdiff(cpoint.coords(), corerefcoords)
2001
+ lig3D = aligntoaxis2(lig3D, cpoint.coords(), corerefcoords, u, bondl)
2002
+ if rempi: # pi-coordinating ligand
2003
+ if len(ligpiatoms) == 2:
2004
+ # align linear (non-arom.) pi-coordinating ligand
2005
+ lig3D = align_linear_pi_lig(corerefcoords, lig3D, atom0, ligpiatoms)
2006
+ else: # 5 and 6 membered rings dealt with here
2007
+ lig3D = align_pi_ring_lig(corerefcoords, lig3D, atom0, ligpiatoms, u)
2008
+ elif lig3D.natoms > 1:
2009
+ # align ligand center of symmetry
2010
+ lig3D = align_lig_centersym(
2011
+ corerefcoords, lig3D, atom0, core3D, EnableAutoLinearBend)
2012
+ if lig3D.natoms > 2:
2013
+ # check for linear molecule and align
2014
+ lig3D = check_rotate_linear_lig(corerefcoords, lig3D, atom0)
2015
+ # check for symmetric molecule
2016
+ lig3D = check_rotate_symm_lig(corerefcoords, lig3D, atom0, core3D)
2017
+ # rotate around M-L axis to minimize steric repulsion
2018
+ lig3D = rotate_MLaxis_minimize_steric(
2019
+ corerefcoords, lig3D, atom0, core3D)
2020
+ lig3D_aligned = mol3D()
2021
+ lig3D_aligned.copymol3D(lig3D)
2022
+ return lig3D_aligned, MLoptbds
2023
+
2024
+
2025
+ def align_dent2_lig(args, cpoint, batoms, m3D, core3D, coreref, ligand, lig3D,
2026
+ catoms, MLb, ANN_flag, ANN_bondl: float, this_diag, MLbonds,
2027
+ MLoptbds: List[float], frozenats: List[int], i: int
2028
+ ) -> Tuple[mol3D, List[int], List[float]]:
2029
+ """Aligns a bidentate ligand to core connecting atom coordinates.
2030
+
2031
+ Parameters
2032
+ ----------
2033
+ args : Namespace
2034
+ Namespace of arguments.
2035
+ cpoint : atom3D
2036
+ atom3D class instance containing backbone connecting point.
2037
+ batoms : list
2038
+ List of backbone atom indices.
2039
+ m3D : mol3D
2040
+ mol3D of backbone template.
2041
+ core3D : mol3D
2042
+ mol3D class instance of partially built complex.
2043
+ coreref : atom3D
2044
+ atom3D of core reference atom.
2045
+ ligand : str
2046
+ Name of ligand for dictionary lookup.
2047
+ lig3D : mol3D
2048
+ mol3D class instance of the ligand.
2049
+ catoms : list
2050
+ List of ligand connecting atom indices.
2051
+ MLb : list
2052
+ Custom M-L bond length (if any).
2053
+ ANN_flag : bool
2054
+ Flag for ANN activation.
2055
+ ANN_bondl : list
2056
+ List of ANN predicted bond lengths.
2057
+ this_diag : run_diag
2058
+ ANN run_diag class instance.
2059
+ MLbonds : dict
2060
+ M-L bond dictionary.
2061
+ MLoptbds : list
2062
+ List of final M-L bond lengths.
2063
+ frozenats : list
2064
+ List of atoms frozen in FF optimization.
2065
+ i : int, optional
2066
+ Ligand serial number. Default is 0.
2067
+
2068
+ Returns
2069
+ -------
2070
+ lig3D_aligned : mol3D
2071
+ mol3D class instance of aligned ligand.
2072
+ frozenats : list
2073
+ List of frozen atoms.
2074
+ MLoptbds : list
2075
+ Updated list of metal ligand bonds.
2076
+
2077
+ """
2078
+ corerefcoords = coreref.coords()
2079
+ r0 = corerefcoords
2080
+ # get cis conformer by rotating rotatable bonds
2081
+ # lig3D = find_rotate_rotatable_bond(lig3D,catoms)
2082
+ # connection atom
2083
+ atom0 = catoms[0]
2084
+ # translate ligand to match first connecting atom to backbone connecting point
2085
+ lig3D.alignmol(lig3D.getAtom(atom0), cpoint)
2086
+ r1 = lig3D.getAtom(atom0).coords()
2087
+ # Crude rotations to bring the 2nd connecting atom closer to its ideal location
2088
+ lig3D, r1b = align_dent2_catom2_coarse(
2089
+ args, lig3D, core3D, catoms, r1, r0, m3D, batoms, corerefcoords)
2090
+ # get bond length
2091
+ bondl = get_MLdist(coreref, args.oxstate, args.spin, lig3D, atom0, ligand,
2092
+ MLb, i, ANN_flag, ANN_bondl, this_diag, MLbonds, args.debug)
2093
+ MLoptbds.append(bondl)
2094
+ MLoptbds.append(bondl)
2095
+ lig3D, dxyz = setPdistance(lig3D, r1, r0, bondl)
2096
+ # get target point for 2nd connecting atom
2097
+ rtarget = getPointu(corerefcoords, bondl, vecdiff(
2098
+ r1b, corerefcoords)) # get second point target
2099
+ if args.ff:
2100
+ # align 2nd connecting atom while balancing the desired location and ligand strain
2101
+ lig3D = align_dent2_catom2_refined(
2102
+ args, lig3D, catoms, bondl, r1, r0, core3D, rtarget, coreref, MLoptbds)
2103
+ else:
2104
+ print('Warning: Ligand FF optimization is inactive.')
2105
+ # rotate connecting atoms to align Hs properly
2106
+ lig3D = rotate_catoms_fix_Hs(lig3D, catoms, corerefcoords, core3D)
2107
+ # freeze local geometry
2108
+ lats = lig3D.getBondedAtoms(catoms[0])+lig3D.getBondedAtoms(catoms[1])
2109
+ for lat in list(set(lats)):
2110
+ frozenats.append(lat+core3D.natoms)
2111
+ lig3D_aligned = mol3D()
2112
+ lig3D_aligned.copymol3D(lig3D)
2113
+ return lig3D_aligned, frozenats, MLoptbds
2114
+
2115
+
2116
+ def align_dent3_lig(args, cpoint, batoms, m3D, core3D, coreref, ligand, lig3D,
2117
+ catoms, MLb, ANN_flag, ANN_bondl, this_diag, MLbonds,
2118
+ MLoptbds: List[float], frozenats: List[int], i: int
2119
+ ) -> Tuple[mol3D, List[int], List[float]]:
2120
+ """Aligns a tridentate ligand to core connecting atom coordinates
2121
+
2122
+ Parameters
2123
+ ----------
2124
+ args : Namespace
2125
+ Namespace of arguments.
2126
+ cpoint : atom3D
2127
+ atom3D class instance containing backbone connecting point.
2128
+ batoms : list
2129
+ List of backbone atom indices.
2130
+ m3D : mol3D
2131
+ mol3D of backbone template.
2132
+ core3D : mol3D
2133
+ mol3D class instance of partially built complex.
2134
+ coreref : atom3D
2135
+ atom3D of core reference atom.
2136
+ ligand : str
2137
+ Name of ligand for dictionary lookup.
2138
+ lig3D : mol3D
2139
+ mol3D class instance of the ligand.
2140
+ catoms : list
2141
+ List of ligand connecting atom indices.
2142
+ MLb : list
2143
+ Custom M-L bond length (if any).
2144
+ ANN_flag : bool
2145
+ Flag for ANN activation.
2146
+ ANN_bondl : list
2147
+ List of ANN predicted bond lengths.
2148
+ this_diag : run_diag
2149
+ ANN run_diag class instance.
2150
+ MLbonds : dict
2151
+ M-L bond dictionary.
2152
+ MLoptbds : list
2153
+ List of final M-L bond lengths.
2154
+ frozenats : list
2155
+ List of atoms frozen in FF optimization.
2156
+ i : int, optional
2157
+ Ligand serial number. Default is 0.
2158
+
2159
+ Returns
2160
+ -------
2161
+ lig3D_aligned : mol3D
2162
+ mol3D class instance of aligned ligand.
2163
+ frozenats : list
2164
+ List of frozen atoms.
2165
+ MLoptbds : list
2166
+ Updated list of metal ligand bonds.
2167
+
2168
+ """
2169
+ atom0 = catoms[1]
2170
+ corerefcoords = coreref.coords()
2171
+ # align molecule according to connection atom and shadow atom
2172
+ lig3D.alignmol(lig3D.getAtom(atom0), m3D.getAtom(batoms[1]))
2173
+ # 1. align ligand connection atoms center of symmetry
2174
+ auxm = mol3D()
2175
+ auxm.addAtom(lig3D.getAtom(catoms[0]))
2176
+ auxm.addAtom(lig3D.getAtom(catoms[2]))
2177
+ r0 = core3D.getAtom(0).coords()
2178
+ lig3Db = mol3D()
2179
+ lig3Db.copymol3D(lig3D)
2180
+ theta, urot = rotation_params(
2181
+ r0, lig3D.getAtom(atom0).coords(), auxm.centersym())
2182
+ lig3D = rotate_around_axis(
2183
+ lig3D, lig3D.getAtom(atom0).coords(), urot, theta)
2184
+ # 2. align with correct plane
2185
+ rl0, rl1, rl2 = lig3D.getAtom(catoms[0]).coords(), lig3D.getAtom(
2186
+ catoms[1]).coords(), lig3D.getAtom(catoms[2]).coords()
2187
+ rc0, rc1, rc2 = m3D.getAtom(batoms[0]).coords(), m3D.getAtom(
2188
+ batoms[1]).coords(), m3D.getAtom(batoms[2]).coords()
2189
+ theta0, ul = rotation_params(rl0, rl1, rl2)
2190
+ theta1, uc = rotation_params(rc0, rc1, rc2)
2191
+ urot = vecdiff(rl1, corerefcoords)
2192
+ theta = vecangle(ul, uc)
2193
+ lig3Db = mol3D()
2194
+ lig3Db.copymol3D(lig3D)
2195
+ lig3D = rotate_around_axis(lig3D, rl1, urot, theta)
2196
+ lig3Db = rotate_around_axis(lig3Db, rl1, urot, 180-theta)
2197
+ rl0, rl1, rl2 = lig3D.getAtom(catoms[0]).coords(), lig3D.getAtom(
2198
+ catoms[1]).coords(), lig3D.getAtom(catoms[2]).coords()
2199
+ rl0b, rl1b, rl2b = lig3Db.getAtom(catoms[0]).coords(), lig3Db.getAtom(
2200
+ catoms[1]).coords(), lig3Db.getAtom(catoms[2]).coords()
2201
+ rc0, rc1, rc2 = m3D.getAtom(batoms[0]).coords(), m3D.getAtom(
2202
+ batoms[1]).coords(), m3D.getAtom(batoms[2]).coords()
2203
+ theta, ul = rotation_params(rl0, rl1, rl2)
2204
+ theta, ulb = rotation_params(rl0b, rl1b, rl2b)
2205
+ theta, uc = rotation_params(rc0, rc1, rc2)
2206
+ d1 = norm(np.cross(ul, uc))
2207
+ d2 = norm(np.cross(ulb, uc))
2208
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
2209
+ # 3. correct if not symmetric
2210
+ theta0, urotaux = rotation_params(lig3D.getAtom(catoms[0]).coords(
2211
+ ), lig3D.getAtom(catoms[1]).coords(), core3D.getAtom(0).coords())
2212
+ theta1, urotaux = rotation_params(lig3D.getAtom(catoms[2]).coords(
2213
+ ), lig3D.getAtom(catoms[1]).coords(), core3D.getAtom(0).coords())
2214
+ dtheta = 0.5*(theta1-theta0)
2215
+ if abs(dtheta) > 0.5:
2216
+ lig3D = rotate_around_axis(
2217
+ lig3D, lig3D.getAtom(atom0).coords(), urot, dtheta)
2218
+ # 4. flip for correct stereochemistry
2219
+ urot = vecdiff(lig3D.getAtom(
2220
+ catoms[1]).coords(), core3D.getAtom(0).coords())
2221
+ lig3Db = mol3D()
2222
+ lig3Db.copymol3D(lig3D)
2223
+ lig3Db = rotate_around_axis(
2224
+ lig3Db, lig3Db.getAtom(catoms[1]).coords(), urot, 180)
2225
+ d1 = min(distance(lig3D.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[2]).coords(
2226
+ )), distance(lig3D.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[0]).coords()))
2227
+ d2 = min(distance(lig3Db.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[2]).coords(
2228
+ )), distance(lig3Db.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[0]).coords()))
2229
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
2230
+ # 5. flip to align 1st and 3rd connection atoms
2231
+ lig3Db = mol3D()
2232
+ lig3Db.copymol3D(lig3D)
2233
+ theta, urot = rotation_params(lig3Db.getAtom(catoms[0]).coords(), lig3Db.getAtom(
2234
+ catoms[1]).coords(), lig3Db.getAtom(catoms[2]).coords())
2235
+ lig3Db = rotate_around_axis(
2236
+ lig3Db, lig3Db.getAtom(catoms[1]).coords(), urot, 180)
2237
+ d1 = min(distance(lig3D.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[2]).coords(
2238
+ )), distance(lig3D.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[0]).coords()))
2239
+ d2 = min(distance(lig3Db.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[2]).coords(
2240
+ )), distance(lig3Db.getAtom(catoms[2]).coords(), m3D.getAtom(batoms[0]).coords()))
2241
+ lig3D = lig3D if d1 < d2 else lig3Db
2242
+ bondl = get_MLdist(m3D.getAtom(0), args.oxstate, args.spin, lig3D, atom0,
2243
+ ligand, MLb, i, ANN_flag, ANN_bondl, this_diag, MLbonds,
2244
+ args.debug)
2245
+ for iib in range(0, 3):
2246
+ MLoptbds.append(bondl)
2247
+ # set correct distance
2248
+ setPdistance(lig3D, lig3D.getAtom(atom0).coords(),
2249
+ m3D.getAtom(0).coords(), bondl)
2250
+ # rotate connecting atoms to align Hs properly
2251
+ lig3D = rotate_catoms_fix_Hs(
2252
+ lig3D, [catoms[0], catoms[1], catoms[2]], m3D.getAtom(0).coords(), core3D)
2253
+ # freeze local geometry
2254
+ lats = lig3D.getBondedAtoms(catoms[0])+lig3D.getBondedAtoms(catoms[1])
2255
+ for lat in list(set(lats)):
2256
+ frozenats.append(lat+core3D.natoms)
2257
+ lig3D_aligned = mol3D()
2258
+ lig3D_aligned.copymol3D(lig3D)
2259
+ return lig3D_aligned, frozenats, MLoptbds
2260
+
2261
+
2262
+ def mcomplex(args: Namespace, ligs: List[str], ligoc: List[int]
2263
+ ) -> Tuple[mol3D, List[mol3D], str, run_diag, List[int], List[int]]:
2264
+ """Main ligand placement routine
2265
+
2266
+ Parameters
2267
+ ----------
2268
+ args : Namespace
2269
+ Namespace of arguments.
2270
+ ligs : list
2271
+ List of ligand names.
2272
+ ligoc : list
2273
+ List of ligand occupations.
2274
+ licores : dict
2275
+ Ligand dictionary as in molSimplify.
2276
+
2277
+ Returns
2278
+ -------
2279
+ core3D : mol3D
2280
+ mol3D class instance for core.
2281
+ complex3D : mol3D
2282
+ mol3D class instance for built complex.
2283
+ emsg : str
2284
+ Flag for error. String if error, with error message.
2285
+ this_diag: run_diag
2286
+ run_diag class instance of the complex.
2287
+ subcatoms_ext : list
2288
+ Substrate connection atoms from TSGen. Deprecated.
2289
+ mligcatoms_ext : list
2290
+ Ligand connection atoms from TSGen. Deprecated.
2291
+
2292
+ """
2293
+ globs = globalvars()
2294
+ # load ligand dictionary
2295
+ licores = getlicores()
2296
+ this_diag = run_diag()
2297
+ if globs.debug:
2298
+ print(('\nGenerating complex with ligands and occupations:', ligs, ligoc))
2299
+ if args.gui:
2300
+ args.gui.iWtxt.setText('\nGenerating complex with core:'+args.core +
2301
+ ' and ligands: ' + ' '.join(ligs)+'\n'+args.gui.iWtxt.toPlainText())
2302
+ args.gui.app.processEvents()
2303
+ # import gui options
2304
+ from Classes.mWidgets import mQDialogWarn
2305
+ # initialize variables
2306
+ emsg = ''
2307
+ complex3D: List[mol3D] = []
2308
+ occs0 = [] # occurrences of each ligand
2309
+ toccs = 0 # total occurrence count (number of ligands)
2310
+ smilesligs = 0 # count how many smiles strings
2311
+ cats0: List[List[Union[int, str]]] = [] # connection atoms for ligands
2312
+ dentl = [] # denticity of ligands
2313
+ connected = [] # indices in core3D of ligand atoms connected to metal
2314
+ frozenats = [] # atoms to be frozen in optimization
2315
+ freezeangles = False # custom angles imposed
2316
+ MLoptbds: List[float] = [] # list of bond lengths
2317
+ rempi = False # remove dummy pi orbital center of mass atom
2318
+ backbatoms: List[List[int]] = []
2319
+ batslist: List[List[int]] = []
2320
+ bats: List[int] = []
2321
+ ffoption_list = [] # for each ligand, keeps track of what the forcefield option is.
2322
+ # load bond data
2323
+ MLbonds = loaddata('/Data/ML.dat')
2324
+ # calculate occurrences, denticities etc for all ligands
2325
+ for i, ligname in enumerate(ligs):
2326
+ # if not in cores -> smiles/file
2327
+ if ligname not in list(licores.keys()):
2328
+ if args.smicat and len(args.smicat) >= (smilesligs+1):
2329
+ if 'pi' in args.smicat[smilesligs]:
2330
+ cats0.append(['c'])
2331
+ else:
2332
+ cats0.append(args.smicat[smilesligs])
2333
+ else:
2334
+ cats0.append([0])
2335
+ dent_i = len(cats0[-1])
2336
+ smilesligs += 1
2337
+ else:
2338
+ cats0.append([])
2339
+ # otherwise get denticity from ligands dictionary
2340
+ if 'pi' in licores[ligname][2]:
2341
+ dent_i = 1
2342
+ else:
2343
+ if isinstance(licores[ligname][2], str):
2344
+ dent_i = 1
2345
+ else:
2346
+ dent_i = int(len(licores[ligname][2]))
2347
+ # get occurrence for each ligand if specified (default 1)
2348
+ oc_i = int(ligoc[i]) if i < len(ligoc) else 1
2349
+ occs0.append(0) # initialize occurrences list
2350
+ dentl.append(dent_i) # append denticity to list
2351
+ # loop over occurrence of ligand i to check for max coordination
2352
+ for j in range(0, oc_i):
2353
+ occs0[i] += 1
2354
+ toccs += dent_i
2355
+
2356
+ if 'l' in args.ffoption: # ligands.dict control for force field option
2357
+ if ligname in list(licores.keys()): # ligand is in the ligands dictionary
2358
+ forcefield_option_current_ligand = licores[ligname][4][0].lower()
2359
+ ffoption_list.append(forcefield_option_current_ligand)
2360
+ else: # default to 'ba' for ligands not in ligands.dict
2361
+ ffoption_list.append('ba')
2362
+
2363
+ if 'l' in args.ffoption: # ligands.dict control for force field option
2364
+ # Setting args.ffoption to the least-action option among the ligands.
2365
+ # So, if at least one of the ligands has 'n' as the forcefield option, no optimization.
2366
+ # Otherwise, if there is any mixture of 'b' and 'a' among the ligands, go with 'n' for consistency.
2367
+ # Otherwise, if there is a mixture of 'b' and 'ba', go with 'b'.
2368
+ # Otherwise, if there is a mixture of 'a' and 'ba', go with 'a'.
2369
+ # Only if all ligands have 'ba' as the forcefield option do we go with 'ba'.
2370
+ ffoption_set = set(ffoption_list)
2371
+ if 'n' in ffoption_set:
2372
+ args.ffoption = 'n'
2373
+ elif 'b' in ffoption_set and 'a' in ffoption_set:
2374
+ args.ffoption = 'n'
2375
+ elif 'b' in ffoption_set:
2376
+ args.ffoption = 'b'
2377
+ elif 'a' in ffoption_set:
2378
+ args.ffoption = 'a'
2379
+ else:
2380
+ args.ffoption = 'ba'
2381
+
2382
+ # sort by descending denticity (needed for adjacent connection atoms)
2383
+ ligandsU, occsU, dentsU = ligs, occs0, dentl # save unordered lists
2384
+ indcs = smartreorderligs(ligs, dentl, args.ligalign)
2385
+ ligands = [ligs[i] for i in indcs] # sort ligands list
2386
+ occs = [occs0[i] for i in indcs] # sort occurrences list
2387
+ tcats = [cats0[i] for i in indcs] # sort connections list
2388
+ dents = [dentl[i] for i in indcs] # sort denticities list
2389
+ # if using decorations, make repeatable list
2390
+ if args.decoration:
2391
+ if not args.decoration_index:
2392
+ print('Warning, no decoration index given, assuming first ligand')
2393
+ args.decoration_index = [[0]]
2394
+ if len(args.decoration_index) != len(ligs):
2395
+ new_decoration_index = []
2396
+ new_decorations = []
2397
+ for i in range(0, len(ligs)):
2398
+ if len(args.decoration_index) > i:
2399
+ new_decoration_index.append(args.decoration_index[i])
2400
+ new_decorations.append(args.decoration[i])
2401
+ else:
2402
+ new_decoration_index.append([])
2403
+ new_decorations.append(False)
2404
+ if args.debug:
2405
+ print('setting decoration:')
2406
+ print(new_decoration_index)
2407
+ print(new_decorations)
2408
+ args.decoration = new_decorations
2409
+ args.decoration_index = new_decoration_index
2410
+ args.decoration_index = [args.decoration_index[i]
2411
+ for i in indcs] # sort decorations list
2412
+ args.decoration = [args.decoration[i]
2413
+ for i in indcs] # sort decorations list
2414
+ # sort keepHs list and unpack into list of tuples representing each connecting atom###
2415
+ keepHs = [k for k in args.keepHs]
2416
+ keepHs = [keepHs[i] for i in indcs]
2417
+ for i, keepH in enumerate(keepHs):
2418
+ keepHs[i] = [keepHs[i]] * dents[i]
2419
+ # sort M-L bond list
2420
+ MLb = []
2421
+ if args.MLbonds:
2422
+ MLb = [k for k in args.MLbonds]
2423
+ for j in range(len(args.MLbonds), len(ligs)):
2424
+ MLb.append(False)
2425
+ MLb = [MLb[i] for i in indcs] # sort MLbonds list
2426
+ # sort ligands custom angles
2427
+ pangles = []
2428
+ if args.pangles:
2429
+ pangles = [k for k in args.pangles]
2430
+ for j in range(len(args.pangles), len(ligs)):
2431
+ pangles.append(False)
2432
+ pangles = [args.pangles[i] for i in indcs] # sort custom langles list
2433
+
2434
+ # compute number of connecting points required
2435
+ cpoints_required = 0
2436
+ for i, ligand in enumerate(ligands):
2437
+ for j in range(0, occs[i]):
2438
+ cpoints_required += dents[i]
2439
+
2440
+ # load core and initialize template
2441
+ m3D, core3D, geom, backbatoms, coord, corerefatoms = init_template(
2442
+ args, cpoints_required)
2443
+ # Get connection points for all the ligands
2444
+ # smart alignment and forced order
2445
+
2446
+ # if geom:
2447
+ if args.ligloc and args.ligalign:
2448
+ batslist0 = []
2449
+ for i, ligand in enumerate(ligandsU):
2450
+ for j in range(0, occsU[i]):
2451
+ # get correct atoms
2452
+ bats, backbatoms = getnupdateb(backbatoms, dentsU[i])
2453
+ batslist0.append(bats)
2454
+ # reorder according to smart reorder
2455
+ for i in indcs:
2456
+ offset = 0
2457
+ for ii in range(0, i):
2458
+ offset += (occsU[ii]-1)
2459
+ for j in range(0, occsU[i]):
2460
+ batslist.append(batslist0[i+j+offset]) # sort connections list
2461
+ else:
2462
+ for i, ligand in enumerate(ligands):
2463
+ for j in range(0, occs[i]):
2464
+ # get correct atoms
2465
+ bats, backbatoms = getnupdateb(backbatoms, dents[i])
2466
+ batslist.append(bats)
2467
+ if not geom:
2468
+ for comb_i, comb in enumerate(batslist):
2469
+ for i in comb:
2470
+ if i == 1:
2471
+ batslist[comb_i][i] = m3D.natoms - coord + 1
2472
+ # initialize ANN
2473
+ ANN_flag, ANN_bondl, ANN_reason, ANN_attributes, catalysis_flag = init_ANN(
2474
+ args, ligands, occs, dents, batslist, tcats, licores)
2475
+
2476
+ this_diag.set_ANN(ANN_flag, ANN_reason, ANN_attributes, catalysis_flag)
2477
+
2478
+ # freeze core
2479
+ for i in range(0, core3D.natoms):
2480
+ frozenats.append(i)
2481
+
2482
+ # loop over ligands and begin functionalization
2483
+ # loop over ligands
2484
+ totlig = 0 # total number of ligands added
2485
+ ligsused = 0 # total number of ligands used
2486
+ subcatoms_ext: List[int] = []
2487
+ mligcatoms_ext: List[int] = []
2488
+ if args.mligcatoms:
2489
+ for i in range(len(args.mligcatoms)):
2490
+ mligcatoms_ext.append(0)
2491
+ for i, ligand in enumerate(ligands):
2492
+ if args.debug:
2493
+ print('************')
2494
+ print(('loading ligand '+str(ligand) + ', number ' +
2495
+ str(i) + ' of ' + str(len(ligands))))
2496
+ if not (ligand == 'x' or ligand == 'X'):
2497
+
2498
+ # load ligand
2499
+ lig, emsg = lig_load(ligand)
2500
+ # add decorations to ligand
2501
+ if args.decoration and args.decoration_index:
2502
+ if len(args.decoration) > i and len(args.decoration_index) > i:
2503
+ if args.decoration[i]:
2504
+ if args.debug:
2505
+ print(('decorating ' + str(ligand) + ' with ' + str(
2506
+ args.decoration[i]) + ' at sites ' + str(args.decoration_index)))
2507
+ lig = decorate_ligand(
2508
+ lig, args.decoration[i], args.decoration_index[i], args.debug)
2509
+ lig.convert2mol3D()
2510
+ # initialize ligand
2511
+ lig3D, rempi, ligpiatoms = init_ligand(args, lig, tcats, keepHs, i)
2512
+ if emsg:
2513
+ return core3D, complex3D, emsg, this_diag, subcatoms_ext, mligcatoms_ext
2514
+ # Skip = False
2515
+ for j in range(0, occs[i]): # The number of occurrences of the current ligand.
2516
+ if args.debug:
2517
+ print(('loading copy '+str(j) + ' of ligand ' +
2518
+ ligand + ' with dent ' + str(dents[i])))
2519
+ print(('totlig is ' + str(totlig)))
2520
+ print(('ligused is ' + str(ligsused)))
2521
+ print(('target BL is ' + str(ANN_bondl[ligsused])))
2522
+ print('******')
2523
+ denticity = dents[i]
2524
+
2525
+ if not (ligand == 'x' or ligand == 'X') and (totlig-1+denticity < coord):
2526
+ # add atoms to connected atoms list
2527
+ catoms = lig.cat # connection atoms
2528
+ initatoms = core3D.natoms # initial number of atoms in core3D
2529
+ if args.debug:
2530
+ print((ligand.lower()))
2531
+ print((args.mlig))
2532
+ print((args.core.lower()))
2533
+ print(('mligcatoms_ext is ' + str(mligcatoms_ext)))
2534
+ for at in catoms:
2535
+ connected.append(initatoms+at)
2536
+ # initialize variables
2537
+ # metal coordinates in backbone
2538
+ mcoords = core3D.getAtom(0).coords()
2539
+ atom0 = 0 # initialize variables
2540
+ coreref = corerefatoms.getAtom(totlig)
2541
+ # connecting point in backbone to align ligand to
2542
+ batoms = get_batoms(args, batslist, ligsused)
2543
+ cpoint = m3D.getAtom(batoms[0])
2544
+ # attach ligand depending on the denticity
2545
+ # optimize geometry by minimizing steric effects
2546
+ if args.debug:
2547
+ print(('backbone atoms: ' + str(batoms)))
2548
+ if (denticity == 1):
2549
+ lig3D, MLoptbds = align_dent1_lig(
2550
+ args, cpoint, core3D, coreref, ligand, lig3D, catoms,
2551
+ rempi, ligpiatoms, MLb, ANN_flag, ANN_bondl[ligsused],
2552
+ this_diag, MLbonds, MLoptbds, i)
2553
+ if args.debug:
2554
+ print(('adding monodentate at distance: ' + str(
2555
+ ANN_bondl[totlig]) + '/'+str(MLb) + '/'+' at catoms ' + str(catoms)))
2556
+ print('printing ligand information')
2557
+ print((lig3D.printxyz()))
2558
+ elif (denticity == 2):
2559
+ lig3D, frozenats, MLoptbds = align_dent2_lig(
2560
+ args, cpoint, batoms, m3D, core3D, coreref, ligand,
2561
+ lig3D, catoms, MLb, ANN_flag, ANN_bondl[ligsused],
2562
+ this_diag, MLbonds, MLoptbds, frozenats, i)
2563
+ elif (denticity == 3):
2564
+ lig3D, frozenats, MLoptbds = align_dent3_lig(
2565
+ args, cpoint, batoms, m3D, core3D, coreref, ligand,
2566
+ lig3D, catoms, MLb, ANN_flag, ANN_bondl[ligsused],
2567
+ this_diag, MLbonds, MLoptbds, frozenats, i)
2568
+ elif (denticity == 4):
2569
+ # note: catoms for ligand should be specified clockwise
2570
+ # connection atoms in backbone
2571
+ if args.antigeoisomer:
2572
+ print('anti geometric isomer requested.')
2573
+ catoms = catoms[::-1]
2574
+ batoms = batslist[ligsused]
2575
+ if len(batoms) < 1:
2576
+ if args.gui:
2577
+ emsg = 'Connecting all ligands is not possible. Check your input!'
2578
+ qqb = mQDialogWarn('Warning', emsg)
2579
+ qqb.setParent(args.gui.wmain)
2580
+ break
2581
+ # connection atom
2582
+ atom0 = catoms[0]
2583
+ # align ligand center of symmetry to core reference atom
2584
+ auxmol_lig = mol3D()
2585
+ auxmol_m3D = mol3D()
2586
+ for iiax in range(0, 4):
2587
+ auxmol_lig.addAtom(lig3D.getAtom(catoms[iiax]))
2588
+ auxmol_m3D.addAtom(m3D.getAtom(batoms[iiax]))
2589
+ lig3D.alignmol(
2590
+ atom3D('C', auxmol_lig.centersym()), m3D.getAtom(0))
2591
+ # necessary to prevent lig3D from being overwritten
2592
+ lig3Dtmp = mol3D()
2593
+ lig3Dtmp.copymol3D(lig3D)
2594
+ # compute average metal-ligand distance
2595
+ auxmol_lig = mol3D()
2596
+ auxmol_m3D = mol3D()
2597
+ sum_MLdists = 0
2598
+ for iiax in range(0, 4):
2599
+ auxmol_lig.addAtom(lig3Dtmp.getAtom(catoms[iiax]))
2600
+ auxmol_m3D.addAtom(m3D.getAtom(batoms[iiax]))
2601
+ sum_MLdists += distance(m3D.getAtomCoords(0),
2602
+ auxmol_lig.getAtomCoords(iiax))
2603
+ avg_MLdists = sum_MLdists/4
2604
+ # scale template by average M-L distance
2605
+ auxmol_m3D.addAtom(m3D.getAtom(0))
2606
+ # TODO BCM definition slightly modified. Keep an eye for unexpected structures
2607
+ for iiax in range(0, 4):
2608
+ auxmol_m3D.BCM(iiax, 4, avg_MLdists)
2609
+ auxmol_m3D.deleteatom(4)
2610
+ # align lig3D to minimize RMSD from template
2611
+ auxmol_lig, U, d0, d1 = kabsch(auxmol_lig, auxmol_m3D)
2612
+ lig3D.translate(d0)
2613
+ lig3D = rotate_mat(lig3D, U)
2614
+
2615
+ bondl = get_MLdist(m3D.getAtom(0), args.oxstate, args.spin,
2616
+ lig3D, atom0, ligand, MLb, i, ANN_flag,
2617
+ ANN_bondl[ligsused], this_diag, MLbonds,
2618
+ args.debug)
2619
+ for iib in range(0, 4):
2620
+ MLoptbds.append(bondl)
2621
+ elif (denticity == 5):
2622
+ # connection atoms in backbone
2623
+ batoms = batslist[ligsused]
2624
+ if len(batoms) < 1:
2625
+ if args.gui:
2626
+ qqb = mQDialogWarn('Warning', emsg)
2627
+ qqb.setParent(args.gui.wmain)
2628
+ emsg = 'Connecting all ligands is not possible. Check your input!'
2629
+ break
2630
+ # get center of mass
2631
+ ligc = mol3D()
2632
+ for c_i in range(0, 4): # 5 is the non-planar atom
2633
+ ligc.addAtom(lig3D.getAtom(catoms[c_i]))
2634
+ # translate ligand to the middle of octahedral
2635
+ lig3D.translate(vecdiff(mcoords, ligc.centersym()))
2636
+ # get plane
2637
+ r0c = m3D.getAtom(batoms[0]).coords()
2638
+ r2c = m3D.getAtom(batoms[1]).coords()
2639
+ r1c = mcoords
2640
+ r0l = lig3D.getAtom(catoms[0]).coords()
2641
+ r2l = lig3D.getAtom(catoms[1]).coords()
2642
+ r1l = mcoords
2643
+ # normal vector to backbone plane
2644
+ theta, uc = rotation_params(r0c, r1c, r2c)
2645
+ # normal vector to ligand plane
2646
+ theta, ul = rotation_params(r0l, r1l, r2l)
2647
+ theta = vecangle(uc, ul)
2648
+ u = np.cross(uc, ul)
2649
+ lig3Db = mol3D()
2650
+ lig3Db.copymol3D(lig3D)
2651
+ # rotate around axis to match planes
2652
+ lig3D = rotate_around_axis(lig3D, mcoords, u, theta)
2653
+ lig3Db = rotate_around_axis(lig3Db, mcoords, u, 180+theta)
2654
+ d1 = distance(lig3D.getAtom(
2655
+ catoms[4]).coords(), m3D.getAtom(batoms[-1]).coords())
2656
+ d2 = distance(lig3Db.getAtom(
2657
+ catoms[4]).coords(), m3D.getAtom(batoms[-1]).coords())
2658
+ lig3D = lig3D if (d2 < d1) else lig3Db # pick best one
2659
+ # rotate around center axis to match backbone atoms
2660
+ r0l = vecdiff(lig3D.getAtom(catoms[0]).coords(), mcoords)
2661
+ r1l = vecdiff(m3D.getAtom(totlig+1).coords(), mcoords)
2662
+ u = np.cross(r0l, r1l)
2663
+ theta = 180*np.arccos(np.dot(r0l, r1l)/(norm(r0l)*norm(r1l)))/np.pi
2664
+ lig3Db = mol3D()
2665
+ lig3Db.copymol3D(lig3D)
2666
+ lig3D = rotate_around_axis(lig3D, mcoords, u, theta)
2667
+ lig3Db = rotate_around_axis(lig3Db, mcoords, u, theta-90)
2668
+ d1 = distance(lig3D.getAtom(
2669
+ catoms[0]).coords(), m3D.getAtom(batoms[0]).coords())
2670
+ d2 = distance(lig3Db.getAtom(
2671
+ catoms[0]).coords(), m3D.getAtom(batoms[0]).coords())
2672
+ lig3D = lig3D if (d1 < d2) else lig3Db # pick best one
2673
+ bondl, exact_match = get_MLdist_database(
2674
+ core3D.getAtom(0), args.oxstate, args.spin, lig3D,
2675
+ catoms[0], ligand, MLbonds, args.debug)
2676
+ # flip if necessary
2677
+ if len(batslist) > ligsused:
2678
+ nextatbats = batslist[ligsused]
2679
+ auxm = mol3D()
2680
+ if len(nextatbats) > 0:
2681
+ for at in nextatbats:
2682
+ auxm.addAtom(m3D.getAtom(at))
2683
+ if lig3D.overlapcheck(auxm, True): # if overlap flip
2684
+ urot = vecdiff(m3D.getAtomCoords(
2685
+ batoms[1]), m3D.getAtomCoords(batoms[0]))
2686
+ lig3D = rotate_around_axis(
2687
+ lig3D, mcoords, urot, 180)
2688
+ for iib in range(0, 5):
2689
+ MLoptbds.append(bondl)
2690
+ elif (denticity == 6):
2691
+ # connection atoms in backbone
2692
+ batoms = batslist[ligsused]
2693
+ if len(batoms) < 1:
2694
+ if args.gui:
2695
+ qqb = mQDialogWarn('Warning', emsg)
2696
+ qqb.setParent(args.gui.wmain)
2697
+ emsg = 'Connecting all ligands is not possible. Check your input!'
2698
+ break
2699
+ # get center of mass
2700
+ ligc = mol3D()
2701
+ for c_i in range(0, 6):
2702
+ ligc.addAtom(lig3D.getAtom(catoms[c_i]))
2703
+ # translate metal to the middle of octahedral
2704
+ core3D.translate(vecdiff(ligc.centersym(), mcoords))
2705
+ bondl, exact_match = get_MLdist_database(
2706
+ core3D.getAtom(0), args.oxstate, args.spin, lig3D,
2707
+ catoms[0], ligand, MLbonds, args.debug)
2708
+ for iib in range(0, 6):
2709
+ MLoptbds.append(bondl)
2710
+ auxm = mol3D()
2711
+ auxm.copymol3D(lig3D)
2712
+ complex3D.append(auxm)
2713
+ if 'a' not in lig.ffopt.lower():
2714
+ for latdix in range(0, lig3D.natoms):
2715
+ if args.debug:
2716
+ print(('a is not ff.lower, so adding atom: ' +
2717
+ str(latdix+core3D.natoms) + ' to freeze'))
2718
+ frozenats.append(latdix+core3D.natoms)
2719
+ # combine molecules
2720
+ core3D = core3D.combine(lig3D)
2721
+ core3D.convert2OBMol()
2722
+ core3D.convert2mol3D()
2723
+ # remove dummy cm atom if requested
2724
+ if rempi:
2725
+ # remove the fictitious center atom, for aromatic-bonding ligands like benzene
2726
+ core3D.deleteatom(core3D.natoms-1)
2727
+ if args.debug:
2728
+ print(('number of atoms in lig3D is ' + str(lig3D.natoms)))
2729
+ if lig3D.natoms < 3:
2730
+ frozenats += list(range(core3D.natoms-2, core3D.natoms))
2731
+ if args.debug:
2732
+ print(
2733
+ (str(list(range(core3D.natoms-2, core3D.natoms))) + ' are frozen.'))
2734
+ if args.calccharge:
2735
+ core3D.charge += lig3D.charge
2736
+ if args.debug:
2737
+ print(('core3D charge is ' + str(core3D.charge)))
2738
+ # perform FF optimization if requested
2739
+ if args.debug:
2740
+ print(('saving a copy of the complex named complex_' +
2741
+ str(i)+'_'+str(j) + '.xyz'))
2742
+ core3D.writexyz('complex_'+str(i)+'_'+str(j) + '.xyz')
2743
+
2744
+ if 'a' in args.ffoption:
2745
+ if args.debug:
2746
+ print('FF optimizing molecule after placing ligand')
2747
+ print(
2748
+ ('in the relax function, passing connected atoms list: ' + str(connected)))
2749
+ # (ff,mol,connected,constopt,frozenats,frozenangles,mlbonds,nsteps,debug=False):
2750
+ core3D, enc = ffopt(ff=args.ff,
2751
+ mol=core3D,
2752
+ connected=connected,
2753
+ constopt=1,
2754
+ frozenats=frozenats,
2755
+ frozenangles=freezeangles,
2756
+ mlbonds=MLoptbds,
2757
+ nsteps='Adaptive',
2758
+ spin=int(args.spin),
2759
+ debug=args.debug)
2760
+ if args.debug:
2761
+ print(
2762
+ ('saving a copy of the complex named complex_'+str(i)+'_'+str(j) + '_ff.xyz'))
2763
+ core3D.writexyz('complex_'+str(i) +
2764
+ '_'+str(j) + '_ff.xyz')
2765
+ if args.debug:
2766
+ print(('done with pair of inds '+str(i)+' and '+str(j)))
2767
+ print('**************************')
2768
+ totlig += denticity
2769
+ ligsused += 1
2770
+ # perform FF optimization if requested
2771
+ if 'a' in args.ffoption or args.ff_final_opt:
2772
+ if args.debug:
2773
+ print('Performing final FF opt')
2774
+ # idxes
2775
+ midx = core3D.findMetal()[0]
2776
+ # If args.ff_final_opt is None (default) use args.ff
2777
+ ff = args.ff_final_opt or args.ff
2778
+ core3D, enc = ffopt(ff=ff,
2779
+ mol=core3D,
2780
+ connected=connected,
2781
+ constopt=1,
2782
+ frozenats=connected + [midx],
2783
+ frozenangles=freezeangles,
2784
+ mlbonds=MLoptbds,
2785
+ nsteps='Adaptive',
2786
+ spin=int(args.spin),
2787
+ debug=args.debug)
2788
+
2789
+ if args.debug:
2790
+ print(
2791
+ 'saving a final debug copy of the complex named complex_after_final_ff.xyz')
2792
+ core3D.writexyz('complex_after_final_ff.xyz')
2793
+ # core3D,enc = ffopt(args.ff,core3D,connected,1,frozenats,freezeangles,MLoptbds,'Adaptive',args.debug)
2794
+
2795
+ return core3D, complex3D, emsg, this_diag, subcatoms_ext, mligcatoms_ext
2796
+
2797
+
2798
+ def generate_report(args: Namespace, ligands: List[str], ligoc: List[int]
2799
+ ) -> Tuple[mol3D, List[mol3D], str, run_diag, List[int], List[int]]:
2800
+ # load ligand dictionary
2801
+ licores = getlicores()
2802
+ ligs: List[ligand_class] = []
2803
+ cons = []
2804
+ # Bunch of unused variables?? RM 2022/02/17
2805
+ # mono_inds = []
2806
+ # bi_inds = []
2807
+ # tri_inds = []
2808
+ # tetra_inds = []
2809
+ # penta_inds = []
2810
+ emsg = ""
2811
+ complex3D: List[mol3D] = []
2812
+ occs0 = [] # occurrences of each ligand
2813
+ toccs = 0 # total occurrence count (number of ligands)
2814
+ # catsmi = [] # SMILES ligands connection atoms
2815
+ smilesligs = 0 # count how many smiles strings
2816
+ cats0: List[List[Union[int, str]]] = [] # connection atoms for ligands
2817
+ dentl = [] # denticity of ligands
2818
+ # connected = [] # indices in core3D of ligand atoms connected to metal
2819
+ # frozenats = [] # atoms to be frozen in optimization
2820
+ # freezeangles = False # custom angles imposed
2821
+ # MLoptbds = [] # list of bond lengths
2822
+ # rempi = False # remove dummy pi orbital center of mass atom
2823
+ backbatoms: List[List[int]] = []
2824
+ batslist: List[List[int]] = []
2825
+ bats: List[int] = []
2826
+ print('ONLY report wanted. Not building structure.')
2827
+ metal_mol = mol3D()
2828
+ metal_mol.addAtom(atom3D(args.core))
2829
+ # CURRENTLY only works for molsimplify ligands...
2830
+ for i, name in enumerate(ligands):
2831
+ this_mol, emsg = lig_load(name)
2832
+ this_mol.convert2mol3D()
2833
+ this_lig = ligand_class(mol3D(), [], this_mol.denticity)
2834
+ this_lig.mol = this_mol
2835
+ this_con = this_mol.cat
2836
+ ligs.append(this_lig)
2837
+ cons.append(this_con)
2838
+ if name not in list(licores.keys()): # ligand is not in the ligands dictionary
2839
+ if args.smicat and len(args.smicat) >= (smilesligs+1):
2840
+ if 'pi' in args.smicat[smilesligs]:
2841
+ cats0.append(['c'])
2842
+ else:
2843
+ cats0.append(args.smicat[smilesligs])
2844
+ else:
2845
+ cats0.append([0])
2846
+ dent_i = len(cats0[-1])
2847
+ smilesligs += 1
2848
+ else:
2849
+ cats0.append([])
2850
+ # otherwise get denticity from ligands dictionary
2851
+ if 'pi' in licores[name][2]:
2852
+ dent_i = 1
2853
+ else:
2854
+ if isinstance(licores[name][2], str):
2855
+ dent_i = 1
2856
+ else:
2857
+ dent_i = int(len(licores[name][2]))
2858
+ oc_i = int(ligoc[i]) if i < len(ligoc) else 1
2859
+ occs0.append(0) # initialize occurrences list
2860
+ dentl.append(dent_i) # append denticity to list
2861
+ # loop over occurrence of ligand i to check for max coordination
2862
+ for j in range(0, oc_i):
2863
+ occs0[i] += 1
2864
+ toccs += dent_i
2865
+ # sort by descending denticity (needed for adjacent connection atoms)
2866
+ ligandsU, occsU, dentsU = ligs, occs0, dentl # save unordered lists
2867
+ indcs = smartreorderligs(ligands, dentl, args.ligalign)
2868
+ occs = [occs0[i] for i in indcs] # sort occurrences list
2869
+ tcats = [cats0[i] for i in indcs] # sort connections list
2870
+ dents = [dentl[i] for i in indcs] # sort denticities list
2871
+
2872
+ cpoints_required = 0
2873
+ for i, ligand_val in enumerate(ligands):
2874
+ for j in range(0, occs[i]):
2875
+ cpoints_required += dents[i]
2876
+
2877
+ # load core and initialize template
2878
+ m3D, core3D, geom, backbatoms, coord, corerefatoms = init_template(
2879
+ args, cpoints_required)
2880
+ # Get connection points for all the ligands
2881
+ # smart alignment and forced order
2882
+
2883
+ # if geom:
2884
+ if args.ligloc and args.ligalign:
2885
+ batslist0 = []
2886
+ for i, ligandU_val in enumerate(ligandsU):
2887
+ for j in range(0, occsU[i]):
2888
+ # get correct atoms
2889
+ bats, backbatoms = getnupdateb(backbatoms, dentsU[i])
2890
+ batslist0.append(bats)
2891
+ # reorder according to smart reorder
2892
+ for i in indcs:
2893
+ offset = 0
2894
+ for ii in range(0, i):
2895
+ offset += (occsU[ii]-1)
2896
+ for j in range(0, occsU[i]):
2897
+ # sort connections list
2898
+ batslist.append(batslist0[i+j+offset])
2899
+ else:
2900
+ for i, ligand_val in enumerate(ligands):
2901
+ for j in range(0, occs[i]):
2902
+ # get correct atoms
2903
+ bats, backbatoms = getnupdateb(backbatoms, dents[i])
2904
+ batslist.append(bats)
2905
+ if not geom:
2906
+ for comb_i, comb in enumerate(batslist):
2907
+ for i in comb:
2908
+ if i == 1:
2909
+ batslist[comb_i][i] = m3D.natoms - coord + 1
2910
+ ANN_flag, ANN_bondl, ANN_reason, ANN_attributes, catalysis_flag = init_ANN(
2911
+ args, ligands, occs, dents, batslist, tcats, licores)
2912
+
2913
+ this_diag = run_diag()
2914
+ this_diag.set_ANN(ANN_flag, ANN_reason, ANN_attributes, catalysis_flag)
2915
+
2916
+ # Unused return arguments
2917
+ subcatoms_ext: List[int] = []
2918
+ mligcatoms_ext: List[int] = []
2919
+ if args.mligcatoms:
2920
+ for i in range(len(args.mligcatoms)):
2921
+ mligcatoms_ext.append(0)
2922
+
2923
+ return core3D, complex3D, emsg, this_diag, subcatoms_ext, mligcatoms_ext
2924
+
2925
+
2926
+ def structgen(args: Namespace, rootdir: str, ligands: List[str], ligoc: List[int],
2927
+ sernum: int, write_files: bool = True) -> Tuple[List[str], str, run_diag]:
2928
+ """Main structure generation routine - multiple structures
2929
+
2930
+ Parameters
2931
+ ----------
2932
+ args : Namespace
2933
+ Namespace of arguments.
2934
+ rootdir : str
2935
+ Directory of current run to generate complex.
2936
+ ligands : list
2937
+ List of ligand names.
2938
+ ligoc : list
2939
+ List of ligand occupations.
2940
+ sernum : int
2941
+ Serial number of complex for naming.
2942
+ write_files : bool, optional
2943
+ Flag to write files. Default is True. False for pythonic generation.
2944
+
2945
+
2946
+ Returns
2947
+ -------
2948
+ strfiles : str
2949
+ List of XYZ files.
2950
+ emsg : str
2951
+ Error message for structure generation. If True, has string.
2952
+ this_diag : run_diag
2953
+ run_diag class instance containing properties of structure.
2954
+
2955
+ """
2956
+ emsg = ''
2957
+
2958
+ strfiles: List[str] = []
2959
+ # build structure
2960
+ sanity = False
2961
+ this_diag = run_diag()
2962
+ if (ligands):
2963
+ if args.reportonly:
2964
+ core3D, complex3D, emsg, this_diag, subcatoms_ext, mligcatoms_ext = generate_report(
2965
+ args, ligands, ligoc)
2966
+ if emsg:
2967
+ return strfiles, emsg, this_diag
2968
+ else:
2969
+ core3D, complex3D, emsg, this_diag, subcatoms_ext, mligcatoms_ext = mcomplex(
2970
+ args, ligands, ligoc)
2971
+ if args.debug:
2972
+ print(('subcatoms_ext are ' + str(subcatoms_ext)))
2973
+ if emsg:
2974
+ return strfiles, emsg, this_diag
2975
+ else:
2976
+ print('You specified no ligands. The whole mcomplex is read from the core.')
2977
+ # read mol3D from core
2978
+ core3D = mol3D()
2979
+ if '.xyz' in args.core:
2980
+ core3D.readfromxyz(args.core)
2981
+ else:
2982
+ atom = atom3D(Sym=args.core, xyz=[0, 0, 0])
2983
+ core3D.addAtom(atom)
2984
+
2985
+ name_core = args.core
2986
+
2987
+ if args.calccharge:
2988
+ args.charge = core3D.charge
2989
+ if args.debug:
2990
+ print(('setting charge to be ' + str(args.charge)))
2991
+ # check for molecule sanity
2992
+ if args.reportonly:
2993
+ this_diag.set_sanity(False, 'graph')
2994
+ else:
2995
+ sanity, d0 = core3D.sanitycheck(True)
2996
+ if sanity:
2997
+ print(('WARNING: Generated complex is not good! Minimum distance between atoms:' +
2998
+ "{0:.2f}".format(d0)+'A\n'))
2999
+ if args.gui:
3000
+ ssmsg = 'Generated complex in folder '+rootdir + \
3001
+ ' is no good! Minimum distance between atoms:' + \
3002
+ "{0:.2f}".format(d0)+'A\n'
3003
+ from Classes.mWidgets import mQDialogWarn
3004
+ qqb = mQDialogWarn('Warning', ssmsg)
3005
+ qqb.setParent(args.gui.wmain)
3006
+ if args.debug:
3007
+ print(('setting sanity diag, min dist at ' +
3008
+ str(d0) + ' (higher is better)'))
3009
+ this_diag.set_sanity(sanity, d0)
3010
+ # generate file name
3011
+ fname = name_complex(rootdir, name_core, args.geometry,
3012
+ ligands, ligoc, sernum, args, 1, sanity)
3013
+ if args.debug:
3014
+ print(('fname is ' + fname))
3015
+
3016
+ # write xyz file
3017
+ if (not args.reportonly) and (write_files):
3018
+ core3D.writexyz(fname)
3019
+ strfiles.append(fname)
3020
+ if write_files:
3021
+ # write report file
3022
+ this_diag.set_mol(core3D)
3023
+ this_diag.write_report(fname+'.report')
3024
+ # write input file from command line arguments
3025
+ getinputargs(args, fname)
3026
+ # (Possibly breaking change) Copy core3D into this_diag
3027
+ core3D_copy = mol3D()
3028
+ core3D_copy.copymol3D(core3D)
3029
+ this_diag.set_mol(core3D_copy)
3030
+
3031
+ del core3D # Legacy code, unsure if needed
3032
+
3033
+ pfold = rootdir.split('/', 1)[-1]
3034
+ if args.gui:
3035
+ args.gui.iWtxt.setText('In folder '+pfold+' generated ' +
3036
+ '1 structure!\n'+args.gui.iWtxt.toPlainText())
3037
+ args.gui.app.processEvents()
3038
+ print(('\nIn folder '+rootdir+' generated 1 structure!'))
3039
+
3040
+ return strfiles, emsg, this_diag