1 #!/bin/env python
2 #
3 # File: Psi4VisualizeFrontierOrbitals.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 GenerateAndVisualizeFrontierOrbitals()
92
93 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
94 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
95
96
97 def GenerateAndVisualizeFrontierOrbitals():
98 """Generate and visualize frontier molecular orbitals."""
99
100 if OptionsInfo["GenerateCubeFiles"]:
101 GenerateFrontierOrbitals()
102
103 if OptionsInfo["VisualizeCubeFiles"]:
104 VisualizeFrontierOrbitals()
105
106
107 def GenerateFrontierOrbitals():
108 """Generate cube files for frontier orbitals."""
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 frontier orbital 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 frontier orbitals 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 frontier orbitals...")
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 frontier orbitals cube files for molecules using multiple processes."""
176
177 MiscUtil.PrintInfo("\nGenerating frontier orbitals 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 frontier orbitals."""
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 = GenerateCubeFilesForFrontierOrbitals(Psi4Handle, WaveFunction, Mol, MolNum)
341
342 # Clean up
343 PerformPsi4Cleanup(Psi4Handle)
344
345 return True
346
347
348 def GenerateCubeFilesForFrontierOrbitals(Psi4Handle, WaveFunction, Mol, MolNum):
349 """Generate cube files for frontier orbitals."""
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": ["FRONTIER_ORBITALS"],
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, "Psi*.cube"))
426 if len(Psi4CubeFiles) == 0:
427 shutil.rmtree(CubeFilesDir)
428 if not OptionsInfo["QuietMode"]:
429 MiscUtil.PrintWarning("Failed to create cube files for frontier orbitals...")
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 VisualizeFrontierOrbitals():
473 """Visualize cube files for frontier orbitals."""
474
475 MiscUtil.PrintInfo("\nVisualizing cube files for frontier orbitals...")
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 orbital views...
528 FirstOrbital = True
529 for OrbitalID in CubeFilesInfo["OrbitalIDs"]:
530 FirstSpin = True
531 for SpinID in CubeFilesInfo["SpinIDs"][OrbitalID]:
532 WriteOrbitalSpinView(OutFH, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID, FirstSpin)
533 FirstSpin = False
534
535 # Setup orbital level group...
536 Enable, Action = [False, "open"]
537 if FirstOrbital:
538 FirstOrbital = False
539 Enable, Action = [True, "open"]
540 GenerateAndWritePMLForGroup(
541 OutFH,
542 PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"],
543 PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"],
544 Enable,
545 Action,
546 )
547
548 # Setup mol level group...
549 Enable, Action = [False, "close"]
550 if FirstMol:
551 FirstMol = False
552 Enable, Action = [True, "open"]
553 GenerateAndWritePMLForGroup(
554 OutFH, PyMOLObjectNames["MolGroup"], PyMOLObjectNames["MolGroupMembers"], Enable, Action
555 )
556
557 OutFH.close()
558
559 MiscUtil.PrintInfo("\nTotal number of molecules: %d" % MolCount)
560 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
561 MiscUtil.PrintInfo("Number of molecules with missing cube files: %d" % CubeFilesMissingCount)
562 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount + CubeFilesMissingCount))
563
564
565 def WritePMLHeader(OutFH, ScriptName):
566 """Write out PML header."""
567
568 HeaderInfo = """\
569 #
570 # This file is automatically generated by the following Psi4 script available in
571 # MayaChemTools: %s
572 #
573 cmd.reinitialize() """ % (ScriptName)
574
575 OutFH.write("%s\n" % HeaderInfo)
576
577
578 def WritePyMOLParameters(OutFH):
579 """Write out PyMOL global parameters."""
580
581 OutFH.write("""\n""\n"Setting up PyMOL gobal parameters..."\n""\n""")
582 PMLCmds = []
583 PMLCmds.append("""cmd.set("mesh_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["MeshQuality"]))
584 PMLCmds.append("""cmd.set("mesh_width", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["MeshWidth"]))
585
586 PMLCmds.append("""cmd.set("surface_quality", %s)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceQuality"]))
587 PMLCmds.append("""cmd.set("transparency", %.2f)""" % (OptionsInfo["PyMOLViewParams"]["SurfaceTransparency"]))
588 PML = "\n".join(PMLCmds)
589 OutFH.write("%s\n" % PML)
590
591 if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"]:
592 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
593 GenerateAndWritePMLForVolumeColorRamp(
594 OutFH,
595 PyMOLViewParams["GlobalVolumeColorRampName"],
596 PyMOLViewParams["ContourLevel1"],
597 PyMOLViewParams["ContourColor1"],
598 PyMOLViewParams["ContourLevel2"],
599 PyMOLViewParams["ContourColor2"],
600 PyMOLViewParams["VolumeContourWindowFactor"],
601 PyMOLViewParams["VolumeColorRampOpacity"],
602 )
603
604
605 def WriteMolView(OutFH, CubeFilesInfo, PyMOLObjectNames):
606 """Write out PML for viewing molecules."""
607
608 MolFile = CubeFilesInfo["MolFile"]
609
610 OutFH.write("""\n""\n"Loading %s and setting up view for molecule..."\n""\n""" % MolFile)
611
612 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
613 PMLCmds = []
614
615 # Molecule...
616 Name = PyMOLObjectNames["Mol"]
617 PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
618 PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
619 PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
620 PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
621 PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
622 if PyMOLViewParams["HideHydrogens"]:
623 PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
624 if re.match("^Sticks$", PyMOLViewParams["DisplayMolecule"]):
625 PMLCmds.append("""cmd.enable("%s")""" % (Name))
626 else:
627 PMLCmds.append("""cmd.disable("%s")""" % (Name))
628
629 # Molecule ball and stick...
630 Name = PyMOLObjectNames["MolBallAndStick"]
631 PMLCmds.append("""cmd.load("%s", "%s")""" % (MolFile, Name))
632 PMLCmds.append("""cmd.hide("everything", "%s")""" % (Name))
633 PMLCmds.append("""util.cbag("%s", _self = cmd)""" % (Name))
634 PMLCmds.append("""cmd.show("sphere", "%s")""" % (Name))
635 PMLCmds.append("""cmd.show("sticks", "%s")""" % (Name))
636 PMLCmds.append("""cmd.set("sphere_scale", %s, "%s")""" % (PyMOLViewParams["DisplaySphereScale"], Name))
637 PMLCmds.append("""cmd.set("stick_radius", %s, "%s")""" % (PyMOLViewParams["DisplayStickRadius"], Name))
638 if PyMOLViewParams["HideHydrogens"]:
639 PMLCmds.append("""cmd.hide("(%s and hydro)")""" % (Name))
640 if re.match("^BallAndStick$", PyMOLViewParams["DisplayMolecule"]):
641 PMLCmds.append("""cmd.enable("%s")""" % (Name))
642 else:
643 PMLCmds.append("""cmd.disable("%s")""" % (Name))
644
645 PML = "\n".join(PMLCmds)
646 OutFH.write("%s\n" % PML)
647
648 # MolAlone group...
649 GenerateAndWritePMLForGroup(
650 OutFH, PyMOLObjectNames["MolAloneGroup"], PyMOLObjectNames["MolAloneGroupMembers"], True, "open"
651 )
652
653
654 def WriteOrbitalSpinView(OutFH, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID, FirstSpin):
655 """Write out PML for viewing spins in an orbital."""
656
657 OutFH.write("""\n""\n"Setting up views for spin %s in orbital %s..."\n""\n""" % (SpinID, OrbitalID))
658
659 # Cube...
660 SpinCubeFile = CubeFilesInfo["FileName"][OrbitalID][SpinID]
661 SpinCubeName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinCube"]
662 PMLCmds = []
663 PMLCmds.append("""cmd.load("%s", "%s")""" % (SpinCubeFile, SpinCubeName))
664 PMLCmds.append("""cmd.disable("%s")""" % (SpinCubeName))
665 PMLCmds.append("")
666 PML = "\n".join(PMLCmds)
667 OutFH.write("%s\n" % PML)
668
669 ContourLevel1 = CubeFilesInfo["ContourLevel1"][OrbitalID][SpinID]
670 ContourLevel2 = CubeFilesInfo["ContourLevel2"][OrbitalID][SpinID]
671
672 ContourColor1 = CubeFilesInfo["ContourColor1"][OrbitalID][SpinID]
673 ContourColor2 = CubeFilesInfo["ContourColor2"][OrbitalID][SpinID]
674
675 ContourWindowFactor = CubeFilesInfo["ContourWindowFactor"][OrbitalID][SpinID]
676 ContourColorOpacity = CubeFilesInfo["ContourColorOpacity"][OrbitalID][SpinID]
677
678 SetupLocalVolumeColorRamp = CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID][SpinID]
679 VolumeColorRampName = CubeFilesInfo["VolumeColorRampName"][OrbitalID][SpinID]
680
681 # Volume ramp...
682 if SetupLocalVolumeColorRamp:
683 GenerateAndWritePMLForVolumeColorRamp(
684 OutFH,
685 VolumeColorRampName,
686 ContourLevel1,
687 ContourColor1,
688 ContourLevel2,
689 ContourColor2,
690 ContourWindowFactor,
691 ContourColorOpacity,
692 )
693
694 # Volume...
695 SpinVolumeName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinVolume"]
696 PMLCmds = []
697 PMLCmds.append("""cmd.volume("%s", "%s", "%s")""" % (SpinVolumeName, SpinCubeName, VolumeColorRampName))
698 PMLCmds.append("""cmd.disable("%s")""" % (SpinVolumeName))
699 PMLCmds.append("")
700 PML = "\n".join(PMLCmds)
701 OutFH.write("%s\n" % PML)
702
703 # Mesh...
704 SpinMeshName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshNegative"]
705 PMLCmds = []
706 PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (SpinMeshName, SpinCubeName, ContourLevel1))
707 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, SpinMeshName))
708 PMLCmds.append("""cmd.enable("%s")""" % (SpinMeshName))
709 PMLCmds.append("")
710
711 SpinMeshName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshPositive"]
712 PMLCmds.append("""cmd.isomesh("%s", "%s", "%s")""" % (SpinMeshName, SpinCubeName, ContourLevel2))
713 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, SpinMeshName))
714 PMLCmds.append("""cmd.enable("%s")""" % (SpinMeshName))
715 PMLCmds.append("")
716 PML = "\n".join(PMLCmds)
717 OutFH.write("%s\n" % PML)
718
719 GenerateAndWritePMLForGroup(
720 OutFH,
721 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroup"],
722 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"],
723 False,
724 "close",
725 )
726
727 # Surface...
728 SpinSurfaceName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceNegative"]
729 PMLCmds = []
730 PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (SpinSurfaceName, SpinCubeName, ContourLevel1))
731 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor1, SpinSurfaceName))
732 PMLCmds.append("""cmd.enable("%s")""" % (SpinSurfaceName))
733 PMLCmds.append("")
734
735 SpinSurfaceName = PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfacePositive"]
736 PMLCmds.append("""cmd.isosurface("%s", "%s", "%s")""" % (SpinSurfaceName, SpinCubeName, ContourLevel2))
737 PMLCmds.append("""util.color_deep("%s", "%s")""" % (ContourColor2, SpinSurfaceName))
738 PMLCmds.append("""cmd.enable("%s")""" % (SpinSurfaceName))
739 PMLCmds.append("")
740 PML = "\n".join(PMLCmds)
741 OutFH.write("%s\n" % PML)
742
743 GenerateAndWritePMLForGroup(
744 OutFH,
745 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroup"],
746 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"],
747 True,
748 "close",
749 )
750
751 Enable = True if FirstSpin else False
752 Action = "open" if FirstSpin else "close"
753 GenerateAndWritePMLForGroup(
754 OutFH,
755 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroup"],
756 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"],
757 Enable,
758 Action,
759 )
760
761
762 def GenerateAndWritePMLForVolumeColorRamp(
763 OutFH,
764 ColorRampName,
765 ContourLevel1,
766 ContourColor1,
767 ContourLevel2,
768 ContourColor2,
769 ContourWindowFactor,
770 ContourColorOpacity,
771 ):
772 """Write out PML for volume color ramp."""
773
774 RampWindowOffset1 = abs(ContourLevel1 * ContourWindowFactor)
775 RampWindowOffset2 = abs(ContourLevel2 * ContourWindowFactor)
776
777 PML = """\
778 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])""" % (
779 ColorRampName,
780 (ContourLevel1 - RampWindowOffset1),
781 ContourColor1,
782 ContourLevel1,
783 ContourColor1,
784 ContourColorOpacity,
785 (ContourLevel1 + RampWindowOffset1),
786 ContourColor1,
787 (ContourLevel2 - RampWindowOffset2),
788 ContourColor2,
789 ContourLevel2,
790 ContourColor2,
791 ContourColorOpacity,
792 (ContourLevel2 + RampWindowOffset2),
793 ContourColor2,
794 )
795
796 OutFH.write("%s\n" % PML)
797
798
799 def GenerateAndWritePMLForGroup(OutFH, GroupName, GroupMembersList, Enable, Action):
800 """Generate and write PML for group."""
801
802 OutFH.write("""\n""\n"Setting up group %s..."\n""\n""" % GroupName)
803
804 PMLCmds = []
805
806 GroupMembers = " ".join(GroupMembersList)
807 PMLCmds.append("""cmd.group("%s", "%s")""" % (GroupName, GroupMembers))
808
809 if Enable is not None:
810 if Enable:
811 PMLCmds.append("""cmd.enable("%s")""" % GroupName)
812 else:
813 PMLCmds.append("""cmd.disable("%s")""" % GroupName)
814
815 if Action is not None:
816 PMLCmds.append("""cmd.group("%s", action="%s")""" % (GroupName, Action))
817
818 PML = "\n".join(PMLCmds)
819
820 OutFH.write("%s\n" % PML)
821
822
823 def RetrieveMolCubeFilesInfo(Mol, MolNum):
824 """Retrieve available cube files info for a molecule."""
825
826 CubeFilesInfo = {}
827 CubeFilesInfo["OrbitalIDs"] = []
828 CubeFilesInfo["SpinIDs"] = {}
829 CubeFilesInfo["FileName"] = {}
830
831 CubeFilesInfo["IsocontourRangeMin"] = {}
832 CubeFilesInfo["IsocontourRangeMax"] = {}
833
834 CubeFilesInfo["ContourLevel1"] = {}
835 CubeFilesInfo["ContourLevel2"] = {}
836 CubeFilesInfo["ContourColor1"] = {}
837 CubeFilesInfo["ContourColor2"] = {}
838
839 CubeFilesInfo["ContourWindowFactor"] = {}
840 CubeFilesInfo["ContourColorOpacity"] = {}
841
842 CubeFilesInfo["VolumeColorRampName"] = {}
843 CubeFilesInfo["SetupLocalVolumeColorRamp"] = {}
844
845 # Setup cube mol info...
846 CubeFilesInfo["MolPrefix"] = SetupMolPrefix(Mol, MolNum)
847 CubeFilesInfo["MolName"] = SetupMolName(Mol, MolNum)
848 CubeFilesInfo["MolFile"] = SetupMolFileName(Mol, MolNum)
849
850 MolPrefix = CubeFilesInfo["MolPrefix"]
851 CubeFilesCount = 0
852 for OrbitalID in ["HOMO", "LUMO", "DOMO", "LVMO", "SOMO"]:
853 CubeFiles = glob.glob("%s*%s.cube" % (MolPrefix, OrbitalID))
854 if not len(CubeFiles):
855 continue
856
857 CubeFilesInfo["OrbitalIDs"].append(OrbitalID)
858 CubeFilesInfo["SpinIDs"][OrbitalID] = []
859 CubeFilesInfo["FileName"][OrbitalID] = {}
860
861 CubeFilesInfo["IsocontourRangeMin"][OrbitalID] = {}
862 CubeFilesInfo["IsocontourRangeMax"][OrbitalID] = {}
863
864 CubeFilesInfo["ContourLevel1"][OrbitalID] = {}
865 CubeFilesInfo["ContourLevel2"][OrbitalID] = {}
866 CubeFilesInfo["ContourColor1"][OrbitalID] = {}
867 CubeFilesInfo["ContourColor2"][OrbitalID] = {}
868
869 CubeFilesInfo["ContourWindowFactor"][OrbitalID] = {}
870 CubeFilesInfo["ContourColorOpacity"][OrbitalID] = {}
871
872 CubeFilesInfo["VolumeColorRampName"][OrbitalID] = {}
873 CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID] = {}
874
875 AlphaSpinCount, BetaSpinCount, SpinCount, CurrentSpinCount = [0] * 4
876 for CubeFile in CubeFiles:
877 if re.match("%s_a_" % MolPrefix, CubeFile):
878 SpinID = "Alpha"
879 AlphaSpinCount += 1
880 CurrentSpinCount = AlphaSpinCount
881 elif re.match("%s_b_" % MolPrefix, CubeFile):
882 SpinID = "Beta"
883 BetaSpinCount += 1
884 CurrentSpinCount = BetaSpinCount
885 else:
886 SpinID = "Spin"
887 SpinCount += 1
888 CurrentSpinCount = SpinCount
889
890 # Set up a unique spin ID...
891 if SpinID in CubeFilesInfo["FileName"][OrbitalID]:
892 SpinID = "%s_%s" % (SpinID, CurrentSpinCount)
893
894 CubeFilesCount += 1
895 CubeFilesInfo["SpinIDs"][OrbitalID].append(SpinID)
896 CubeFilesInfo["FileName"][OrbitalID][SpinID] = CubeFile
897
898 IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2 = SetupIsocontourRangeAndContourLevels(
899 CubeFile
900 )
901
902 CubeFilesInfo["IsocontourRangeMin"][OrbitalID][SpinID] = IsocontourRangeMin
903 CubeFilesInfo["IsocontourRangeMax"][OrbitalID][SpinID] = IsocontourRangeMax
904
905 CubeFilesInfo["ContourLevel1"][OrbitalID][SpinID] = ContourLevel1
906 CubeFilesInfo["ContourLevel2"][OrbitalID][SpinID] = ContourLevel2
907 CubeFilesInfo["ContourColor1"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["ContourColor1"]
908 CubeFilesInfo["ContourColor2"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"]["ContourColor2"]
909
910 CubeFilesInfo["ContourWindowFactor"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"][
911 "VolumeContourWindowFactor"
912 ]
913 CubeFilesInfo["ContourColorOpacity"][OrbitalID][SpinID] = OptionsInfo["PyMOLViewParams"][
914 "VolumeColorRampOpacity"
915 ]
916
917 VolumeColorRampName = SetupVolumeColorRampName(CubeFilesInfo)
918 CubeFilesInfo["VolumeColorRampName"][OrbitalID][SpinID] = VolumeColorRampName
919 CubeFilesInfo["SetupLocalVolumeColorRamp"][OrbitalID][SpinID] = (
920 False if OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] else True
921 )
922
923 Status = True if CubeFilesCount > 0 else False
924
925 return (Status, CubeFilesInfo)
926
927
928 def SetupVolumeColorRampName(CubeFilesInfo):
929 """Setup volume color ramp name and status."""
930
931 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
932
933 # Setup info for generating local volume color ramps for individual cube files...
934 VolumeColorRampName = PyMOLViewParams["VolumeColorRamp"]
935 if PyMOLViewParams["VolumeColorRampAuto"]:
936 VolumeColorRampName = SetupDefaultVolumeRampName()
937 if PyMOLViewParams["ContourLevel1Auto"] or PyMOLViewParams["ContourLevel2Auto"]:
938 VolumeColorRampName = "%s_%s" % (CubeFilesInfo["MolPrefix"], VolumeColorRampName)
939
940 return VolumeColorRampName
941
942
943 def SetupIsocontourRangeAndContourLevels(CubeFileName):
944 """Setup isocontour range."""
945
946 PyMOLViewParams = OptionsInfo["PyMOLViewParams"]
947
948 # Setup isocontour range and contour levels...
949 IsocontourRangeMin, IsocontourRangeMax = Psi4Util.RetrieveIsocontourRangeFromCubeFile(CubeFileName)
950
951 DefaultIsocontourRange = 0.06
952 if IsocontourRangeMin is None:
953 IsocontourRangeMin = -DefaultIsocontourRange
954 if not OptionsInfo["QuietMode"]:
955 MiscUtil.PrintInfo(
956 "Failed to retrieve isocontour range from the cube file. Setting min isocontour value to %s..."
957 % (IsocontourRangeMin)
958 )
959
960 if IsocontourRangeMax is None:
961 IsocontourRangeMax = DefaultIsocontourRange
962 if not OptionsInfo["QuietMode"]:
963 MiscUtil.PrintInfo(
964 "Failed to retrieve isocontour range from the cube file. Setting max isocontour value to %s..."
965 % (IsocontourRangeMax)
966 )
967
968 # Setup contour levels...
969 ContourLevel = max(abs(IsocontourRangeMin), abs(IsocontourRangeMax)) * PyMOLViewParams["ContourLevelAutoAt"]
970 if ContourLevel >= 0.01:
971 ContourLevel = float("%.2f" % ContourLevel)
972 elif ContourLevel >= 0.001:
973 ContourLevel = float("%.3f" % ContourLevel)
974 elif ContourLevel >= 0.0001:
975 ContourLevel = float("%.4f" % ContourLevel)
976
977 ContourLevel1 = -ContourLevel if PyMOLViewParams["ContourLevel1Auto"] else PyMOLViewParams["ContourLevel1"]
978 ContourLevel2 = ContourLevel if PyMOLViewParams["ContourLevel2Auto"] else PyMOLViewParams["ContourLevel2"]
979
980 if not OptionsInfo["QuietMode"]:
981 if IsocontourRangeMin is not None and IsocontourRangeMax is not None:
982 MiscUtil.PrintInfo(
983 "CubeFileName: %s; Isocontour range for %s percent of the density: %.4f to %.4f; ContourLevel1: %s; ContourLevel2: %s"
984 % (
985 CubeFileName,
986 (100 * OptionsInfo["Psi4CubeFilesParams"]["IsoContourThreshold"]),
987 IsocontourRangeMin,
988 IsocontourRangeMax,
989 ContourLevel1,
990 ContourLevel2,
991 )
992 )
993
994 return (IsocontourRangeMin, IsocontourRangeMax, ContourLevel1, ContourLevel2)
995
996
997 def SetupPyMOLObjectNames(Mol, MolNum, CubeFilesInfo):
998 """Setup PyMOL object names."""
999
1000 PyMOLObjectNames = {}
1001 PyMOLObjectNames["Orbitals"] = {}
1002 PyMOLObjectNames["Spins"] = {}
1003
1004 SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames)
1005
1006 for OrbitalID in CubeFilesInfo["OrbitalIDs"]:
1007 SetupPyMOLObjectNamesForOrbital(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID)
1008 for SpinID in CubeFilesInfo["SpinIDs"][OrbitalID]:
1009 SetupPyMOLObjectNamesForSpin(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID)
1010
1011 return PyMOLObjectNames
1012
1013
1014 def SetupPyMOLObjectNamesForMol(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames):
1015 """Setup groups and objects for molecule."""
1016
1017 MolFileRoot = CubeFilesInfo["MolPrefix"]
1018
1019 MolGroupName = "%s" % MolFileRoot
1020 PyMOLObjectNames["MolGroup"] = MolGroupName
1021 PyMOLObjectNames["MolGroupMembers"] = []
1022
1023 # Molecule alone group...
1024 MolAloneGroupName = "%s.Molecule" % (MolGroupName)
1025 PyMOLObjectNames["MolAloneGroup"] = MolAloneGroupName
1026 PyMOLObjectNames["MolGroupMembers"].append(MolAloneGroupName)
1027
1028 PyMOLObjectNames["MolAloneGroupMembers"] = []
1029
1030 # Molecule...
1031 MolName = "%s.Molecule" % (MolAloneGroupName)
1032 PyMOLObjectNames["Mol"] = MolName
1033 PyMOLObjectNames["MolAloneGroupMembers"].append(MolName)
1034
1035 # BallAndStick...
1036 MolBallAndStickName = "%s.BallAndStick" % (MolAloneGroupName)
1037 PyMOLObjectNames["MolBallAndStick"] = MolBallAndStickName
1038 PyMOLObjectNames["MolAloneGroupMembers"].append(MolBallAndStickName)
1039
1040
1041 def SetupPyMOLObjectNamesForOrbital(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID):
1042 """Setup groups and objects for orbital."""
1043
1044 PyMOLObjectNames["Orbitals"][OrbitalID] = {}
1045 PyMOLObjectNames["Spins"][OrbitalID] = {}
1046
1047 MolGroupName = PyMOLObjectNames["MolGroup"]
1048
1049 # Setup orbital group...
1050 OrbitalGroupName = "%s.%s" % (MolGroupName, OrbitalID)
1051 PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"] = OrbitalGroupName
1052 PyMOLObjectNames["MolGroupMembers"].append(OrbitalGroupName)
1053 PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"] = []
1054
1055
1056 def SetupPyMOLObjectNamesForSpin(Mol, MolNum, CubeFilesInfo, PyMOLObjectNames, OrbitalID, SpinID):
1057 """Setup groups and objects for spin."""
1058
1059 PyMOLObjectNames["Spins"][OrbitalID][SpinID] = {}
1060
1061 OrbitalGroupName = PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroup"]
1062
1063 # Setup spin group at orbital level...
1064 SpinGroupName = "%s.%s" % (OrbitalGroupName, SpinID)
1065 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroup"] = SpinGroupName
1066 PyMOLObjectNames["Orbitals"][OrbitalID]["OrbitalGroupMembers"].append(SpinGroupName)
1067 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"] = []
1068
1069 # Cube...
1070 SpinCubeName = "%s.Cube" % SpinGroupName
1071 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinCube"] = SpinCubeName
1072 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinCubeName)
1073
1074 # Volume...
1075 SpinVolumeName = "%s.Volume" % SpinGroupName
1076 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinVolume"] = SpinVolumeName
1077 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinVolumeName)
1078
1079 # Mesh...
1080 SpinMeshGroupName = "%s.Mesh" % SpinGroupName
1081 SpinMeshNegativeName = "%s.Negative_Phase" % SpinMeshGroupName
1082 SpinMeshPositiveName = "%s.Positive_Phase" % SpinMeshGroupName
1083
1084 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroup"] = SpinMeshGroupName
1085 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinMeshGroupName)
1086
1087 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshNegative"] = SpinMeshNegativeName
1088 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshPositive"] = SpinMeshPositiveName
1089 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"] = []
1090 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"].append(SpinMeshNegativeName)
1091 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinMeshGroupMembers"].append(SpinMeshPositiveName)
1092
1093 # Surface...
1094 SpinSurfaceGroupName = "%s.Surface" % SpinGroupName
1095 SpinSurfaceNegativeName = "%s.Negative_Phase" % SpinSurfaceGroupName
1096 SpinSurfacePositiveName = "%s.Positive_Phase" % SpinSurfaceGroupName
1097
1098 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroup"] = SpinSurfaceGroupName
1099 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinGroupMembers"].append(SpinSurfaceGroupName)
1100
1101 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceNegative"] = SpinSurfaceNegativeName
1102 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfacePositive"] = SpinSurfacePositiveName
1103 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"] = []
1104 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"].append(SpinSurfaceNegativeName)
1105 PyMOLObjectNames["Spins"][OrbitalID][SpinID]["SpinSurfaceGroupMembers"].append(SpinSurfacePositiveName)
1106
1107
1108 def SetupMolFileName(Mol, MolNum):
1109 """Setup SD file name for a molecule."""
1110
1111 MolPrefix = SetupMolPrefix(Mol, MolNum)
1112 MolFileName = "%s.sdf" % MolPrefix
1113
1114 return MolFileName
1115
1116
1117 def SetupMolCubeFileName(Mol, MolNum, Psi4CubeFileName):
1118 """Setup cube file name for a molecule."""
1119
1120 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Psi4CubeFileName)
1121 CubeFileName = "%s.%s" % (FileName, FileExt)
1122
1123 # Replace Psi by MolPrefix...
1124 MolPrefix = SetupMolPrefix(Mol, MolNum)
1125 CubeFileName = re.sub("^Psi", MolPrefix, CubeFileName)
1126
1127 # Replace prime (') and dprime ('') in the cube file names..
1128 CubeFileName = re.sub('"', "dblprime", CubeFileName)
1129 CubeFileName = re.sub("'", "prime", CubeFileName)
1130
1131 return CubeFileName
1132
1133
1134 def SetupMolPrefix(Mol, MolNum):
1135 """Get molecule prefix for generating files and directories."""
1136
1137 MolNamePrefix = ""
1138 if Mol.HasProp("_Name"):
1139 MolNamePrefix = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
1140
1141 MolNumPrefix = "Mol%s" % MolNum
1142
1143 if OptionsInfo["UseMolNumPrefix"] and OptionsInfo["UseMolNamePrefix"]:
1144 MolPrefix = MolNumPrefix
1145 if len(MolNamePrefix):
1146 MolPrefix = "%s_%s" % (MolNumPrefix, MolNamePrefix)
1147 elif OptionsInfo["UseMolNamePrefix"]:
1148 MolPrefix = MolNamePrefix if len(MolNamePrefix) else MolNumPrefix
1149 elif OptionsInfo["UseMolNumPrefix"]:
1150 MolPrefix = MolNumPrefix
1151
1152 return MolPrefix
1153
1154
1155 def SetupMolName(Mol, MolNum):
1156 """Get molecule name."""
1157
1158 # Test for creating PyMOL object name for molecule...
1159 if Mol.HasProp("_Name"):
1160 MolName = re.sub("[^a-zA-Z0-9]", "_", Mol.GetProp("_Name"))
1161 else:
1162 MolName = "Mol%s" % MolNum
1163
1164 return MolName
1165
1166
1167 def SetupDefaultVolumeRampName():
1168 """Setup a default name for a volume ramp."""
1169
1170 return "psi4_cube_mo"
1171
1172
1173 def SetupPsi4Mol(Psi4Handle, Mol, MolCount=None):
1174 """Setup a Psi4 molecule object."""
1175
1176 MolGeometry = RDKitUtil.GetPsi4XYZFormatString(Mol, NoCom=True, NoReorient=True)
1177
1178 try:
1179 Psi4Mol = Psi4Handle.geometry(MolGeometry)
1180 except Exception as ErrMsg:
1181 Psi4Mol = None
1182 if not OptionsInfo["QuietMode"]:
1183 MiscUtil.PrintWarning("Failed to create Psi4 molecule from geometry string: %s\n" % ErrMsg)
1184 MolName = RDKitUtil.GetMolName(Mol, MolCount)
1185 MiscUtil.PrintWarning("Ignoring molecule: %s" % MolName)
1186
1187 return Psi4Mol
1188
1189
1190 def PerformPsi4Cleanup(Psi4Handle):
1191 """Perform clean up."""
1192
1193 # Clean up after Psi4 run...
1194 Psi4Handle.core.clean()
1195
1196 # Clean up any leftover scratch files...
1197 if OptionsInfo["MPMode"]:
1198 Psi4Util.RemoveScratchFiles(Psi4Handle, OptionsInfo["Psi4RunParams"]["OutputFile"])
1199
1200
1201 def CheckAndValidateMolecule(Mol, MolCount=None):
1202 """Validate molecule for Psi4 calculations."""
1203
1204 if Mol is None:
1205 if not OptionsInfo["QuietMode"]:
1206 MiscUtil.PrintInfo("\nProcessing molecule number %s..." % MolCount)
1207 return False
1208
1209 MolName = RDKitUtil.GetMolName(Mol, MolCount)
1210 if not OptionsInfo["QuietMode"]:
1211 MiscUtil.PrintInfo("\nProcessing molecule %s..." % MolName)
1212
1213 if RDKitUtil.IsMolEmpty(Mol):
1214 if not OptionsInfo["QuietMode"]:
1215 MiscUtil.PrintWarning("Ignoring empty molecule: %s\n" % MolName)
1216 return False
1217
1218 if not RDKitUtil.ValidateElementSymbols(RDKitUtil.GetAtomSymbols(Mol)):
1219 if not OptionsInfo["QuietMode"]:
1220 MiscUtil.PrintWarning("Ignoring molecule containing invalid element symbols: %s\n" % MolName)
1221 return False
1222
1223 # Check for 3D flag...
1224 if not Mol.GetConformer().Is3D():
1225 if not OptionsInfo["QuietMode"]:
1226 MiscUtil.PrintWarning("3D tag is not set for molecule: %s\n" % MolName)
1227
1228 # Check for missing hydrogens...
1229 if RDKitUtil.AreHydrogensMissingInMolecule(Mol):
1230 if not OptionsInfo["QuietMode"]:
1231 MiscUtil.PrintWarning("Missing hydrogens in molecule: %s\n" % MolName)
1232
1233 return True
1234
1235
1236 def SetupMethodNameAndBasisSet(Mol):
1237 """Setup method name and basis set."""
1238
1239 MethodName = OptionsInfo["MethodName"]
1240 if OptionsInfo["MethodNameAuto"]:
1241 MethodName = "B3LYP"
1242
1243 BasisSet = OptionsInfo["BasisSet"]
1244 if OptionsInfo["BasisSetAuto"]:
1245 BasisSet = "6-31+G**" if RDKitUtil.IsAtomSymbolPresentInMol(Mol, "S") else "6-31G**"
1246
1247 return (MethodName, BasisSet)
1248
1249
1250 def SetupReferenceWavefunction(Mol):
1251 """Setup reference wavefunction."""
1252
1253 Reference = OptionsInfo["Reference"]
1254 if OptionsInfo["ReferenceAuto"]:
1255 Reference = "UHF" if (RDKitUtil.GetSpinMultiplicity(Mol) > 1) else "RHF"
1256
1257 return Reference
1258
1259
1260 def CheckAndSetupOutfilesDir():
1261 """Check and setup outfiles directory."""
1262
1263 if OptionsInfo["GenerateCubeFiles"]:
1264 if os.path.isdir(OptionsInfo["OutfilesDir"]):
1265 if not Options["--overwrite"]:
1266 MiscUtil.PrintError(
1267 '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'
1268 % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])
1269 )
1270
1271 if OptionsInfo["VisualizeCubeFiles"]:
1272 if not Options["--overwrite"]:
1273 if os.path.exists(os.path.join(OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])):
1274 MiscUtil.PrintError(
1275 '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'
1276 % (OptionsInfo["Outfile"], OptionsInfo["OutfilesDir"])
1277 )
1278
1279 if not OptionsInfo["GenerateCubeFiles"]:
1280 if not os.path.isdir(OptionsInfo["OutfilesDir"]):
1281 MiscUtil.PrintError(
1282 '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'
1283 % (OptionsInfo["OutfilesDir"], OptionsInfo["Outfile"])
1284 )
1285
1286 CubeFiles = glob.glob(os.path.join(OptionsInfo["OutfilesDir"], "*.cube"))
1287 if not len(CubeFiles):
1288 MiscUtil.PrintError(
1289 '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'
1290 % (OptionsInfo["OutfilesDir"])
1291 )
1292
1293 OutfilesDir = OptionsInfo["OutfilesDir"]
1294 if OptionsInfo["GenerateCubeFiles"]:
1295 if not os.path.isdir(OutfilesDir):
1296 MiscUtil.PrintInfo("\nCreating directory %s..." % OutfilesDir)
1297 os.mkdir(OutfilesDir)
1298
1299 MiscUtil.PrintInfo("\nChanging directory to %s..." % OutfilesDir)
1300 os.chdir(OutfilesDir)
1301
1302
1303 def ProcessOptions():
1304 """Process and validate command line arguments and options."""
1305
1306 MiscUtil.PrintInfo("Processing options...")
1307
1308 # Validate options...
1309 ValidateOptions()
1310
1311 OptionsInfo["Infile"] = Options["--infile"]
1312 OptionsInfo["InfilePath"] = os.path.abspath(Options["--infile"])
1313
1314 ParamsDefaultInfoOverride = {"RemoveHydrogens": False}
1315 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
1316 "--infileParams",
1317 Options["--infileParams"],
1318 InfileName=Options["--infile"],
1319 ParamsDefaultInfo=ParamsDefaultInfoOverride,
1320 )
1321
1322 Outfile = Options["--outfile"]
1323 FileDir, FileName, FileExt = MiscUtil.ParseFileName(Outfile)
1324
1325 OptionsInfo["Outfile"] = Outfile
1326 OptionsInfo["OutfileRoot"] = FileName
1327 OptionsInfo["OutfileExt"] = FileExt
1328
1329 OutfilesDir = Options["--outfilesDir"]
1330 if re.match("^auto$", OutfilesDir, re.I):
1331 OutfilesDir = "%s_FrontierOrbitals" % OptionsInfo["OutfileRoot"]
1332 OptionsInfo["OutfilesDir"] = OutfilesDir
1333
1334 OptionsInfo["Overwrite"] = Options["--overwrite"]
1335
1336 # Method, basis set, and reference wavefunction...
1337 OptionsInfo["BasisSet"] = Options["--basisSet"]
1338 OptionsInfo["BasisSetAuto"] = True if re.match("^auto$", Options["--basisSet"], re.I) else False
1339
1340 OptionsInfo["MethodName"] = Options["--methodName"]
1341 OptionsInfo["MethodNameAuto"] = True if re.match("^auto$", Options["--methodName"], re.I) else False
1342
1343 OptionsInfo["Reference"] = Options["--reference"]
1344 OptionsInfo["ReferenceAuto"] = True if re.match("^auto$", Options["--reference"], re.I) else False
1345
1346 # Run, options, and cube files parameters...
1347 OptionsInfo["Psi4OptionsParams"] = Psi4Util.ProcessPsi4OptionsParameters(
1348 "--psi4OptionsParams", Options["--psi4OptionsParams"]
1349 )
1350 OptionsInfo["Psi4RunParams"] = Psi4Util.ProcessPsi4RunParameters(
1351 "--psi4RunParams", Options["--psi4RunParams"], InfileName=OptionsInfo["Infile"]
1352 )
1353
1354 ParamsDefaultInfoOverride = {}
1355 OptionsInfo["Psi4CubeFilesParams"] = Psi4Util.ProcessPsi4CubeFilesParameters(
1356 "--psi4CubeFilesParams", Options["--psi4CubeFilesParams"], ParamsDefaultInfo=ParamsDefaultInfoOverride
1357 )
1358
1359 ParamsDefaultInfoOverride = {
1360 "ContourColor1": "red",
1361 "ContourColor2": "blue",
1362 "ContourLevel1": "auto",
1363 "ContourLevel2": "auto",
1364 "ContourLevelAutoAt": 0.75,
1365 "DisplayMolecule": "BallAndStick",
1366 "DisplaySphereScale": 0.2,
1367 "DisplayStickRadius": 0.1,
1368 "HideHydrogens": True,
1369 "MeshWidth": 0.5,
1370 "MeshQuality": 2,
1371 "SurfaceQuality": 2,
1372 "SurfaceTransparency": 0.25,
1373 "VolumeColorRamp": "auto",
1374 "VolumeColorRampOpacity": 0.2,
1375 "VolumeContourWindowFactor": 0.05,
1376 }
1377 OptionsInfo["PyMOLViewParams"] = MiscUtil.ProcessOptionPyMOLCubeFileViewParameters(
1378 "--pymolViewParams", Options["--pymolViewParams"], ParamsDefaultInfo=ParamsDefaultInfoOverride
1379 )
1380
1381 SetupGlobalVolumeColorRamp = False
1382 GlobalVolumeColorRampName = OptionsInfo["PyMOLViewParams"]["VolumeColorRamp"]
1383 if OptionsInfo["PyMOLViewParams"]["VolumeColorRampAuto"]:
1384 GlobalVolumeColorRampName = SetupDefaultVolumeRampName()
1385 if not (
1386 OptionsInfo["PyMOLViewParams"]["ContourLevel1Auto"] or OptionsInfo["PyMOLViewParams"]["ContourLevel2Auto"]
1387 ):
1388 SetupGlobalVolumeColorRamp = True
1389 OptionsInfo["PyMOLViewParams"]["GlobalVolumeColorRampName"] = GlobalVolumeColorRampName
1390 OptionsInfo["PyMOLViewParams"]["SetupGlobalVolumeColorRamp"] = SetupGlobalVolumeColorRamp
1391
1392 Mode = Options["--mode"]
1393 if re.match("^GenerateCubeFiles$", Mode, re.I):
1394 GenerateCubeFiles = True
1395 VisualizeCubeFiles = False
1396 elif re.match("^VisualizeCubeFiles$", Mode, re.I):
1397 GenerateCubeFiles = False
1398 VisualizeCubeFiles = True
1399 else:
1400 GenerateCubeFiles = True
1401 VisualizeCubeFiles = True
1402 OptionsInfo["Mode"] = Mode
1403 OptionsInfo["GenerateCubeFiles"] = GenerateCubeFiles
1404 OptionsInfo["VisualizeCubeFiles"] = VisualizeCubeFiles
1405
1406 OptionsInfo["MPMode"] = True if re.match("^yes$", Options["--mp"], re.I) else False
1407 OptionsInfo["MPParams"] = MiscUtil.ProcessOptionMultiprocessingParameters("--mpParams", Options["--mpParams"])
1408
1409 OutfilesMolPrefix = Options["--outfilesMolPrefix"]
1410 if re.match("^MolName$", OutfilesMolPrefix, re.I):
1411 UseMolNamePrefix = True
1412 UseMolNumPrefix = False
1413 elif re.match("^MolNum$", OutfilesMolPrefix, re.I):
1414 UseMolNamePrefix = False
1415 UseMolNumPrefix = True
1416 else:
1417 UseMolNamePrefix = True
1418 UseMolNumPrefix = True
1419 OptionsInfo["OutfilesMolPrefix"] = OutfilesMolPrefix
1420 OptionsInfo["UseMolNamePrefix"] = UseMolNamePrefix
1421 OptionsInfo["UseMolNumPrefix"] = UseMolNumPrefix
1422
1423 OptionsInfo["QuietMode"] = True if re.match("^yes$", Options["--quiet"], re.I) else False
1424
1425 CheckAndSetupOutfilesDir()
1426
1427
1428 def RetrieveOptions():
1429 """Retrieve command line arguments and options."""
1430
1431 # Get options...
1432 global Options
1433 Options = docopt(_docoptUsage_)
1434
1435 # Set current working directory to the specified directory...
1436 WorkingDir = Options["--workingdir"]
1437 if WorkingDir:
1438 os.chdir(WorkingDir)
1439
1440 # Handle examples option...
1441 if "--examples" in Options and Options["--examples"]:
1442 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
1443 sys.exit(0)
1444
1445
1446 def ValidateOptions():
1447 """Validate option values."""
1448
1449 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
1450 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol")
1451
1452 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "pml")
1453
1454 MiscUtil.ValidateOptionTextValue("--mode", Options["--mode"], "GenerateCubeFiles VisualizeCubeFiles Both")
1455 MiscUtil.ValidateOptionTextValue("--mp", Options["--mp"], "yes no")
1456
1457 MiscUtil.ValidateOptionTextValue("--outfilesMolPrefix", Options["--outfilesMolPrefix"], "MolNum MolName Both")
1458 MiscUtil.ValidateOptionTextValue("-q, --quiet", Options["--quiet"], "yes no")
1459
1460
1461 # Setup a usage string for docopt...
1462 _docoptUsage_ = """
1463 Psi4VisualizeFrontierOrbitals.py - Visualize frontier molecular orbitals
1464
1465 Usage:
1466 Psi4VisualizeFrontierOrbitals.py [--basisSet <text>] [--infileParams <Name,Value,...>] [--methodName <text>]
1467 [--mode <GenerateCubeFiles, VisualizeCubeFiles, Both>] [--mp <yes or no>] [--mpParams <Name, Value,...>]
1468 [--outfilesDir <text>] [--outfilesMolPrefix <MolNum, MolName, Both> ] [--overwrite]
1469 [--psi4CubeFilesParams <Name,Value,...>] [--psi4OptionsParams <Name,Value,...>]
1470 [--psi4RunParams <Name,Value,...>] [--pymolViewParams <Name,Value,...>] [--quiet <yes or no>]
1471 [--reference <text>] [-w <dir>] -i <infile> -o <outfile>
1472 Psi4VisualizeFrontierOrbitals.py -h | --help | -e | --examples
1473
1474 Description:
1475 Generate and visualize frontier molecular orbitals for molecules in input
1476 file. A set of cube files, corresponding to frontier orbitals, is generated for
1477 molecules. The cube files are used to create a PyMOL visualization file for
1478 viewing volumes, meshes, and surfaces representing frontier molecular
1479 orbitals. An option is available to skip the generation of new cube files
1480 and use existing cube files to visualize frontier molecular orbitals.
1481
1482 A Psi4 XYZ format geometry string is automatically generated for each molecule
1483 in input file. It contains atom symbols and 3D coordinates for each atom in a
1484 molecule. In addition, the formal charge and spin multiplicity are present in the
1485 the geometry string. These values are either retrieved from molecule properties
1486 named 'FormalCharge' and 'SpinMultiplicty' or dynamically calculated for a
1487 molecule.
1488
1489 A set of cube and SD output files is generated for each molecule in input file
1490 as shown below:
1491
1492 Ouput dir: <OutfileRoot>_FrontierOrbitals or <OutfilesDir>
1493
1494 Closed-shell systems:
1495
1496 <MolIDPrefix>.sdf
1497 <MolIDPrefix>*HOMO.cube
1498 <MolIDPrefix>*LUMO.cube
1499
1500 Open-shell systems:
1501
1502 <MolIDPrefix>.sdf
1503 <MolIDPrefix>*DOMO.cube
1504 <MolIDPrefix>*LVMO.cube
1505 <MolIDPrefix>*SOMO.cube
1506
1507 <MolIDPrefix>: Mol<Num>_<MolName>, Mol<Num>, or <MolName>
1508
1509 Abbreviations:
1510
1511 HOMO: Highest Occupied Molecular Orbital
1512 LUMO: Lowest Unoccupied Molecular Orbital
1513
1514 DOMO: Double Occupied Molecular Orbital
1515 SOMO: Singly Occupied Molecular Orbital
1516 LVMO: Lowest Virtual (Unoccupied) Molecular Orbital
1517
1518 In addition, a <OutfileRoot>.pml is generated containing frontier molecular
1519 orbitals for all molecules in input file.
1520
1521 The supported input file formats are: Mol (.mol), SD (.sdf, .sd)
1522
1523 The supported output file formats are: PyMOL script file (.pml)
1524
1525 A variety of PyMOL groups and objects are created for visualization of frontier
1526 molecular orbitals as shown below:
1527
1528 Closed-shell systems:
1529
1530 <MoleculeID>
1531 .Molecule
1532 .Molecule
1533 .BallAndStick
1534 .HOMO
1535 .Alpha
1536 .Cube
1537 .Volume
1538 .Mesh
1539 .Negative_Phase
1540 .Positive_Phase
1541 .Surface
1542 .Negative_Phase
1543 .Positive_Phase
1544 .LUMO
1545 .Alpha
1546 .Cube
1547 .Volume
1548 .Mesh
1549 .Negative_Phase
1550 .Positive_Phase
1551 .Surface
1552 .Negative_Phase
1553 .Positive_Phase
1554 <MoleculeID>
1555 .Molecule
1556 ... ... ...
1557 .HOMO
1558 ... ... ...
1559 .LUMO
1560 ... ... ...
1561
1562 Open-shell systems:
1563
1564 <MoleculeID>
1565 .Molecule
1566 .Molecule
1567 .BallAndStick
1568 .DOMO
1569 .Alpha
1570 .Cube
1571 .Volume
1572 .Mesh
1573 .Negative_Phase
1574 .Positive_Phase
1575 .Surface
1576 .Negative_Phase
1577 .Positive_Phase
1578 .Beta
1579 .Cube
1580 .Volume
1581 .Mesh
1582 .Negative_Phase
1583 .Positive_Phase
1584 .Surface
1585 .Negative_Phase
1586 .Positive_Phase
1587 .LVMO
1588 .Alpha
1589 .Cube
1590 .Volume
1591 .Mesh
1592 .Negative_Phase
1593 .Positive_Phase
1594 .Surface
1595 .Negative_Phase
1596 .Positive_Phase
1597 .Beta
1598 .Cube
1599 .Volume
1600 .Mesh
1601 .Negative_Phase
1602 .Positive_Phase
1603 .Surface
1604 .Negative_Phase
1605 .Positive_Phase
1606 .SOMO
1607 .Alpha
1608 .Cube
1609 .Volume
1610 .Mesh
1611 .Negative_Phase
1612 .Positive_Phase
1613 .Surface
1614 .Negative_Phase
1615 .Positive_Phase
1616 .Alpha_<Num>
1617 ... ... ...
1618 <MoleculeID>
1619 .Molecule
1620 ... ... ...
1621 .DOMO
1622 ... ... ...
1623 .LVMO
1624 ... ... ...
1625 .SOMO
1626 ... ... ...
1627
1628 Options:
1629 -b, --basisSet <text> [default: auto]
1630 Basis set to use for calculating single point energy before generating
1631 cube files for frontier molecular orbitals. Default: 6-31+G** for sulfur
1632 containing molecules; Otherwise, 6-31G** [ Ref 150 ]. The specified
1633 value must be a valid Psi4 basis set. No validation is performed.
1634
1635 The following list shows a representative sample of basis sets available
1636 in Psi4:
1637
1638 STO-3G, 6-31G, 6-31+G, 6-31++G, 6-31G*, 6-31+G*, 6-31++G*,
1639 6-31G**, 6-31+G**, 6-31++G**, 6-311G, 6-311+G, 6-311++G,
1640 6-311G*, 6-311+G*, 6-311++G*, 6-311G**, 6-311+G**, 6-311++G**,
1641 cc-pVDZ, cc-pCVDZ, aug-cc-pVDZ, cc-pVDZ-DK, cc-pCVDZ-DK, def2-SVP,
1642 def2-SVPD, def2-TZVP, def2-TZVPD, def2-TZVPP, def2-TZVPPD
1643
1644 -e, --examples
1645 Print examples.
1646 -h, --help
1647 Print this help message.
1648 -i, --infile <infile>
1649 Input file name.
1650 --infileParams <Name,Value,...> [default: auto]
1651 A comma delimited list of parameter name and value pairs for reading
1652 molecules from files. The supported parameter names for different file
1653 formats, along with their default values, are shown below:
1654
1655 SD, MOL: removeHydrogens,no,sanitize,yes,strictParsing,yes
1656
1657 -m, --methodName <text> [default: auto]
1658 Method to use for calculating single point energy before generating
1659 cube files for frontier molecular orbitals. Default: B3LYP [ Ref 150 ].
1660 The specified value must be a valid Psi4 method name. No validation is
1661 performed.
1662
1663 The following list shows a representative sample of methods available
1664 in Psi4:
1665
1666 B1LYP, B2PLYP, B2PLYP-D3BJ, B2PLYP-D3MBJ, B3LYP, B3LYP-D3BJ,
1667 B3LYP-D3MBJ, CAM-B3LYP, CAM-B3LYP-D3BJ, HF, HF-D3BJ, HF3c, M05,
1668 M06, M06-2x, M06-HF, M06-L, MN12-L, MN15, MN15-D3BJ,PBE, PBE0,
1669 PBEH3c, PW6B95, PW6B95-D3BJ, WB97, WB97X, WB97X-D, WB97X-D3BJ
1670
1671 --mode <GenerateCubeFiles, VisualizeCubeFiles, or Both> [default: Both]
1672 Generate and visualize cube files for frontier molecular orbitals. The
1673 'VisualizeCubes' value skips the generation of new cube files and uses
1674 existing cube files for visualization of molecular orbitals. Multiprocessing
1675 is not supported during 'VisualizeCubeFiles' value of '--mode' option.
1676 --mp <yes or no> [default: no]
1677 Use multiprocessing.
1678
1679 By default, input data is retrieved in a lazy manner via mp.Pool.imap()
1680 function employing lazy RDKit data iterable. This allows processing of
1681 arbitrary large data sets without any additional requirements memory.
1682
1683 All input data may be optionally loaded into memory by mp.Pool.map()
1684 before starting worker processes in a process pool by setting the value
1685 of 'inputDataMode' to 'InMemory' in '--mpParams' option.
1686
1687 A word to the wise: The default 'chunkSize' value of 1 during 'Lazy' input
1688 data mode may adversely impact the performance. The '--mpParams' section
1689 provides additional information to tune the value of 'chunkSize'.
1690 --mpParams <Name,Value,...> [default: auto]
1691 A comma delimited list of parameter name and value pairs to configure
1692 multiprocessing.
1693
1694 The supported parameter names along with their default and possible
1695 values are shown below:
1696
1697 chunkSize, auto
1698 inputDataMode, Lazy [ Possible values: InMemory or Lazy ]
1699 numProcesses, auto [ Default: mp.cpu_count() ]
1700
1701 These parameters are used by the following functions to configure and
1702 control the behavior of multiprocessing: mp.Pool(), mp.Pool.map(), and
1703 mp.Pool.imap().
1704
1705 The chunkSize determines chunks of input data passed to each worker
1706 process in a process pool by mp.Pool.map() and mp.Pool.imap() functions.
1707 The default value of chunkSize is dependent on the value of 'inputDataMode'.
1708
1709 The mp.Pool.map() function, invoked during 'InMemory' input data mode,
1710 automatically converts RDKit data iterable into a list, loads all data into
1711 memory, and calculates the default chunkSize using the following method
1712 as shown in its code:
1713
1714 chunkSize, extra = divmod(len(dataIterable), len(numProcesses) * 4)
1715 if extra: chunkSize += 1
1716
1717 For example, the default chunkSize will be 7 for a pool of 4 worker processes
1718 and 100 data items.
1719
1720 The mp.Pool.imap() function, invoked during 'Lazy' input data mode, employs
1721 'lazy' RDKit data iterable to retrieve data as needed, without loading all the
1722 data into memory. Consequently, the size of input data is not known a priori.
1723 It's not possible to estimate an optimal value for the chunkSize. The default
1724 chunkSize is set to 1.
1725
1726 The default value for the chunkSize during 'Lazy' data mode may adversely
1727 impact the performance due to the overhead associated with exchanging
1728 small chunks of data. It is generally a good idea to explicitly set chunkSize to
1729 a larger value during 'Lazy' input data mode, based on the size of your input
1730 data and number of processes in the process pool.
1731
1732 The mp.Pool.map() function waits for all worker processes to process all
1733 the data and return the results. The mp.Pool.imap() function, however,
1734 returns the the results obtained from worker processes as soon as the
1735 results become available for specified chunks of data.
1736
1737 The order of data in the results returned by both mp.Pool.map() and
1738 mp.Pool.imap() functions always corresponds to the input data.
1739 -o, --outfile <outfile>
1740 Output file name for PyMOL PML file. The PML output file, along with cube
1741 files, is generated in a local directory corresponding to '--outfilesDir'
1742 option.
1743 --outfilesDir <text> [default: auto]
1744 Directory name containing PML and cube files. Default:
1745 <OutfileRoot>_FrontierOrbitals. This directory must be present during
1746 'VisualizeCubeFiles' value of '--mode' option.
1747 --outfilesMolPrefix <MolNum, MolName, Both> [default: Both]
1748 Molecule prefix to use for the names of cube files. Possible values:
1749 MolNum, MolName, or Both. By default, both molecule number and name
1750 are used. The format of molecule prefix is as follows: MolNum - Mol<Num>;
1751 MolName - <MolName>, Both: Mol<Num>_<MolName>. Empty molecule names
1752 are ignored. Molecule numbers are used for empty molecule names.
1753 --overwrite
1754 Overwrite existing files.
1755 --psi4CubeFilesParams <Name,Value,...> [default: auto]
1756 A comma delimited list of parameter name and value pairs for generating
1757 Psi4 cube files.
1758
1759 The supported parameter names along with their default and possible
1760 values are shown below:
1761
1762 gridSpacing, 0.2, gridOverage, 4.0, isoContourThreshold, 0.85
1763
1764 gridSpacing: Grid spacing for generating cube files. Units: Bohr. A higher
1765 value reduces the size of the cube files on the disk. This option corresponds
1766 to Psi4 option CUBIC_GRID_SPACING.
1767
1768 gridOverage: Grid overage for generating cube files. Units: Bohr.This option
1769 corresponds to Psi4 option CUBIC_GRID_OVERAGE.
1770
1771 isoContourThreshold: IsoContour values for generating cube files that capture
1772 specified percent of the probability density using the least amount of grid
1773 points. Default: 0.85 (85%). This option corresponds to Psi4 option
1774 CUBEPROP_ISOCONTOUR_THRESHOLD.
1775 --psi4OptionsParams <Name,Value,...> [default: none]
1776 A comma delimited list of Psi4 option name and value pairs for setting
1777 global and module options. The names are 'option_name' for global options
1778 and 'module_name__option_name' for options local to a module. The
1779 specified option names must be valid Psi4 names. No validation is
1780 performed.
1781
1782 The specified option name and value pairs are processed and passed to
1783 psi4.set_options() as a dictionary. The supported value types are float,
1784 integer, boolean, or string. The float value string is converted into a float.
1785 The valid values for a boolean string are yes, no, true, false, on, or off.
1786 --psi4RunParams <Name,Value,...> [default: auto]
1787 A comma delimited list of parameter name and value pairs for configuring
1788 Psi4 jobs.
1789
1790 The supported parameter names along with their default and possible
1791 values are shown below:
1792
1793 MemoryInGB, 1
1794 NumThreads, 1
1795 OutputFile, auto [ Possible values: stdout, quiet, or FileName ]
1796 ScratchDir, auto [ Possivle values: DirName]
1797 RemoveOutputFile, yes [ Possible values: yes, no, true, or false]
1798
1799 These parameters control the runtime behavior of Psi4.
1800
1801 The default file name for 'OutputFile' is <InFileRoot>_Psi4.out. The PID
1802 is appended to output file name during multiprocessing as shown:
1803 <InFileRoot>_Psi4_<PIDNum>.out. The 'stdout' value for 'OutputType'
1804 sends Psi4 output to stdout. The 'quiet' or 'devnull' value suppresses
1805 all Psi4 output.
1806
1807 The default 'Yes' value of 'RemoveOutputFile' option forces the removal
1808 of any existing Psi4 before creating new files to append output from
1809 multiple Psi4 runs.
1810
1811 The option 'ScratchDir' is a directory path to the location of scratch
1812 files. The default value corresponds to Psi4 default. It may be used to
1813 override the deafult path.
1814 --pymolViewParams <Name,Value,...> [default: auto]
1815 A comma delimited list of parameter name and value pairs for visualizing
1816 cube files in PyMOL.
1817
1818 contourColor1, red, contourColor2, blue,
1819 contourLevel1, auto, contourLevel2, auto,
1820 contourLevelAutoAt, 0.75,
1821 displayMolecule, BallAndStick, displaySphereScale, 0.2,
1822 displayStickRadius, 0.1, hideHydrogens, yes,
1823 meshWidth, 0.5, meshQuality, 2,
1824 surfaceQuality, 2, surfaceTransparency, 0.25,
1825 volumeColorRamp, auto, volumeColorRampOpacity,0.2
1826 volumeContourWindowFactor,0.05
1827
1828 contourColor1 and contourColor2: Color to use for visualizing volumes,
1829 meshes, and surfaces corresponding to the negative and positive values
1830 in cube files. The specified values must be valid PyMOL color names. No
1831 validation is performed.
1832
1833 contourLevel1 and contourLevel2: Contour levels to use for visualizing
1834 volumes, meshes, and surfaces corresponding to the negative and positive
1835 values in cube files. Default: auto. The specified values for contourLevel1
1836 and contourLevel2 must be negative and positive numbers.
1837
1838 The contour levels are automatically calculated by default. The isocontour
1839 range for specified percent of the density is retrieved from the cube files.
1840 The contour levels are set at 'contourLevelAutoAt' of the absolute maximum
1841 value of the isocontour range. For example: contour levels are set to plus and
1842 minus 0.03 at 'contourLevelAutoAt' of 0.5 for isocontour range of -0.06 to
1843 0.06 covering specified percent of the density.
1844
1845 contourLevelAutoAt: Set contour levels at specified fraction of the absolute
1846 maximum value of the isocontour range retrieved from the cube files. This
1847 option is only used during the automatic calculations of the contour levels.
1848
1849 hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
1850 values: yes or no.
1851
1852 displayMolecule: Display mode for molecules. Possible values: Sticks or
1853 BallAndStick. Both displays objects are created for molecules.
1854
1855 displaySphereScale: Sphere scale for displaying molecule during
1856 BallAndStick display.
1857
1858 displayStickRadius: Stick radius for displaying molecule during Sticks
1859 and BallAndStick display.
1860
1861 hideHydrogens: Hide hydrogens in molecules. Default: yes. Possible
1862 values: yes or no.
1863 meshWidth: Line width for mesh lines to visualize cube files.
1864
1865 meshQuality: Mesh quality for meshes to visualize cube files. The
1866 higher values represents better quality.
1867
1868 meshWidth: Line width for mesh lines to visualize cube files.
1869
1870 surfaceQuality: Surface quality for surfaces to visualize cube files.
1871 The higher values represents better quality.
1872
1873 surfaceTransparency: Surface transparency for surfaces to visualize cube
1874 files.
1875
1876 volumeColorRamp: Name of a PyMOL volume color ramp to use for visualizing
1877 cube files. Default name(s): <OutfielsMolPrefix>_psi4_cube_mo or psi4_cube_mo
1878 The default volume color ramps are automatically generated using contour
1879 levels and colors during 'auto' value of 'volumeColorRamp'. An explicitly
1880 specified value must be a valid PyMOL volume color ramp. No validation is
1881 preformed.
1882
1883 VolumeColorRampOpacity: Opacity for generating volume color ramps
1884 for visualizing cube files. This value is equivalent to 1 minus Transparency.
1885
1886 volumeContourWindowFactor: Fraction of contour level representing window
1887 widths around contour levels during generation of volume color ramps for
1888 visualizing cube files. For example, the value of 0.05 implies a ramp window
1889 size of 0.0015 at contour level of 0.03.
1890 -q, --quiet <yes or no> [default: no]
1891 Use quiet mode. The warning and information messages will not be printed.
1892 -r, --reference <text> [default: auto]
1893 Reference wave function to use for calculating single point energy before
1894 generating cube files for frontier molecular orbitals. Default: RHF or UHF.
1895 The default values are Restricted Hartree-Fock (RHF) for closed-shell molecules
1896 with all electrons paired and Unrestricted artree-Fock (UHF) for open-shell
1897 molecules with unpaired electrons.
1898
1899 The specified value must be a valid Psi4 reference wave function. No validation
1900 is performed. For example: ROHF, CUHF, RKS, etc.
1901
1902 The spin multiplicity determines the default value of reference wave function
1903 for input molecules. It is calculated from number of free radical electrons using
1904 Hund's rule of maximum multiplicity defined as 2S + 1 where S is the total
1905 electron spin. The total spin is 1/2 the number of free radical electrons in a
1906 molecule. The value of 'SpinMultiplicity' molecule property takes precedence
1907 over the calculated value of spin multiplicity.
1908 -w, --workingdir <dir>
1909 Location of working directory which defaults to the current directory.
1910
1911 Examples:
1912 To generate and visualize frontier molecular orbitals based on a single point
1913 energy calculation using B3LYP/6-31G** and B3LYP/6-31+G** for non-sulfur
1914 and sulfur containing molecules in a SD file with 3D structures, use RHF and
1915 UHF for closed-shell and open-shell molecules, and write a new PML file, type:
1916
1917 % Psi4VisualizeFrontierOrbitals.py -i Psi4Sample3D.sdf
1918 -o Psi4Sample3DOut.pml
1919
1920 To run the first example to only generate cube files and skip generation of
1921 a PML file to visualize frontier molecular orbitals, type:
1922
1923 % Psi4VisualizeFrontierOrbitals.py --mode GenerateCubeFiles
1924 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1925
1926 To run the first example to skip generation of cube files and use existing cube
1927 files to visualize frontier molecular orbitals and write out a PML file, type:
1928
1929 % Psi4VisualizeFrontierOrbitals.py --mode VisualizeCubeFiles
1930 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1931
1932 To run the first example in multiprocessing mode on all available CPUs
1933 without loading all data into memory and write out a PML file, type:
1934
1935 % Psi4VisualizeFrontierOrbitals.py --mp yes -i Psi4Sample3D.sdf
1936 -o Psi4Sample3DOut.pml
1937
1938 To run the first example in multiprocessing mode on all available CPUs
1939 by loading all data into memory and write out a PML file, type:
1940
1941 % Psi4VisualizeFrontierOrbitals.py --mp yes --mpParams "inputDataMode,
1942 InMemory" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1943
1944 To run the first example in multiprocessing mode on all available CPUs
1945 without loading all data into memory along with multiple threads for each
1946 Psi4 run and write out a SD file, type:
1947
1948 % Psi4VisualizeFrontierOrbitals.py --mp yes --psi4RunParams
1949 "NumThreads,2" -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1950
1951 To run the first example in using a specific set of parameters to generate and
1952 visualize frontier molecular orbitals and write out a PML file, type:
1953
1954 % Psi4VisualizeFrontierOrbitals.py --mode both -m SCF -b aug-cc-pVDZ
1955 --psi4CubeFilesParams "gridSpacing, 0.2, gridOverage, 4.0"
1956 --psi4RunParams "MemoryInGB, 2" --pymolViewParams "contourColor1,
1957 red, contourColor2, blue,contourLevel1, -0.04, contourLevel2, 0.04,
1958 contourLevelAutoAt, 0.75,volumeColorRamp, auto,
1959 volumeColorRampOpacity,0.25, volumeContourWindowFactor,0.05"
1960 -i Psi4Sample3D.sdf -o Psi4Sample3DOut.pml
1961
1962 Author:
1963 Manish Sud(msud@san.rr.com)
1964
1965 See also:
1966 Psi4PerformMinimization.py, Psi4GenerateConformers.py,
1967 Psi4VisualizeDualDescriptors.py, Psi4VisualizeElectrostaticPotential.py
1968
1969 Copyright:
1970 Copyright (C) 2026 Manish Sud. All rights reserved.
1971
1972 The functionality available in this script is implemented using Psi4, an
1973 open source quantum chemistry software package, and RDKit, an open
1974 source toolkit for cheminformatics developed by Greg Landrum.
1975
1976 This file is part of MayaChemTools.
1977
1978 MayaChemTools is free software; you can redistribute it and/or modify it under
1979 the terms of the GNU Lesser General Public License as published by the Free
1980 Software Foundation; either version 3 of the License, or (at your option) any
1981 later version.
1982
1983 """
1984
1985 if __name__ == "__main__":
1986 main()