Skip to content

Commit

Permalink
Adjusted NWChem specification parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphaelRobidas committed Sep 1, 2024
1 parent 2806dcd commit 9744e28
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 115 deletions.
164 changes: 75 additions & 89 deletions ccinput/packages/nwchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
get_basis_set,
clean_xyz,
warn,
parse_specifications,
)
from ccinput.constants import (
CalcType,
Expand Down Expand Up @@ -246,96 +247,81 @@ def parse_custom_basis_set(self, base_bs):
self.basis_set += "\n".join(to_append_ecp)
self.basis_set += " end"

def handle_specifications(self):
if self.clean(self.calc.parameters.specifications).strip() != "":
temp = "\n" # Here we will store frequency related specifiations in case of FREQOPT calculations
s = self.separate_lines(self.calc.parameters.specifications)
# Could be more sophisticated to catch other incorrect specifications
if s.count("(") != s.count(")"):
raise InvalidParameter(
"Invalid specifications: parenthesis not matching"
def add_option(self, key, option):
temp = "\n" # Here we will store frequency related specifications in case of FREQOPT calculation
if option == "":
# To make a difference between NEB (default MEP method) and freezing string method,
# User has to put some of the following keyword as specification, independant of what calculation was specified in input
if key in [
"string",
"freezing string sethod",
"fsm",
"freezing string",
]:
self.tasks = self.tasks.replace("neb", "string")
else:
self.additional_block += f"{key} \n"
else:
"""
command = matched.group(1)
if command.find(",") != -1:
command = command.replace(",", "\n").strip()
"""
command = option.replace("=", " ")
block_name = key # spec[: matched.span(1)[0] - 1]
if block_name == "scf" or block_name == "dft" or block_name == "hf":
if command == "adft" and self.calc.parameters.density_fitting == "":
raise InvalidParameter("adft keyword requires auxilary basis set")
self.method_block += f"{command} \n"
elif (block_name == "opt" or block_name == "ts") and (
self.calc.type
in [
CalcType.CONSTR_OPT,
CalcType.OPT,
CalcType.TS,
CalcType.OPTFREQ,
]
):
if self.calculation_block == "":
self.calculation_block += f"\n driver \n"
self.calculation_block += f"{command} \n"
elif block_name == "nmr" and self.calc.type == CalcType.NMR:
self.calculation_block += f"{command} \n"
elif block_name == "freq" and self.calc.type == CalcType.FREQ:
if self.calculation_block == "":
self.calculation_block += f"\n freq \n"
self.calculation_block += f"{command} \n"
elif block_name == "freq" and self.calc.type == CalcType.OPTFREQ:
temp += f"{command} \n"
elif (
block_name in ["neb", "string", "fsm", "mep"]
and self.calc.type == CalcType.MEP
):
if self.calculation_block == "":
self.calculation_block += f"\n neb \n"
self.calculation_block += f"{command} \n"
elif block_name == "sol" or block_name == "cosmo" or block_name == "smd":
self.additional_block = self.additional_block.replace(
"cosmo \n", f"cosmo \n {command} \n"
)
for spec in s.split("\n"):
# format of the specifications is BLOCK_NAME1(command1);BLOCK_NAME2(command2);...
matched = re.search(r".*\((.*)\)", spec)
if matched == None:
# To make a difference between neb(defualt mep method) and freezing string method
# User has to put some of the following keyword as specification, independant of what calculation was specified in input
if spec in [
"string",
"freezing string sethod",
"fsm",
"freezing string",
]:
self.tasks = self.tasks.replace("neb", "string")
else:
self.additional_block += f"{spec} \n"
else:
command = matched.group(1)
if command.find(",") != -1:
command = command.replace(",", "\n").strip()
block_name = spec[: matched.span(1)[0] - 1]
if block_name == "scf" or block_name == "dft" or block_name == "hf":
if (
command == "adft"
and self.calc.parameters.density_fitting == ""
):
raise InvalidParameter(
"adft keyword requires auxilary basis set"
)
self.method_block += f"{command} \n"
elif (block_name == "opt" or block_name == "ts") and (
self.calc.type
in [
CalcType.CONSTR_OPT,
CalcType.OPT,
CalcType.TS,
CalcType.OPTFREQ,
]
):
if self.calculation_block == "":
self.calculation_block += f"\n driver \n"
self.calculation_block += f"{command} \n"
elif block_name == "nmr" and self.calc.type == CalcType.NMR:
self.calculation_block += f"{command} \n"
elif block_name == "freq" and self.calc.type == CalcType.FREQ:
if self.calculation_block == "":
self.calculation_block += f"\n freq \n"
self.calculation_block += f"{command} \n"
elif block_name == "freq" and self.calc.type == CalcType.OPTFREQ:
temp += f"{command} \n"
elif (
block_name in ["neb", "string", "fsm", "mep"]
and self.calc.type == CalcType.MEP
):
if self.calculation_block == "":
self.calculation_block += f"\n neb \n"
self.calculation_block += f"{command} \n"
elif (
block_name == "sol"
or block_name == "cosmo"
or block_name == "smd"
):
self.additional_block = self.additional_block.replace(
"cosmo \n", f"cosmo \n {command} \n"
)
elif (
block_name == "mp2"
and self.calc.parameters.theory_level == "mp2"
):
self.method_block += f"{command} \n"
elif (
block_name == "cc"
and self.calc.parameters.theory_level == "ccsd"
):
self.method_block += f"{command} \n"
elif (
block_name in ["mcscf", "casscf"]
and self.calc.type == CalcType.SP
):
self.method_block += f"{command} \n"
if temp != "\n":
self.additional_block += f"\n freq {temp} end \n"
elif block_name == "mp2" and self.calc.parameters.theory_level == "mp2":
self.method_block += f"{command} \n"
elif block_name == "cc" and self.calc.parameters.theory_level == "ccsd":
self.method_block += f"{command} \n"
elif block_name in ["mcscf", "casscf"] and self.calc.type == CalcType.SP:
self.method_block += f"{command} \n"

if temp != "\n":
self.additional_block += f"\n freq {temp} end \n"

def handle_specifications(self):
clean_specs = self.clean(
self.calc.parameters.specifications.replace(";", " ")
).strip()

if clean_specs != "":
parse_specifications(clean_specs, self.add_option, condense=False)

if self.tasks.find("string") != -1:
self.calculation_block = self.calculation_block.replace("neb", "string")
# Check if there are necessary specifications for mcscf calculation:
Expand Down
21 changes: 0 additions & 21 deletions ccinput/packages/orca.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,6 @@ def handle_specifications(self):

parse_specifications(_specifications, self.add_option, condense=False)

"""
if _specifications != "":
sspecs = _specifications.split()
ind = 0
while ind < len(sspecs):
spec = sspecs[ind]
if spec == "--phirshfeld":
self.add_to_block("output", ["Print[ P_Hirshfeld] 1"])
elif spec == "--nimages":
nimages = sspecs[ind + 1]
try:
nimages = int(nimages)
except ValueError:
raise InvalidParameter("Invalid specifications")
self.specifications["nimages"] = nimages
ind += 1
ind += 1
"""

if self.calc.parameters.d3:
self.specifications_list.append("d3zero")
elif self.calc.parameters.d3bj:
Expand Down
108 changes: 108 additions & 0 deletions ccinput/tests/test_nwchem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2616,6 +2616,114 @@ def test_specifications_alternative2(self):

self.assertTrue(self.is_equivalent(REF, inp.input_file))

def test_specifications_alternative3(self):
params = {
"nproc": 8,
"mem": "10000MB",
"type": "Geometrical Optimisation",
"file": "ethanol.xyz",
"software": "nwchem",
"method": "M06-2X",
"basis_set": "Def2-SVP",
"charge": "0",
"specifications": "SCF(Tight); opt(maxiter=5) scf(direct)",
}

inp = self.generate_calculation(**params)

REF = """
TITLE "File created by ccinput"
start ethanol
memory total 1250 mb
charge 0
geometry units angstroms noautosym
C -1.31970000 -0.64380000 0.00000000
H -0.96310000 -1.65260000 0.00000000
H -0.96310000 -0.13940000 -0.87370000
H -2.38970000 -0.64380000 0.00000000
C -0.80640000 0.08220000 1.25740000
H -1.16150000 1.09160000 1.25640000
H -1.16470000 -0.42110000 2.13110000
O 0.62360000 0.07990000 1.25870000
H 0.94410000 0.53240000 2.04240000
end
basis
* library Def2-SVP
end
dft
xc m06-2x
mult 1
tight
direct
end
driver
maxiter 5
end
task dft optimize
"""

self.assertTrue(self.is_equivalent(REF, inp.input_file))

def test_specifications_alternative4(self):
params = {
"nproc": 8,
"mem": "10000MB",
"type": "Geometrical Optimisation",
"file": "ethanol.xyz",
"software": "nwchem",
"method": "M06-2X",
"basis_set": "Def2-SVP",
"charge": "0",
"specifications": "SCF(Tight, direct); opt(maxITer=5, truST=0.2, convggm 5.0d-04)",
}

inp = self.generate_calculation(**params)

REF = """
TITLE "File created by ccinput"
start ethanol
memory total 1250 mb
charge 0
geometry units angstroms noautosym
C -1.31970000 -0.64380000 0.00000000
H -0.96310000 -1.65260000 0.00000000
H -0.96310000 -0.13940000 -0.87370000
H -2.38970000 -0.64380000 0.00000000
C -0.80640000 0.08220000 1.25740000
H -1.16150000 1.09160000 1.25640000
H -1.16470000 -0.42110000 2.13110000
O 0.62360000 0.07990000 1.25870000
H 0.94410000 0.53240000 2.04240000
end
basis
* library Def2-SVP
end
dft
xc m06-2x
mult 1
tight
direct
end
driver
maxiter 5
trust 0.2
convggm 5.0d-04
end
task dft optimize
"""

self.assertTrue(self.is_equivalent(REF, inp.input_file))

def test_specifications_mixed(self):
params = {
"nproc": 8,
Expand Down
39 changes: 39 additions & 0 deletions ccinput/tests/test_orca.py
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,45 @@ def test_NEB2(self):
"""
self.assertTrue(self.is_equivalent(REF, inp.input_file))

def test_NEB3(self):
params = {
"nproc": 8,
"type": "Minimum Energy Path",
"file": "elimination_substrate.xyz",
"auxiliary_file": "elimination_product.xyz",
"software": "ORCA",
"specifications": "neb(nimages=12,printlevel=2)",
"charge": -1,
"method": "gfn2-xtb",
}

inp = self.generate_calculation(**params)

REF = """!NEB xtb2
*xyz -1 1
C -0.74277 0.14309 0.12635
C 0.71308 -0.12855 -0.16358
Cl 0.90703 -0.47793 -1.61303
H -0.84928 0.38704 1.20767
H -1.36298 -0.72675 -0.06978
H -1.11617 0.99405 -0.43583
H 1.06397 -0.95639 0.44985
H 1.30839 0.75217 0.07028
O -0.91651 0.74066 3.00993
H -1.82448 0.94856 3.28105
*
%neb
nimages 12
printlevel 2
product "calc2.xyz"
end
%pal
nprocs 8
end
%MaxCore 125
"""
self.assertTrue(self.is_equivalent(REF, inp.input_file))

def test_NEB_aux_name(self):
params = {
"nproc": 8,
Expand Down
2 changes: 1 addition & 1 deletion ccinput/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ def parse_specifications(specs, add_option_fn, condense=True):
elif c == ")":
remove = False

for spec in _specifications.split(" "):
for spec in _specifications.lower().split(" "):
if spec.strip() == "":
continue
if spec.find("(") != -1:
Expand Down
Loading

0 comments on commit 9744e28

Please sign in to comment.