1 #!/bin/env python
2 #
3 # File: Psi4VisualizeDualDescriptors.py
4 # Author: Manish Sud <msud@san.rr.com>
5 #
6 # Copyright (C) 2026 Manish Sud. All rights reserved.
7 #
8 # The functionality available in this script is implemented using Psi4, an
9 # open source quantum chemistry software package, and RDKit, an open
10 # source toolkit for cheminformatics developed by Greg Landrum.
11 #
12 # This file is part of MayaChemTools.
13 #
14 # MayaChemTools is free software; you can redistribute it and/or modify it under
15 # the terms of the GNU Lesser General Public License as published by the Free
16 # Software Foundation; either version 3 of the License, or (at your option) any
17 # later version.
18 #
19 # MayaChemTools is distributed in the hope that it will be useful, but without
20 # any warranty; without even the implied warranty of merchantability of fitness
21 # for a particular purpose. See the GNU Lesser General Public License for more
22 # details.
23 #
24 # You should have received a copy of the GNU Lesser General Public License
25 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
26 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
27 # Boston, MA, 02111-1307, USA.
28 #
29
30 from __future__ import print_function
31
32 import os
33 import sys
34 import time
35 import re
36 import glob
37 import shutil
38 import multiprocessing as mp
39
40 # Psi4 imports...
41 if hasattr(shutil, "which") and shutil.which("psi4") is None:
42 sys.stderr.write("\nWarning: Failed to find 'psi4' in your PATH indicating potential issues with your\n")
43 sys.stderr.write("Psi4 environment. The 'import psi4' directive in the global scope of the script\n")
44 sys.stderr.write("interferes with the multiprocessing functionality. It is imported later in the\n")
45 sys.stderr.write("local scope during the execution of the script and may fail. Check/update your\n")
46 sys.stderr.write("Psi4 environment and try again.\n\n")
47
48 # RDKit imports...
49 try:
50 from rdkit import rdBase
51 from rdkit import Chem
52 except ImportError as ErrMsg:
53 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
54 sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
55 sys.exit(1)
56
57 # MayaChemTools imports...
58 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
59 try:
60 from docopt import docopt
61 import MiscUtil
62 import Psi4Util
63 import RDKitUtil
64 except ImportError as ErrMsg:
65 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
66 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
67 sys.exit(1)
68
69 ScriptName = os.path.basename(sys.argv[0])
70 Options = {}
71 OptionsInfo = {}
72
73
74 def main():
75 """Start execution of the script."""
76
77 MiscUtil.PrintInfo(
78 "\n%s (Psi4: Imported later; RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
79 % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())
80 )
81
82 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
83
84 # Retrieve command line arguments and options...
85 RetrieveOptions()
86
87 # Process and validate command line arguments and options...
88 ProcessOptions()
89
90 # Perform actions required by the script...
91 GenerateAndVisualizeDualDescriptors()
92
93 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
94 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
95
96
97 def GenerateAndVisualizeDualDescriptors():
98 """Generate and visualize dual descriptors."""
99
100 if OptionsInfo["GenerateCubeFiles"]:
101 GenerateDualDescriptors()
102
103 if OptionsInfo["VisualizeCubeFiles"]:
104 VisualizeDualDescriptors()
105
106
107 def GenerateDualDescriptors():
108 """Generate cube files for dual descriptors."""
109
110 # Setup a molecule reader...
111 MiscUtil.PrintInfo("\nProcessing file %s..." % OptionsInfo["Infile"])
112 Mols = RDKitUtil.ReadMolecules(OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"])
113
114 MolCount, ValidMolCount, CubeFilesFailedCount = ProcessMolecules(Mols)
115
116 MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
117 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
118 MiscUtil.PrintInfo("Number of molecules failed during generation of cube files: %d" % CubeFilesFailedCount)
119 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesFailedCount))
120
121
122 def ProcessMolecules(Mols):
123 """Process and generate cube files for molecules."""
124
125 if OptionsInfo["MPMode"]:
126 return ProcessMoleculesUsingMultipleProcesses(Mols)
127 else:
128 return ProcessMoleculesUsingSingleProcess(Mols)
129
130
131 def ProcessMoleculesUsingSingleProcess(Mols):
132 """Process and generate dual descriptors cube files for molecules using a single process."""
133
134 # Intialize Psi4...
135 MiscUtil.PrintInfo("\nInitializing Psi4...")
136 Psi4Handle = Psi4Util.InitializePsi4(
137 Psi4RunParams=OptionsInfo["Psi4RunParams"],
138 Psi4OptionsParams=OptionsInfo["Psi4OptionsParams"],
139 PrintVersion=True,
140 PrintHeader=True,
141 )
142 OptionsInfo["psi4"] = Psi4Handle
143
144 MiscUtil.PrintInfo("\nGenerating cube files for dual descriptors...")
145
146 (MolCount, ValidMolCount, CubeFilesFailedCount) = [0] * 3
147 for Mol in Mols:
148 MolCount += 1
149
150 if not CheckAndValidateMolecule(Mol, MolCount):
151 continue
152
153 # Setup a Psi4 molecule...
154 Psi4Mol = SetupPsi4Mol(Psi4Handle, Mol, MolCount)
155 if Psi4Mol is None:
156 continue
157
158 ValidMolCount += 1
159
160 CalcStatus = GenerateMolCubeFiles(Psi4Handle, Psi4Mol, Mol, MolCount)
161
162 if not CalcStatus:
163 if not OptionsInfo["QuietMode"]:
164 MiscUtil.PrintWarning(
165 "Failed to generate cube files for molecule %s" % RDKitUtil.GetMolName(Mol, MolCount)
166 )
167
168 CubeFilesFailedCount += 1
169 continue
170
171 return (MolCount, ValidMolCount, CubeFilesFailedCount)
172
173
174 def ProcessMoleculesUsingMultipleProcesses(Mols):
175 """Process and generate dual descriptors cube files for molecules using multiple processes."""
176
177 MiscUtil.PrintInfo("\nGenerating dual descriptors using multiprocessing...")
178
179 MPParams = OptionsInfo["MPParams"]
180
181 # Setup data for initializing a worker process...
182 InitializeWorkerProcessArgs = (
183 MiscUtil.ObjectToBase64EncodedString(Options),
184 MiscUtil.ObjectToBase64EncodedString(OptionsInfo),
185 )
186
187 # Setup a encoded mols data iterable for a worker process...
188 WorkerProcessDataIterable = RDKitUtil.GenerateBase64EncodedMolStrings(Mols)
189
190 # Setup process pool along with data initialization for each process...
191 if not OptionsInfo["QuietMode"]:
192 MiscUtil.PrintInfo(
193 "\nConfiguring multiprocessing using %s method..."
194 % ("mp.Pool.imap()" if re.match("^Lazy$", MPParams["InputDataMode"], re.I) else "mp.Pool.map()")
195 )
196 MiscUtil.PrintInfo(
197 "NumProcesses: %s; InputDataMode: %s; ChunkSize: %s\n"
198 % (
199 MPParams["NumProcesses"],
200 MPParams["InputDataMode"],
201 ("automatic" if MPParams["ChunkSize"] is None else MPParams["ChunkSize"]),
202 )
203 )
204
205 ProcessPool = mp.Pool(MPParams["NumProcesses"], InitializeWorkerProcess, InitializeWorkerProcessArgs)
206
207 # Start processing...
208 if re.match("^Lazy$", MPParams["InputDataMode"], re.I):
209 Results = ProcessPool.imap(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
210 elif re.match("^InMemory$", MPParams["InputDataMode"], re.I):
211 Results = ProcessPool.map(WorkerProcess, WorkerProcessDataIterable, MPParams["ChunkSize"])
212 else:
213 MiscUtil.PrintError(
214 'The value, %s, specified for "--inputDataMode" is not supported.' % (MPParams["InputDataMode"])
215 )
216
217 # Print out Psi4 version in the main process...
218 MiscUtil.PrintInfo("\nInitializing Psi4...\n")
219 Psi4Handle = Psi4Util.InitializePsi4(PrintVersion=True, PrintHeader=False)
220 OptionsInfo["psi4"] = Psi4Handle
221
222 (MolCount, ValidMolCount, CubeFilesFailedCount) = [0] * 3
223 for Result in Results:
224 MolCount += 1
225 MolIndex, EncodedMol, CalcStatus = Result
226
227 if EncodedMol is None:
228 continue
229
230 ValidMolCount += 1
231
232 Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
233
234 if not CalcStatus:
235 if not OptionsInfo["QuietMode"]:
236 MiscUtil.PrintWarning(
237 "Failed to generate cube files for molecule %s" % RDKitUtil.GetMolName(Mol, MolCount)
238 )
239
240 CubeFilesFailedCount += 1
241 continue
242
243 return (MolCount, ValidMolCount, CubeFilesFailedCount)
244
245
246 def InitializeWorkerProcess(*EncodedArgs):
247 """Initialize data for a worker process."""
248
249 global Options, OptionsInfo
250
251 if not OptionsInfo["QuietMode"]:
252 MiscUtil.PrintInfo("Starting process (PID: %s)..." % os.getpid())
253
254 # Decode Options and OptionInfo...
255 Options = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[0])
256 OptionsInfo = MiscUtil.ObjectFromBase64EncodedString(EncodedArgs[1])
257
258 # Psi4 is initialized in the worker process to avoid creation of redundant Psi4
259 # output files for each process...
260 OptionsInfo["Psi4Initialized"] = False
261
262
263 def InitializePsi4ForWorkerProcess():
264 """Initialize Psi4 for a worker process."""
265
266 if OptionsInfo["Psi4Initialized"]:
267 return
268
269 OptionsInfo["Psi4Initialized"] = True
270
271 # Update output file...
272 OptionsInfo["Psi4RunParams"]["OutputFile"] = Psi4Util.UpdatePsi4OutputFileUsingPID(
273 OptionsInfo["Psi4RunParams"]["OutputFile"], os.getpid()
274 )
275
276 # Intialize Psi4...
277 OptionsInfo["psi4"] = Psi4Util.InitializePsi4(
278 Psi4RunParams=OptionsInfo["Psi4RunParams"],
279 Psi4OptionsParams=OptionsInfo["Psi4OptionsParams"],
280 PrintVersion=False,
281 PrintHeader=True,
282 )
283
284
285 def WorkerProcess(EncodedMolInfo):
286 """Process data for a worker process."""
287
288 if not OptionsInfo["Psi4Initialized"]:
289 InitializePsi4ForWorkerProcess()
290
291 MolIndex, EncodedMol = EncodedMolInfo
292
293 CalcStatus = False
294
295 if EncodedMol is None:
296 return [MolIndex, None, CalcStatus]
297
298 Mol = RDKitUtil.MolFromBase64EncodedMolString(EncodedMol)
299 MolCount = MolIndex + 1
300
301 if not CheckAndValidateMolecule(Mol, MolCount):
302 return [MolIndex, None, CalcStatus]
303
304 # Setup a Psi4 molecule...
305 Psi4Mol = SetupPsi4Mol(OptionsInfo["psi4"], Mol, MolCount)
306 if Psi4Mol is None:
307 return [MolIndex, None, CalcStatus]
308
309 CalcStatus = GenerateMolCubeFiles(OptionsInfo["psi4"], Psi4Mol, Mol, MolCount)
310
311 return [
312 MolIndex,
313 RDKitUtil.MolToBase64EncodedMolString(
314 Mol, PropertyPickleFlags=Chem.PropertyPickleOptions.MolProps | Chem.PropertyPickleOptions.PrivateProps
315 ),
316 CalcStatus,
317 ]
318
319
320 def GenerateMolCubeFiles(Psi4Handle, Psi4Mol, Mol, MolNum=None):
321 """Generate cube files for dual descriptors."""
322
323 # Setup reference wave function...
324 Reference = SetupReferenceWavefunction(Mol)
325 Psi4Handle.set_options({"Reference": Reference})
326
327 # Setup method name and basis set...
328 MethodName, BasisSet = SetupMethodNameAndBasisSet(Mol)
329
330 # Calculate single point energy to setup a wavefunction...
331 Status, Energy, WaveFunction = Psi4Util.CalculateSinglePointEnergy(
332 Psi4Handle, Psi4Mol, MethodName, BasisSet, ReturnWaveFunction=True, Quiet=OptionsInfo["QuietMode"]
333 )
334
335 if not Status:
336 PerformPsi4Cleanup(Psi4Handle)
337 return False
338
339 # Generate cube files...
340 Status = GenerateCubeFilesForDualDescriptors(Psi4Handle, WaveFunction, Mol, MolNum)
341
342 # Clean up
343 PerformPsi4Cleanup(Psi4Handle)
344
345 return True
346
347
348 def GenerateCubeFilesForDualDescriptors(Psi4Handle, WaveFunction, Mol, MolNum):
349 """Generate cube files for dual descriptors."""
350
351 # Setup a temporary local directory to generate cube files...
352 Status, CubeFilesDir, CubeFilesDirPath = SetupMolCubeFilesDir(Mol, MolNum)
353 if not Status:
354 return False
355
356 # Generate cube files using psi4.cubeprop...
357 Status, Psi4CubeFiles = GenerateCubeFilesUsingCubeprop(
358 Psi4Handle, WaveFunction, Mol, MolNum, CubeFilesDir, CubeFilesDirPath
359 )
360 if not Status:
361 return False
362
363 # Copy and rename cube files...
364 if not MoveAndRenameCubeFiles(Mol, MolNum, Psi4CubeFiles):
365 return False
366
367 # Delete temporary local directory...
368 if os.path.isdir(CubeFilesDir):
369 shutil.rmtree(CubeFilesDir)
370
371 if not GenerateMolFile(Mol, MolNum):
372 return False
373
374 return True
375
376
377 def SetupMolCubeFilesDir(Mol, MolNum):
378 """Setup a directory for generating cube files for a molecule."""
379
380 MolPrefix = SetupMolPrefix(Mol, MolNum)
381
382 CubeFilesDir = "%s_CubeFiles" % (MolPrefix)
383 CubeFilesDirPath = os.path.join(os.getcwd(), CubeFilesDir)
384 try:
385 if os.path.isdir(CubeFilesDir):
386 shutil.rmtree(CubeFilesDir)
387 os.mkdir(CubeFilesDir)
388 except Exception as ErrMsg:
389 if not OptionsInfo["QuietMode"]:
390 MiscUtil.PrintWarning("Failed to create cube files: %s\n" % ErrMsg)
391 MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
392 return (False, None, None)
393
394 return (True, CubeFilesDir, CubeFilesDirPath)
395
396
397 def GenerateCubeFilesUsingCubeprop(Psi4Handle, WaveFunction, Mol, MolNum, CubeFilesDir, CubeFilesDirPath):
398 """Generate cube files using cubeprop."""
399
400 Psi4CubeFilesParams = OptionsInfo["Psi4CubeFilesParams"]
401
402 # Generate cube files...
403 GridSpacing = Psi4CubeFilesParams["GridSpacing"]
404 GridOverage = Psi4CubeFilesParams["GridOverage"]
405 IsoContourThreshold = Psi4CubeFilesParams["IsoContourThreshold"]
406 try:
407 Psi4Handle.set_options(
408 {
409 "CUBEPROP_TASKS": ["DUAL_DESCRIPTOR"],
410 "CUBIC_GRID_SPACING": [GridSpacing, GridSpacing, GridSpacing],
411 "CUBIC_GRID_OVERAGE": [GridOverage, GridOverage, GridOverage],
412 "CUBEPROP_FILEPATH": CubeFilesDirPath,
413 "CUBEPROP_ISOCONTOUR_THRESHOLD": IsoContourThreshold,
414 }
415 )
416 Psi4Handle.cubeprop(WaveFunction)
417 except Exception as ErrMsg:
418 shutil.rmtree(CubeFilesDir)
419 if not OptionsInfo["QuietMode"]:
420 MiscUtil.PrintWarning("Failed to create cube files: %s\n" % ErrMsg)
421 MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
422 return (False, None)
423
424 # Collect cube files...
425 Psi4CubeFiles = glob.glob(os.path.join(CubeFilesDir, "DUAL*.cube"))
426 if len(Psi4CubeFiles) == 0:
427 shutil.rmtree(CubeFilesDir)
428 if not OptionsInfo["QuietMode"]:
429 MiscUtil.PrintWarning("Failed to create cube files for dual descriptors...")
430 MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
431 return (False, None)
432
433 return (True, Psi4CubeFiles)
434
435
436 def MoveAndRenameCubeFiles(Mol, MolNum, Psi4CubeFiles):
437 """Move and rename cube files."""
438
439 for Psi4CubeFileName in Psi4CubeFiles:
440 try:
441 NewCubeFileName = SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName)
442 shutil.move(Psi4CubeFileName, NewCubeFileName)
443 except Exception as ErrMsg:
444 if not OptionsInfo["QuietMode"]:
445 MiscUtil.PrintWarning("Failed to move cube file: %s\n" % ErrMsg)
446 MiscUtil.PrintWarning("Ignoring molecule: %s" % RDKitUtil.GetMolName(Mol, MolNum))
447 return False
448
449 return True
450
451
452 def GenerateMolFile(Mol, MolNum):
453 """Generate a SD file for molecules corresponding to cube files."""
454
455 Outfile = SetupMolFileName(Mol, MolNum)
456
457 OutfileParams = {}
458 Writer = RDKitUtil.MoleculesWriter(Outfile, **OutfileParams)
459 if Writer is None:
460 if not OptionsInfo["QuietMode"]:
461 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
462 return False
463
464 Writer.write(Mol)
465
466 if Writer is not None:
467 Writer.close()
468
469 return True
470
471
472 def VisualizeDualDescriptors():
473 """Visualize cube files for dual descriptors."""
474
475 MiscUtil.PrintInfo("\nVisualizing cube files for dual descriptors...")
476
477 if not OptionsInfo["GenerateCubeFiles"]:
478 MiscUtil.PrintInfo("\nInitializing Psi4...\n")
479 Psi4Handle = Psi4Util.InitializePsi4(PrintVersion=True, PrintHeader=False)
480 OptionsInfo["psi4"] = Psi4Handle
481
482 # Setup a molecule reader...
483 MiscUtil.PrintInfo("Processing file %s..." % OptionsInfo["Infile"])
484 Mols = RDKitUtil.ReadMolecules(OptionsInfo["InfilePath"], **OptionsInfo["InfileParams"])
485
486 # Setup for writing a PyMOL PML file...
487 Outfile = OptionsInfo["Outfile"]
488 OutFH = open(Outfile, "w")
489 if OutFH is None:
490 MiscUtil.PrintError("Failed to open output fie %s " % Outfile)
491 MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)
492
493 # Setup header...
494 WritePMLHeader(OutFH, ScriptName)
495 WritePyMOLParameters(OutFH)
496
497 # Process cube files for molecules...
498 FirstMol = True
499 (MolCount, ValidMolCount, CubeFilesMissingCount) = [0] * 3
500 for Mol in Mols:
501 MolCount += 1
502
503 if Mol is None:
504 if not OptionsInfo["QuietMode"]:
505 MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
506 continue
507 ValidMolCount += 1
508
509 MolName = RDKitUtil.GetMolName(Mol, MolCount)
510 if not OptionsInfo["QuietMode"]:
511 MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
512
513 # Retrieve cube files...
514 CubeFilesStatus, CubeFilesInfo = RetrieveMolCubeFilesInfo(Mol, MolCount)
515 if not CubeFilesStatus:
516 CubeFilesMissingCount += 1
517 if not OptionsInfo["QuietMode"]:
518 MiscUtil.PrintWarning("Ignoring molecule with missing cube file...\n")
519 continue
520
521 # Setup PyMOL object names..
522 PyMOLObjectNames = SetupPyMOLObjectNames(Mol, MolCount, CubeFilesInfo)
523
524 # Setup molecule view...
525 WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames)
526
527 # Setup dual descriptor views...
528 WriteDualDescriptorView(OutFH, CubeFilesInfo, PyMOLObjectNames)
529
530 # Setup dual descriptor level group...
531 Enable, Action = [True, "open"]
532 GenerateAndWritePMLForGroup(
533 OutFH, PyMOLObjectNames["DualGroup"], PyMOLObjectNames["DualGroupMembers"], Enable, Action
534 )
535
536 # Setup mol level group...
537 Enable, Action = [False, "close"]
538 if FirstMol:
539 FirstMol = False
540 Enable, Action = [True, "open"]
541 GenerateAndWritePMLForGroup(
542 OutFH, PyMOLObjectNames["MolGroup"], PyMOLObjectNames["MolGroupMembers"], Enable, Action
543 )
544
545 OutFH.close()
546
547 MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
548 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
549 MiscUtil.PrintInfo("Number of molecules with missing cube files: %d" % CubeFilesMissingCount)
550 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesMissingCount))
551
552
553 def WritePMLHeader(OutFH, ScriptName):
554 """Write out PML header."""
555
556 HeaderInfo = """\
557 #
558 # This file is automatically generated by the following Psi4 script available in
559 # MayaChemTools: %s
560 #
561 cmd.reinitialize() """ % (ScriptName)
562
563 OutFH.write("%s\n" % HeaderInfo)
564
565
566 def WritePyMOLParameters(OutFH):
567 """Write out PyMOL global parameters."""
568
569 OutFH.write("""\n""\n"Setting up PyMOL gobal parameters..."\n""\n""")
570 PMLCmds = []
571 PMLCmds.append("""cmd.set("mesh_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["MeshQuality"]))
572 PMLCmds.append("""cmd.set("mesh_width", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["MeshWidth"]))
573
574 PMLCmds.append("""cmd.set("surface_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceQuality"]))
575 PMLCmds.append("""cmd.set("transparency", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceTransparency"]))
576 PML = "\n".join(PMLCmds)
577 OutFH.write("%s\n" % PML)
578
579 if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"]:
580 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
581 GenerateAndWritePMLForVolumeColorRamp(
582 OutFH,
583 PyMOLViewParams["GlobalVolumeColorRampName"],
584 PyMOLViewParams["ContourLevel1"],
585 PyMOLViewParams["ContourColor1"],
586 PyMOLViewParams["ContourLevel2"],
587 PyMOLViewParams["ContourColor2"],
588 PyMOLViewParams["VolumeContourWindowFactor"],
589 PyMOLViewParams["VolumeColorRampOpacity"],
590 )
591
592
593 def WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames):
594 """Write out PML for viewing molecules."""
595
596 MolFile = CubeFilesInfo["MolFile"]
597
598 OutFH.write("""\n""\n"Loading %s and setting up view for molecule..."\n""\n""" % MolFile)
599
600 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
601 PMLCmds = []
602
603 # Molecule...
604 Name = PyMOLObjectNames["Mol"]
605 PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
606 PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
607 PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
608 PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
609 PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
610 if PyMOLViewParams["HideHydrogens"]:
611 PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
612 if re.match("^Sticks$", PyMOLViewParams["DisplayMolecule"]):
613 PMLCmds.append("""cmd.enable("%s")""" % (Name))
614 else:
615 PMLCmds.append("""cmd.disable("%s")""" % (Name))
616
617 # Molecule ball and stick...
618 Name = PyMOLObjectNames["MolBallAndStick"]
619 PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
620 PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
621 PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
622 PMLCmds.append("""cmd.show("sphere", "%s")""" % (Name))
623 PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
624 PMLCmds.append("""cmd.set("sphere_scale", %s, "%s")""" % (PyMOLViewParams["DisplaySphereScale"], Name))
625 PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
626 if PyMOLViewParams["HideHydrogens"]:
627 PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
628 if re.match("^BallAndStick$", PyMOLViewParams["DisplayMolecule"]):
629 PMLCmds.append("""cmd.enable("%s")""" % (Name))
630 else:
631 PMLCmds.append("""cmd.disable("%s")""" % (Name))
632
633 PML = "\n".join(PMLCmds)
634 OutFH.write("%s\n" % PML)
635
636 # MolAlone group...
637 GenerateAndWritePMLForGroup(
638 OutFH, PyMOLObjectNames["MolAloneGroup"], PyMOLObjectNames["MolAloneGroupMembers"], True, "open"
639 )
640
641
642 def WriteDualDescriptorView(OutFH, CubeFilesInfo, PyMOLObjectNames):
643 """Write out PML for viewing dual descriptors in a molecule."""
644
645 OutFH.write("""\n""\n"Setting up views for dual descriptor..."\n""\n""")
646
647 # Cube...
648 DualCubeFile = CubeFilesInfo["FileName"]
649 DualCubeName = PyMOLObjectNames["DualCube"]
650 PMLCmds = []
651 PMLCmds.append("""cmd.load("%s", "%s")""" % (DualCubeFile, DualCubeName))
652 PMLCmds.append("""cmd.disable("%s")""" % (DualCubeName))
653 PMLCmds.append("")
654 PML = "\n".join(PMLCmds)
655 OutFH.write("%s\n" % PML)
656
657 ContourLevel1 = CubeFilesInfo["ContourLevel1"]
658 ContourLevel2 = CubeFilesInfo["ContourLevel2"]
659
660 ContourColor1 = CubeFilesInfo["ContourColor1"]
661 ContourColor2 = CubeFilesInfo["ContourColor2"]
662
663 ContourWindowFactor = CubeFilesInfo["ContourWindowFactor"]
664 ContourColorOpacity = CubeFilesInfo["ContourColorOpacity"]
665
666 # Volume ramp...
667 if CubeFilesInfo["SetupLocalVolumeColorRamp"]:
668 GenerateAndWritePMLForVolumeColorRamp(
669 OutFH,
670 CubeFilesInfo["VolumeColorRampName"],
671 ContourLevel1,
672 ContourColor1,
673 ContourLevel2,
674 ContourColor2,
675 ContourWindowFactor,
676 ContourColorOpacity,
677 )
678
679 # Volume...
680 DualVolumeName = PyMOLObjectNames["DualVolume"]
681 PMLCmds = []
682 PMLCmds.append(
683 """cmd.volume("%s", "%s", "%s")""" % (DualVolumeName, DualCubeName, CubeFilesInfo["VolumeColorRampName"])
684 )
685 PMLCmds.append("""cmd.disable("%s")""" % (DualVolumeName))
686 PMLCmds.append("")
687 PML = "\n".join(PMLCmds)
688 OutFH.write("%s\n" % PML)
689
690 # Mesh...
691 PMLCmds = []
692 DualMeshName = PyMOLObjectNames["DualMeshPositive"]
693 PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (DualMeshName, DualCubeName, ContourLevel2))
694 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, DualMeshName))
695 PMLCmds.append("""cmd.enable("%s")""" % (DualMeshName))
696 PMLCmds.append("")
697
698 DualMeshName = PyMOLObjectNames["DualMeshNegative"]
699 PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (DualMeshName, DualCubeName, ContourLevel1))
700 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, DualMeshName))
701 PMLCmds.append("""cmd.enable("%s")""" % (DualMeshName))
702 PMLCmds.append("")
703 PML = "\n".join(PMLCmds)
704 OutFH.write("%s\n" % PML)
705
706 GenerateAndWritePMLForGroup(
707 OutFH, PyMOLObjectNames["DualMeshGroup"], PyMOLObjectNames["DualMeshGroupMembers"], False, "open"
708 )
709
710 # Surface...
711 PMLCmds = []
712 DualSurfaceName = PyMOLObjectNames["DualSurfacePositive"]
713 PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (DualSurfaceName, DualCubeName, ContourLevel2))
714 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, DualSurfaceName))
715 PMLCmds.append("""cmd.enable("%s")""" % (DualSurfaceName))
716 PMLCmds.append("")
717
718 DualSurfaceName = PyMOLObjectNames["DualSurfaceNegative"]
719 PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (DualSurfaceName, DualCubeName, ContourLevel1))
720 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, DualSurfaceName))
721 PMLCmds.append("""cmd.enable("%s")""" % (DualSurfaceName))
722 PMLCmds.append("")
723 PML = "\n".join(PMLCmds)
724 OutFH.write("%s\n" % PML)
725
726 GenerateAndWritePMLForGroup(
727 OutFH, PyMOLObjectNames["DualSurfaceGroup"], PyMOLObjectNames["DualSurfaceGroupMembers"], True, "open"
728 )
729
730
731 def GenerateAndWritePMLForVolumeColorRamp(
732 OutFH,
733 ColorRampName,
734 ContourLevel1,
735 ContourColor1,
736 ContourLevel2,
737 ContourColor2,
738 ContourWindowFactor,
739 ContourColorOpacity,
740 ):
741 """Write out PML for volume color ramp."""
742
743 RampWindowOffset1 = abs(ContourLevel1 * ContourWindowFactor)
744 RampWindowOffset2 = abs(ContourLevel2 * ContourWindowFactor)
745
746 PML = """\
747 cmd.volume_ramp_new("%s", [%.4f, "%s", 0.00, %.4f, "%s", %s, %.4f, "%s", 0.00, %4f, "%s", 0.00, %.4f, "%s", %s, %4f, "%s", 0.00])""" % (
748 ColorRampName,
749 (ContourLevel1 - RampWindowOffset1),
750 ContourColor1,
751 ContourLevel1,
752 ContourColor1,
753 ContourColorOpacity,
754 (ContourLevel1 + RampWindowOffset1),
755 ContourColor1,
756 (ContourLevel2 - RampWindowOffset2),
757 ContourColor2,
758 ContourLevel2,
759 ContourColor2,
760 ContourColorOpacity,
761 (ContourLevel2 + RampWindowOffset2),
762 ContourColor2,
763 )
764
765 OutFH.write("%s\n" % PML)
766
767
768 def GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembersList, Enable, Action):
769 """Generate and write PML for group."""
770
771 OutFH.write("""\n""\n"Setting up group %s..."\n""\n""" % GroupName)
772
773 PMLCmds = []
774
775 GroupMembers = " ".join(GroupMembersList)
776 PMLCmds.append("""cmd.group("%s", "%s")""" % (GroupName, GroupMembers))
777
778 if Enable is not None:
779 if Enable:
780 PMLCmds.append("""cmd.enable("%s")""" % GroupName)
781 else:
782 PMLCmds.append("""cmd.disable("%s")""" % GroupName)
783
784 if Action is not None:
785 PMLCmds.append("""cmd.group("%s", action="%s")""" % (GroupName, Action))
786
787 PML = "\n".join(PMLCmds)
788
789 OutFH.write("%s\n\n" % PML)
790
791
792 def RetrieveMolCubeFilesInfo(Mol, MolNum):
793 """Retrieve available cube files info for a molecule."""
794
795 # Initialize cube files info...
796 CubeFilesInfo = {}
797 CubeFilesInfo["FileName"] = None
798 CubeFilesInfo["IsocontourRangeMin"] = None
799 CubeFilesInfo["IsocontourRangeMax"] = None
800
801 CubeFilesInfo["ContourLevel1"] = None
802 CubeFilesInfo["ContourLevel2"] = None
803
804 CubeFilesInfo["ContourColor1"] = None
805 CubeFilesInfo["ContourColor2"] = None
806
807 CubeFilesInfo["ContourWindowFactor"] = None
808 CubeFilesInfo["ContourColorOpacity"] = None
809
810 CubeFilesInfo["VolumeColorRampName"] = None
811 CubeFilesInfo["SetupLocalVolumeColorRamp"] = False
812
813 # Setup cube mol info...
814 CubeFilesInfo["MolPrefix"] = SetupMolPrefix(Mol, MolNum)
815 CubeFilesInfo["MolName"] = SetupMolName(Mol, MolNum)
816 CubeFilesInfo["MolFile"] = SetupMolFileName(Mol, MolNum)
817
818 # Retrieve cube files...
819 CubeFiles = glob.glob("%s*DUAL*.cube" % (CubeFilesInfo["MolPrefix"]))
820
821 if len(CubeFiles) == 0:
822 return (False, CubeFilesInfo)
823
824 CubeFileName = CubeFiles[0]
825 if len(CubeFiles) > 1:
826 if not OptionsInfo["QuietMode"]:
827 MiscUtil.PrintWarning(
828 "Multiple cube files available for molecule %s; Using first cube file, %s, to visualize dual descriptor..."
829 % (RDKitUtil.GetMolName(Mol, MolNum), CubeFileName)
830 )
831 CubeFilesInfo["FileName"] = CubeFileName
832
833 IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2 = SetupIsocontourRangeAndContourLevels(
834 CubeFileName
835 )
836 CubeFilesInfo["IsocontourRangeMin"] = IsocontourRangeMin
837 CubeFilesInfo["IsocontourRangeMax"] = IsocontourRangeMax
838
839 CubeFilesInfo["ContourLevel1"] = ContourLevel1
840 CubeFilesInfo["ContourLevel2"] = ContourLevel2
841 CubeFilesInfo["ContourColor1"] = OptionsInfo["PyMOLViewParams"]["ContourColor1"]
842 CubeFilesInfo["ContourColor2"] = OptionsInfo["PyMOLViewParams"]["ContourColor2"]
843
844 CubeFilesInfo["ContourWindowFactor"] = OptionsInfo["PyMOLViewParams"]["VolumeContourWindowFactor"]
845 CubeFilesInfo["ContourColorOpacity"] = OptionsInfo["PyMOLViewParams"]["VolumeColorRampOpacity"]
846
847 VolumeColorRampName = SetupVolumeColorRampName(CubeFilesInfo)
848 CubeFilesInfo["VolumeColorRampName"] = VolumeColorRampName
849 CubeFilesInfo["SetupLocalVolumeColorRamp"] = (
850 False if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] else True
851 )
852
853 return (True, CubeFilesInfo)
854
855
856 def SetupVolumeColorRampName(CubeFilesInfo):
857 """Setup volume color ramp name and status."""
858
859 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
860
861 # Setup info for generating local volume color ramps for individual cube files...
862 VolumeColorRampName = PyMOLViewParams["VolumeColorRamp"]
863 if PyMOLViewParams["VolumeColorRampAuto"]:
864 VolumeColorRampName = SetupDefaultVolumeRampName()
865 if PyMOLViewParams["ContourLevel1Auto"] or PyMOLViewParams["ContourLevel2Auto"]:
866 VolumeColorRampName = "%s_%s" % (CubeFilesInfo["MolPrefix"], VolumeColorRampName)
867
868 return VolumeColorRampName
869
870
871 def SetupIsocontourRangeAndContourLevels(CubeFileName):
872 """Setup isocontour range."""
873
874 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
875
876 # Setup isocontour range and contour levels...
877 IsocontourRangeMin, IsocontourRangeMax = Psi4Util.RetrieveIsocontourRangeFromCubeFile(CubeFileName)
878
879 DefaultIsocontourRange = 0.06
880 if IsocontourRangeMin is None:
881 IsocontourRangeMin = -DefaultIsocontourRange
882 if not OptionsInfo["QuietMode"]:
883 MiscUtil.PrintInfo(
884 "Failed to retrieve isocontour range from the cube file. Setting min isocontour value to %s..."
885 % (IsocontourRangeMin)
886 )
887
888 if IsocontourRangeMax is None:
889 IsocontourRangeMax = DefaultIsocontourRange
890 if not OptionsInfo["QuietMode"]:
891 MiscUtil.PrintInfo(
892 "Failed to retrieve isocontour range from the cube file. Setting max isocontour value to %s..."
893 % (IsocontourRangeMax)
894 )
895
896 # Setup contour levels...
897 ContourLevel = max(abs(IsocontourRangeMin), abs(IsocontourRangeMax)) * PyMOLViewParams["ContourLevelAutoAt"]
898 if ContourLevel >= 0.01:
899 ContourLevel = float("%.2f" % ContourLevel)
900 elif ContourLevel >= 0.001:
901 ContourLevel = float("%.3f" % ContourLevel)
902 elif ContourLevel >= 0.0001:
903 ContourLevel = float("%.4f" % ContourLevel)
904
905 ContourLevel1 = -ContourLevel if PyMOLViewParams["ContourLevel1Auto"] else PyMOLViewParams["ContourLevel1"]
906 ContourLevel2 = ContourLevel if PyMOLViewParams["ContourLevel2Auto"] else PyMOLViewParams["ContourLevel2"]
907
908 if not OptionsInfo["QuietMode"]:
909 if IsocontourRangeMin is not None and IsocontourRangeMax is not None:
910 MiscUtil.PrintInfo(
911 "CubeFileName: %s; Isocontour range for %s percent of the density: %.4f to %.4f; ContourLevel1: %s; ContourLevel2: %s"
912 % (
913 CubeFileName,
914 (100 * OptionsInfo["Psi4CubeFilesParams"]["IsoContourThreshold"]),
915 IsocontourRangeMin,
916 IsocontourRangeMax,
917 ContourLevel1,
918 ContourLevel2,
919 )
920 )
921
922 return (IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2)
923
924
925 def SetupPyMOLObjectNames(Mol, MolNum, CubeFilesInfo):
926 """Setup PyMOL object names."""
927
928 PyMOLObjectNames = {}
929
930 SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
931 SetupPyMOLObjectNamesForDualDescriptors(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
932
933 return PyMOLObjectNames
934
935
936 def SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
937 """Setup groups and objects for molecule."""
938
939 MolFileRoot = CubeFilesInfo["MolPrefix"]
940
941 MolGroupName = "%s" % MolFileRoot
942 PyMOLObjectNames["MolGroup"] = MolGroupName
943 PyMOLObjectNames["MolGroupMembers"] = []
944
945 # Molecule alone group...
946 MolAloneGroupName = "%s.Molecule" % (MolGroupName)
947 PyMOLObjectNames["MolAloneGroup"] = MolAloneGroupName
948 PyMOLObjectNames["MolGroupMembers"].append(MolAloneGroupName)
949
950 PyMOLObjectNames["MolAloneGroupMembers"] = []
951
952 # Molecule...
953 MolName = "%s.Molecule" % (MolAloneGroupName)
954 PyMOLObjectNames["Mol"] = MolName
955 PyMOLObjectNames["MolAloneGroupMembers"].append(MolName)
956
957 # BallAndStick...
958 MolBallAndStickName = "%s.BallAndStick" % (MolAloneGroupName)
959 PyMOLObjectNames["MolBallAndStick"] = MolBallAndStickName
960 PyMOLObjectNames["MolAloneGroupMembers"].append(MolBallAndStickName)
961
962
963 def SetupPyMOLObjectNamesForDualDescriptors(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
964 """Setup groups and objects for dual descriptors."""
965
966 MolGroupName = PyMOLObjectNames["MolGroup"]
967
968 # Setup dual group..
969 DualGroupName = "%s.Dual" % (MolGroupName)
970 PyMOLObjectNames["DualGroup"] = DualGroupName
971 PyMOLObjectNames["MolGroupMembers"].append(DualGroupName)
972 PyMOLObjectNames["DualGroupMembers"] = []
973
974 # Cube...
975 DualCubeName = "%s.Cube" % DualGroupName
976 PyMOLObjectNames["DualCube"] = DualCubeName
977 PyMOLObjectNames["DualGroupMembers"].append(DualCubeName)
978
979 # Volume...
980 DualVolumeName = "%s.Volume" % DualGroupName
981 PyMOLObjectNames["DualVolume"] = DualVolumeName
982 PyMOLObjectNames["DualGroupMembers"].append(DualVolumeName)
983
984 # Mesh...
985 DualMeshGroupName = "%s.Mesh" % DualGroupName
986 DualMeshPositiveName = "%s.Positive_Electrophilic" % DualMeshGroupName
987 DualMeshNegativeName = "%s.Negative_Nucleophilic" % DualMeshGroupName
988
989 PyMOLObjectNames["DualMeshGroup"] = DualMeshGroupName
990 PyMOLObjectNames["DualGroupMembers"].append(DualMeshGroupName)
991
992 PyMOLObjectNames["DualMeshPositive"] = DualMeshPositiveName
993 PyMOLObjectNames["DualMeshNegative"] = DualMeshNegativeName
994 PyMOLObjectNames["DualMeshGroupMembers"] = []
995 PyMOLObjectNames["DualMeshGroupMembers"].append(DualMeshPositiveName)
996 PyMOLObjectNames["DualMeshGroupMembers"].append(DualMeshNegativeName)
997
998 # Surface....
999 DualSurfaceGroupName = "%s.Surface" % DualGroupName
1000 DualSurfacePositiveName = "%s.Positive_Electrophilic" % DualSurfaceGroupName
1001 DualSurfaceNegativeName = "%s.Negative_Nucleophilic" % DualSurfaceGroupName
1002
1003 PyMOLObjectNames["DualSurfaceGroup"] = DualSurfaceGroupName
1004 PyMOLObjectNames["DualGroupMembers"].append(DualSurfaceGroupName)
1005
1006 PyMOLObjectNames["DualSurfacePositive"] = DualSurfacePositiveName
1007 PyMOLObjectNames["DualSurfaceNegative"] = DualSurfaceNegativeName
1008 PyMOLObjectNames["DualSurfaceGroupMembers"] = []
1009 PyMOLObjectNames["DualSurfaceGroupMembers"].append(DualSurfacePositiveName)
1010 PyMOLObjectNames["DualSurfaceGroupMembers"].append(DualSurfaceNegativeName)
1011
1012
1013 def SetupMolFileName(Mol, MolNum):
1014 """Setup SD file name for a molecule."""
1015
1016 MolPrefix = SetupMolPrefix(Mol, MolNum)
1017 MolFileName = "%s.sdf" % MolPrefix
1018
1019 return MolFileName
1020
1021
1022 def SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName):
1023 """Setup cube file name for a molecule."""
1024
1025 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Psi4CubeFileName)
1026 CubeFileName = "%s.%s" % (FileName, FileExt)
1027
1028 # Add MolPrefix...
1029 MolPrefix = SetupMolPrefix(Mol, MolNum)
1030 CubeFileName = "%s_%s" % (MolPrefix, CubeFileName)
1031
1032 return CubeFileName
1033
1034
1035 def SetupMolPrefix(Mol, MolNum):
1036 """Get molecule prefix for generating files and directories."""
1037
1038 MolNamePrefix = ""
1039 if Mol.HasProp("_Name"):
1040 MolNamePrefix = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
1041
1042 MolNumPrefix = "Mol%s" % MolNum
1043
1044 if OptionsInfo["UseMolNumPrefix"] and OptionsInfo["UseMolNamePrefix"]:
1045 MolPrefix = MolNumPrefix
1046 if len(MolNamePrefix):
1047 MolPrefix = "%s_%s" % (MolNumPrefix, MolNamePrefix)
1048 elif OptionsInfo["UseMolNamePrefix"]:
1049 MolPrefix = MolNamePrefix if len(MolNamePrefix) else MolNumPrefix
1050 elif OptionsInfo["UseMolNumPrefix"]:
1051 MolPrefix = MolNumPrefix
1052
1053 return MolPrefix
1054
1055
1056 def SetupMolName(Mol, MolNum):
1057 """Get molecule name."""
1058
1059 # Test for creating PyMOL object name for molecule...
1060 if Mol.HasProp("_Name"):
1061 MolName = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
1062 else:
1063 MolName = "Mol%s" % MolNum
1064
1065 return MolName
1066
1067
1068 def SetupDefaultVolumeRampName():
1069 """Setup a default name for a volume ramp."""
1070
1071 return "psi4_cube_dual"
1072
1073
1074 def SetupPsi4Mol(Psi4Handle, Mol, MolCount=None):
1075 """Setup a Psi4 molecule object."""
1076
1077 MolGeometry = RDKitUtil.GetPsi4XYZFormatString(Mol, NoCom=True, NoReorient=True)
1078
1079 try:
1080 Psi4Mol = Psi4Handle.geometry(MolGeometry)
1081 except Exception as ErrMsg:
1082 Psi4Mol = None
1083 if not OptionsInfo["QuietMode"]:
1084 MiscUtil.PrintWarning("Failed to create Psi4 molecule from geometry string: %s\n" % ErrMsg)
1085 MolName = RDKitUtil.GetMolName(Mol, MolCount)
1086 MiscUtil.PrintWarning("Ignoring molecule: %s" % MolName)
1087
1088 return Psi4Mol
1089
1090
1091 def PerformPsi4Cleanup(Psi4Handle):
1092 """Perform clean up."""
1093
1094 # Clean up after Psi4 run ...
1095 Psi4Handle.core.clean()
1096
1097 # Clean up any leftover scratch files...
1098 if OptionsInfo["MPMode"]:
1099 Psi4Util.RemoveScratchFiles(Psi4Handle, OptionsInfo["Psi4RunParams"]["OutputFile"])
1100
1101
1102 def CheckAndValidateMolecule(Mol, MolCount=None):
1103 """Validate molecule for Psi4 calculations."""
1104
1105 if Mol is None:
1106 if not OptionsInfo["QuietMode"]:
1107 MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
1108 return False
1109
1110 MolName = RDKitUtil.GetMolName(Mol, MolCount)
1111 if not OptionsInfo["QuietMode"]:
1112 MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
1113
1114 if RDKitUtil.IsMolEmpty(Mol):
1115 if not OptionsInfo["QuietMode"]:
1116 MiscUtil.PrintWarning("Ignoring empty molecule: %s\n" % MolName)
1117 return False
1118
1119 if not RDKitUtil.ValidateElementSymbols(RDKitUtil.GetAtomSymbols(Mol)):
1120 if not OptionsInfo["QuietMode"]:
1121 MiscUtil.PrintWarning("Ignoring molecule containing invalid element symbols: %s\n" % MolName)
1122 return False
1123
1124 # Check for spin multiplicity to warn about open-shell systems...
1125 if RDKitUtil.GetSpinMultiplicity(Mol) > 1:
1126 if not OptionsInfo["QuietMode"]:
1127 MiscUtil.PrintWarning(
1128 "Generation of dual descriptors not supported for open-shell systems. Ignoring molecule containing unpaired electrons: %s\n"
1129 % MolName
1130 )
1131 return False
1132
1133 # Check for 3D flag...
1134 if not Mol.GetConformer().Is3D():
1135 if not OptionsInfo["QuietMode"]:
1136 MiscUtil.PrintWarning("3D tag is not set for molecule: %s\n" % MolName)
1137
1138 # Check for missing hydrogens...
1139 if RDKitUtil.AreHydrogensMissingInMolecule(Mol):
1140 if not OptionsInfo["QuietMode"]:
1141 MiscUtil.PrintWarning("Missing hydrogens in molecule: %s\n" % MolName)
1142
1143 return True
1144
1145
1146 def SetupMethodNameAndBasisSet(Mol):
1147 """Setup method name and basis set."""
1148
1149 MethodName = OptionsInfo["MethodName"]
1150 if OptionsInfo["MethodNameAuto"]:
1151 MethodName = "B3LYP"
1152
1153 BasisSet = OptionsInfo["BasisSet"]
1154 if OptionsInfo["BasisSetAuto"]:
1155 BasisSet = "6-31+G**" if RDKitUtil.IsAtomSymbolPresentInMol(Mol, "S") else "6-31G**"
1156
1157 return (MethodName, BasisSet)
1158
1159
1160 def SetupReferenceWavefunction(Mol):
1161 """Setup reference wavefunction."""
1162
1163 Reference = OptionsInfo["Reference"]
1164 if OptionsInfo["ReferenceAuto"]:
1165 Reference = "UHF" if (RDKitUtil.GetSpinMultiplicity(Mol) > 1) else "RHF"
1166
1167 return Reference
1168
1169
1170 def CheckAndSetupOutfilesDir():
1171 """Check and setup outfiles directory."""
1172
1173 if OptionsInfo["GenerateCubeFiles"]:
1174 if os.path.isdir(OptionsInfo["OutfilesDir"]):
1175 if not Options["--overwrite"]:
1176 MiscUtil.PrintError(
1177 'The outfiles directory, %s, corresponding to output file specified, %s, for option "-o, --outfile" already exist. Use option "--overwrite" or "--ov" and try again to generate and visualize cube files...\n'
1178 % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])
1179 )
1180
1181 if OptionsInfo["VisualizeCubeFiles"]:
1182 if not Options["--overwrite"]:
1183 if os.path.exists(os.path.join(OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])):
1184 MiscUtil.PrintError(
1185 'The outfile file specified, %s, for option "-o, --outfile" already exist in outfiles dir, %s. Use option "--overwrite" or "--ov" to overwrite and try again to generate and visualize cube files.\n'
1186 % (OptionsInfo["Outfile"], OptionsInfo["OutfilesDir"])
1187 )
1188
1189 if not OptionsInfo["GenerateCubeFiles"]:
1190 if not os.path.isdir(OptionsInfo["OutfilesDir"]):
1191 MiscUtil.PrintError(
1192 'The outfiles directory, %s, corresponding to output file specified, %s, for option "-o, --outfile" doesn\'t exist. Use value, GenerateCubeFiles or Both, for option "--mode" and try again to generate and visualize cube files...\n'
1193 % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])
1194 )
1195
1196 CubeFiles = glob.glob(os.path.join(OptionsInfo["OutfilesDir"], "*.cube"))
1197 if not len(CubeFiles):
1198 MiscUtil.PrintError(
1199 'The outfiles directory, %s, contains no cube files, "*.cube". Use value, GenerateCubeFiles or Both, for option "--mode" and try again to generate and visualize cube...\n'
1200 % (OptionsInfo["OutfilesDir"])
1201 )
1202
1203 OutfilesDir = OptionsInfo["OutfilesDir"]
1204 if OptionsInfo["GenerateCubeFiles"]:
1205 if not os.path.isdir(OutfilesDir):
1206 MiscUtil.PrintInfo("\nCreating directory %s..." % OutfilesDir)
1207 os.mkdir(OutfilesDir)
1208
1209 MiscUtil.PrintInfo("\nChanging directory to %s..." % OutfilesDir)
1210 os.chdir(OutfilesDir)
1211
1212
1213 def ProcessOptions():
1214 """Process and validate command line arguments and options."""
1215
1216 MiscUtil.PrintInfo("Processing options...")
1217
1218 # Validate options...
1219 ValidateOptions()
1220
1221 OptionsInfo["Infile"] = Options["--infile"]
1222 OptionsInfo["InfilePath"] = os.path.abspath(Options["--infile"])
1223
1224 ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
1225 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
1226 "--infileParams",
1227 Options["--infileParams"],
1228 InfileName=Options["--infile"],
1229 ParamsDefaultInfo=ParamsDefaultInfoOverride,
1230 )
1231
1232 Outfile = Options["--outfile"]
1233 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Outfile)
1234
1235 OptionsInfo["Outfile"] = Outfile
1236 OptionsInfo["OutfileRoot"] = FileName
1237 OptionsInfo["OutfileExt"] = FileExt
1238
1239 OutfilesDir = Options["--outfilesDir"]
1240 if re.match("^auto$", OutfilesDir, re.I):
1241 OutfilesDir = "%s_DualDescriptors" % OptionsInfo["OutfileRoot"]
1242 OptionsInfo["OutfilesDir"] = OutfilesDir
1243
1244 OptionsInfo["Overwrite"] = Options["--overwrite"]
1245
1246 # Method, basis set, and reference wavefunction...
1247 OptionsInfo["BasisSet"] = Options["--basisSet"]
1248 OptionsInfo["BasisSetAuto"] = True if re.match("^auto$", Options["--basisSet"], re.I) else False
1249
1250 OptionsInfo["MethodName"] = Options["--methodName"]
1251 OptionsInfo["MethodNameAuto"] = True if re.match("^auto$", Options["--methodName"], re.I) else False
1252
1253 OptionsInfo["Reference"] = Options["--reference"]
1254 OptionsInfo["ReferenceAuto"] = True if re.match("^auto$", Options["--reference"], re.I) else False
1255
1256 # Run, options, and cube files parameters...
1257 OptionsInfo["Psi4OptionsParams"] = Psi4Util.ProcessPsi4OptionsParameters(
1258 "--psi4OptionsParams", Options["--psi4OptionsParams"]
1259 )
1260 OptionsInfo["Psi4RunParams"] = Psi4Util.ProcessPsi4RunParameters(
1261 "--psi4RunParams", Options["--psi4RunParams"], InfileName=OptionsInfo["Infile"]
1262 )
1263
1264 ParamsDefaultInfoOverride = {}
1265 OptionsInfo["Psi4CubeFilesParams"] = Psi4Util.ProcessPsi4CubeFilesParameters(
1266 "--psi4CubeFilesParams", Options["--psi4CubeFilesParams"], ParamsDefaultInfo=ParamsDefaultInfoOverride
1267 )
1268
1269 ParamsDefaultInfoOverride = {
1270 "ContourColor1": "red",
1271 "ContourColor2": "blue",
1272 "ContourLevel1": "auto",
1273 "ContourLevel2": "auto",
1274 "ContourLevelAutoAt": 0.5,
1275 "DisplayMolecule": "BallAndStick",
1276 "DisplaySphereScale": 0.2,
1277 "DisplayStickRadius": 0.1,
1278 "HideHydrogens": True,
1279 "MeshWidth": 0.5,
1280 "MeshQuality": 2,
1281 "SurfaceQuality": 2,
1282 "SurfaceTransparency": 0.25,
1283 "VolumeColorRamp": "auto",
1284 "VolumeColorRampOpacity": 0.2,
1285 "VolumeContourWindowFactor": 0.05,
1286 }
1287 OptionsInfo["PyMOLViewParams"] = MiscUtil.ProcessOptionPyMOLCubeFileViewParameters(
1288 "--pymolViewParams", Options["--pymolViewParams"], ParamsDefaultInfo=ParamsDefaultInfoOverride
1289 )
1290
1291 SetupGlobalVolumeColorRamp = False
1292 GlobalVolumeColorRampName = OptionsInfo["PyMOLViewParams"]["VolumeColorRamp"]
1293 if OptionsInfo["PyMOLViewParams"]["VolumeColorRampAuto"]:
1294 GlobalVolumeColorRampName = SetupDefaultVolumeRampName()
1295 if not (
1296 OptionsInfo["PyMOLViewParams"]["ContourLevel1Auto"] or OptionsInfo["PyMOLViewParams"]["ContourLevel2Auto"]
1297 ):
1298 SetupGlobalVolumeColorRamp = True
1299 OptionsInfo["PyMOLViewParams"]["GlobalVolumeColorRampName"] = GlobalVolumeColorRampName
1300 OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] = SetupGlobalVolumeColorRamp
1301
1302 Mode = Options["--mode"]
1303 if re.match("^GenerateCubeFiles$", Mode, re.I):
1304 GenerateCubeFiles = True
1305 VisualizeCubeFiles = False
1306 elif re.match("^VisualizeCubeFiles$", Mode, re.I):
1307 GenerateCubeFiles = False
1308 VisualizeCubeFiles = True
1309 else:
1310 GenerateCubeFiles = True
1311 VisualizeCubeFiles = True
1312 OptionsInfo["Mode"] = Mode
1313 OptionsInfo["GenerateCubeFiles"] = GenerateCubeFiles
1314 OptionsInfo["VisualizeCubeFiles"] = VisualizeCubeFiles
1315
1316 OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
1317 OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
1318
1319 OutfilesMolPrefix = Options["--outfilesMolPrefix"]
1320 if re.match("^MolName$", OutfilesMolPrefix, re.I):
1321 UseMolNamePrefix = True
1322 UseMolNumPrefix = False
1323 elif re.match("^MolNum$", OutfilesMolPrefix, re.I):
1324 UseMolNamePrefix = False
1325 UseMolNumPrefix = True
1326 else:
1327 UseMolNamePrefix = True
1328 UseMolNumPrefix = True
1329 OptionsInfo["OutfilesMolPrefix"] = OutfilesMolPrefix
1330 OptionsInfo["UseMolNamePrefix"] = UseMolNamePrefix
1331 OptionsInfo["UseMolNumPrefix"] = UseMolNumPrefix
1332
1333 OptionsInfo["QuietMode"] = True if re.match("^yes$", Options["--quiet"], re.I) else False
1334
1335 CheckAndSetupOutfilesDir()
1336
1337
1338 def RetrieveOptions():
1339 """Retrieve command line arguments and options."""
1340
1341 # Get options...
1342 global Options
1343 Options = docopt(_docoptUsage_)
1344
1345 # Set current working directory to the specified directory...
1346 WorkingDir = Options["--workingdir"]
1347 if WorkingDir:
1348 os.chdir(WorkingDir)
1349
1350 # Handle examples option...
1351 if "--examples" in Options and Options["--examples"]:
1352 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
1353 sys.exit(0)
1354
1355
1356 def ValidateOptions():
1357 """Validate option values."""
1358
1359 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
1360 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol")
1361
1362 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pml")
1363
1364 MiscUtil.ValidateOptionTextValue("--mode", Options["--mode"], "GenerateCubeFiles VisualizeCubeFiles Both")
1365 MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
1366
1367 MiscUtil.ValidateOptionTextValue("--outfilesMolPrefix", Options["--outfilesMolPrefix"], "MolNum MolName Both")
1368 MiscUtil.ValidateOptionTextValue("-q, --quiet", Options["--quiet"], "yes no")
1369
1370
1371 # Setup a usage string for docopt...
1372 _docoptUsage_ = """
1373 Psi4VisualizeDualDescriptors.py - Visualize dual descriptors for frontier orbitals
1374
1375 Usage:
1376 Psi4VisualizeDualDescriptors.py [--basisSet <text>] [--infileParams <Name,Value,...>] [--methodName <text>]
1377 [--mode <GenerateCubeFiles, VisualizeCubeFiles, Both>] [--mp <yes or no>] [--mpParams <Name, Value,...>]
1378 [--outfilesDir <text>] [--outfilesMolPrefix <MolNum, MolName, Both> ] [--overwrite]
1379 [--psi4CubeFilesParams <Name,Value,...>] [--psi4OptionsParams <Name,Value,...>]
1380 [--psi4RunParams <Name,Value,...>] [--pymolViewParams <Name,Value,...>] [--quiet <yes or no>]
1381 [--reference <text>] [-w <dir>] -i <infile> -o <outfile>
1382 Psi4VisualizeDualDescriptors.py -h | --help | -e | --examples
1383
1384 Description:
1385 Generate and visualize dual descriptors [ Ref 151 ] corresponding to frontier
1386 molecular orbitals for molecules in input file. A set of cube files, corresponding
1387 to dual descriptors, is generated for molecules. The cube files are used to
1388 create a PyMOL visualization file for viewing volumes, meshes, and surfaces
1389 representing dual descriptors for frontier molecular orbitals. An option is
1390 available to skip the generation of new cube files and use existing cube files
1391 to visualize frontier molecular orbitals.
1392
1393 The dual descriptor, a measure of electrophilicity and nucleophilicity for
1394 a molecule, is calculated by Psi4 from frontier molecular orbitals [ Ref 151]:
1395 f2(r) = rhoLUMO(r) - rhoHOMO(r). The sign of the dual descriptor value
1396 corresponds to electrophilic and nucleophilic sites in molecules as shown
1397 below:
1398
1399 Sign Site Reactivity
1400 Positive Electrophilic Favored for a nucleophilic attack
1401 Negative Nucleophilic Favored for a electrophilic attack
1402
1403 A Psi4 XYZ format geometry string is automatically generated for each molecule
1404 in input file. It contains atom symbols and 3D coordinates for each atom in a
1405 molecule. In addition, the formal charge and spin multiplicity are present in the
1406 the geometry string. These values are either retrieved from molecule properties
1407 named 'FormalCharge' and 'SpinMultiplicty' or dynamically calculated for a
1408 molecule.
1409
1410 A set of cube and SD output files is generated for each closed-shell molecule
1411 in input file as shown below:
1412
1413 Ouput dir: <OutfileRoot>_DualDescriptors or <OutfilesDir>
1414
1415 <MolIDPrefix>.sdf
1416 <MolIDPrefix>*DUAL*.cube
1417
1418 In addition, a <OutfileRoot>.pml is generated containing dual descriptors for
1419 frontier molecular orbitals for all molecules in input file.
1420
1421 The supported input file formats are: Mol (.mol), SD (.sdf, .sd)
1422
1423 The supported output file formats are: PyMOL script file (.pml)
1424
1425 A variety of PyMOL groups and objects are created for visualization of dual
1426 descriptors for closed-shell molecules as shown below:
1427
1428 <MoleculeID>
1429 .Molecule
1430 .Molecule
1431 .BallAndStick
1432 .Dual
1433 .Cube
1434 .Volume
1435 .Mesh
1436 .Positive_Electrophilic
1437 .Negative_Nucleophilic
1438 .Surface
1439 .Positive_Electrophilic
1440 .Negative_Nucleophilic
1441 <MoleculeID>
1442 .Molecule
1443 ... ... ...
1444 .Dual
1445 ... ... ...
1446
1447 Options:
1448 -b, --basisSet <text> [default: auto]
1449 Basis set to use for calculating single point energy before generating
1450 cube files corresponding to dual descriptors for frontier molecular orbitals.
1451 Default: 6-31+G** for sulfur containing molecules; Otherwise, 6-31G**
1452 [ Ref 150 ]. The specified value must be a valid Psi4 basis set. No validation
1453 is performed.
1454
1455 The following list shows a representative sample of basis sets available
1456 in Psi4:
1457
1458 STO-3G, 6-31G, 6-31+G, 6-31++G, 6-31G*, 6-31+G*, 6-31++G*,
1459 6-31G**, 6-31+G**, 6-31++G**, 6-311G, 6-311+G, 6-311++G,
1460 6-311G*, 6-311+G*, 6-311++G*, 6-311G**, 6-311+G**, 6-311++G**,
1461 cc-pVDZ, cc-pCVDZ, aug-cc-pVDZ, cc-pVDZ-DK, cc-pCVDZ-DK, def2-SVP,
1462 def2-SVPD, def2-TZVP, def2-TZVPD, def2-TZVPP, def2-TZVPPD
1463
1464 -e, --examples
1465 Print examples.
1466 -h, --help
1467 Print this help message.
1468 -i, --infile <infile>
1469 Input file name.
1470 --infileParams <Name,Value,...> [default: auto]
1471 A comma delimited list of parameter name and value pairs for reading
1472 molecules from files. The supported parameter names for different file
1473 formats, along with their default values, are shown below:
1474
1475 SD, MOL: removeHydrogens,no,sanitize,yes,strictParsing,yes
1476
1477 -m, --methodName <text> [default: auto]
1478 Method to use for calculating single point energy before generating
1479 cube files corresponding to dual descriptors for frontier molecular
1480 orbitals. Default: B3LYP [ Ref 150 ]. The specified value must be a
1481 valid Psi4 method name. No validation is performed.
1482
1483 The following list shows a representative sample of methods available
1484 in Psi4:
1485
1486 B1LYP, B2PLYP, B2PLYP-D3BJ, B2PLYP-D3MBJ, B3LYP, B3LYP-D3BJ,
1487 B3LYP-D3MBJ, CAM-B3LYP, CAM-B3LYP-D3BJ, HF, HF-D3BJ, HF3c, M05,
1488 M06, M06-2x, M06-HF, M06-L, MN12-L, MN15, MN15-D3BJ,PBE, PBE0,
1489 PBEH3c, PW6B95, PW6B95-D3BJ, WB97, WB97X, WB97X-D, WB97X-D3BJ
1490
1491 --mode <GenerateCubeFiles, VisualizeCubeFiles, or Both> [default: Both]
1492 Generate and visualize cube files corresponding to dual descriptors for
1493 frontier molecular orbitals. The 'VisualizeCubes' value skips the generation
1494 of new cube files and uses existing cube files for visualization of dual
1495 descriptors. Multiprocessing is not supported during 'VisualizeCubeFiles'
1496 value of '--mode' option.
1497 --mp <yes or no> [default: no]
1498 Use multiprocessing.
1499
1500 By default, input data is retrieved in a lazy manner via mp.Pool.imap()
1501 function employing lazy RDKit data iterable. This allows processing of
1502 arbitrary large data sets without any additional requirements memory.
1503
1504 All input data may be optionally loaded into memory by mp.Pool.map()
1505 before starting worker processes in a process pool by setting the value
1506 of 'inputDataMode' to 'InMemory' in '--mpParams' option.
1507
1508 A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
1509 data mode may adversely impact the performance. The '--mpParams' section
1510 provides additional information to tune the value of 'chunkSize'.
1511 --mpParams <Name,Value,...> [default: auto]
1512 A comma delimited list of parameter name and value pairs to configure
1513 multiprocessing.
1514
1515 The supported parameter names along with their default and possible
1516 values are shown below:
1517
1518 chunkSize, auto
1519 inputDataMode, Lazy [ Possible values: InMemory or Lazy ]
1520 numProcesses, auto [ Default: mp.cpu_count() ]
1521
1522 These parameters are used by the following functions to configure and
1523 control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
1524 mp.Pool.imap().
1525
1526 The chunkSize determines chunks of input data passed to each worker
1527 process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
1528 The default value of chunkSize is dependent on the value of 'inputDataMode'.
1529
1530 The mp.Pool.map() function, invoked during 'InMemory' input data mode,
1531 automatically converts RDKit data iterable into a list, loads all data into
1532 memory, and calculates the default chunkSize using the following method
1533 as shown in its code:
1534
1535 chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
1536 if extra: chunkSize += 1
1537
1538 For example, the default chunkSize will be 7 for a pool of 4 worker processes
1539 and 100 data items.
1540
1541 The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
1542 'lazy' RDKit data iterable to retrieve data as needed, without loading all the
1543 data into memory. Consequently, the size of input data is not known a priori.
1544 It's not possible to estimate an optimal value for the chunkSize. The default
1545 chunkSize is set to 1.
1546
1547 The default value for the chunkSize during 'Lazy' data mode may adversely
1548 impact the performance due to the overhead associated with exchanging
1549 small chunks of data. It is generally a good idea to explicitly set chunkSize to
1550 a larger value during 'Lazy' input data mode, based on the size of your input
1551 data and number of processes in the process pool.
1552
1553 The mp.Pool.map() function waits for all worker processes to process all
1554 the data and return the results. The mp.Pool.imap() function, however,
1555 returns the the results obtained from worker processes as soon as the
1556 results become available for specified chunks of data.
1557
1558 The order of data in the results returned by both mp.Pool.map() and
1559 mp.Pool.imap() functions always corresponds to the input data.
1560 -o, --outfile <outfile>
1561 Output file name for PyMOL PML file. The PML output file, along with cube
1562 files, is generated in a local directory corresponding to '--outfilesDir'
1563 option.
1564 --outfilesDir <text> [default: auto]
1565 Directory name containing PML and cube files. Default:
1566 <OutfileRoot>_DualDescriptors. This directory must be present during
1567 'VisualizeCubeFiles' value of '--mode' option.
1568 --outfilesMolPrefix <MolNum, MolName, Both> [default: Both]
1569 Molecule prefix to use for the names of cube files. Possible values:
1570 MolNum, MolName, or Both. By default, both molecule number and name
1571 are used. The format of molecule prefix is as follows: MolNum - Mol<Num>;
1572 MolName - <MolName>, Both: Mol<Num>_<MolName>. Empty molecule names
1573 are ignored. Molecule numbers are used for empty molecule names.
1574 --overwrite
1575 Overwrite existing files.
1576 --psi4CubeFilesParams <Name,Value,...> [default: auto]
1577 A comma delimited list of parameter name and value pairs for generating
1578 Psi4 cube files.
1579
1580 The supported parameter names along with their default and possible
1581 values are shown below:
1582
1583 gridSpacing, 0.2, gridOverage, 4.0, isoContourThreshold, 0.85
1584
1585 gridSpacing: Grid spacing for generating cube files. Units: Bohr. A higher
1586 value reduces the size of the cube files on the disk. This option corresponds
1587 to Psi4 option CUBIC_GRID_SPACING.
1588
1589 gridOverage: Grid overage for generating cube files. Units: Bohr.This option
1590 corresponds to Psi4 option CUBIC_GRID_OVERAGE.
1591
1592 isoContourThreshold: IsoContour values for generating cube files that capture
1593 specified percent of the probability density using the least amount of grid
1594 points. Default: 0.85 (85%). This option corresponds to Psi4 option
1595 CUBEPROP_ISOCONTOUR_THRESHOLD.
1596 --psi4OptionsParams <Name,Value,...> [default: none]
1597 A comma delimited list of Psi4 option name and value pairs for setting
1598 global and module options. The names are 'option_name' for global options
1599 and 'module_name__option_name' for options local to a module. The
1600 specified option names must be valid Psi4 names. No validation is
1601 performed.
1602
1603 The specified option name and value pairs are processed and passed to
1604 psi4.set_options() as a dictionary. The supported value types are float,
1605 integer, boolean, or string. The float value string is converted into a float.
1606 The valid values for a boolean string are yes, no, true, false, on, or off.
1607 --psi4RunParams <Name,Value,...> [default: auto]
1608 A comma delimited list of parameter name and value pairs for configuring
1609 Psi4 jobs.
1610
1611 The supported parameter names along with their default and possible
1612 values are shown below:
1613
1614 MemoryInGB, 1
1615 NumThreads, 1
1616 OutputFile, auto [ Possible values: stdout, quiet, or FileName ]
1617 ScratchDir, auto [ Possivle values: DirName]
1618 RemoveOutputFile, yes [ Possible values: yes, no, true, or false]
1619
1620 These parameters control the runtime behavior of Psi4.
1621
1622 The default file name for 'OutputFile' is <InFileRoot>_Psi4.out. The PID
1623 is appended to output file name during multiprocessing as shown:
1624 <InFileRoot>_Psi4_<PIDNum>.out. The 'stdout' value for 'OutputType'
1625 sends Psi4 output to stdout. The 'quiet' or 'devnull' value suppresses
1626 all Psi4 output.
1627
1628 The default 'Yes' value of 'RemoveOutputFile' option forces the removal
1629 of any existing Psi4 before creating new files to append output from
1630 multiple Psi4 runs.
1631
1632 The option 'ScratchDir' is a directory path to the location of scratch
1633 files. The default value corresponds to Psi4 default. It may be used to
1634 override the deafult path.
1635 --pymolViewParams <Name,Value,...> [default: auto]
1636 A comma delimited list of parameter name and value pairs for visualizing
1637 cube files in PyMOL.
1638
1639 contourColor1, red, contourColor2, blue,
1640 contourLevel1, auto, contourLevel2, auto,
1641 contourLevelAutoAt, 0.5,
1642 displayMolecule, BallAndStick, displaySphereScale, 0.2,
1643 displayStickRadius, 0.1, hideHydrogens, yes,
1644 meshWidth, 0.5, meshQuality, 2,
1645 surfaceQuality, 2, surfaceTransparency, 0.25,
1646 volumeColorRamp, auto, volumeColorRampOpacity,0.2
1647 volumeContourWindowFactor,0.05
1648
1649 contourColor1 and contourColor2: Color to use for visualizing volumes,
1650 meshes, and surfaces corresponding to the negative and positive values
1651 in cube files. The specified values must be valid PyMOL color names. No
1652 validation is performed.
1653
1654 contourLevel1 and contourLevel2: Contour levels to use for visualizing
1655 volumes, meshes, and surfaces corresponding to the negative and positive
1656 values in cube files. Default: auto. The specified values for contourLevel1
1657 and contourLevel2 must be negative and positive numbers.
1658
1659 The contour levels are automatically calculated by default. The isocontour
1660 range for specified percent of the density is retrieved from the cube files.
1661 The contour levels are set at 'contourLevelAutoAt' of the absolute maximum
1662 value of the isocontour range. For example: contour levels are set to plus and
1663 minus 0.03 at 'contourLevelAutoAt' of 0.5 for isocontour range of -0.06 to
1664 0.06 covering specified percent of the density.
1665
1666 contourLevelAutoAt: Set contour levels at specified fraction of the absolute
1667 maximum value of the isocontour range retrieved from the cube files. This
1668 option is only used during the automatic calculations of the contour levels.
1669
1670 displayMolecule: Display mode for molecules. Possible values: Sticks or
1671 BallAndStick. Both displays objects are created for molecules.
1672
1673 displaySphereScale: Sphere scale for displaying molecule during
1674 BallAndStick display.
1675
1676 displayStickRadius: Stick radius for displaying molecule during Sticks
1677 and BallAndStick display.
1678
1679 hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
1680 values: yes or no.
1681
1682 meshQuality: Mesh quality for meshes to visualize cube files. The
1683 higher values represents better quality.
1684
1685 meshWidth: Line width for mesh lines to visualize cube files.
1686
1687 surfaceQuality: Surface quality for surfaces to visualize cube files.
1688 The higher values represents better quality.
1689
1690 surfaceTransparency: Surface transparency for surfaces to visualize cube
1691 files.
1692
1693 volumeColorRamp: Name of a PyMOL volume color ramp to use for visualizing
1694 cube files. Default name(s): <OutfielsMolPrefix>_psi4_cube_dual or psi4_cube_dual
1695 The default volume color ramps are automatically generated using contour
1696 levels and colors during 'auto' value of 'volumeColorRamp'. An explicitly
1697 specified value must be a valid PyMOL volume color ramp. No validation is
1698 preformed.
1699
1700 VolumeColorRampOpacity: Opacity for generating volume color ramps
1701 for visualizing cube files. This value is equivalent to 1 minus Transparency.
1702
1703 volumeContourWindowFactor: Fraction of contour level representing window
1704 widths around contour levels during generation of volume color ramps for
1705 visualizing cube files. For example, the value of 0.05 implies a ramp window
1706 size of 0.0015 at contour level of 0.03.
1707 -q, --quiet <yes or no> [default: no]
1708 Use quiet mode. The warning and information messages will not be printed.
1709 -r, --reference <text> [default: auto]
1710 Reference wave function to use for calculating single point energy before
1711 generating cube files for dual descriptors corresponding to frontier molecular
1712 orbitals. Default: RHF or UHF. The default values are Restricted Hartree-Fock (RHF)
1713 for closed-shell molecules with all electrons paired and Unrestricted artree-Fock (UHF)
1714 for open-shell molecules with unpaired electrons.
1715
1716 The specified value must be a valid Psi4 reference wave function. No validation
1717 is performed. For example: ROHF, CUHF, RKS, etc.
1718
1719 The spin multiplicity determines the default value of reference wave function
1720 for input molecules. It is calculated from number of free radical electrons using
1721 Hund's rule of maximum multiplicity defined as 2S + 1 where S is the total
1722 electron spin. The total spin is 1/2 the number of free radical electrons in a
1723 molecule. The value of 'SpinMultiplicity' molecule property takes precedence
1724 over the calculated value of spin multiplicity.
1725 -w, --workingdir <dir>
1726 Location of working directory which defaults to the current directory.
1727
1728 Examples:
1729 To generate and visualize dual descriptors from frontier orbitals based on a
1730 single point energy calculation using B3LYP/6-31G** and B3LYP/6-31+G** for
1731 non-sulfur and sulfur containing closed-shell molecules in a SD file with 3D
1732 structures, and write a new PML file, type:
1733
1734 % Psi4VisualizeDualDescriptors.py -i Psi4Sample3D.sdf
1735 -o Psi4Sample3DOut.pml
1736
1737 To run the first example to only generate cube files and skip generation of
1738 a PML file to visualize dual descriptors for frontier molecular orbitals, type:
1739
1740 % Psi4VisualizeDualDescriptors.py --mode GenerateCubeFiles
1741 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1742
1743 To run the first example to skip generation of cube files and use existing cube
1744 files to visualize dual descriptors for frontier molecular orbitals and write out
1745 a PML file, type:
1746
1747 % Psi4VisualizeDualDescriptors.py --mode VisualizeCubeFiles
1748 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1749
1750 To run the first example in multiprocessing mode on all available CPUs
1751 without loading all data into memory and write out a PML file, type:
1752
1753 % Psi4VisualizeDualDescriptors.py --mp yes -i Psi4Sample3D.sdf
1754 -o Psi4Sample3DOut.pml
1755
1756 To run the first example in multiprocessing mode on all available CPUs
1757 by loading all data into memory and write out a PML file, type:
1758
1759 % Psi4VisualizeFrontierOrbitals.py --mp yes --mpParams "inputDataMode,
1760 InMemory" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1761
1762 To run the first example in multiprocessing mode on all available CPUs
1763 without loading all data into memory along with multiple threads for each
1764 Psi4 run and write out a SD file, type:
1765
1766 % Psi4VisualizeDualDescriptors.py --mp yes --psi4RunParams
1767 "NumThreads,2" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1768
1769 To run the first example in using a specific set of parameters to generate and
1770 visualize dual descriptors for frontier molecular orbitals and write out a PML file,
1771 type:
1772
1773 % Psi4VisualizeDualDescriptors.py --mode both -m SCF -b aug-cc-pVDZ
1774 --psi4CubeFilesParams "gridSpacing, 0.2, gridOverage, 4.0"
1775 --psi4RunParams "MemoryInGB, 2" --pymolViewParams "contourColor1,
1776 red, contourColor2, blue,contourLevel1, -0.04, contourLevel2, 0.04,
1777 contourLevelAutoAt, 0.75,volumeColorRamp, auto,
1778 volumeColorRampOpacity,0.25, volumeContourWindowFactor,0.05"
1779 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1780
1781 Author:
1782 Manish Sud(msud@san.rr.com)
1783
1784 See also:
1785 Psi4PerformMinimization.py, Psi4GenerateConformers.py,
1786 Psi4VisualizeElectrostaticPotential.py , Psi4VisualizeFrontierOrbitals.py
1787
1788 Copyright:
1789 Copyright (C) 2026 Manish Sud. All rights reserved.
1790
1791 The functionality available in this script is implemented using Psi4, an
1792 open source quantum chemistry software package, and RDKit, an open
1793 source toolkit for cheminformatics developed by Greg Landrum.
1794
1795 This file is part of MayaChemTools.
1796
1797 MayaChemTools is free software; you can redistribute it and/or modify it under
1798 the terms of the GNU Lesser General Public License as published by the Free
1799 Software Foundation; either version 3 of the License, or (at your option) any
1800 later version.
1801
1802 """
1803
1804 if __name__ == "__main__":
1805 main()