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,2330 @@
1
+ # @file ligand.py
2
+ # Defines ligand class for postprocessing DFT results by measuring ligand properties
3
+ #
4
+ # Written by JP Janet for HJK Group
5
+ #
6
+ # Dpt of Chemical Engineering, MIT
7
+ import numpy as np
8
+ from itertools import combinations
9
+ from molSimplify.Classes.mol3D import mol3D
10
+ from molSimplify.Classes.globalvars import bondivdw, vdwrad
11
+ from molSimplify.utils.decorators import deprecated
12
+
13
+
14
+ # Ligand class for postprocessing DFT results by measuring ligand properties
15
+ class ligand:
16
+ """Ligand class for postprocessing DFT results to measure ligand properties.
17
+
18
+ Parameters
19
+ ----------
20
+ master_mol : mol3D
21
+ mol3D class instance for ligand mol.
22
+ index_list : list
23
+ List for ligand connection atom indices.
24
+ dent : int
25
+ Denticity of the ligand
26
+ read_lig : bool, optional
27
+ str of file or (xyz/mol2 string) of saved ligand, by default False
28
+ Raises
29
+ ------
30
+ ValueError
31
+ If read_ligand type is unknown.
32
+ """
33
+ def __init__(self, master_mol, index_list, dent, read_lig=False):
34
+ if isinstance(read_lig, str):
35
+ thismol = mol3D()
36
+ if 'TRIPOS' in read_lig:
37
+ thismol.readfrommol2(read_lig, readstring=True)
38
+ # Xyz filename
39
+ elif read_lig[-4:] == '.xyz':
40
+ thismol.readfromxyz(read_lig)
41
+ # mol2 filename
42
+ elif read_lig[-5:] == '.mol2':
43
+ thismol.readfrommol2(read_lig)
44
+ # checking for number at start of string -> indicates xyz string
45
+ elif (len(read_lig.split('\n')) > 3) & (read_lig.split('\n')[0].replace(' ', '').isnumeric()):
46
+ thismol.readfromstring(read_lig)
47
+ # checking for similar file without header
48
+ elif (len(read_lig.split('\n')[0].split()) == 4) and read_lig.split('\n')[0].split()[0]:
49
+ thismol.readfromstring(read_lig)
50
+ else:
51
+ raise ValueError('Not Recognized Structure Type!')
52
+ self.master_mol = thismol
53
+ met = thismol.findMetal()[0] # Pull out metal
54
+ self.index_list = [x for x in range(thismol.natoms) if x != met]
55
+ self.dent = len(thismol.getBondedAtomsSmart(met))
56
+ self.ext_int_dict = {i: j for i, j in enumerate(self.index_list)}
57
+ self.mol2string = thismol.writemol2('ligand', writestring=True)
58
+ self.lig_mol_graph_det = thismol.get_mol_graph_det()
59
+ self.percent_buried_volume = False
60
+ else:
61
+ self.master_mol = master_mol
62
+ self.index_list = sorted(index_list)
63
+ self.dent = dent
64
+ self.ext_int_dict = dict() # store
65
+ self.mol2string = False
66
+ self.lig_mol_graph_det = False
67
+ self.percent_buried_volume = False
68
+
69
+ def __repr__(self):
70
+ if hasattr(self, 'mol'):
71
+ return f'ligand({self.mol.make_formula(latex=False)})'
72
+ else:
73
+ # Default python repr:
74
+ return object.__repr__(self)
75
+
76
+ def obtain_mol3d(self):
77
+ """Getting the mol3D of the ligand. Deprecated. Map between int and ext indcies.
78
+ Obtain the ligand from the complex mol3D object.
79
+ """
80
+ this_mol = mol3D()
81
+ this_ext_int_dict = dict()
82
+ j = 0
83
+ # the old routine where all atoms in the master_mol are gone through from 0 to natoms-1
84
+ # for i in range(0, self.master_mol.natoms):
85
+ # if i in self.index_list:
86
+ # the new rountine where the indices are taken out directly. This way the order of atoms is preserved
87
+ for i in self.index_list:
88
+ this_mol.addAtom(self.master_mol.getAtom(i))
89
+ this_ext_int_dict.update({i: j})
90
+ j += 1 # keep count of how many are added
91
+ self.mol = this_mol
92
+ if len(self.master_mol.graph): # Save graph to ligand mol3D object
93
+ delete_inds = [x for x in range(self.master_mol.natoms) if x not in self.index_list]
94
+ self.mol.graph = np.delete(np.delete(self.master_mol.graph, delete_inds, 0), delete_inds, 1)
95
+ if self.master_mol.bo_dict:
96
+ save_bo_dict = self.master_mol.get_bo_dict_from_inds(self.index_list)
97
+ self.mol.bo_dict = save_bo_dict
98
+ if hasattr(self.master_mol, 'prop_tensor'):
99
+ prop_tensor = np.zeros(shape=(len(self.index_list), len(self.master_mol.prop_name)))
100
+ c = 0
101
+ for i in self.index_list:
102
+ prop_tensor[c, :] = self.master_mol.prop_tensor[i, :]
103
+ c += 1
104
+ self.mol.prop_tensor = prop_tensor
105
+ self.mol.prop_name = self.master_mol.prop_name
106
+ self.ext_int_dict = this_ext_int_dict
107
+
108
+ def get_lig_mol2(self, transition_metals_only=True, inds=None, include_metal=True, bimetal=False):
109
+ """Write out ligand mol2 string and molecular graph determinant.
110
+ Include Metal flagged with Symbol "X" for placeholder status.
111
+ Parameters
112
+ ----------
113
+ transition_metals_only : bool
114
+ flag only transition metals with findMetal() function.
115
+ inds : list
116
+ indices of metals if passing in multimetal system
117
+
118
+ Returns
119
+ -------
120
+ lig_mol_graph_det : str
121
+ Molecular graph determinant.
122
+ ligand_mol2_string : str
123
+ Mol2 string for the ligand.
124
+ catoms_indices : list
125
+ List of catom (connection atom) indices - only returned if include_metal is set to False. Zero-indexed.
126
+
127
+ """
128
+ this_mol2 = mol3D()
129
+ if inds:
130
+ metal_ind = inds
131
+ else:
132
+ metal_ind = self.master_mol.findMetal(transition_metals_only=transition_metals_only)
133
+ this_mol2_inds = self.index_list.copy()
134
+ if include_metal:
135
+ this_mol2_inds += metal_ind
136
+ this_mol2_inds = sorted(this_mol2_inds)
137
+
138
+ if (not bimetal):
139
+ # Set up a binary vector indicating whether each atom is a connecting atom (1) or not (0)
140
+ catoms_indices = self.master_mol.getBondedAtomsSmart(metal_ind)
141
+ catom_selector = np.zeros(self.master_mol.natoms)
142
+ catom_selector[catoms_indices] = 1
143
+
144
+ # Add the metal with symbol = 'M'
145
+ new_metal_inds = []
146
+ for j, i in enumerate(this_mol2_inds):
147
+ if i in metal_ind:
148
+ atom = self.master_mol.getAtom(i)
149
+ atom.mutate('X')
150
+ this_mol2.addAtom(atom)
151
+ new_metal_inds.append(j)
152
+ else:
153
+ this_mol2.addAtom(self.master_mol.getAtom(i))
154
+ if len(self.master_mol.graph): # Save graph to ligand mol3D object
155
+ delete_inds = [x for x in range(self.master_mol.natoms) if x not in this_mol2_inds]
156
+ this_mol2.graph = np.delete(np.delete(self.master_mol.graph, delete_inds, 0), delete_inds, 1)
157
+ if (not bimetal):
158
+ catom_selector = np.delete(catom_selector, delete_inds)
159
+ catoms_indices = np.nonzero(catom_selector)[0]
160
+
161
+ # #### Check for multiple metal centers. Save more coordinated one.
162
+ if include_metal:
163
+ if len(new_metal_inds) == 2:
164
+ minds = new_metal_inds
165
+ metal_cns = [len(this_mol2.getBondedAtomsSmart(x)) for x in minds]
166
+ delind = minds[np.argmin(metal_cns)]
167
+ this_mol2.deleteatom(minds[np.argmin(metal_cns)])
168
+ del this_mol2_inds[delind]
169
+ if self.master_mol.bo_dict:
170
+ save_bo_dict = self.master_mol.get_bo_dict_from_inds(this_mol2_inds)
171
+ this_mol2.bo_dict = save_bo_dict
172
+ try:
173
+ lig_mol_graph_det = this_mol2.get_mol_graph_det()
174
+ except:
175
+ lig_mol_graph_det = None
176
+ lig_mol2_string = this_mol2.writemol2('ligand', writestring=True)
177
+ self.mol2string = lig_mol2_string
178
+ self.lig_mol_graph_det = lig_mol_graph_det
179
+
180
+ if include_metal or bimetal:
181
+ return lig_mol_graph_det, lig_mol2_string
182
+ else:
183
+ return lig_mol_graph_det, lig_mol2_string, catoms_indices
184
+
185
+ def percent_buried_vol(self, radius=3.5, gridspec=0.1,
186
+ bondiscale=1.17, hydrogens=True):
187
+ """Calculate the percent buried volume as described in https://doi.org/10.1039/B922984A,
188
+ and https://chemistry-europe.onlinelibrary.wiley.com/doi/abs/10.1002/ejic.200801160.
189
+ Bondi VDW radii are used where possible
190
+
191
+ Parameters
192
+ ----------
193
+ radius : float, optional
194
+ Radius of sphere around metal center consider in Angstroms, by default 3.5
195
+ Paper recommends 3.5
196
+ gridspec : float, optional
197
+ xyz grid spacing in Angstroms, by default 0.1
198
+ Paper recommends 0.05 - this is slower
199
+ bondiscale : float, optional
200
+ Scale of the bondi vdw radii for ligand atoms, by default 1.17
201
+ Paper recommends 1.17
202
+ hydrogens : bool, optional
203
+ Include hydrogens in percent buried volume, by default True
204
+ Paper recommends to not include hydrogens.
205
+
206
+ Returns
207
+ -------
208
+ percent_buried_volume : float
209
+ Percentage of the volume of a sphere around the metal that is buried under ligand atoms.
210
+ Represents bulkiness of the ligand around the metal center.
211
+ """
212
+ if not self.mol2string:
213
+ _, _ = self.get_lig_mol2()
214
+ thismol = mol3D()
215
+ thismol.readfrommol2(self.mol2string, readstring=True)
216
+ # Accounting for hydrogens by default - otherwise deleting.
217
+ if not hydrogens:
218
+ thismol.deleteHs()
219
+ natoms = thismol.natoms
220
+ # Catch case where just hydrogen ligand
221
+ if natoms == 1:
222
+ percent_buried = 0.0
223
+ else:
224
+ met = thismol.findMetal()[0]
225
+ coords = thismol.coordsvect() - thismol.coordsvect()[met] # Center coordinates to the metal
226
+ syms = [x for i, x in enumerate(thismol.symvect()) if i != met]
227
+ radvect = []
228
+ for sym in syms:
229
+ if sym in bondivdw: # Take in the bondivdw radii reccomeneded in paper
230
+ radvect.append(bondivdw[sym]*bondiscale)
231
+ else: # Else take in Newer definition of vdw radii
232
+ radvect.append(vdwrad[sym]*bondiscale)
233
+ radiusvect = np.array(radvect)
234
+ inds = np.array([x for x in range(natoms) if x != met])
235
+ coords = coords[inds] # Get rid of metal location from coords
236
+ x_ = np.arange(-radius, radius, gridspec)
237
+ y_ = np.arange(-radius, radius, gridspec)
238
+ z_ = np.arange(-radius, radius, gridspec)
239
+ grid = np.meshgrid(x_, y_, z_, indexing='ij')
240
+ mgrid = list(map(np.ravel, grid))
241
+ combined = np.vstack(mgrid).T # Flatten meshgrid to nx3
242
+ init_coords = np.array([[0, 0, 0]]) # We have set metal to (0,0,0)
243
+ # Get distance of all gridpoints from (0,0,0) -> Filter out gridpoints further
244
+ init_dists = np.linalg.norm(init_coords[:, None, :]-combined[None, :, :], axis=-1)
245
+ # Get rid of gridpoints further away than radius and metal
246
+ combined = combined[np.where(init_dists[0, :] <= radius)[0]]
247
+ # Get rid of ligand atoms that won't interact at all
248
+ met_ds = np.linalg.norm(init_coords[:, None, :]-coords[None, :, :], axis=-1)
249
+ met_ds = met_ds - radius - radiusvect
250
+ mfilter = np.where(met_ds[0] < 0)
251
+ coords = coords[mfilter]
252
+ radiusvect = radiusvect[mfilter]
253
+ # Calc distance of all remaining atomic coords to all grid points
254
+ ds = np.linalg.norm(coords[:, None, :]-combined[None, :, :], axis=-1)
255
+ # Compare distances to radii of atoms, flag any less than than or equal to the radii as buried
256
+ buried = len(np.where(np.any(np.less_equal(ds[:, :], radiusvect[:, None]), axis=0))[0])
257
+ total = ds.shape[1]
258
+ # Percent buried volume
259
+ percent_buried = float(buried)/total*100
260
+ self.percent_buried_volume = percent_buried
261
+ return percent_buried
262
+
263
+
264
+ def ligand_breakdown(mol, BondedOct=False, silent=True, transition_metals_only=True):
265
+ """Extract axial and equatorial components of a octahedral complex.
266
+
267
+ Parameters
268
+ ----------
269
+ mol : mol3D
270
+ mol3D class instance for the complex.
271
+ BondedOct : bool, optional
272
+ Enforce octahedral bonding. Default is False.
273
+ silent : bool, optional
274
+ Silence extra printout. Default is True.
275
+
276
+ Returns
277
+ -------
278
+ liglist : list of list of int
279
+ List of ligands. Length is the number of ligands. Each inner list contains the global indices of a ligand
280
+ ligdents : list of int
281
+ List of ligand denticities. Length is the number of ligands
282
+ ligcons : list of list of int
283
+ List of ligand connection indices (in mol). Length is the number of ligands
284
+
285
+ """
286
+ # this function takes an octahedral
287
+ # complex and returns ligands
288
+ metal_index = mol.findMetal(transition_metals_only=transition_metals_only)[0]
289
+ # print(BondedOct)
290
+ bondedatoms = mol.getBondedAtomsSmart(metal_index, oct=BondedOct)
291
+ # print('!!!!!boundatoms', bondedatoms)
292
+ # print('from get oct' + str(bondedatoms))
293
+ # print('***\n')
294
+ bonded_atom_symbols = [mol.getAtom(i).symbol() for i in bondedatoms]
295
+ if not silent:
296
+ print(('result of ligand ligand_breakdown', bonded_atom_symbols))
297
+ liglist = []
298
+ ligdents = []
299
+ ligcons = []
300
+ for atom in bondedatoms:
301
+ if not silent:
302
+ print(('this atom type is ' + mol.getAtom(atom).symbol()))
303
+ print(('conection number ' + str(atom) + " of " + str(bondedatoms)))
304
+ fragment = mol.findsubMol(atom, metal_index)
305
+ this_cons = [x for x in fragment if (x in bondedatoms)]
306
+ if not silent:
307
+ print(('fragment', fragment))
308
+ print(('this_cons', this_cons))
309
+ unique = True
310
+ for i, unique_ligands in enumerate(liglist):
311
+ if sorted(fragment) == sorted(unique_ligands):
312
+ unique = False
313
+ matched = i
314
+ if unique:
315
+ liglist.append(sorted(fragment))
316
+ ligdents.append(1)
317
+ ligcons.append(this_cons)
318
+ else:
319
+ ligdents[matched] += 1
320
+ return liglist, ligdents, ligcons
321
+
322
+
323
+ @deprecated('Use ligand_assign_consistent instead.')
324
+ def ligand_assign(mol, liglist, ligdents, ligcons, loud=False, name=False, eq_sym_match=False):
325
+ """Assign axial and equatorial portions. Deprecated. Use ligand_assign_consistent. For octahedral geometries.
326
+
327
+ Parameters
328
+ ----------
329
+ mol : mol3D
330
+ mol3D class instance for the complex.
331
+ liglist : list
332
+ List of ligands
333
+ ligdents : list
334
+ List of ligand denticities
335
+ ligcons : list
336
+ List of ligand connection indices (in mol)
337
+ loud : bool, optional
338
+ Enable extra printout. Default is False.
339
+ name : bool, optional
340
+ Name ligands to write to XYZ. Default is False. Broken, do not use.
341
+ eq_sym_match : bool, optional
342
+ Default is False. Broken, do not use.
343
+
344
+
345
+ Returns
346
+ -------
347
+ ax_ligand_list : list
348
+ Ligands designated as axial ligands.
349
+ eq_ligand_list : list
350
+ Ligands designated as equatorial ligands.
351
+ ax_natoms_list : list
352
+ Number of atoms in axial ligands.
353
+ eq_natoms_list : list
354
+ Number of atoms in equatorial ligands.
355
+ ax_con_int_list : list
356
+ Connecting atoms indices of axial ligands.
357
+ eq_con_int_list : list
358
+ Connecting atoms indices of equatorial ligands.
359
+ ax_con_list : list
360
+ Connecting atoms of axial ligands.
361
+ eq_con_list : list
362
+ Connecting atoms of equatorial ligands.
363
+ built_ligand_list : list
364
+ List of ligand classes for all ligands.
365
+
366
+ """
367
+ valid = True
368
+ # loud = False
369
+ pentadentate = False
370
+ hexadentate = False
371
+ built_ligand_list = list()
372
+ lig_natoms_list = list()
373
+ unique_ligands = list()
374
+ ligand_counts = list()
375
+ all_ligand_counts = [0, 0, 0, 0, 0, 0]
376
+ ligand_records = list()
377
+ ax_con_int_list = list()
378
+ eq_con_int_list = list()
379
+ ax_natoms_list = list()
380
+ eq_natoms_list = list()
381
+ n_ligs = len(liglist)
382
+ max_dent = max(ligdents)
383
+ min_dent = min(ligdents)
384
+ if loud:
385
+ print('********************************************')
386
+ print(("n_ligs = " + str(n_ligs)))
387
+ print(("max d = " + str(max_dent)))
388
+ print(("min_dent = " + str(min_dent)))
389
+ print(("ligand list is" + str(liglist)))
390
+ print(('denticities are ' + str(ligdents)))
391
+ if (max(ligdents) == 4) and (min(ligdents) != 1):
392
+ valid = False
393
+ print(('bad denticities: ' + str(ligdents)))
394
+ print(('min denticities: ' + str(min(ligdents))))
395
+ if max(ligdents) > 4:
396
+ # ### Handling of pentadentate ligands goes here. #####
397
+ if max(ligdents) == 5 and min(ligdents) == 1:
398
+ pentadentate = True
399
+ elif max(ligdents) == 6 and min(ligdents) == 6:
400
+ hexadentate = True
401
+ else:
402
+ valid = False
403
+ print(('bad denticities: ' + str(ligdents)))
404
+ print(('max denticities: ' + str(min(ligdents))))
405
+ if n_ligs > 3 and min(ligdents) > 1:
406
+ valid = False
407
+ print(('too many ligs ' + str((n_ligs))))
408
+ eq_lig_list = list()
409
+ ax_lig_list = list()
410
+ ax_con_list = list()
411
+ eq_con_list = list()
412
+ for i, ligand_indices in enumerate(liglist):
413
+ this_ligand = ligand(mol, ligand_indices, ligdents[i])
414
+ this_ligand.obtain_mol3d()
415
+ # lig_natoms_list.append(this_ligand.mol.natoms) ## old one with obtain_mol3d
416
+ built_ligand_list.append(this_ligand)
417
+ lig_natoms_list.append(len(this_ligand.index_list))
418
+ for j, built_ligs in enumerate(built_ligand_list):
419
+ # test if ligand is unique
420
+ sl = [atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)]
421
+ # _sl = [atom.symbol() for atom in built_ligs.mol.getAtoms()] ## old one with obtain_mol3d
422
+ if loud:
423
+ print(('checking lig ' + str(j) + ' : ' + str(sl)))
424
+ unique = 1
425
+ for i, other_sl in enumerate(unique_ligands):
426
+ if sorted(sl) == sorted(other_sl):
427
+ # duplicate
428
+ unique = 0
429
+ ligand_counts[i] += 1
430
+ if unique == 1:
431
+ unique_ligands.append(sl)
432
+ ligand_counts.append(1)
433
+ ligand_records.append(j)
434
+ # loop to bin ligands:
435
+ for j, built_ligs in enumerate(built_ligand_list):
436
+ # test if ligand is unique
437
+ sl = [atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)]
438
+ for i, other_sl in enumerate(unique_ligands):
439
+ if sorted(sl) == sorted(other_sl):
440
+ # duplicate
441
+ # print(i,ligand_counts[i])
442
+ all_ligand_counts[j] = ligand_counts[i]
443
+
444
+ if loud:
445
+ print(('unique ligands' + str(unique_ligands)))
446
+ print(('ligand counts' + str(ligand_counts)))
447
+ print(('ligand records ' + str(ligand_records)))
448
+ print((str(max(ligand_counts)) +
449
+ ' is the max and min in ' + str(min(ligand_counts))))
450
+ n_unique_ligs = len(unique_ligands)
451
+ if (n_ligs == 3) or (n_ligs == 4): # most common case,
452
+ # one/two equatorial and 2 axial mono
453
+ # or three bidentate
454
+ for i, ligs in enumerate(liglist):
455
+ if ligdents[i] == 1 and min_dent == 1: # anything with equatorial monos will
456
+ # have higher than 4 n_ligs
457
+ ax_lig_list.append(i)
458
+ if loud:
459
+ print(('choosing ' + str(i) + ' as ax based on dent =1'))
460
+ ax_con_list.append(ligcons[i])
461
+ if (ligdents[i] >= 2) and (min_dent == 1):
462
+ eq_lig_list.append(i)
463
+ if loud:
464
+ print(('choosing lig ' + str(i) + ' as eq based on high dent'))
465
+ eq_con_list.append(ligcons[i])
466
+ if (n_ligs == 3) and (min_dent == max_dent):
467
+ if n_unique_ligs == 1:
468
+ # take any 2, they are all the same
469
+ if loud:
470
+ print('triple bidentate case')
471
+ ax_lig_list.append(0)
472
+ eq_lig_list.append(1)
473
+ eq_lig_list.append(2)
474
+ ax_con_list.append(ligcons[0])
475
+ eq_con_list.append(ligcons[1])
476
+ eq_con_list.append(ligcons[2])
477
+ elif min_dent == 2 and max_dent == 2 and n_ligs == 3 and not n_unique_ligs == 1:
478
+ # this is a hetero/bidentate case
479
+ for i, ligs in enumerate(liglist):
480
+ if all_ligand_counts[i] == 2:
481
+ eq_lig_list.append(i)
482
+ eq_con_list.append(ligcons[i])
483
+ elif all_ligand_counts[i] == 1:
484
+ ax_lig_list.append(i)
485
+ ax_con_list.append(ligcons[i])
486
+ elif (n_ligs == 6): # all mono case,
487
+ minz = 500
488
+ maxz = -500
489
+ if loud:
490
+ print('monodentate case')
491
+ allowed = list(range(0, 6))
492
+ not_eq = list()
493
+ for j, built_ligs in enumerate(built_ligand_list):
494
+ this_z = sum([mol.getAtom(ii).coords()[2]
495
+ for ii in ligcons[j]]) / len(ligcons[j])
496
+ if this_z < minz:
497
+ minz = this_z
498
+ bot_lig = j
499
+ bot_con = ligcons[j]
500
+ if loud:
501
+ print(('updating bot axial to ' + str(bot_lig)))
502
+ if this_z > maxz:
503
+ maxz = this_z
504
+ top_lig = j
505
+ top_con = ligcons[j]
506
+ if loud:
507
+ print(('updating top axial to ' + str(top_lig)))
508
+ not_eq.append(bot_lig)
509
+ not_eq.append(top_lig)
510
+
511
+ allowed = [x for x in allowed if ((x not in not_eq))]
512
+ if len(allowed) != 4:
513
+ print(('error in decomp of monodentate case!', allowed))
514
+ eq_lig_list = allowed
515
+ eq_con_list = [ligcons[i] for i in allowed]
516
+ ax_lig_list = [top_lig, bot_lig]
517
+ ax_con_list = [top_con, bot_con]
518
+ if loud:
519
+ print(('geometric eq_list ' + str(eq_lig_list)))
520
+ print(('geometric ax_list ' + str(eq_lig_list)))
521
+ if (max(ligand_counts) != 4) or (min(ligand_counts) != 2):
522
+ if loud:
523
+ print('not a 4-6 case')
524
+ if (max(ligand_counts) == 6):
525
+ if loud:
526
+ print('6-homoleptic, using geo values')
527
+ # ax=ligand_records[ligand_counts.index(6)]
528
+ # eq_lig=ligand_records[ligand_counts.index(6)]
529
+ else:
530
+ if loud:
531
+ print('monodentates not the same, using geo values ')
532
+ print(ligand_counts)
533
+ print(unique_ligands)
534
+ elif n_unique_ligs == 2:
535
+ if loud:
536
+ print('this is a 4-6 case')
537
+ allowed = list(range(0, 6))
538
+ ax_lig_list = [i for i in allowed if (all_ligand_counts[i] == 2)]
539
+ eq_lig_list = [i for i in allowed if (all_ligand_counts[i] == 4)]
540
+ ax_con_list = [ligcons[i] for i in ax_lig_list]
541
+ eq_con_list = [ligcons[i] for i in eq_lig_list]
542
+ elif n_ligs == 2 and pentadentate:
543
+ # ### Handling for pentadentate scaffolds ####
544
+ if loud:
545
+ print('pentadentate case')
546
+ allowed = [0, 1]
547
+ not_eq = list()
548
+ for j, built_ligs in enumerate(built_ligand_list):
549
+ if len(ligcons[j]) == 1:
550
+ # ### This is the axial ligand ####
551
+ # print(j, 'axial lig')
552
+ top_lig = j
553
+ top_con = ligcons[j]
554
+ not_eq.append(top_lig)
555
+ else:
556
+ pentadentate_coord_list = np.array([mol.getAtom(
557
+ ii).coords() for ii in ligcons[j]])
558
+ # #### Adjusting this so that by default, any 4 within the same plane will be assigned as eq. ###
559
+ if loud:
560
+ print('pentadentate coord LIST!')
561
+ print(pentadentate_coord_list)
562
+ point_combos = combinations([0, 1, 2, 3, 4], 4)
563
+ error_list = []
564
+ combo_list = []
565
+ for i, combo in enumerate(point_combos):
566
+ combo_list.append(list(combo))
567
+ A = np.zeros((4, 3))
568
+ b = np.zeros(4)
569
+ for point_i, point_num in enumerate(combo):
570
+ coordlist = pentadentate_coord_list[point_num]
571
+ A[point_i, :] = [coordlist[0], coordlist[1], 1]
572
+ b[point_i] = coordlist[2]
573
+ # #### This code builds the best fit plane between 4 points,
574
+ # #### Then calculates the variance of the 4 points with respect to the plane
575
+ # #### The 4 that have the least variance are flagged as the eq plane.
576
+ try:
577
+ fit = np.linalg.pinv(A.T @ A) @ A.T @ b
578
+ errors = b - A @ fit
579
+ error_var = np.var(errors)
580
+ error_list.append(error_var)
581
+ except np.linalg.LinAlgError:
582
+ error_list.append(0)
583
+ if loud:
584
+ print('combos below')
585
+ print(combo_list)
586
+ print('errors next, argmin combo selected')
587
+ print(error_list)
588
+ not_ax_points = combo_list[np.argmin(error_list)]
589
+ if len(set(not_ax_points)) != 4:
590
+ print('The equatorial plane is not being assigned correctly. Please check.')
591
+ # sardines
592
+ else:
593
+ bot_idx = list(set(range(5)) - set(not_ax_points))[0]
594
+ if loud:
595
+ print(('This is bot_idx', bot_idx))
596
+ bot_lig = j
597
+ bot_con = [ligcons[j][bot_idx]]
598
+
599
+ allowed = list(set(allowed) - set(not_eq))
600
+ if loud:
601
+ print(('this is the allowed list', allowed, not_eq))
602
+ eq_lig_list = allowed
603
+ eq_con_list = [
604
+ list(set([ligcons[i] for i in allowed][0]) - set(top_con) - set(bot_con))]
605
+ ax_lig_list = [top_lig, bot_lig]
606
+ ax_con_list = [top_con, bot_con]
607
+ if loud:
608
+ print(('con lists', eq_con_list, ax_con_list))
609
+ ###########################################################################################
610
+ # In the above, the pentadentate ligand is classified as both axial and equatorial. #
611
+ # The lc atoms are decided by the z-position. Thus the pentadentate ligand has 4 eq-lc #
612
+ # and 1 ax-lc. Currently should be able to check this and set that up. #
613
+ ###########################################################################################
614
+ elif n_ligs == 1 and hexadentate:
615
+ allowed = [0, 1]
616
+ if loud:
617
+ print('hexadentate case')
618
+ not_eq = list()
619
+ for j, built_ligs in enumerate(built_ligand_list):
620
+ hexadentate_coord_list = np.array([mol.getAtom(
621
+ ii).coords() for ii in ligcons[j]])
622
+ # #### Adjusting this so that by default, any 4 within the same plane will be assigned as eq. ###
623
+ if loud:
624
+ print('hexadentate coord LIST!')
625
+ print(hexadentate_coord_list)
626
+ # point_combos = combinations([1,2,3,4,5,6],4)
627
+ pair_combos = list(combinations([0, 1, 2, 3, 4, 5], 2))
628
+ angle_list = []
629
+ pair_list = []
630
+ for i, pair in enumerate(pair_combos):
631
+ pair_list.append(list(pair))
632
+ p1 = np.squeeze(np.array(hexadentate_coord_list[list(pair)[0]]))
633
+ p2 = np.squeeze(np.array(hexadentate_coord_list[list(pair)[1]]))
634
+ m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
635
+ v1u = np.squeeze(np.array((m - p1) / np.linalg.norm((m - p1))))
636
+ v2u = np.squeeze(np.array((m - p2) / np.linalg.norm((m - p2))))
637
+ # print('v1v2',v1u,v2u)
638
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
639
+ if loud:
640
+ print(('pair of atoms, then angle', pair, angle))
641
+ angle_list.append(angle)
642
+ argsort_angle_list = np.squeeze(np.array(angle_list)).argsort()[-3:][::-1]
643
+ point_combos = [pair_list[argsort_angle_list[0]] + pair_list[argsort_angle_list[1]],
644
+ pair_list[argsort_angle_list[1]] + pair_list[argsort_angle_list[2]],
645
+ pair_list[argsort_angle_list[2]] + pair_list[argsort_angle_list[0]]]
646
+ error_list = []
647
+ combo_list = []
648
+ fitlist = []
649
+ for i, combo in enumerate(point_combos):
650
+ combo_list.append(combo)
651
+ A = np.zeros((4, 3))
652
+ b = np.zeros(4)
653
+ for point_i, point_num in enumerate(combo):
654
+ coordlist = hexadentate_coord_list[point_num]
655
+ A[point_i, :] = [coordlist[0], coordlist[1], 1]
656
+ b[point_i] = coordlist[2]
657
+ # #### This code builds the best fit plane between 4 points,
658
+ # #### Then calculates the variance of the 4 points with respect to the plane
659
+ # #### The 4 that have the least variance are flagged as the eq plane.
660
+ fit = np.linalg.pinv(A.T @ A) @ A.T @ b
661
+ fitlist.append(fit)
662
+ errors = b - A @ fit
663
+ error_var = np.var(errors)
664
+ error_list.append(error_var)
665
+ if loud:
666
+ print('combos below')
667
+ print(combo_list)
668
+ print('errors next, argmin combo selected')
669
+ print(error_list)
670
+ best_fit_planes = np.squeeze(np.array(error_list)).argsort()[:3]
671
+ perpdist = []
672
+ perpcombo = []
673
+ for fitnum, best_fit in enumerate(best_fit_planes):
674
+ temp_fit = fitlist[best_fit]
675
+ temp_combo = combo_list[best_fit]
676
+ perpcombo.append(int(best_fit))
677
+ temp_ax = set(range(0, 6)) - set(temp_combo)
678
+ ax_dist = []
679
+ for point_num in temp_ax:
680
+ coordlist = hexadentate_coord_list[point_num]
681
+ planez = [coordlist[0], coordlist[1], 1] * temp_fit
682
+ # planez = temp_fit[0] * coordlist[0] + temp_fit[1] * coordlist[1] + fit[2]
683
+ plane_coords = [coordlist[0], coordlist[1], planez]
684
+ adjusted_coords = [coordlist[0], coordlist[1], coordlist[2]]
685
+ squared_dist = np.sum((np.array(adjusted_coords) - np.array(plane_coords)) ** 2)
686
+ dist = np.squeeze(np.sqrt(squared_dist))
687
+ if loud:
688
+ print(('dist', dist))
689
+ ax_dist.append(dist)
690
+ perpdist.append(np.mean(ax_dist))
691
+ if loud:
692
+ print(("Perpendicular distance is", perpdist, perpcombo, len(perpdist), len(best_fit_planes)))
693
+ not_ax_points = combo_list[perpcombo[np.argmax(np.array(perpdist))]]
694
+ if len(set(not_ax_points)) != 4:
695
+ print('The equatorial plane is not being assigned correctly. Please check.')
696
+ # sardines
697
+ else:
698
+ bot_idx = list(set(range(6)) - set(not_ax_points))[0]
699
+ top_idx = list(set(range(6)) - set(not_ax_points))[1]
700
+ if loud:
701
+ print(('This is bot_idx', bot_idx))
702
+ bot_lig = j
703
+ top_lig = j
704
+ bot_con = [ligcons[j][bot_idx]]
705
+ top_con = [ligcons[j][top_idx]]
706
+ allowed = list(set(allowed) - set(not_eq))
707
+ if loud:
708
+ print(('this is the allowed list', allowed, not_eq))
709
+ eq_lig_list = [top_lig]
710
+ eq_con_list = eq_con_list = [
711
+ list(set([ligcons[0][i] for i in not_ax_points]))]
712
+ ax_lig_list = [top_lig, bot_lig]
713
+ ax_con_list = [top_con, bot_con]
714
+ if loud:
715
+ print(('con lists', eq_con_list, ax_con_list))
716
+
717
+ # ############## DONE WITH CLASSIFICATION ######
718
+ # ax_lig=ligand_records[ligand_counts.index(2)]
719
+ # eq_lig=ligand_records[ligand_counts.index(4)]
720
+ ax_ligand_list = [built_ligand_list[i] for i in ax_lig_list]
721
+ eq_ligand_list = [built_ligand_list[i] for i in eq_lig_list]
722
+ if loud and valid:
723
+ print(('lig_nat_list', lig_natoms_list))
724
+ print(('eq_liq is ind ', eq_lig_list))
725
+ print(('ax_liq is ind ', ax_lig_list))
726
+ print(('ax built lig [0] ext ind :' +
727
+ str(list(built_ligand_list[ax_lig_list[0]].ext_int_dict.keys()))))
728
+ if len(ax_lig_list) > 1:
729
+ print(('ax built lig [1] ext ind :' +
730
+ str(list(built_ligand_list[ax_lig_list[1]].ext_int_dict.keys()))))
731
+ print(('eq built lig [0] ext ind: ' +
732
+ str(list(built_ligand_list[eq_lig_list[0]].ext_int_dict.keys()))))
733
+ print(('eq_con is ' + str((eq_con_list))))
734
+ print(('ax_con is ' + str((ax_con_list))))
735
+ for j, ax_con in enumerate(ax_con_list):
736
+ current_ligand_index_list = built_ligand_list[ax_lig_list[j]].index_list
737
+ ax_con_int_list.append([current_ligand_index_list.index(i) for i in ax_con])
738
+ # ax_con_int_list.append(
739
+ # [built_ligand_list[ax_lig_list[j]].ext_int_dict[i] for i in ax_con])
740
+ # # convert to interal index ## old one with obtain_mol3d
741
+ for j, eq_con in enumerate(eq_con_list):
742
+ current_ligand_index_list = built_ligand_list[eq_lig_list[j]].index_list
743
+ eq_con_int_list.append([current_ligand_index_list.index(i) for i in eq_con])
744
+ # eq_con_int_list.append(
745
+ # [built_ligand_list[eq_lig_list[j]].ext_int_dict[i] for i in eq_con])
746
+ # # convert to interal index ## old one with obtain_mol3d
747
+ if loud:
748
+ print(('int eq ' + str(eq_con_int_list)))
749
+ print(('ext eq ' + str(eq_con_list)))
750
+ print('**********************************************')
751
+ for ax_lig in ax_lig_list:
752
+ ax_natoms_list.append(lig_natoms_list[ax_lig])
753
+ for eq_lig in eq_lig_list:
754
+ eq_natoms_list.append(lig_natoms_list[eq_lig])
755
+ return (ax_ligand_list, eq_ligand_list, ax_natoms_list, eq_natoms_list,
756
+ ax_con_int_list, eq_con_int_list, ax_con_list, eq_con_list, built_ligand_list)
757
+
758
+
759
+ # ## DISCLAIMER!!! Please be careful while modifying any part of 'ligand_assign_consistent'
760
+ # as that could affect everything else ###
761
+ def ligand_assign_consistent(mol, liglist, ligdents, ligcons, loud=False,
762
+ name=False, use_z=False, eq_sym_match=False):
763
+ """This ligand assignment code handles octahedral complexes consistently. Assigns any octahedral complex.
764
+
765
+ Parameters
766
+ ----------
767
+ mol : mol3D
768
+ mol3D class instance for the complex.
769
+ liglist : list
770
+ List of ligands
771
+ ligdents : list
772
+ List of ligand denticities
773
+ ligcons : list
774
+ List of ligand connection indices (in mol)
775
+ loud : bool, optional
776
+ Enable extra printout. Default is False.
777
+ name : bool, optional
778
+ Name ligands to write to XYZ. Default is False.
779
+ use_z : bool, optional
780
+ Use z-dimension to determine axial ligands, as oppossed to connectivity.
781
+ eq_sym_match : bool, optional
782
+ Enforce eq plane to have connecting atoms with same symbol. Default is False.
783
+
784
+
785
+ Returns
786
+ -------
787
+ ax_ligand_list : list
788
+ Ligands designated as axial ligands.
789
+ eq_ligand_list : list
790
+ Ligands designated as equatorial ligands.
791
+ ax_natoms_list : list
792
+ Number of atoms in axial ligands.
793
+ eq_natoms_list : list
794
+ Number of atoms in equatorial ligands.
795
+ ax_con_int_list : list
796
+ Connecting atoms indices of axial ligands.
797
+ eq_con_int_list : list
798
+ Connecting atoms indices of equatorial ligands.
799
+ ax_con_list : list
800
+ Connecting atoms of axial ligands.
801
+ eq_con_list : list
802
+ Connecting atoms of equatorial ligands.
803
+ built_ligand_list : list
804
+ List of ligand classes for all ligands.
805
+
806
+ """
807
+ # ###### This ligand assignment code handles octahedral complexes consistently.
808
+ # ###### It should be able to assign any octahedral complex
809
+ angle_cutoff = 130 # Angle cutoff for linear
810
+ valid = True
811
+ hexadentate = False
812
+ pentadentate = False
813
+ metal_index = mol.findMetal()[0] # Get metal index and coords
814
+ m_coord = np.array(mol.getAtom(metal_index).coords())
815
+ ligand_records = list()
816
+ ax_con_int_list = list() # Atom refs in for ligand mol3D objects in ax
817
+ eq_con_int_list = list() # Atom refs in for ligand mol3D objects in eq
818
+ ax_natoms_list = list()
819
+ eq_natoms_list = list()
820
+ eq_lig_list = list()
821
+ ax_lig_list = list()
822
+ ax_con_list = list()
823
+ eq_con_list = list()
824
+ symbol_list = list()
825
+ n_ligs = len(liglist)
826
+ max_dent = max(ligdents)
827
+ min_dent = min(ligdents)
828
+ # ##### Utility functions for ligand MW and Angles
829
+
830
+ def getMW(lig): # Get total MW of ligand mol3d object
831
+ mol = lig.master_mol
832
+ lig_idx = lig.index_list
833
+ mw = 0
834
+ for i, atom in enumerate(mol.getAtoms()):
835
+ if i in lig_idx:
836
+ mw += atom.mass
837
+ return mw
838
+ # ## Below, take all combinations of two atoms, and measure their angles through the metal center
839
+
840
+ def getAngle(coord_list, pair, m_coord): # Get Angle of atom pair through metal center (stored at coord_list[0])
841
+ # print("coord_list: ", coord_list)
842
+ # print("pair: ", pair)
843
+ try:
844
+ p1 = np.squeeze(np.array(coord_list[pair[0]]))
845
+ p2 = np.squeeze(np.array(coord_list[pair[1]]))
846
+ m = m_coord
847
+ v1u = np.squeeze(np.array((m - p1) / np.linalg.norm((m - p1))))
848
+ v2u = np.squeeze(np.array((m - p2) / np.linalg.norm((m - p2))))
849
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
850
+ # print("angle: ", angle)
851
+ except IndexError:
852
+ angle = 0
853
+ return angle
854
+ if loud:
855
+ print('********************************************')
856
+ print(("n_ligs = " + str(n_ligs)))
857
+ print(("max d = " + str(max_dent)))
858
+ print(("min_dent = " + str(min_dent)))
859
+ print(("ligand list is" + str(liglist)))
860
+ print(('denticities are ' + str(ligdents)))
861
+ # Flag Hexa/Pentadentate, check if denticities incorrect for Octahedral Complex
862
+ if max(ligdents) > 4: # Hexa/Pentadentate ligands flagging
863
+ print(max(ligdents))
864
+ if max(ligdents) == 5 and min(ligdents) == 1:
865
+ pentadentate = True
866
+ elif max(ligdents) == 6 and min(ligdents) == 6:
867
+ hexadentate = True
868
+ elif max(ligdents) == 5 and min(ligdents) == 5:
869
+ print("here")
870
+ hexadentate = True
871
+ else:
872
+ valid = False
873
+ print(('bad denticities: ' + str(ligdents)))
874
+ print(('max denticities: ' + str(min(ligdents))))
875
+ elif n_ligs > 3 and min(ligdents) > 1: # Catch errors in ligands
876
+ valid = False
877
+ print(('too many ligs ' + str((n_ligs))))
878
+ # Build Ligands and get MWs of ligands
879
+ built_ligand_list = list()
880
+ lig_natoms_list = list()
881
+ lig_mol_weights = list()
882
+ for i, ligand_indices in enumerate(liglist):
883
+ this_ligand = ligand(mol, ligand_indices, ligdents[i])
884
+ this_ligand.obtain_mol3d()
885
+ built_ligand_list.append(this_ligand)
886
+ lig_natoms_list.append(len(this_ligand.index_list))
887
+ lig_mol_weights.append(getMW(this_ligand))
888
+ # ## Here, set a flat list of all connecting atoms (should be of length 6 for octahedral complexes)
889
+ flat_ligcons = list()
890
+ flat_lig_mol_weights = list()
891
+ flat_lig_refs = list()
892
+ for i, item in enumerate(ligcons):
893
+ flat_ligcons += item # Flat ligand list
894
+ flat_lig_mol_weights += [lig_mol_weights[i]]*len(item) # Flat lig mws
895
+ flat_lig_refs += [i]*len(item) # Referred back to individual ligands
896
+ lig_con_weights = [atom.mass for atom in mol.getAtomwithinds(flat_ligcons)] # Use for hexadentates
897
+ # ## Obtain coordinates for the connecting atoms. Flat coord list ends up being used for comparisons.
898
+ flat_coord_list = np.array([mol.getAtom(ii).coords() for ii in flat_ligcons])
899
+ # Bin and sort ligands as Unique
900
+ unique_ligands = list()
901
+ lig_con_symbols_list = list()
902
+ unique_ligcons = list()
903
+ ligand_counts = list()
904
+ for j, built_ligs in enumerate(built_ligand_list):
905
+ # test if ligand is unique
906
+ sl = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
907
+ # Added check for if ligand connecting atoms are also identical
908
+ sl_ligcons = sorted([atom.symbol() for atom in mol.getAtomwithinds(ligcons[j])])
909
+ symbol_list.append(sl)
910
+ lig_con_symbols_list.append(sl_ligcons)
911
+ if loud:
912
+ print(('checking lig ' + str(j) + ' : ' + str(sl)))
913
+ unique = 1 # Flag for detecting unique ligands
914
+ for i, other_sl in enumerate(unique_ligands):
915
+ if sl == other_sl and sl_ligcons == unique_ligcons[i]:
916
+ # Duplicate
917
+ unique = 0
918
+ ligand_counts[i] += 1
919
+ if unique == 1:
920
+ unique_ligands.append(sl)
921
+ unique_ligcons.append(sl_ligcons)
922
+ ligand_counts.append(1)
923
+ ligand_records.append(j)
924
+ # Pair list contains all pairs of combinations of connecting atom points
925
+ pair_combos = list(combinations([0, 1, 2, 3, 4, 5], 2)) # Pairs of coordinates of ligand-connecting atoms
926
+ angle_list = list()
927
+ pair_list = list()
928
+ for i, pair in enumerate(pair_combos):
929
+ pair_list.append(list(pair))
930
+ angle = getAngle(flat_coord_list, pair_list[-1], m_coord)
931
+ if loud:
932
+ print(('pair of atoms, then angle', pair, angle))
933
+ angle_list.append(angle) # Save angle between connecting atom points thorugh metal.
934
+ argsort_angle_list = np.squeeze(np.array(angle_list)).argsort()[-3:][::-1]
935
+ # ## Get the 3 largest angles through the metal, which should define the x, y, and z planes of symmetry
936
+ # ## Then define those planes of symmetry with the atom indices (point_combos)
937
+ # ## Each of the point combos represents 4 points that should most approximately be in a plane
938
+ # ## Defining the x,y, and z axes of the octahedral complex
939
+ point_combos = [pair_list[argsort_angle_list[0]] + pair_list[argsort_angle_list[1]],
940
+ pair_list[argsort_angle_list[1]] + pair_list[argsort_angle_list[2]],
941
+ pair_list[argsort_angle_list[2]] + pair_list[argsort_angle_list[0]]]
942
+ # ### Next, fit each of these planes with a best fit plane.
943
+ # ### The plane that fits best will be the equatorial plane by default.
944
+ # ### In some cases, the plane must be overruled (i.e seesaw tetradentates)
945
+ # ### For special cases like the seesaw, there is consistent handling such that
946
+ # ### there is consistent behavior for the ones within the same ligand class.
947
+ error_list = list() # Error of planes
948
+ combo_list = list()
949
+ fitlist = list() # Fit of planes
950
+ mw_plane_list = list() # Total ligand MW in plane for monodentates/bi/tridentates
951
+ mw_plane_lig_con_list = list() # LC-atom MW in plane for hexadentates / non-planar tridentates
952
+ for i, combo in enumerate(point_combos):
953
+ combo_list.append(combo)
954
+ if loud:
955
+ print(('combo', combo))
956
+ A = np.zeros((4, 3))
957
+ b = np.zeros(4)
958
+ mw_plane = 0
959
+ mw_lig_cons = 0
960
+ for point_i, point_num in enumerate(combo):
961
+ coordlist = flat_coord_list[point_num]
962
+ mw_plane += flat_lig_mol_weights[point_num]
963
+ mw_lig_cons += lig_con_weights[point_num]
964
+ A[point_i, :] = [coordlist[0], coordlist[1], 1]
965
+ b[point_i] = coordlist[2]
966
+ mw_plane_list.append(mw_plane)
967
+ mw_plane_lig_con_list.append(mw_lig_cons)
968
+ try:
969
+ fit = np.linalg.pinv(A.T @ A) @ A.T @ b
970
+ fitlist.append(fit)
971
+ errors = b - A @ fit
972
+ error_var = np.var(errors)
973
+ error_list.append(error_var)
974
+ except np.linalg.LinAlgError:
975
+ error_list.append(0) # perfect fit plane may suffer matrix singularity issues.
976
+ # Print errors if loud
977
+ if loud:
978
+ print('combos below')
979
+ print(combo_list)
980
+ print('errors next, argmin combo selected')
981
+ print(error_list)
982
+ # ### Find the points that correspond to the best fit plane through 4 points.
983
+ # ### Eq points are used later. Eq points has the number of the connection atoms
984
+ # ### across from each other. It pulls from a list of 0, 1, 2, 3, 4, 5, and gets
985
+ # ### which 4 are the atoms that are in the equatorial plane.
986
+ eq_points = combo_list[np.argmin(np.array(error_list))]
987
+ max_mw_idx = np.argmax(np.array(mw_plane_list)) # Saved for planar 3X3 dentate cases
988
+ eq_points_max_mw = combo_list[max_mw_idx]
989
+ eq_points_max_con_mw = combo_list[np.argmax(np.array(mw_plane_lig_con_list))]
990
+ # If there is no difference in MW bewteen different planes, flag as symmetric compound
991
+ if np.var(np.array(mw_plane_list)) == 0.0:
992
+ symmetric = True
993
+ else:
994
+ symmetric = False
995
+ # Print out state of affairs if loud
996
+ if loud:
997
+ print(('unique ligands' + str(unique_ligands)))
998
+ print(('ligand counts' + str(ligand_counts)))
999
+ print(('ligand records ' + str(ligand_records)))
1000
+ print((str(max(ligand_counts)) +
1001
+ ' is the max and min in ' + str(min(ligand_counts))))
1002
+ n_unique_ligs = len(unique_ligands) # Number of unique ligands
1003
+ if (n_ligs == 6): # All monodentate
1004
+ allowed = list(range(0, 6))
1005
+ if n_unique_ligs == 1: # Purely Homoleptic monodentate, by best fit plane
1006
+ if loud:
1007
+ print('homoleptic monodentate')
1008
+ eq_lig_list = eq_points # Assign 4 lig_cons to equatorial plane, by best fit plane
1009
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1010
+ ax_lig_list = list(set(allowed)-set(eq_lig_list)) # Last 2 lig_cons to axial positions
1011
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1012
+ elif n_unique_ligs == 2: # Mix of 2 monodentates
1013
+ if loud:
1014
+ print(('monodentate {}+{} ligands'.format(max(ligand_counts), min(ligand_counts))))
1015
+ print((ligand_counts, unique_ligands))
1016
+ eq_lig_list = list()
1017
+ if use_z:
1018
+ minz = 500
1019
+ maxz = -500
1020
+ if loud:
1021
+ print('monodentate case')
1022
+ allowed = list(range(0, 6))
1023
+ not_eq = list()
1024
+ for j, built_ligs in enumerate(built_ligand_list):
1025
+ this_z = sum([mol.getAtom(ii).coords()[2]
1026
+ for ii in ligcons[j]]) / len(ligcons[j])
1027
+ if this_z < minz:
1028
+ minz = this_z
1029
+ bot_lig = j
1030
+ bot_con = ligcons[j]
1031
+ if loud:
1032
+ print(('updating bot axial to ' + str(bot_lig)))
1033
+ if this_z > maxz:
1034
+ maxz = this_z
1035
+ top_lig = j
1036
+ top_con = ligcons[j]
1037
+ if loud:
1038
+ print(('updating top axial to ' + str(top_lig)))
1039
+ not_eq.append(bot_lig)
1040
+ not_eq.append(top_lig)
1041
+ allowed = [x for x in allowed if ((x not in not_eq))]
1042
+ if len(allowed) != 4:
1043
+ print(('error in decomp of monodentate case!', allowed))
1044
+ eq_lig_list = allowed
1045
+ eq_con_list = [ligcons[i] for i in allowed]
1046
+ ax_lig_list = [top_lig, bot_lig]
1047
+ ax_con_list = [top_con, bot_con]
1048
+ else:
1049
+ if max(ligand_counts) == 5: # 5+1 Monodentate
1050
+ five_repeats = list()
1051
+ for i, ligand_count in enumerate(ligand_counts):
1052
+ temp_unique = unique_ligands[i]
1053
+ temp_ligsym_unique = unique_ligcons[i]
1054
+ for j, built_ligs in enumerate(built_ligand_list):
1055
+ sym_list = sorted([
1056
+ atom.symbol() for atom in
1057
+ built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
1058
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
1059
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
1060
+ if sym_list != temp_unique or sl_ligcon != temp_ligsym_unique:
1061
+ continue
1062
+ elif (ligand_count == 5):
1063
+ # ### In the 5+1 monodentate case, 4 of the 5 are assigned to be equatorial,
1064
+ five_repeats.append(j)
1065
+ elif (ligand_count == 1):
1066
+ ax_lig_list.append(j)
1067
+ ax_con_list.append(ligcons[j])
1068
+ # ## Calculate which of the 5 repeats has highest angle from 1 unique
1069
+ # ## Set highest angle as axial, rest assigned as equatorial
1070
+ coord_list = [flat_coord_list[ax_lig_list[0]]]+[flat_coord_list[x] for x in five_repeats]
1071
+ pair_combos = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
1072
+ angle_list = list()
1073
+ for pair in pair_combos:
1074
+ angle = getAngle(coord_list, pair, m_coord)
1075
+ if loud:
1076
+ print(('pair of atoms, angle', pair, angle))
1077
+ angle_list.append(angle)
1078
+ ax_lig = five_repeats[np.argmax(angle_list)]
1079
+ ax_lig_list.append(ax_lig)
1080
+ ax_con_list.append(ligcons[ax_lig])
1081
+ eq_lig_list = list(set(five_repeats) - set(ax_lig_list))
1082
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1083
+ elif max(ligand_counts) == 4: # This can be either seesaw configuration or planar configuration, 4+2 cases
1084
+ four_repeats = list()
1085
+ for i, ligand_count in enumerate(ligand_counts):
1086
+ temp_unique = unique_ligands[i]
1087
+ temp_ligsym_unique = unique_ligcons[i]
1088
+ for j, built_ligs in enumerate(built_ligand_list):
1089
+ sym_list = sorted([
1090
+ atom.symbol() for atom in
1091
+ built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
1092
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
1093
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
1094
+ if sym_list != temp_unique or sl_ligcon != temp_ligsym_unique:
1095
+ continue
1096
+ elif (ligand_count == 4) and len(four_repeats) < 4:
1097
+ four_repeats.append(j)
1098
+ if loud:
1099
+ print(('this is four repeats', four_repeats))
1100
+ four_repeats_cons = [ligcons[j] for j in four_repeats]
1101
+ pair_combos = list(combinations([0, 1, 2, 3], 2))
1102
+ angle_list = list()
1103
+ pair_list = list()
1104
+ coord_list = np.array([mol.getAtom(ii[0]).coords() for ii in four_repeats_cons])
1105
+ for i, pair in enumerate(pair_combos):
1106
+ pair_list.append(list(pair))
1107
+ angle = getAngle(coord_list, list(pair), m_coord)
1108
+ if loud:
1109
+ print(('pair of atoms, then angle', pair, angle))
1110
+ angle_list.append(angle)
1111
+ # ### Seesaws will have only 1 ~180 degree angle, whereas planar ligands will have two.
1112
+ # ### Thus, after measuring the angles for all tetradentate connecting atoms through the metal,
1113
+ # ### looking at the angle of the first (not zeroeth) element tells us whether or not we have a seesaw.
1114
+ test_angle = np.sort(np.array(angle_list))[::-1][1]
1115
+ if test_angle < angle_cutoff:
1116
+ seesaw = True
1117
+ # ### In the seesaw, the two furthest apart are denoted as axial.
1118
+ # ### The equatorial plane consists of 2 seesaw connecting atoms, and two monodentates.
1119
+ axial_pair = [four_repeats_cons[val] for val in list(pair_list[np.argmax(np.array(angle_list))])]
1120
+ else:
1121
+ seesaw = False
1122
+ # ### In the planar set, the two monodentates are axial, and tetradentate is equatorial.
1123
+ axial_pair = list(set(allowed) - set(four_repeats))
1124
+ if not seesaw:
1125
+ eq_lig_list = four_repeats
1126
+ eq_con_list = four_repeats_cons # ADDED
1127
+ ax_lig_list = axial_pair
1128
+ ax_con_list = [ligcons[axial_pair[0]], ligcons[axial_pair[1]]]
1129
+ else: # 2 points in eq plane, seesaw case
1130
+ # ### In the seesaw, the two furthest apart are denoted as axial.
1131
+ # ### The equatorial plane consists of 2 seesaw connecting atoms, and two monodentates.
1132
+ eq_plane_cons = list(set(flat_ligcons) - set([val[0] for val in axial_pair]))
1133
+ eq_con_list = [ligcons[j] for j in range(len(ligcons)) if ligcons[j] in eq_plane_cons]
1134
+ eq_lig_list = [i for i in range(len(ligcons)) if ligcons[i] in eq_plane_cons]
1135
+ ax_lig_list = [i for i in range(len(ligcons)) if ligcons[i] not in eq_plane_cons]
1136
+ ax_con_list = [ligcons[j] for j in range(len(ligcons)) if ligcons[j] not in eq_plane_cons]
1137
+ else: # 3,3 monodentate case - just use the planes to define ax vs eq by maximum MW
1138
+ if symmetric: # If MWs of ligands match, but connecting atoms different, choose max MW con atom
1139
+ eq_ligcons = list(set([flat_ligcons[j] for j in eq_points_max_con_mw]))
1140
+ else:
1141
+ eq_ligcons = list(set([flat_ligcons[j] for j in eq_points_max_mw]))
1142
+ eq_lig_list = eq_points_max_mw
1143
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1144
+ ax_lig_list = list(set(allowed)-set(eq_points_max_mw))
1145
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1146
+ elif n_unique_ligs == 3: # Mix of 3 monodentates
1147
+ if loud:
1148
+ print(f'monodentate {max(ligand_counts)}+{min(ligand_counts)}+{6-max(ligand_counts)-min(ligand_counts)}')
1149
+ # ### Need to identify if in seesaw-style configuration or planar configuration
1150
+ if use_z:
1151
+ minz = 500
1152
+ maxz = -500
1153
+ if loud:
1154
+ print('monodentate case')
1155
+ allowed = list(range(0, 6))
1156
+ not_eq = list()
1157
+ for j, built_ligs in enumerate(built_ligand_list):
1158
+ this_z = sum([mol.getAtom(ii).coords()[2]
1159
+ for ii in ligcons[j]]) / len(ligcons[j])
1160
+ if this_z < minz:
1161
+ minz = this_z
1162
+ bot_lig = j
1163
+ bot_con = ligcons[j]
1164
+ if loud:
1165
+ print(('updating bot axial to ' + str(bot_lig)))
1166
+ if this_z > maxz:
1167
+ maxz = this_z
1168
+ top_lig = j
1169
+ top_con = ligcons[j]
1170
+ if loud:
1171
+ print(('updating top axial to ' + str(top_lig)))
1172
+ not_eq.append(bot_lig)
1173
+ not_eq.append(top_lig)
1174
+ allowed = [x for x in allowed if ((x not in not_eq))]
1175
+ if len(allowed) != 4:
1176
+ print(('error in decomp of monodentate case!', allowed))
1177
+ eq_lig_list = allowed
1178
+ eq_con_list = [ligcons[i] for i in allowed]
1179
+ ax_lig_list = [top_lig, bot_lig]
1180
+ ax_con_list = [top_con, bot_con]
1181
+ else:
1182
+ if max(ligand_counts) == 4: # Seesaw vs. Planar
1183
+ four_repeats = list()
1184
+ for i, ligand_count in enumerate(ligand_counts):
1185
+ temp_unique = unique_ligands[i]
1186
+ temp_ligsym_unique = unique_ligcons[i]
1187
+ for j, built_ligs in enumerate(built_ligand_list):
1188
+ sym_list = sorted([
1189
+ atom.symbol() for atom in
1190
+ built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
1191
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
1192
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
1193
+ if (sym_list == temp_unique) and (sl_ligcon == temp_ligsym_unique) and \
1194
+ (ligand_count == 4) and len(four_repeats) < 4:
1195
+ four_repeats.append(j)
1196
+ if loud:
1197
+ print(('this is four repeats', four_repeats))
1198
+ four_repeats_cons = [ligcons[j] for j in four_repeats]
1199
+ pair_combos = list(combinations([0, 1, 2, 3], 2))
1200
+ angle_list = []
1201
+ pair_list = []
1202
+ coord_list = np.array([mol.getAtom(ii[0]).coords() for ii in four_repeats_cons])
1203
+ for i, pair in enumerate(pair_combos):
1204
+ pair_list.append(list(pair))
1205
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1206
+ if loud:
1207
+ print(('pair of atoms, then angle', pair, angle))
1208
+ angle_list.append(angle)
1209
+ # ### Seesaws will have only 1 ~180 degree angle, whereas planar ligands will have two.
1210
+ # ### Thus, after measuring the angles for all tetradentate connecting atoms through the metal,
1211
+ # ### looking at the angle of the first (not zeroeth) element tells us whether or not we have a seesaw.
1212
+ test_angle = np.sort(np.array(angle_list))[::-1][1]
1213
+ if test_angle < angle_cutoff:
1214
+ seesaw = True
1215
+ # ### In the seesaw, the two furthest apart are denoted as axial.
1216
+ # ### The equatorial plane consists of 2 seesaw connecting atoms, and two monodentates.
1217
+ axial_pair = [four_repeats_cons[val] for val in list(pair_list[np.argmax(np.array(angle_list))])]
1218
+ else:
1219
+ seesaw = False
1220
+ # ### In the planar set, the two monodentates are axial, and tetradentate is equatorial.
1221
+ axial_pair = list(set(allowed) - set(four_repeats))
1222
+ if not seesaw: # Planar
1223
+ eq_lig_list = four_repeats
1224
+ eq_con_list = four_repeats_cons # ADDED
1225
+ ax_lig_list = axial_pair
1226
+ ax_con_list = [ligcons[axial_pair[0]], ligcons[axial_pair[1]]]
1227
+ else: # 2 points in eq plane, seesaw case
1228
+ # ### In the seesaw, the two furthest apart are denoted as axial.
1229
+ # ### The equatorial plane consists of 2 seesaw connecting atoms, and two monodentates.
1230
+ eq_plane_cons = list(set(flat_ligcons) - set([val[0] for val in axial_pair]))
1231
+ eq_con_list = [ligcons[j] for j in range(len(ligcons)) if ligcons[j] in eq_plane_cons]
1232
+ eq_lig_list = [i for i in range(len(ligcons)) if ligcons[i] in eq_plane_cons]
1233
+ ax_lig_list = [i for i in range(len(ligcons)) if ligcons[i] not in eq_plane_cons]
1234
+ ax_con_list = [ligcons[j] for j in range(len(ligcons)) if ligcons[j] not in eq_plane_cons]
1235
+ elif max(ligand_counts) == 3: # 3+2+1 - Planar 3 or not
1236
+ three_repeats = list()
1237
+ two_repeats = list()
1238
+ for i, ligand_count in enumerate(ligand_counts):
1239
+ temp_unique = unique_ligands[i]
1240
+ temp_ligsym_unique = unique_ligcons[i]
1241
+ for j, built_ligs in enumerate(built_ligand_list):
1242
+ sym_list = sorted([
1243
+ atom.symbol() for atom in
1244
+ built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
1245
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
1246
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
1247
+ if sym_list != temp_unique or sl_ligcon != temp_ligsym_unique:
1248
+ continue
1249
+ elif (ligand_count == 3) and len(three_repeats) < 3:
1250
+ three_repeats.append(j)
1251
+ elif (ligand_count == 2) and len(two_repeats) < 2:
1252
+ two_repeats.append(j)
1253
+ if loud:
1254
+ print(('this is three repeats', three_repeats))
1255
+ three_repeats_cons = [ligcons[j] for j in three_repeats]
1256
+ two_repeats_cons = [ligcons[j] for j in two_repeats]
1257
+ pair_combos = list(combinations([0, 1, 2], 2))
1258
+ angle_list = []
1259
+ pair_list = []
1260
+ coord_list = np.array([mol.getAtom(ii[0]).coords() for ii in three_repeats_cons])
1261
+ for i, pair in enumerate(pair_combos): # Test angle between 3 identical
1262
+ pair_list.append(list(pair))
1263
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1264
+ if loud:
1265
+ print(('pair of atoms, then angle', pair, angle))
1266
+ angle_list.append(angle)
1267
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1268
+ if test_angle < angle_cutoff: # If less than cutoff, non-planar
1269
+ planar = False
1270
+ coord_list_two = np.array([mol.getAtom(ii[0]).coords() for ii in two_repeats_cons])
1271
+ coord_list_three = np.array([mol.getAtom(ii[0]).coords() for ii in three_repeats_cons])
1272
+ angle_list = list()
1273
+ # unused
1274
+ # m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
1275
+ pair_combos = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
1276
+ for k, pair in enumerate(pair_combos):
1277
+ p1 = np.squeeze(np.array(coord_list_two[pair[0]]))
1278
+ p2 = np.squeeze(np.array(coord_list_three[pair[1]]))
1279
+ v1u = np.squeeze(np.array((m_coord - p1) / np.linalg.norm((m_coord - p1))))
1280
+ v2u = np.squeeze(np.array((m_coord - p2) / np.linalg.norm((m_coord - p2))))
1281
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1282
+ angle_list.append(angle)
1283
+ # ### If not planar, considering the plane with the bidentate to be equatorial.
1284
+ # ### Along with 2 opposite Tridentate connecting atoms
1285
+ # ### Consistent with tridentate cases.
1286
+ top_2_angles = np.squeeze(np.array(angle_list)).argsort()[-2:][::-1]
1287
+ first_pair = pair_combos[top_2_angles[0]]
1288
+ second_pair = pair_combos[top_2_angles[1]]
1289
+ conlist = list()
1290
+ conlist += two_repeats_cons[first_pair[0]]+three_repeats_cons[first_pair[1]]
1291
+ conlist += two_repeats_cons[second_pair[0]]+three_repeats_cons[second_pair[1]]
1292
+ if loud:
1293
+ print(('eq points reassigned', conlist))
1294
+ eq_points_defined = [j for j in allowed if ligcons[j][0] in conlist]
1295
+ else:
1296
+ planar = True
1297
+ mono_dentate_idx_set = list(set(allowed)-set(three_repeats))
1298
+ coord_list = np.array([mol.getAtom(ii[0]).coords() for ii in three_repeats_cons])
1299
+ m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
1300
+ mono_con_list = []
1301
+ for mono_dentate_idx in mono_dentate_idx_set:
1302
+ current_con = ligcons[mono_dentate_idx]
1303
+ mono_con_list.append(current_con[0])
1304
+ p1 = np.array(mol.getAtom(current_con[0]).coords())
1305
+ angle_list = []
1306
+ for k, tri_con in enumerate(three_repeats_cons):
1307
+ p2 = np.squeeze(np.array(coord_list[k]))
1308
+ v1u = np.squeeze(np.array((m - p1) / np.linalg.norm((m - p1))))
1309
+ v2u = np.squeeze(np.array((m - p2) / np.linalg.norm((m - p2))))
1310
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1311
+ angle_list.append(angle)
1312
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1313
+ # ### If the tridentate is planar, only one of the bidentate connecting atoms
1314
+ # ### will be across the tridentate. Thus the equatorial plane will be defined
1315
+ # ### as the planar tridentate plus one connection atom for the bidentate
1316
+ # ### The monodentate and the second bidentate connecting atoms are denoted axial.
1317
+ if test_angle > angle_cutoff:
1318
+ monodentate_eq_con = current_con
1319
+ monodentate_eq_idx = mono_dentate_idx
1320
+ if planar:
1321
+ eq_lig_list = three_repeats+[monodentate_eq_idx]
1322
+ eq_con_list = three_repeats_cons+[monodentate_eq_con] # ADDED
1323
+ ax_con_list = [[val] for val in mono_con_list if val not in monodentate_eq_con]
1324
+ ax_lig_list = [i for i in range(len(ligcons)) if ligcons[i] not in eq_con_list]
1325
+ else:
1326
+ # ## Eq points set based on 2 + 2 of (3) Eq and mono+ 1(3) Ax
1327
+ # ## Consistent with 3+2+1 below by denticity
1328
+ eq_lig_list = eq_points_defined
1329
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1330
+ ax_lig_list = [val for i, val in enumerate(allowed) if val not in eq_points_defined]
1331
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1332
+ else: # Max mw determines eq plane (2+2+2 different monodentates)
1333
+ eq_lig_list = eq_points_max_mw
1334
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1335
+ ax_lig_list = list(set(allowed)-set(eq_points_max_mw))
1336
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1337
+ else: # with more than 4 ligands, the ax/eq breaks down. Eq plane defined by max mw plane.
1338
+ if loud:
1339
+ print('monodentate more than 3 unique')
1340
+ eq_lig_list = eq_points_max_mw
1341
+ eq_con_list = [ligcons[j] for j in eq_lig_list]
1342
+ ax_lig_list = list(set(allowed)-set(eq_points_max_mw))
1343
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1344
+ # ###### Find and put trans species in eq plane in order opposite
1345
+ # ###### from each other [0,2],[1,3] in indicies for monodentates
1346
+ eq_con_coords = [flat_coord_list[x] for x in eq_lig_list]
1347
+ combos = [(0, 1), (0, 2), (0, 3)]
1348
+ angle_list_eq = list()
1349
+ for pair in combos:
1350
+ angle = getAngle(eq_con_coords, pair, m_coord)
1351
+ if loud:
1352
+ print(('pair of atoms, angle', pair, angle))
1353
+ angle_list_eq.append(angle)
1354
+ opposite_lig = combos[np.argmax(angle_list_eq)][1]
1355
+ others = list(set([1, 2, 3])-set([opposite_lig]))
1356
+ eq_order = [0, others[0], opposite_lig, others[1]]
1357
+ # Reorder equatorial plane
1358
+ eq_con_list = [eq_con_list[x] for x in eq_order]
1359
+ eq_lig_list = [eq_lig_list[x] for x in eq_order]
1360
+ elif (n_ligs == 5): # 2+1+1+1+1
1361
+ # unused
1362
+ # allowed = list(range(0, 5))
1363
+ if loud:
1364
+ print('bidentate 2+1+1+1+1 case')
1365
+ bidentate_ligand_idx = np.argmax(ligdents)
1366
+ bidentate_cons = ligcons[bidentate_ligand_idx]
1367
+ mono_dentate_idx_set = list(set(range(len(ligdents)))-set([bidentate_ligand_idx]))
1368
+ # unused
1369
+ # monodentate_cons = [ligcons[val] for val in mono_dentate_idx_set]
1370
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in bidentate_cons])
1371
+ mono_con_list = list()
1372
+ monodentate_eq_cons = list()
1373
+ monodentate_eq_idxs = list()
1374
+ # 2-dentate in eq plane and opposite monodentates selected
1375
+ bidentate_axial = False
1376
+ for mono_dentate_idx in mono_dentate_idx_set:
1377
+ current_con = ligcons[mono_dentate_idx]
1378
+ mono_con_list.append(current_con[0])
1379
+ p1 = np.array(mol.getAtom(current_con[0]).coords())
1380
+ for k, bi_con in enumerate(bidentate_cons):
1381
+ p2 = np.squeeze(np.array(coord_list[k]))
1382
+ v1u = np.squeeze(np.array((m_coord - p1) / np.linalg.norm((m_coord - p1))))
1383
+ v2u = np.squeeze(np.array((m_coord - p2) / np.linalg.norm((m_coord - p2))))
1384
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1385
+ if angle > angle_cutoff:
1386
+ monodentate_eq_cons.append(current_con)
1387
+ monodentate_eq_idxs.append(mono_dentate_idx)
1388
+ if len(monodentate_eq_cons) < 2:
1389
+ bidentate_axial = True
1390
+ if not bidentate_axial: # Normal
1391
+ eq_lig_list = [bidentate_ligand_idx, monodentate_eq_idxs[0], monodentate_eq_idxs[1]]
1392
+ eq_con_list = [bidentate_cons, monodentate_eq_cons[0], monodentate_eq_cons[1]] # ADDED
1393
+ ax_lig_list = [val for val in mono_dentate_idx_set if val not in monodentate_eq_idxs]
1394
+ ax_con_list = [[val] for val in mono_con_list if val not in [x[0] for x in monodentate_eq_cons]]
1395
+ else:
1396
+ ax_lig_list = [bidentate_ligand_idx, bidentate_ligand_idx]
1397
+ ax_con_list = [[val] for val in bidentate_cons]
1398
+ eq_lig_list = [val for val in mono_dentate_idx_set]
1399
+ eq_con_list = [[val] for val in mono_con_list]
1400
+ elif (n_ligs == 4): # 2+2+1+1 and 3+1+1+1 cases
1401
+ allowed = list(range(0, 4))
1402
+ if max(ligdents) == 3:
1403
+ if loud:
1404
+ print('3+1+1+1 case')
1405
+ tridentate_ligand_idx = np.argmax(ligdents)
1406
+ tridentate_cons = ligcons[tridentate_ligand_idx]
1407
+ mono_dentate_idx_set = list(set(range(len(ligdents)))-set([tridentate_ligand_idx]))
1408
+ # unused
1409
+ # monodentate_cons = [ligcons[val] for val in mono_dentate_idx_set]
1410
+ pair_combos = list(combinations([0, 1, 2], 2))
1411
+ angle_list = []
1412
+ pair_list = []
1413
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons])
1414
+ for i, pair in enumerate(pair_combos):
1415
+ pair_list.append(list(pair))
1416
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1417
+ if loud:
1418
+ print(('pair of atoms, then angle', pair, angle))
1419
+ angle_list.append(angle)
1420
+ # ### The tridentate can either be planar or not. If planar, it will have at least one
1421
+ # ### angle that is close to 180 between connecting atoms, through the metal.
1422
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1423
+ planar = False
1424
+ if test_angle > angle_cutoff:
1425
+ planar = True
1426
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons])
1427
+ m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
1428
+ mono_con_list = []
1429
+ for mono_dentate_idx in mono_dentate_idx_set:
1430
+ current_con = ligcons[mono_dentate_idx]
1431
+ mono_con_list.append(current_con[0])
1432
+ p1 = np.array(mol.getAtom(current_con[0]).coords())
1433
+ angle_list = []
1434
+ for k, tri_con in enumerate(tridentate_cons):
1435
+ p2 = np.squeeze(np.array(coord_list[k]))
1436
+ v1u = np.squeeze(np.array((m - p1) / np.linalg.norm((m - p1))))
1437
+ v2u = np.squeeze(np.array((m - p2) / np.linalg.norm((m - p2))))
1438
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1439
+ angle_list.append(angle)
1440
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1441
+ # ### If the tridentate is planar, only one of the bidentate connecting atoms
1442
+ # ### will be across the tridentate. Thus the equatorial plane will be defined
1443
+ # ### as the planar tridentate plus one connection atom for the bidentate
1444
+ # ### The monodentate and the second bidentate connecting atoms are denoted axial.
1445
+ if test_angle > angle_cutoff:
1446
+ monodentate_eq_con = current_con
1447
+ monodentate_eq_idx = mono_dentate_idx
1448
+ if planar:
1449
+ eq_lig_list = [tridentate_ligand_idx, monodentate_eq_idx]
1450
+ eq_con_list = [tridentate_cons, monodentate_eq_con] # ADDED
1451
+ ax_lig_list = [val for val in mono_dentate_idx_set if val not in [monodentate_eq_idx]]
1452
+ ax_con_list = [[val] for val in mono_con_list if val not in monodentate_eq_con]
1453
+ else: # WORKs
1454
+ # ## any equatorial plane will have 2 of the tridentate con atoms so take the eq plane with max mw
1455
+ tri_eq_ligcons = list(set(tridentate_cons).intersection(
1456
+ set([flat_ligcons[x] for x in eq_points_max_mw])))
1457
+ mono_eq_ligcons = list(set([flat_ligcons[x] for x in eq_points_max_mw])-set(tri_eq_ligcons))
1458
+ eq_lig_list = [tridentate_ligand_idx, flat_lig_refs[flat_ligcons.index(mono_eq_ligcons[0])],
1459
+ flat_lig_refs[flat_ligcons.index(mono_eq_ligcons[1])]]
1460
+ eq_con_list = [tri_eq_ligcons, [mono_eq_ligcons[0]], [mono_eq_ligcons[1]]]
1461
+ ax_lig_list = [tridentate_ligand_idx,
1462
+ list(set(mono_dentate_idx_set).difference(set([x for x in eq_lig_list[1:]])))[0]]
1463
+ tri_ax_ligcon = list(set(tridentate_cons).difference(
1464
+ set(tri_eq_ligcons)))
1465
+ ax_con_list = [tri_ax_ligcon,
1466
+ list(set(flat_ligcons).difference(
1467
+ set(mono_eq_ligcons+tri_ax_ligcon+tri_eq_ligcons)))]
1468
+ elif max(ligdents) == 2:
1469
+ # ### Need to handle case with both equatorial and triple-bidentate style.
1470
+ if loud:
1471
+ print('2+2+1+1 case')
1472
+ allowed = list(range(0, 4))
1473
+ bidentate_ligand_idx1 = np.squeeze(np.array(ligdents)).argsort()[-2:][::-1][0]
1474
+ bidentate_ligand_idx2 = np.squeeze(np.array(ligdents)).argsort()[-2:][::-1][1]
1475
+ bidentate_cons1 = ligcons[bidentate_ligand_idx1]
1476
+ bidentate_cons2 = ligcons[bidentate_ligand_idx2]
1477
+ pair_combos = list(combinations([0, 1, 2, 3], 2))
1478
+ angle_list = []
1479
+ pair_list = []
1480
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in bidentate_cons1]
1481
+ + [mol.getAtom(ii).coords() for ii in bidentate_cons2])
1482
+ for i, pair in enumerate(pair_combos):
1483
+ pair_list.append(list(pair))
1484
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1485
+ if loud:
1486
+ print(('pair of atoms, then angle', pair, angle))
1487
+ angle_list.append(angle)
1488
+ # ### Seesaws will have only 1 ~180 degree angle, whereas planar ligands will have two.
1489
+ # ### Thus, after measuring the angles for all tetradentate connecting atoms through the metal,
1490
+ # ### looking at the angle of the first (not zeroeth) element tells us whether or not we have a seesaw.
1491
+ test_angle = np.sort(np.array(angle_list))[::-1][1]
1492
+ if loud:
1493
+ print(('ANGLE LIST', angle_list, 'sorted', np.sort(np.array(angle_list))[::-1]))
1494
+ if test_angle < angle_cutoff:
1495
+ seesaw = True
1496
+ temp_cons = bidentate_cons1+bidentate_cons2
1497
+ # Axial pair in bidentates identified
1498
+ axial_pair = [[temp_cons[val]] for val in list(pair_list[np.argmax(np.array(angle_list))])]
1499
+ else:
1500
+ seesaw = False
1501
+ if not seesaw: # Planar
1502
+ eq_lig_list = [bidentate_ligand_idx1, bidentate_ligand_idx2]
1503
+ eq_con_list = [bidentate_cons1, bidentate_cons2]
1504
+ ax_lig_list = list(set(allowed)-set([bidentate_ligand_idx1, bidentate_ligand_idx2]))
1505
+ ax_con_list = [ligcons[j] for j in ax_lig_list]
1506
+ else: # 2 points in eq plane, seesaw case
1507
+ ax_con_list = axial_pair
1508
+ flat_ax_con_list = [item for sublist in ax_con_list for item in sublist]
1509
+ ax_lig_list = [j for j, val in enumerate(ligcons) if flat_ax_con_list[0] in val] + \
1510
+ [j for j, val in enumerate(ligcons) if flat_ax_con_list[1] in val]
1511
+ eq_ligcons = set(flat_ligcons) - set(flat_ax_con_list)
1512
+ eq_con_bidentate_list = [
1513
+ list(set(ligcons[0]).intersection(eq_ligcons)),
1514
+ list(set(ligcons[1]).intersection(eq_ligcons)),
1515
+ list(set(ligcons[2]).intersection(eq_ligcons)),
1516
+ list(set(ligcons[3]).intersection(eq_ligcons))]
1517
+ eq_con_bidentate_list = [val for val in eq_con_bidentate_list
1518
+ if len(val) > 0]
1519
+ eq_con_list = eq_con_bidentate_list
1520
+ flat_eq_con_list = [item for sublist in eq_con_list for item in sublist]
1521
+ eq_lig_list = [
1522
+ j for j, val in enumerate(ligcons) if
1523
+ len(set(val).intersection(set(flat_eq_con_list))) > 0]
1524
+ elif (n_ligs == 3): # 2+2+2 or 4+1+1, can be seesaw/planar or 3+2+1, seesaw/planar
1525
+ if max(ligdents) == 4: # 4+1+1
1526
+ if loud:
1527
+ print('4+1+1 case')
1528
+ allowed = list(range(0, 3))
1529
+ tetradentate_ligand_idx = np.argmax(ligdents)
1530
+ tetradentate_cons = ligcons[tetradentate_ligand_idx]
1531
+ pair_combos = list(combinations([0, 1, 2, 3], 2))
1532
+ angle_list = []
1533
+ pair_list = []
1534
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tetradentate_cons])
1535
+ for i, pair in enumerate(pair_combos):
1536
+ pair_list.append(list(pair))
1537
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1538
+ if loud:
1539
+ print(('pair of atoms, then angle', pair, angle))
1540
+ angle_list.append(angle)
1541
+ # ### Seesaws will have only 1 ~180 degree angle, whereas planar ligands will have two.
1542
+ # ### Thus, after measuring the angles for all tetradentate connecting atoms through the metal,
1543
+ # ### looking at the angle of the first (not zeroeth) element tells us whether or not we have a seesaw.
1544
+ test_angle = np.sort(np.array(angle_list))[::-1][1]
1545
+ if test_angle < angle_cutoff:
1546
+ seesaw = True
1547
+ # ### In the seesaw, the two furthest apart are denoted as axial.
1548
+ # ### The equatorial plane consists of 2 seesaw connecting atoms, and two monodentates.
1549
+ axial_pair = [tetradentate_cons[val] for val in list(pair_list[np.argmax(np.array(angle_list))])]
1550
+ else:
1551
+ seesaw = False
1552
+ # ### In the planar set, the two monodentates are axial, and tetradentate is equatorial.
1553
+ axial_pair = list(set(allowed) - set([tetradentate_ligand_idx]))
1554
+ if not seesaw: # Planar
1555
+ eq_lig_list = [tetradentate_ligand_idx]
1556
+ eq_con_list = [tetradentate_cons] # ADDED
1557
+ ax_lig_list = axial_pair
1558
+ ax_con_list = [ligcons[axial_pair[0]], ligcons[axial_pair[1]]]
1559
+ else: # 2 points in eq plane, seesaw case
1560
+ eq_plane = list(set(flat_ligcons) - set(axial_pair))
1561
+ # ### Find the two connecting atoms of the seesaw that lie in the equatorial plane
1562
+ tet_eq = list(set(eq_plane).intersection(set(tetradentate_cons)))
1563
+ eq_con_list = [ligcons[j] if len(ligcons[j]) == 1 else tet_eq for j in range(len(ligcons))]
1564
+ eq_lig_list = allowed
1565
+ ax_lig_list = [tetradentate_ligand_idx, tetradentate_ligand_idx]
1566
+ ax_con_list = [[axial_pair[0]], [axial_pair[1]]]
1567
+ elif max(ligdents) == 3: # 3+2+1
1568
+ if loud:
1569
+ print('3+2+1 case')
1570
+ allowed = list(range(0, 3))
1571
+ tridentate_ligand_idx = np.argmax(ligdents)
1572
+ monodentate_ligand_idx = np.argmin(ligdents)
1573
+ bidentate_ligand_idx = list(set(allowed) - set([tridentate_ligand_idx]) - set([monodentate_ligand_idx]))[0]
1574
+ tridentate_cons = ligcons[tridentate_ligand_idx]
1575
+ bidentate_cons = ligcons[bidentate_ligand_idx]
1576
+ monodentate_cons = ligcons[monodentate_ligand_idx]
1577
+ pair_combos = list(combinations([0, 1, 2], 2))
1578
+ angle_list = []
1579
+ pair_list = []
1580
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons])
1581
+ for i, pair in enumerate(pair_combos):
1582
+ pair_list.append(list(pair))
1583
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1584
+ if loud:
1585
+ print(('pair of atoms, then angle', pair, angle))
1586
+ angle_list.append(angle)
1587
+ # ### The tridentate can either be planar or not. If planar, it will have at least one
1588
+ # ### angle that is close to 180 between connecting atoms, through the metal.
1589
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1590
+ bidentate_axial = False
1591
+ if test_angle < angle_cutoff:
1592
+ planar = False
1593
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons])
1594
+ p1 = np.array(mol.getAtom(monodentate_cons[0]).coords())
1595
+ angle_list = []
1596
+ for k, tri_con in enumerate(tridentate_cons):
1597
+ p2 = np.squeeze(np.array(coord_list[k]))
1598
+ v1u = np.squeeze(np.array((m_coord - p1) / np.linalg.norm((m_coord - p1))))
1599
+ v2u = np.squeeze(np.array((m_coord - p2) / np.linalg.norm((m_coord - p2))))
1600
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1601
+ angle_list.append(angle)
1602
+ # ### If not planar, one connection atom (for the monodentate ligand) will be opposite
1603
+ # ### the connections for the tridentate ligands. Assigning this atom as the opposite monodentate.
1604
+ # ### If not planar, considering the plane with the bidentate to be equatorial.
1605
+ opposite_monodentate = [tridentate_cons[np.argmax(np.array(angle_list))]]
1606
+ tridentate_cons.remove(tridentate_cons[np.argmax(np.array(angle_list))])
1607
+ else: # Planar
1608
+ planar = True
1609
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons])
1610
+ # unused:
1611
+ # m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
1612
+ bidentate_eq_con = []
1613
+ for bi_con in bidentate_cons:
1614
+ p1 = np.array(mol.getAtom(bi_con).coords())
1615
+ angle_list = []
1616
+ for k, tri_con in enumerate(tridentate_cons):
1617
+ p2 = np.squeeze(np.array(coord_list[k]))
1618
+ v1u = np.squeeze(np.array((m_coord - p1) / np.linalg.norm((m_coord - p1))))
1619
+ v2u = np.squeeze(np.array((m_coord - p2) / np.linalg.norm((m_coord - p2))))
1620
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1621
+ angle_list.append(angle)
1622
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1623
+ # ### If the tridentate is planar, only one of the bidentate connecting atoms
1624
+ # ### will be across the tridentate. Thus the equatorial plane will be defined
1625
+ # ### as the planar tridentate plus one connection atom for the bidentate
1626
+ # ### The monodentate and the second bidentate connecting atoms are denoted axial.
1627
+ if test_angle > angle_cutoff:
1628
+ bidentate_eq_con = [bi_con]
1629
+ bidentate_ax_con = [list(set(bidentate_cons) - set([bi_con]))[0]]
1630
+ if len(bidentate_eq_con) < 1:
1631
+ bidentate_axial = True
1632
+ if planar and not bidentate_axial:
1633
+ eq_lig_list = [tridentate_ligand_idx, bidentate_ligand_idx]
1634
+ eq_con_list = [tridentate_cons, bidentate_eq_con] # ADDED
1635
+ ax_lig_list = [monodentate_ligand_idx, bidentate_ligand_idx]
1636
+ ax_con_list = [monodentate_cons, bidentate_ax_con]
1637
+ elif bidentate_axial:
1638
+ eq_lig_list = [tridentate_ligand_idx, monodentate_ligand_idx]
1639
+ eq_con_list = [tridentate_cons, monodentate_cons]
1640
+ ax_lig_list = [bidentate_ligand_idx, bidentate_ligand_idx]
1641
+ ax_con_list = [[val] for val in bidentate_cons]
1642
+ else:
1643
+ eq_lig_list = [tridentate_ligand_idx, bidentate_ligand_idx]
1644
+ eq_con_list = [tridentate_cons, bidentate_cons] # ADDED
1645
+ ax_lig_list = [monodentate_ligand_idx, tridentate_ligand_idx]
1646
+ ax_con_list = [monodentate_cons, opposite_monodentate]
1647
+ elif (max(ligdents) == 2) and (min(ligdents) == 2): # 2+2+2
1648
+ if loud:
1649
+ print('triple bidentate case')
1650
+ allowed = list(range(0, 3))
1651
+ bidentate_cons_1 = ligcons[0]
1652
+ bidentate_cons_2 = ligcons[1]
1653
+ bidentate_cons_3 = ligcons[2]
1654
+ # ### Using previously defined eq plane with max_mw to define equatorial plane
1655
+ eq_ligcons = set([flat_ligcons[j] for j in eq_points_max_mw])
1656
+ eq_con_bidentate_list = [list(set(bidentate_cons_1).intersection(eq_ligcons)),
1657
+ list(set(bidentate_cons_2).intersection(eq_ligcons)),
1658
+ list(set(bidentate_cons_3).intersection(eq_ligcons))]
1659
+ # ### all 3 ligands classified as equatorial because at least one atom connects to the equatorial plane for all
1660
+ # ### axial only has two, where the axial connection is defined
1661
+ if any([len(x) < 1 for x in eq_con_bidentate_list]):
1662
+ eq_con_list = [x for x in eq_con_bidentate_list if len(x) > 0]
1663
+ eq_lig_list = [i for i, x in enumerate(eq_con_bidentate_list)
1664
+ if len(x) > 0]
1665
+ ax_lig = list(set(allowed)-set(eq_lig_list))[0]
1666
+ ax_lig_list = [ax_lig, ax_lig]
1667
+ ax_con_list = [[val] for val in ligcons[ax_lig]]
1668
+ else:
1669
+ eq_con_list = [eq_con_bidentate_list[0], eq_con_bidentate_list[1], eq_con_bidentate_list[2]]
1670
+ eq_lig_list = allowed
1671
+ ax_con_list = [list(set(ligcons[i]) - set(eq_con_bidentate_list[i])) for i in allowed if
1672
+ (len(eq_con_bidentate_list[i]) == 1)]
1673
+ ax_lig_list = [i for i in allowed if (len(eq_con_bidentate_list[i]) == 1)]
1674
+ elif (n_ligs == 2 and not pentadentate): # 4+2 or 3+3
1675
+ if ((max(ligdents) == 4) and (min(ligdents) == 2)): # 4+2
1676
+ if loud:
1677
+ print('4+2 dentate case')
1678
+ # ### 4+2 cases are handled in the exact same way as 4+1+1 seesaws. See above for explanation.
1679
+ # ### The seesaw tetradentate ligand has two axial and two equatorial connecting atoms.
1680
+ allowed = list(range(0, 2))
1681
+ tetradentate_ligand_idx = np.argmax(ligdents)
1682
+ tetradentate_cons = ligcons[tetradentate_ligand_idx]
1683
+ bidentate_ligand_idx = np.argmin(ligdents)
1684
+ bidentate_cons = ligcons[bidentate_ligand_idx]
1685
+ pair_combos = list(combinations([0, 1, 2, 3], 2))
1686
+ angle_list = []
1687
+ pair_list = []
1688
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tetradentate_cons])
1689
+ for i, pair in enumerate(pair_combos):
1690
+ pair_list.append(list(pair))
1691
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1692
+ if loud:
1693
+ print(('pair of atoms, then angle', pair, angle))
1694
+ angle_list.append(angle)
1695
+ bidentate_axial = False
1696
+ if len(np.where(np.array(angle_list) > angle_cutoff)[0]) > 1:
1697
+ bidentate_axial = True
1698
+ if not bidentate_axial:
1699
+ axial_pair = [tetradentate_cons[val] for val in list(pair_list[np.argmax(np.array(angle_list))])]
1700
+ eq_plane = list(set(flat_ligcons) - set(axial_pair))
1701
+ tet_eq = list(set(eq_plane).intersection(set(tetradentate_cons)))
1702
+ eq_con_list = [ligcons[j] if len(ligcons[j]) == 2 else tet_eq for j in range(len(ligcons))]
1703
+ eq_lig_list = allowed
1704
+ ax_lig_list = [tetradentate_ligand_idx, tetradentate_ligand_idx]
1705
+ ax_con_list = [[axial_pair[0]], [axial_pair[1]]]
1706
+ else: # Bidentate is Axial
1707
+ ax_lig_list = [bidentate_ligand_idx, bidentate_ligand_idx]
1708
+ ax_con_list = [[val] for val in bidentate_cons]
1709
+ eq_lig_list = [tetradentate_ligand_idx]
1710
+ eq_con_list = [tetradentate_cons]
1711
+ if ((max(ligdents) == 3) and (min(ligdents) == 3)): # 3+3
1712
+ if loud:
1713
+ print('3+3 dentate case')
1714
+ # unused
1715
+ # allowed = list(range(0, 2))
1716
+ tridentate_cons_1 = ligcons[0]
1717
+ tridentate_cons_2 = ligcons[1]
1718
+ pair_combos = list(combinations([0, 1, 2], 2))
1719
+ angle_list = list()
1720
+ pair_list = list()
1721
+ coord_list = np.array([mol.getAtom(ii).coords() for ii in tridentate_cons_1])
1722
+ for i, pair in enumerate(pair_combos):
1723
+ pair_list.append(list(pair))
1724
+ angle = getAngle(coord_list, pair_list[-1], m_coord)
1725
+ if loud:
1726
+ print(('pair of atoms, then angle', pair, angle))
1727
+ angle_list.append(angle)
1728
+ test_angle = np.sort(np.array(angle_list))[::-1][0]
1729
+ # ### Like the above 3+2+1 case, the tridentate must be classified into planar or not.
1730
+ # ### If not planar, both ligands are both axial and equatorial. (by con Mol Weight)
1731
+ # ### If planar, the ligand that falls on the eq_points (as found above), is only equatorial,
1732
+ # ### but the tridentate ligand with one equatorial position and two axial is classified as both.
1733
+ if test_angle < angle_cutoff:
1734
+ planar = False
1735
+ else:
1736
+ planar = True
1737
+ if not planar: # Seesaw
1738
+ eq_ligcons = set([flat_ligcons[j] for j in eq_points_max_con_mw])
1739
+ eq_con_list = [list(set(tridentate_cons_1).intersection(eq_ligcons)),
1740
+ list(set(tridentate_cons_2).intersection(eq_ligcons))]
1741
+ ax_con_list = [list(set(tridentate_cons_1) - set(eq_con_list[0])),
1742
+ list(set(tridentate_cons_2) - set(eq_con_list[1]))]
1743
+ eq_lig_list = [0, 1]
1744
+ ax_lig_list = [0, 1]
1745
+ else: # Planar
1746
+ eq_ligcons = set([flat_ligcons[j] for j in eq_points_max_mw])
1747
+ eq_lig1 = list(set(tridentate_cons_1).intersection(eq_ligcons))
1748
+ eq_lig2 = list(set(tridentate_cons_2).intersection(eq_ligcons))
1749
+ if len(eq_lig1) == 2 or len(eq_lig2) == 2: # Catch cases where 2 + 2 planar
1750
+ new_combo_idx = list(set([x for x in range(3)])-set([max_mw_idx]))[0]
1751
+ eq_points_max_mw = combo_list[new_combo_idx]
1752
+ eq_ligcons = set([flat_ligcons[j] for j in eq_points_max_mw])
1753
+ eq_lig1 = list(set(tridentate_cons_1).intersection(eq_ligcons))
1754
+ eq_lig2 = list(set(tridentate_cons_2).intersection(eq_ligcons))
1755
+ eq_con_list = [eq_lig1, eq_lig2]
1756
+ eq_lig_list = [0, 1]
1757
+ if len(eq_lig1) == 1:
1758
+ ax_con_list = [list(set(tridentate_cons_1) - set(eq_lig1))]
1759
+ ax_lig_list = [0]
1760
+ else:
1761
+ ax_con_list = [list(set(tridentate_cons_2) - set(eq_lig2))]
1762
+ ax_lig_list = [1]
1763
+ elif (n_ligs == 2 and pentadentate): # 5+1
1764
+ # ### Handling for pentadentate scaffolds ####
1765
+ if loud:
1766
+ print('pentadentate case')
1767
+ # unused
1768
+ # allowed = [0, 1]
1769
+ not_eq = list()
1770
+ if len(ligcons[0]) == 1:
1771
+ # ### This is the axial ligand ####
1772
+ # print(j, 'axial lig')
1773
+ top_lig = 0
1774
+ top_con = ligcons[0]
1775
+ not_eq.append(top_lig)
1776
+ pent_lig = 1
1777
+ else:
1778
+ top_lig = 1
1779
+ top_con = ligcons[1]
1780
+ not_eq.append(top_lig)
1781
+ pent_lig = 0
1782
+ pentadentate_coord_list = np.array([mol.getAtom(
1783
+ ii).coords() for ii in ligcons[pent_lig]])
1784
+ # #### Adjusting this so that by default, any 4 within the same plane will be assigned as eq. ###
1785
+ m = np.array([mol.getAtom(mol.findMetal()[0]).coords()])
1786
+ p1 = np.array(mol.getAtom(top_con[0]).coords())
1787
+ angle_list = []
1788
+ for coord in pentadentate_coord_list:
1789
+ p2 = coord
1790
+ v1u = np.squeeze(np.array((m - p1) / np.linalg.norm((m - p1))))
1791
+ v2u = np.squeeze(np.array((m - p2) / np.linalg.norm((m - p2))))
1792
+ angle = np.rad2deg(np.arccos(np.clip(np.dot(v1u, v2u), -1.0, 1.0)))
1793
+ angle_list.append(angle)
1794
+ bot_idx = np.argmax(np.array(angle_list))
1795
+ bot_con = [ligcons[pent_lig][bot_idx]]
1796
+ bot_lig = pent_lig
1797
+ # unused:
1798
+ # not_ax_points = combo_list[np.argmin(error_list)]
1799
+ if loud:
1800
+ print(('This is bot_idx', bot_idx))
1801
+ print((flat_ligcons, top_con, bot_con))
1802
+ eq_lig_list = [bot_lig]
1803
+ eq_con_list = [list(set(flat_ligcons) - set(top_con) - set(bot_con))]
1804
+ ax_lig_list = [top_lig, bot_lig]
1805
+ ax_con_list = [top_con, bot_con]
1806
+ if loud:
1807
+ print(('con lists', eq_con_list, ax_con_list))
1808
+ ###########################################################################################
1809
+ # In the above, the pentadentate ligand is classified as both axial and equatorial. #
1810
+ # The lc atoms are decided by the z-position. Thus the pentadentate ligand has 4 eq-lc #
1811
+ # and 1 ax-lc. Currently should be able to check this and set that up. #
1812
+ ###########################################################################################
1813
+ elif (n_ligs == 1 and hexadentate): # 6
1814
+ if loud:
1815
+ print('hexadentate case')
1816
+ not_ax_points = eq_points_max_con_mw # Define by maximum mw plane for hexadentates
1817
+ bot_idx = list(set(range(6)) - set(not_ax_points))[0]
1818
+ top_idx = list(set(range(6)) - set(not_ax_points))[1]
1819
+ if lig_con_weights[top_idx] > lig_con_weights[bot_idx]: # Move heavier atom to bottom
1820
+ tmp_idx = top_idx
1821
+ top_idx = bot_idx
1822
+ bot_idx = tmp_idx
1823
+ if loud:
1824
+ print(('This is bot_idx', bot_idx))
1825
+ bot_con = [ligcons[0][bot_idx]]
1826
+ top_con = [ligcons[0][top_idx]]
1827
+ eq_lig_list = [0]
1828
+ eq_con_list = [list(set([ligcons[0][i] for i in not_ax_points]))]
1829
+ ax_lig_list = [0, 0]
1830
+ ax_con_list = [top_con, bot_con]
1831
+ if loud:
1832
+ print(('con lists', eq_con_list, ax_con_list))
1833
+ if eq_sym_match: # Enforce eq plane to have connecting atoms with same symbol
1834
+ flat_eq_con_list = [item for sublist in eq_con_list for item in sublist]
1835
+ flat_eq_con_syms = set([mol.getAtom(item).symbol() for item in flat_eq_con_list])
1836
+ if len(flat_eq_con_syms) != 1: # If more than 1 different type of symbol in eq plane!
1837
+ if loud:
1838
+ print('Correcting for eq plane with identical chemical symbols for con atoms.')
1839
+ pair_combos = list(combinations([0, 1, 2, 3, 4, 5], 2))
1840
+ pair_list = list()
1841
+ for pair in pair_combos:
1842
+ pair_list.append(list(pair))
1843
+ point_combos = [pair_list[argsort_angle_list[0]] + pair_list[argsort_angle_list[1]],
1844
+ pair_list[argsort_angle_list[1]] + pair_list[argsort_angle_list[2]],
1845
+ pair_list[argsort_angle_list[2]] + pair_list[argsort_angle_list[0]]]
1846
+ symbols_combos = list()
1847
+ for combo in point_combos:
1848
+ tmp_sym_combos = set()
1849
+ for point_num in combo:
1850
+ tmp_sym_combos.add(mol.getAtom(flat_ligcons[point_num]).symbol())
1851
+ symbols_combos.append(len(tmp_sym_combos)) # Save number of distinct con_atoms
1852
+ # Get plane with fewest distinct types of connecting atoms
1853
+ eq_plane_min_atom_types = point_combos[np.argmin(symbols_combos)]
1854
+ eq_ligcons = [flat_ligcons[i] for i in eq_plane_min_atom_types]
1855
+ ax_ligcons = list(set(flat_ligcons)-set(eq_ligcons))
1856
+ eq_lig_list = list()
1857
+ eq_con_list = list()
1858
+ for lig_con in eq_ligcons:
1859
+ lig_ref = flat_lig_refs[flat_ligcons.index(lig_con)]
1860
+ if lig_ref in eq_lig_list:
1861
+ eq_con_list[eq_lig_list.index(lig_ref)].append(lig_con)
1862
+ else:
1863
+ eq_con_list.append([lig_con])
1864
+ eq_lig_list.append(lig_ref)
1865
+ ax_lig_list = list()
1866
+ ax_con_list = list()
1867
+ for lig_con in ax_ligcons:
1868
+ lig_ref = flat_lig_refs[flat_ligcons.index(lig_con)]
1869
+ if lig_ref in ax_lig_list:
1870
+ ax_con_list[ax_lig_list.index(lig_ref)].append(lig_con)
1871
+ else:
1872
+ ax_con_list.append([lig_con])
1873
+ ax_lig_list.append(lig_ref)
1874
+ # ###### Code in case further atom-wise bond-length constraints wanted ######
1875
+ # else: # All eq same symbol. Ensure axial are flagged as two different bond lengths.
1876
+ # con_bond_lengths = [np.round(mol.getDistToMetal(x,metal_index),6) for x in flat_ligcons]
1877
+ # bl_set = list(set(con_bond_lengths)) # Check if there are 2 different bond lengths
1878
+ # if len(bl_set) == 2 and (con_bond_lengths.count(bl_set[0])==2
1879
+ # or con_bond_lengths.count(bl_set[1])==2):
1880
+ # con_bls = np.array(con_bond_lengths)
1881
+ # type1_idxs = np.where(con_bls==bl_set[0])[0]
1882
+ # type2_idxs = np.where(con_bls==bl_set[1])[0]
1883
+ # if len(type1_idxs) > len(type2_idxs):
1884
+ # eq_ligcons = [flat_ligcons[i] for i in type1_idxs]
1885
+ # ax_ligcons = list(set(flat_ligcons)-set(eq_ligcons))
1886
+ # else:
1887
+ # eq_ligcons = [flat_ligcons[i] for i in type2_idxs]
1888
+ # ax_ligcons = list(set(flat_ligcons)-set(eq_ligcons))
1889
+ # eq_lig_list = list()
1890
+ # eq_con_list = list()
1891
+ # for lig_con in eq_ligcons:
1892
+ # lig_ref = flat_lig_refs[flat_ligcons.index(lig_con)]
1893
+ # if lig_ref in eq_lig_list:
1894
+ # eq_con_list[eq_lig_list.index(lig_ref)].append(lig_con)
1895
+ # else:
1896
+ # eq_con_list.append([lig_con])
1897
+ # eq_lig_list.append(lig_ref)
1898
+ # ax_lig_list = list()
1899
+ # ax_con_list = list()
1900
+ # for lig_con in ax_ligcons:
1901
+ # lig_ref = flat_lig_refs[flat_ligcons.index(lig_con)]
1902
+ # if lig_ref in ax_lig_list:
1903
+ # ax_con_list[ax_lig_list.index(lig_ref)].append(lig_con)
1904
+ # else:
1905
+ # ax_con_list.append([lig_con])
1906
+ # ax_lig_list.append(lig_ref)
1907
+ # Build ligand list for ax/eq positions, compile all information
1908
+ ax_ligand_list = [built_ligand_list[i] for i in ax_lig_list]
1909
+ eq_ligand_list = [built_ligand_list[i] for i in eq_lig_list]
1910
+ if loud and valid:
1911
+ print(('lig_nat_list', lig_natoms_list))
1912
+ print(('eq_liq is ind ', eq_lig_list))
1913
+ print(('ax_liq is ind ', ax_lig_list))
1914
+ print(('ax built lig [0] ext ind :' +
1915
+ str(list(built_ligand_list[ax_lig_list[0]].ext_int_dict.keys()))))
1916
+ if len(ax_lig_list) > 1:
1917
+ print(('ax built lig [1] ext ind :' +
1918
+ str(list(built_ligand_list[ax_lig_list[1]].ext_int_dict.keys()))))
1919
+ print(('eq built lig [0] ext ind: ' +
1920
+ str(list(built_ligand_list[eq_lig_list[0]].ext_int_dict.keys()))))
1921
+ print(('eq_con is ' + str((eq_con_list))))
1922
+ print(('ax_con is ' + str((ax_con_list))))
1923
+
1924
+ for j, ax_con in enumerate(ax_con_list):
1925
+ current_ligand_index_list = built_ligand_list[ax_lig_list[j]].index_list
1926
+ ax_con_int_list.append([current_ligand_index_list.index(i) for i in ax_con])
1927
+ for j, eq_con in enumerate(eq_con_list):
1928
+ current_ligand_index_list = built_ligand_list[eq_lig_list[j]].index_list
1929
+ eq_con_int_list.append([current_ligand_index_list.index(i) for i in eq_con])
1930
+ if loud:
1931
+ print(('int eq ' + str(eq_con_int_list)))
1932
+ print(('ext eq ' + str(eq_con_list)))
1933
+ print('**********************************************')
1934
+ for ax_lig in ax_lig_list:
1935
+ ax_natoms_list.append(lig_natoms_list[ax_lig])
1936
+ for eq_lig in eq_lig_list:
1937
+ eq_natoms_list.append(lig_natoms_list[eq_lig])
1938
+ return (ax_ligand_list, eq_ligand_list, ax_natoms_list, eq_natoms_list,
1939
+ ax_con_int_list, eq_con_int_list, ax_con_list, eq_con_list, built_ligand_list)
1940
+
1941
+
1942
+ def ligand_assign_alleq(mol, liglist, ligdents, ligcons):
1943
+ ax_ligand_list, ax_con_int_list = [], []
1944
+ eq_ligand_list, eq_con_int_list = [], []
1945
+ for i, ligand_indices in enumerate(liglist):
1946
+ this_ligand = ligand(mol, ligand_indices, ligdents[i])
1947
+ this_ligand.obtain_mol3d()
1948
+ eq_con = ligcons[i]
1949
+ eq_ligand_list.append(this_ligand)
1950
+ current_ligand_index_list = this_ligand.index_list
1951
+ eq_con_int_list.append([current_ligand_index_list.index(i) for i in eq_con])
1952
+ return ax_ligand_list, eq_ligand_list, ax_con_int_list, eq_con_int_list
1953
+
1954
+
1955
+ def get_lig_symmetry(mol, loud=False, htol=3):
1956
+ """Handles ligand symmetry assignment.
1957
+
1958
+ Parameters
1959
+ ----------
1960
+ mol : mol3D
1961
+ mol3D class instance.
1962
+ loud : bool
1963
+ Flag for extra printout. Default is False.
1964
+ htol : int, optional
1965
+ Tolerance for hydrogens in matching ligands. Default is 3.
1966
+
1967
+ Returns
1968
+ -------
1969
+ outstring : str
1970
+ ligand symmetry plane
1971
+
1972
+ """
1973
+ liglist, ligdents, ligcons = ligand_breakdown(mol, BondedOct=True)
1974
+ ax_ligand_list, eq_ligand_list, ax_natoms_list, eq_natoms_list, \
1975
+ ax_con_int_list, eq_con_int_list, ax_con_list, \
1976
+ eq_con_list, built_ligand_list = ligand_assign_consistent(mol, liglist, ligdents, ligcons, loud=loud)
1977
+ max_dent = max(ligdents)
1978
+ min_dent = min(ligdents)
1979
+ flat_ligcons = [item for sublist in ligcons for item in sublist]
1980
+ # ## Below, take all combinations of two atoms, and measure their angles through the metal center
1981
+
1982
+ def compare_ligs(ligs):
1983
+ unique_ligands = list()
1984
+ unique_ligcons = list()
1985
+ unique_hs = list()
1986
+ for j, built_ligs in enumerate(ligs):
1987
+ # test if ligand is unique without hydrogens added?
1988
+ sl = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
1989
+ hs = len([item for item in sl if item == 'H'])
1990
+ sl = [item for item in sl if item != 'H']
1991
+ if len(sl) < 1:
1992
+ sl = ['H']
1993
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
1994
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
1995
+ unique = 1 # Flag for detecting unique ligands
1996
+ for i, other_sl in enumerate(unique_ligands):
1997
+ if sl == other_sl and sl_ligcon == unique_ligcons[i] and np.isclose(hs, unique_hs[i], atol=float(htol)):
1998
+ # Duplicate
1999
+ unique = 0
2000
+ if unique == 1:
2001
+ unique_ligands.append(sl)
2002
+ unique_ligcons.append(sl_ligcon)
2003
+ unique_hs.append(hs)
2004
+ if len(unique_ligands) < len(ligs):
2005
+ copy_in_list = True
2006
+ else:
2007
+ copy_in_list = False
2008
+ return copy_in_list
2009
+ # Build Ligands and get MWs of ligands
2010
+ built_ligand_list = list()
2011
+ for i, ligand_indices in enumerate(liglist):
2012
+ this_ligand = ligand(mol, ligand_indices, ligdents[i])
2013
+ this_ligand.obtain_mol3d()
2014
+ built_ligand_list.append(this_ligand)
2015
+ # ## Obtain coordinates for the connecting atoms. Flat coord list ends up being used for comparisons.
2016
+ # Bin and sort ligands as Unique
2017
+ unique_ligands = list()
2018
+ unique_ligcons = list()
2019
+ unique_counts = list()
2020
+ unique_hs = list()
2021
+ for j, built_ligs in enumerate(built_ligand_list):
2022
+ # test if ligand is unique without hydrogens added
2023
+ sl = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(built_ligs.index_list)])
2024
+ hs = len([item for item in sl if item == 'H'])
2025
+ sl = [item for item in sl if item != 'H']
2026
+ if len(sl) < 1:
2027
+ sl = ['H']
2028
+ ligcon_inds = [x for x in built_ligs.index_list if x in flat_ligcons]
2029
+ sl_ligcon = sorted([atom.symbol() for atom in built_ligs.master_mol.getAtomwithinds(ligcon_inds)])
2030
+ unique = 1 # Flag for detecting unique ligands
2031
+ for i, other_sl in enumerate(unique_ligands):
2032
+ if sl == other_sl and sl_ligcon == unique_ligcons[i] and np.isclose(hs, unique_hs[i], atol=float(htol)):
2033
+ # Duplicate
2034
+ unique = 0
2035
+ unique_counts[i] += 1
2036
+ if unique == 1:
2037
+ unique_ligands.append(sl)
2038
+ unique_ligcons.append(sl_ligcon)
2039
+ unique_counts.append(1)
2040
+ unique_hs.append(hs)
2041
+ n_unique_ligs = len(unique_ligands) # Number of unique ligands
2042
+ max_eq_count = max([len(x) for x in eq_con_list]) # Maximum cons of same lig in eq plane
2043
+ if max_dent == 6:
2044
+ outstring = '6'
2045
+ elif max_dent == 5 and min_dent == 1:
2046
+ outstring = '51'
2047
+ elif max_dent == 4 and min_dent == 2:
2048
+ if liglist[np.argmin(ligdents)] == ax_ligand_list[0].index_list:
2049
+ outstring = '4planar2'
2050
+ else:
2051
+ outstring = '42'
2052
+ elif max_dent == 4 and min_dent == 1:
2053
+ # 411c, 411t, 4|11|c, 4|11|t
2054
+ if n_unique_ligs == 2:
2055
+ # 4|11|c, 4|11|t
2056
+ if max_eq_count == 4:
2057
+ outstring = '4|11|t'
2058
+ elif max_eq_count == 2:
2059
+ outstring = '4|11|c'
2060
+ else:
2061
+ outstring = 'Error411'
2062
+ elif n_unique_ligs == 3:
2063
+ # 411c, 411t
2064
+ if max_eq_count == 4:
2065
+ outstring = '411t'
2066
+ elif max_eq_count == 2:
2067
+ outstring = '411c'
2068
+ else:
2069
+ outstring = 'Error411'
2070
+ else:
2071
+ outstring = 'Error411'
2072
+ elif max_dent == 3 and min_dent == 3:
2073
+ # 33f, 33m, |33|f, |33|m
2074
+ if n_unique_ligs == 2:
2075
+ if max_eq_count == 2:
2076
+ outstring = '33f'
2077
+ elif max_eq_count == 3:
2078
+ outstring = '33m'
2079
+ else:
2080
+ outstring = 'Error33'
2081
+ else:
2082
+ if max_eq_count == 2:
2083
+ outstring = '|33|f'
2084
+ elif max_eq_count == 3:
2085
+ outstring = '|33|m'
2086
+ else:
2087
+ outstring = 'Error33'
2088
+ elif max_dent == 3 and 2 in ligdents:
2089
+ # 3f21, 3m21
2090
+ if max_eq_count == 2:
2091
+ outstring = '3f21'
2092
+ elif max_eq_count == 3:
2093
+ if ax_ligand_list[1].index_list == ax_ligand_list[0].index_list:
2094
+ outstring = '3m2t1'
2095
+ else:
2096
+ outstring = '3m21'
2097
+ else:
2098
+ outstring = 'Error321'
2099
+ elif max_dent == 3 and 2 not in ligdents:
2100
+ # 3f111, 3m111, 3f|11|1, 3m|11|t1, 3m|11|c1,
2101
+ # 3m|111|, 3f|111|
2102
+ if n_unique_ligs == 4:
2103
+ if max_eq_count == 2:
2104
+ outstring = '3f111'
2105
+ else:
2106
+ outstring = '3m111'
2107
+ elif n_unique_ligs == 3:
2108
+ if max_eq_count == 2:
2109
+ outstring = '3f|11|1'
2110
+ else:
2111
+ if compare_ligs(ax_ligand_list):
2112
+ outstring = '3m|11|t1'
2113
+ else:
2114
+ outstring = '3m|11|c1'
2115
+ elif n_unique_ligs == 2:
2116
+ if max_eq_count == 2:
2117
+ outstring = '3f|111|'
2118
+ else:
2119
+ outstring = '3m|111|'
2120
+ else:
2121
+ outstring = 'Error3111'
2122
+ elif max_dent == 2 and min_dent == 2:
2123
+ # 222, 2|22|, |222|
2124
+ if ax_ligand_list[1].index_list == ax_ligand_list[0].index_list:
2125
+ outstring = '22planar2'
2126
+ elif n_unique_ligs == 3:
2127
+ outstring = '222'
2128
+ elif n_unique_ligs == 2:
2129
+ outstring = '|22|2'
2130
+ elif n_unique_ligs == 1:
2131
+ outstring = '|222|'
2132
+ else:
2133
+ outstring = 'Error222'
2134
+ elif max_dent == 2 and len(ligdents) == 4:
2135
+ # 2211c, 2211t, |22|11c, |22|11t, 22|11|c, 22|11|t
2136
+ # |22||11|c, |22||11|t
2137
+ if ax_ligand_list[1].index_list == ax_ligand_list[0].index_list:
2138
+ outstring = '2t211'
2139
+ elif max_eq_count == 2: # Must be planar bidentates, trans monodentates
2140
+ if compare_ligs(eq_ligand_list): # Bidentates identical
2141
+ if compare_ligs(ax_ligand_list): # Monodentates identical
2142
+ outstring = '|22||11|t'
2143
+ else:
2144
+ outstring = '|22|11t'
2145
+ else: # Bidentates different
2146
+ if compare_ligs(ax_ligand_list): # Monodentates identical
2147
+ outstring = '22|11|t'
2148
+ else:
2149
+ outstring = '2211t'
2150
+ elif max_eq_count == 1: # Must seesaw bidentates, cis-monodentates
2151
+ if compare_ligs(ax_ligand_list): # Bidentates identical
2152
+ eq_ligand_list.remove(ax_ligand_list[0]) # remove one bidentate
2153
+ if compare_ligs(eq_ligand_list): # Monodentates identical
2154
+ outstring = '|22||11|c'
2155
+ else:
2156
+ outstring = '|22|11c'
2157
+ else: # Bidentates different
2158
+ if compare_ligs(eq_ligand_list): # Monodentates identical
2159
+ outstring = '22|11|c'
2160
+ else:
2161
+ outstring = '2211c'
2162
+ else:
2163
+ outstring = 'Error2211'
2164
+ elif max_dent == 2 and len(ligdents) == 5:
2165
+ # 21111, 2|11|t11, 2|11|c11t, 2|11|c11c , 2|111|m1, 2|111|f1
2166
+ # 2|1111|, 2|11|c|11|t, 2|11|c|11|c
2167
+ if liglist[np.argmax(ligdents)] == ax_ligand_list[0].index_list:
2168
+ outstring = '2t1111'
2169
+ elif n_unique_ligs == 5:
2170
+ outstring = '21111'
2171
+ elif n_unique_ligs == 4:
2172
+ if compare_ligs(ax_ligand_list): # trans
2173
+ outstring = '2|11|t11'
2174
+ else:
2175
+ if compare_ligs(eq_ligand_list): # trans different monodentates
2176
+ outstring = '2|11|c11t'
2177
+ else:
2178
+ outstring = '2|11|c11c'
2179
+ elif n_unique_ligs == 3:
2180
+ if max(unique_counts) == 3:
2181
+ if compare_ligs(ax_ligand_list): # mer
2182
+ outstring = '2|111|m1'
2183
+ else:
2184
+ outstring = '2|111|f1'
2185
+ elif max(unique_counts) == 2:
2186
+ if compare_ligs(ax_ligand_list): # trans
2187
+ outstring = '2|11|c|11|t'
2188
+ else:
2189
+ outstring = '2|11|c|11|c'
2190
+ else:
2191
+ outstring = 'Error21111'
2192
+ elif n_unique_ligs == 2:
2193
+ outstring = '2|1111|'
2194
+ else:
2195
+ outstring = 'Error21111'
2196
+ elif max_dent == 1:
2197
+ trans_pairs = [ax_ligand_list, [eq_ligand_list[0], eq_ligand_list[2]],
2198
+ [eq_ligand_list[1], eq_ligand_list[3]]]
2199
+ if n_unique_ligs == 6:
2200
+ outstring = '111111'
2201
+ elif n_unique_ligs == 5:
2202
+ # Compare all trans positions. If not trans set as cis
2203
+ trans_count = 0
2204
+ for pair in trans_pairs:
2205
+ if compare_ligs(pair):
2206
+ trans_count += 1
2207
+ if trans_count == 1:
2208
+ outstring = '|11|t1111'
2209
+ elif trans_count == 0:
2210
+ outstring = '|11|c1111'
2211
+ else:
2212
+ outstring = 'Error|11|1111'
2213
+ elif n_unique_ligs == 4:
2214
+ # |11|c|11|t11, |11|c|11|c11t, |11|c|11|c11c, |11|t|11|t11,
2215
+ # |111|f/m111
2216
+ if max(unique_counts) == 3: # fac/mer
2217
+ mer = False
2218
+ for pair in trans_pairs:
2219
+ if compare_ligs(pair):
2220
+ mer = True
2221
+ if mer:
2222
+ outstring = '|111|m111'
2223
+ else:
2224
+ outstring = '|111|f111'
2225
+ elif max(unique_counts) == 2:
2226
+ trans_count = 0
2227
+ for pair in trans_pairs:
2228
+ if compare_ligs(pair):
2229
+ trans_count += 1
2230
+ if trans_count == 2:
2231
+ outstring = '|11|t|11|t11'
2232
+ elif trans_count == 1:
2233
+ outstring = '|11|c|11|t11'
2234
+ elif trans_count == 0:
2235
+ planes = [eq_ligand_list, [eq_ligand_list[0]]+[eq_ligand_list[2]]+ax_ligand_list,
2236
+ [eq_ligand_list[1]]+[eq_ligand_list[3]]+ax_ligand_list]
2237
+ plane_counts = 0
2238
+ for plane in planes:
2239
+ if compare_ligs(plane):
2240
+ plane_counts += 1
2241
+ if plane_counts == 1:
2242
+ outstring = '|11|c|11|c11t'
2243
+ elif plane_counts == 2:
2244
+ outstring = '|11|c|11|c11c'
2245
+ else:
2246
+ outstring = 'Error|11|c|11|c11'
2247
+ else:
2248
+ outstring = 'Error|11||11|11'
2249
+ else:
2250
+ outstring = 'Error|11||11|11'
2251
+ elif n_unique_ligs == 3:
2252
+ # |11|c|11|c|11|t, |11|t|11|t|11|t, |11|c|11|c|11|c
2253
+ # |1111|11c, |1111|11t
2254
+ # |111|f|11|1, |111|m|11|t1, |111|m|11|c1
2255
+ if max(unique_counts) == 4:
2256
+ trans_count = 0
2257
+ for pair in trans_pairs:
2258
+ if compare_ligs(pair):
2259
+ trans_count += 1
2260
+ if trans_count == 2:
2261
+ outstring = '|1111|11t'
2262
+ elif trans_count == 1:
2263
+ outstring = '|1111|11c'
2264
+ else:
2265
+ outstring = 'Error|1111|11'
2266
+ elif max(unique_counts) == 3:
2267
+ trans_count = 0
2268
+ for pair in trans_pairs:
2269
+ if compare_ligs(pair):
2270
+ trans_count += 1
2271
+ if trans_count == 2:
2272
+ outstring = '|111|m|11|t1'
2273
+ elif trans_count == 1:
2274
+ outstring = '|111|m|11|c1'
2275
+ elif trans_count == 0:
2276
+ outstring = '|111|f|11|1'
2277
+ else:
2278
+ outstring = 'Error|111||11|1'
2279
+ elif max(unique_counts) == 2:
2280
+ trans_count = 0
2281
+ for pair in trans_pairs:
2282
+ if compare_ligs(pair):
2283
+ trans_count += 1
2284
+ if trans_count == 3:
2285
+ outstring = '|11|t|11|t|11|t'
2286
+ elif trans_count == 1:
2287
+ outstring = '|11|c|11|c|11|t'
2288
+ elif trans_count == 0:
2289
+ outstring = '|11|c|11|c|11|c'
2290
+ else:
2291
+ outstring = 'Error|11||11|11|'
2292
+ else:
2293
+ outstring = 'Error_3U_mono'
2294
+ elif n_unique_ligs == 2:
2295
+ # |11111|1
2296
+ # |111|f|111|,|111|m|111|
2297
+ # |1111||11|t, |1111||11|c
2298
+ if max(unique_counts) == 5:
2299
+ outstring = '|11111|1'
2300
+ elif max(unique_counts) == 4:
2301
+ trans_count = 0
2302
+ for pair in trans_pairs:
2303
+ if compare_ligs(pair):
2304
+ trans_count += 1
2305
+ if trans_count == 3:
2306
+ outstring = '|1111||11|t'
2307
+ elif trans_count == 1:
2308
+ outstring = '|1111||11|c'
2309
+ else:
2310
+ outstring = 'Error|1111||11|'
2311
+ elif max(unique_counts) == 3:
2312
+ trans_count = 0
2313
+ for pair in trans_pairs:
2314
+ if compare_ligs(pair):
2315
+ trans_count += 1
2316
+ if trans_count == 2:
2317
+ outstring = '|111|m|111|'
2318
+ elif trans_count == 0:
2319
+ outstring = '|111|f|111|'
2320
+ else:
2321
+ outstring = 'Error|111||111|'
2322
+ else:
2323
+ outstring = 'Error_2U_mono'
2324
+ elif n_unique_ligs == 1:
2325
+ outstring = '|111111|'
2326
+ else:
2327
+ outstring = 'Error_Monodentate'
2328
+ else:
2329
+ outstring = 'Error_None_Fit'
2330
+ return outstring