Skip to content

Commit 6f6a443

Browse files
authored
Merge pull request #68 from TimotheMelin/add_pyscf_implicit_solvent
Use PYSCF for PCM calculations
2 parents 89ed160 + e4416f9 commit 6f6a443

File tree

7 files changed

+706
-20
lines changed

7 files changed

+706
-20
lines changed

PoltypeModules/optimization.py

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -715,11 +715,23 @@ def GeometryOptimization(poltype,mol,totcharge,suffix='1',loose=False,checkbonds
715715
comoptfname=poltype.comoptfname.replace('_1','_'+suffix)
716716
chkoptfname=poltype.chkoptfname.replace('_1','_'+suffix)
717717

718-
if (poltype.use_gaus==True or poltype.use_gausoptonly==True): # try to use gaussian for opt
718+
# Define which software will be used for the QM optimization
719+
if not poltype.use_gaus or not poltype.use_gausoptonly:
720+
if poltype.optpcm==True or (poltype.optpcm==-1 and poltype.pcm):
721+
Soft = 'PySCF'
722+
else:
723+
Soft = 'Psi4'
724+
else:
725+
Soft = 'Gaussian'
726+
727+
728+
if Soft == 'Gaussian': # try to use gaussian for opt
719729
term,error=poltype.CheckNormalTermination(logoptfname,errormessages=None,skiperrors=True)
720730
if not term or overridecheckterm==True:
721731

722732
poltype.WriteToLog("QM Geometry Optimization")
733+
poltype.WriteToLog(f'The software {Soft} will be used to do the QM optimization')
734+
723735
gen_optcomfile(poltype,comoptfname,poltype.numproc,poltype.maxmem,poltype.maxdisk,chkoptfname,mol,modred,torsionrestraints)
724736
cmdstr = 'GAUSS_SCRDIR=' + poltype.scrtmpdirgau + ' ' + poltype.gausexe + " " + comoptfname
725737
jobtooutputlog={cmdstr:os.getcwd()+r'/'+logoptfname}
@@ -749,15 +761,103 @@ def GeometryOptimization(poltype,mol,totcharge,suffix='1',loose=False,checkbonds
749761
optmol = load_structfile(poltype,logoptfname)
750762
optmol=rebuild_bonds(poltype,optmol,mol)
751763

752-
753-
else:
764+
if Soft == 'PySCF':
765+
766+
import importlib
767+
768+
try:
769+
importlib.import_module('pyscf')
770+
poltype.WriteToLog("PySCF is installed.\n")
771+
except ImportError:
772+
poltype.WriteToLog("PySCF is not installed.\n Make sure that pyscf version 2.7.0 is installed along with dft3, dft4 and pyscf-dispersion.\n You can install using: \n `pip install pyscf==2.7.0 dft3 dft4 pyscf-dispersion`")
773+
raise ImportError("PySCF is not installed.\n Make sure that pyscf version 2.7.0 is installed along with dft3, dft4 and pyscf-dispersion.\n You can install using: \n `pip install pyscf==2.7.0 dft3 dft4 pyscf-dispersion`")
774+
775+
776+
gen_optcomfile(poltype,comoptfname,poltype.numproc,poltype.maxmem,poltype.maxdisk,chkoptfname,mol,modred,torsionrestraints)
777+
term,error=poltype.CheckNormalTermination(logoptfname,errormessages=None,skiperrors=True)
778+
modred=False
779+
780+
# Import both PySCF setup class
781+
from pyscf_setup import PySCF_init_setup
782+
from pyscf_setup import PySCF_post_run
783+
784+
# Instantiate the initial pyscf setup object
785+
Opt_prep = PySCF_init_setup(poltype,os.getcwd(),comoptfname,mol,\
786+
skipscferror,charge)
787+
788+
# Read the gaussian input coordinate
789+
Opt_prep.read_Gauss_inp_coord()
790+
791+
# Write the initial xyz file to be used by PySCF
792+
Opt_prep.write_xyz('init')
793+
794+
# Write the torsion constraints to be used
795+
# during the QM optimization
796+
Opt_prep.write_geometric_tor_const('init',torsionrestraints)
797+
798+
# Write the PySCF input file
799+
Opt_prep.write_PySCF_input(True,False)
800+
801+
# Define the cmd to run PySCF
802+
cmd = f'python {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_inp_file} &> {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}'
803+
804+
# Create the dictionnary with the commnd to be used by CallJobsSeriallyLocalHost
805+
cmd_to_run = {cmd: f'{Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}'}
806+
807+
# If the user only want to create input file
808+
if poltype.checkinputonly==True:
809+
sys.exit()
810+
811+
# Make sure to delete the PySCF output file
812+
# before running it
813+
if os.path.isfile(f'{Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}'):
814+
os.remove(f'{Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}')
815+
816+
# Run PySCF with either external API
817+
# or SerialLocalHost
818+
if poltype.externalapi==None:
819+
finishedjobs,errorjobs=poltype.CallJobsSeriallyLocalHost(cmd_to_run,skiperrors)
820+
else:
821+
jobtoinputfilepaths = {cmd: [f'{Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_inp_file}']}
822+
jobtooutputfiles = {cmd: [f'{Opt_prep.PySCF_out_file}']}
823+
jobtoabsolutebinpath = {cmd: poltype.which('pyscf')}
824+
scratchdir = poltype.scrtmpdirpsi4
825+
jobtologlistfilepathprefix = f'{Opt_prep.init_data["topdir"]}/optimization_jobtolog_{poltype.molecprefix}'
826+
827+
call.CallExternalAPI(poltype,jobtoinputfilepaths,jobtooutputfiles,jobtoabsolutebinpath,scratchdir,jobtologlistfilepathprefix)
828+
finishedjobs,errorjobs=poltype.WaitForTermination(f'{Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}',False)
829+
830+
# Check normal termination of PySCF job
831+
term,error=poltype.CheckNormalTermination(f'{Opt_prep.PySCF_out_file}',None,skiperrors)
832+
833+
if error and term==False and skiperrors==False:
834+
# This poltype method does not exists...
835+
poltype.RaiseOutputFileError(f'{Opt_prep.PySCF_out_file}')
836+
837+
# Instantiate the post process PySCF class
838+
Opt_post = PySCF_post_run(poltype,os.getcwd(),f'{Opt_prep.PySCF_out_file}',mol)
839+
840+
# Read the output file from PySCF
841+
Opt_post.read_out()
842+
843+
# Use OpenBabel to build a molecule out of the optimized structure
844+
optmol = load_structfile(poltype,Opt_post.final_opt_xyz)
845+
optmol=rebuild_bonds(poltype,optmol,mol)
846+
847+
# Here we redefine logoptfname and poltype.logoptfname
848+
# to make sure that hereafter, the pyscf output is used!
849+
logoptfname = Opt_prep.PySCF_out_file
850+
poltype.logoptfname = Opt_prep.PySCF_out_file
851+
852+
if Soft == 'Psi4':
754853
gen_optcomfile(poltype,comoptfname,poltype.numproc,poltype.maxmem,poltype.maxdisk,chkoptfname,mol,modred,torsionrestraints)
755854
term,error=poltype.CheckNormalTermination(logoptfname,errormessages=None,skiperrors=True)
756855
modred=False
757856

758857
inputname,outputname=CreatePsi4OPTInputFile(poltype,comoptfname,comoptfname,mol,modred,bondanglerestraints,skipscferror,charge,loose,torsionrestraints)
759858
if term==False or overridecheckterm==True:
760859
poltype.WriteToLog("QM Geometry Optimization")
860+
poltype.WriteToLog(f'The software {Soft} will be used to do the QM optimization')
761861
cmdstr=' '.join(['psi4 ',poltype.psi4_args,inputname,logoptfname])
762862
jobtooutputlog={cmdstr:os.getcwd()+r'/'+logoptfname}
763863
jobtolog={cmdstr:os.getcwd()+r'/'+poltype.logfname}
@@ -767,10 +867,10 @@ def GeometryOptimization(poltype,mol,totcharge,suffix='1',loose=False,checkbonds
767867
jobtoinputfilepaths={cmdstr:[inputfilepath]}
768868
jobtooutputfiles={cmdstr:[logoptfname]}
769869
jobtoabsolutebinpath={cmdstr:poltype.which('psi4')}
870+
770871
if poltype.checkinputonly==True:
771872
sys.exit()
772873

773-
774874
if os.path.isfile(logoptfname):
775875
os.remove(logoptfname)
776876
if poltype.externalapi==None:
@@ -787,7 +887,10 @@ def GeometryOptimization(poltype,mol,totcharge,suffix='1',loose=False,checkbonds
787887
optmol = load_structfile(poltype,logoptfname.replace('.log','.xyz'))
788888
optmol=rebuild_bonds(poltype,optmol,mol)
789889
optmol.SetTotalCharge(totcharge)
790-
GrabFinalXYZStructure(poltype,logoptfname,logoptfname.replace('.log','.xyz'),mol)
890+
891+
if Soft != 'PySCF':
892+
GrabFinalXYZStructure(poltype,logoptfname,logoptfname.replace('.log','.xyz'),mol)
893+
791894
return optmol,error,torsionrestraints
792895

793896

@@ -801,6 +904,7 @@ def load_structfile(poltype,structfname):
801904
Referenced By: run_gaussian, tor_opt_sp, compute_qm_tor_energy, compute_mm_tor_energy
802905
Description: -
803906
"""
907+
804908
strctext = os.path.splitext(structfname)[1]
805909
tmpconv = openbabel.OBConversion()
806910
if strctext in '.fchk':

PoltypeModules/poltype.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@ class PolarizableTyper():
456456
espbasisset:str="aug-cc-pVTZ"
457457
torspbasisset:str="6-311+G*"
458458
optmethod:str='MP2'
459+
pyscf_opt_meth:str = 'wb97x_d3'
460+
pyscf_sol_imp:str = 'IEF-PCM' # C-PCM, SS(V)PE, COSMO
461+
pyscf_sol_eps:float = 78.3553 # Water
459462
toroptmethod:str='xtb'
460463
torspmethod:str='wB97X-D'
461464
dmamethod:str='MP2'
@@ -1189,6 +1192,12 @@ def __post_init__(self):
11891192
warnings.warn(f"Could not locate prmmod file '{fpath1}'")
11901193
elif newline.startswith("optmethod"):
11911194
self.optmethod = a
1195+
elif newline.startswith("pyscf_opt_met"):
1196+
self.pyscf_opt_met = a
1197+
elif newline.startswith("pyscf_sol_imp"):
1198+
self.pyscf_sol_imp = a
1199+
elif newline.startswith("pyscf_sol_eps"):
1200+
self.pyscf_sol_eps = a
11921201
elif newline.startswith("espmethod"):
11931202
self.espmethod = a
11941203
elif "torspmethod" in newline:
@@ -3373,6 +3382,7 @@ def CheckNormalTermination(self,logfname,errormessages=None,skiperrors=False):
33733382
5. If error occured, write out line containing error to poltype log file.
33743383
6. xtb only outputs the same filename for optimization jobs, so be sure to copy to desired filename after job finishes.
33753384
"""
3385+
33763386
error=False
33773387
term=False
33783388
lastupdatetofilename={}
@@ -3412,7 +3422,7 @@ def CheckNormalTermination(self,logfname,errormessages=None,skiperrors=False):
34123422
else:
34133423
if "Final optimized geometry" in line or "Electrostatic potential computed" in line or 'Psi4 exiting successfully' in line or "LBFGS -- Normal Termination due to SmallGrad" in line or "Normal termination" in line or 'Normal Termination' in line or 'Total Potential Energy' in line or 'Psi4 stopped on' in line or 'finished run' in line or ('Converged! =D' in line):
34143424
term=True
3415-
if ('Tinker is Unable to Continue' in line or 'error' in line or ' Error ' in line or ' ERROR ' in line or 'impossible' in line or 'software termination' in line or 'segmentation violation, address not mapped to object' in line or 'galloc: could not allocate memory' in line or 'Erroneous write.' in line or 'Optimization failed to converge!' in line) and 'DIIS' not in line and 'mpi' not in line and 'RMS Error' not in line:
3425+
if ('Tinker is Unable to Continue' in line or 'error' in line or ' Error ' in line or ' ERROR ' in line or 'impossible' in line or 'software termination' in line or 'segmentation violation, address not mapped to object' in line or 'galloc: could not allocate memory' in line or 'Erroneous write.' in line or 'Optimization failed to converge!' in line or 'Geometry optimization is not converged' in line or 'Error' in line) and 'DIIS' not in line and 'mpi' not in line and 'RMS Error' not in line:
34163426
error=True
34173427
errorline=line
34183428
if 'segmentation violation' in line and 'address not mapped to object' not in line or 'Waiting' in line or ('OptimizationConvergenceError' in line and 'except' in line) or "Error on total polarization charges" in line or 'Erroneous write' in line:
@@ -3514,7 +3524,13 @@ def call_subsystem(self,cmdstrs,wait=False,skiperrors=False):
35143524
# STEP 1
35153525
self.WriteToLog("Submitting: " + cmdstr+' '+'path'+' = '+os.getcwd())
35163526
# STEP 2
3517-
p = subprocess.Popen(cmdstr,shell=True,stdout=self.logfh, stderr=self.logfh)
3527+
if 'pyscf' in cmdstr:
3528+
out = cmdstr.split()[-1]
3529+
out_pyscf = open(out, 'w')
3530+
p = subprocess.Popen(cmdstr,shell=True,stdout=out_pyscf, stderr=out_pyscf)
3531+
else:
3532+
p = subprocess.Popen(cmdstr,shell=True,stdout=self.logfh, stderr=self.logfh)
3533+
35183534
procs.append(p)
35193535
self.cmdtopid[cmdstr]=p
35203536

@@ -4737,6 +4753,9 @@ def GenerateParameters(self):
47374753
self.localframe1 = [ 0 ] * mol.NumAtoms()
47384754
self.localframe2 = [ 0 ] * mol.NumAtoms()
47394755
self.WriteToLog("Atom Type Classification")
4756+
self.WriteToLog('Poltype image build time: ')
4757+
self.WriteToLog('Tinker commit: c9698d2101c5f66ce1d413f4aa2d5f62e4c22df2')
4758+
self.WriteToLog('Poltype commit: 51b235f8f0af0975c9afae789f671d698b44d7e5')
47404759
self.idxtosymclass,self.symmetryclass=symm.gen_canonicallabels(self,mol,None,self.usesymtypes,True)
47414760
# STEP 15
47424761
torgen.FindPartialDoubleBonds(self,m,mol) # need to find something to hardcode transfer for partial double amide/acid, currently will derive torsion parameters if doesnt find "good" match in torsion database
@@ -5155,6 +5174,9 @@ def GenerateParameters(self):
51555174
shutil.copy(self.xyzfname,self.xyzoutfile)
51565175
shutil.copy(self.xyzoutfile,self.tmpxyzfile)
51575176
shutil.copy(self.key7fname,self.tmpkeyfile)
5177+
5178+
shutil.copy(self.key7fname,'TEST_tim.key')
5179+
51585180
# STEP 51
51595181
if self.writeoutpolarize and self.writeoutmultipole==True:
51605182
opt.StructureMinimization(self,torsionrestraints)

PoltypeModules/pyscf_for_frag.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Import both PySCF setup class
2+
from pyscf_setup import PySCF_init_setup
3+
from pyscf_setup import PySCF_post_run
4+
import os
5+
6+
def setup_frag_opt(poltype,newtorset,toranglelist,\
7+
pre,inputmol,inputname,inputstruc):
8+
9+
"""
10+
This function setup the PySCF file to do
11+
a geometry optimization.
12+
13+
Inputs:
14+
- poltype: poltype class object (class obj)
15+
- newtorset: list of torsion to restrain (list of list)
16+
- toranglelist: list of torsion angle to restrain (list of float)
17+
- pre: prefix name (string)
18+
- inputmol: molecule object (babel or rdkit mol obj)
19+
- inputname: name of the input file (string)
20+
- inputstruc: name if the file with input geometry (string)
21+
22+
Outputs:
23+
- Opt_prep.PySCF_inp_file: Name of PySCF input file
24+
- Opt_prep.PySCF_out_file: Name of PySCF output file
25+
- cmd: command to run
26+
27+
"""
28+
29+
# Define the charge of the molecule
30+
charge = inputmol.GetTotalCharge()
31+
32+
# Instantiate the PySCF initial setup object
33+
Opt_prep = PySCF_init_setup(poltype,os.getcwd(),pre,inputmol,\
34+
False,charge)
35+
36+
# Define the input geometry file
37+
Opt_prep.PySCF_inp_xyz = inputstruc
38+
39+
# Write the geometric constrain file
40+
Opt_prep.write_geometric_tor_const(pre,newtorset)
41+
42+
# Write the PySCF input file: is_opt = True, is_frag = True
43+
Opt_prep.write_PySCF_input(True,True)
44+
45+
# Define the command to run
46+
cmd = f'python {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_inp_file} &> {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}'
47+
48+
return Opt_prep.PySCF_inp_file,Opt_prep.PySCF_out_file,cmd
49+
50+
51+
def read_frag_opt(poltype,outputlog,mol):
52+
53+
"""
54+
This function creates the post run PySCf object and
55+
reads the optimized geometry
56+
57+
Inputs:
58+
- poltype: poltype class object (class obj)
59+
- outputlog: Name of the output file (string)
60+
- mol: molecule object (babel or rdkit mol obj)
61+
62+
"""
63+
64+
# Instantiate the PySCF post run class
65+
Opt_post = PySCF_post_run(poltype,os.getcwd(),outputlog,mol)
66+
67+
# Read and create the optimized geometry file
68+
Opt_post.read_out()
69+
70+
71+
def setup_frag_sp(poltype,inputmol,inputstruc,pre):
72+
73+
"""
74+
This function set up the fragment SP calculation
75+
76+
Inputs:
77+
- poltype: poltype class object (class obj)
78+
- inputmol: molecule object (babel or rdkit mol obj)
79+
- inputstruc: name if the file with input geometry (string)
80+
- pre: prefix name (string)
81+
82+
Outputs:
83+
- Opt_prep.PySCF_inp_file: Name of PySCF input file
84+
- Opt_prep.PySCF_out_file: Name of PySCF output file
85+
- cmd: command to run
86+
87+
"""
88+
89+
# Define the charge of the molecule
90+
charge = inputmol.GetTotalCharge()
91+
92+
# Instantiate the PySCF initial setup object
93+
Opt_prep = PySCF_init_setup(poltype,os.getcwd(),pre,inputmol,\
94+
False,charge)
95+
96+
# Define the input geometry file
97+
Opt_prep.PySCF_inp_xyz = inputstruc
98+
99+
# Write the PySCF input file: is_opt = False, is_frag = True
100+
Opt_prep.write_PySCF_input(False,True)
101+
102+
# Define the command to run
103+
cmd = f'python {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_inp_file} &> {Opt_prep.init_data["topdir"]}/{Opt_prep.PySCF_out_file}'
104+
105+
return Opt_prep.PySCF_inp_file,Opt_prep.PySCF_out_file,cmd
106+

0 commit comments

Comments
 (0)