Source code for pysisyphus.calculators.Psi4
import re
import textwrap
import numpy as np
from pysisyphus.calculators.Calculator import Calculator
[docs]
class Psi4(Calculator):
conf_key = "psi4"
def __init__(
self,
method,
basis,
to_set=None,
to_import=None,
pcm="iefpcm",
solvent=None,
write_fchk=False,
**kwargs,
):
super().__init__(**kwargs)
self.method = method
self.basis = basis
self.to_set = {} if to_set is None else dict(to_set)
self.to_import = [] if to_import is None else list(to_import)
self.pcm = pcm
self.solvent = solvent
self.write_fchk = write_fchk
self.inp_fn = "psi4.inp"
self.out_fn = "psi4.out"
self.to_keep = ("inp", "psi4.out", "grad.npy", "hessian.npy")
self.parser_funcs = {
"energy": self.parse_energy,
"grad": self.parse_grad,
"hessian": self.parse_hessian,
}
self.base_cmd = self.get_cmd()
self.inp = textwrap.dedent(
"""
{to_import}
molecule mol{{
{xyz}
{charge} {mult}
no_reorient
no_com
symmetry c1
}}
set_num_threads({pal})
memory {mem} MB
{basis}
{to_set}
{pcm}
{method}
{fchk}
"""
)
[docs]
def get_fchk_str(self):
fchk_str = ""
if self.write_fchk:
fchk_fn = self.make_fn("wfn.fchk")
fchk_str = (
"fchk_writer = psi4.FCHKWriter(wfn)\n" f"fchk_writer.write('{fchk_fn}')"
)
return fchk_str
[docs]
def prepare_input(self, atoms, coords, calc_type):
xyz = self.prepare_coords(atoms, coords)
calc_types = {
"energy": "E, wfn = energy('{}', return_wfn=True)",
# Right now we don't need the wavefunction
# "grad": "G, wfn = gradient('{}', return_wfn=True)\n" \
# "G_arr = np.array(G)\n" \
# "np.save('grad', G_arr)",
# "hessian": "H, wfn = hessian('{}', return_wfn=True)\n" \
# "H_arr = np.array(H)\n" \
# "np.save('hessian', H_arr)",
"grad": "G, wfn = gradient('{}', return_wfn=True)\n"
"G_arr = np.array(G)\n"
"np.save('grad', G_arr)",
"hessian": "H, wfn = hessian('{}', return_wfn=True)\n"
"H_arr = np.array(H)\n"
"np.save('hessian', H_arr)",
}
method = calc_types[calc_type].format(self.method)
wfn_path = self.make_fn("wfn.npy")
method += f"\nWavefunction.to_file(wfn, '{wfn_path}')"
method += "\nprint('PARSE ENERGY:', wfn.energy())"
set_strs = [f"set {key} {value}" for key, value in self.to_set.items()]
set_strs = "\n".join(set_strs)
import_strs = [f"import {mod}" for mod in self.to_import]
import_strs = "\n".join(import_strs)
# Basis section
basis = self.basis
# Construct more complex basis input
if isinstance(basis, dict):
# Check if a global basis is given for all atoms. This must come
# first, otherwise Psi4 throws an error.
basis_lines = [
"basis {",
]
try:
basis_lines.append(f"assign {basis['assign']}")
except KeyError:
pass
# Add remaining lines
basis_lines.extend(
[
f"assign {atms} {bas}"
for atms, bas in basis.items()
if atms != "assign"
]
)
basis_lines.append("}")
basis = "\n".join(basis_lines)
# Use set when self.basis is a string
else:
basis = f"set basis {basis}"
# PCM section
pcm = ""
if self.solvent:
pcm = textwrap.dedent(
f"""
set pcm true
pcm = {{
Medium {{
SolverType = {self.pcm}
Solvent = {self.solvent}
}}
Cavity {{
Type = GePol
}}
}}
"""
)
inp = self.inp.format(
xyz=xyz,
charge=self.charge,
mult=self.mult,
basis=basis,
to_import=import_strs,
to_set=set_strs,
pcm=pcm,
method=method,
pal=self.pal,
mem=self.mem,
fchk=self.get_fchk_str(),
)
# inp = "\n".join([line.strip() for line in inp.split("\n")])
return inp
[docs]
def get_energy(self, atoms, coords, **prepare_kwargs):
calc_type = "energy"
inp = self.prepare_input(atoms, coords, calc_type)
results = self.run(inp, calc="energy")
return results
[docs]
def get_forces(self, atoms, coords, **prepare_kwargs):
calc_type = "grad"
inp = self.prepare_input(atoms, coords, calc_type)
results = self.run(inp, calc="grad")
return results
[docs]
def get_hessian(self, atoms, coords, **prepare_kwargs):
calc_type = "hessian"
inp = self.prepare_input(atoms, coords, calc_type)
results = self.run(inp, calc="hessian")
return results
[docs]
def run_calculation(self, atoms, coords, **prepare_kwargs):
return self.get_energy(atoms, coords)
[docs]
def parse_energy(self, path):
with open(path / "psi4.out") as handle:
text = handle.read()
en_regex = re.compile(r"PARSE ENERGY: ([\d\-\.]+)")
mobj = en_regex.search(text)
result = {"energy": float(mobj[1])}
return result
[docs]
def parse_grad(self, path):
gradient = np.load(path / "grad.npy")
forces = -gradient.flatten()
result = {
"forces": forces,
}
result.update(self.parse_energy(path))
return result
[docs]
def parse_hessian(self, path):
hessian = np.load(path / "hessian.npy")
result = {
"hessian": hessian,
}
result.update(self.parse_energy(path))
return result
def __str__(self):
return f"Psi4({self.name})"