Skip to content

Commit 2806dcd

Browse files
Changed how ORCA specifications work
1 parent f6112fa commit 2806dcd

File tree

4 files changed

+156
-64
lines changed

4 files changed

+156
-64
lines changed

ccinput/packages/gaussian.py

+2-33
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_npxyz,
1313
check_fragments,
1414
add_fragments_xyz,
15+
parse_specifications,
1516
)
1617
from ccinput.constants import CalcType, ATOMIC_NUMBER, LOWERCASE_ATOMIC_SYMBOLS
1718
from ccinput.exceptions import InvalidParameter, ImpossibleCalculation
@@ -104,39 +105,7 @@ def add_commands(self, commands):
104105
def handle_specifications(self):
105106
s = self.clean(self.calc.parameters.specifications.lower())
106107

107-
# Could be more sophisticated to catch other incorrect specifications
108-
if s.count("(") != s.count(")"):
109-
raise InvalidParameter("Invalid specifications: parenthesis not matching")
110-
111-
_specifications = ""
112-
remove = False
113-
for c in s:
114-
if c == " " and remove:
115-
continue
116-
_specifications += c
117-
if c == "(":
118-
remove = True
119-
elif c == ")":
120-
remove = False
121-
122-
for spec in _specifications.split(" "):
123-
if spec.strip() == "":
124-
continue
125-
if spec.find("(") != -1:
126-
key, options = spec.split("(")
127-
options = options.replace(")", "")
128-
for option in options.split(","):
129-
if option.strip() != "":
130-
self.add_option(key, option)
131-
elif spec.find("=") != -1:
132-
try:
133-
key, option = spec.split("=")
134-
except ValueError:
135-
raise InvalidParameter(f"Invalid specification: {spec}")
136-
self.add_option(key, option)
137-
else:
138-
self.add_option(spec, "")
139-
108+
parse_specifications(s, self.add_option)
140109
if self.calc.parameters.d3:
141110
self.add_option("EmpiricalDispersion", "GD3")
142111
elif self.calc.parameters.d3bj:

ccinput/packages/orca.py

+35-21
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
get_abs_basis_set,
1414
clean_xyz,
1515
warn,
16+
parse_specifications,
1617
)
1718
from ccinput.exceptions import (
1819
InvalidParameter,
@@ -69,6 +70,7 @@ def __init__(self, calc):
6970
self.specifications = {}
7071
self.solvation_radii = {}
7172
self.aux_basis_sets = {}
73+
self.specifications_list = []
7274

7375
if self.calc.type not in self.CALC_TYPES:
7476
raise ImpossibleCalculation(
@@ -98,7 +100,7 @@ def confirmed_specifications(self):
98100

99101
def clean(self, s):
100102
WHITELIST = set(
101-
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/()=-,. "
103+
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/()=-,. {}_[]"
102104
)
103105
return "".join([c for c in s if c in WHITELIST])
104106

@@ -108,12 +110,27 @@ def add_to_block(self, block, lines):
108110
else:
109111
self.blocks[block] = lines
110112

113+
def add_option(self, key, option):
114+
if option == "":
115+
if key[-2:] == "/c":
116+
self.aux_basis_sets["C"] = get_basis_set(key[:-2], "orca")
117+
elif key[-3:] == "/jk":
118+
self.aux_basis_sets["JK"] = get_basis_set(key[:-3], "orca")
119+
elif key[-2:] == "/j":
120+
self.aux_basis_sets["J"] = get_basis_set(key[:-2], "orca")
121+
elif key not in self.specifications_list:
122+
self.specifications_list.append(key)
123+
else:
124+
self.add_to_block(key, [option.replace("=", " ")])
125+
111126
def handle_specifications(self):
112127
_specifications = (
113128
self.clean(self.calc.parameters.specifications).lower().strip()
114129
)
115130

116-
specifications_list = []
131+
parse_specifications(_specifications, self.add_option, condense=False)
132+
133+
"""
117134
if _specifications != "":
118135
sspecs = _specifications.split()
119136
ind = 0
@@ -129,24 +146,15 @@ def handle_specifications(self):
129146
raise InvalidParameter("Invalid specifications")
130147
self.specifications["nimages"] = nimages
131148
ind += 1
132-
elif spec[-2:] == "/c":
133-
self.aux_basis_sets["C"] = get_basis_set(spec[:-2], "orca")
134-
elif spec[-3:] == "/jk":
135-
self.aux_basis_sets["JK"] = get_basis_set(spec[:-3], "orca")
136-
elif spec[-2:] == "/j":
137-
self.aux_basis_sets["J"] = get_basis_set(spec[:-2], "orca")
138-
elif spec not in specifications_list:
139-
specifications_list.append(spec)
149+
140150
141151
ind += 1
152+
"""
142153

143154
if self.calc.parameters.d3:
144-
specifications_list.append("d3zero")
155+
self.specifications_list.append("d3zero")
145156
elif self.calc.parameters.d3bj:
146-
specifications_list.append("d3bj")
147-
148-
if len(specifications_list) > 0:
149-
self.additional_commands = " ".join(specifications_list)
157+
self.specifications_list.append("d3bj")
150158

151159
def handle_command(self):
152160
if self.calc.type == CalcType.NMR:
@@ -244,13 +252,15 @@ def handle_command(self):
244252
self.command_line = "SP "
245253
elif self.calc.type == CalcType.MEP: #### Second structure to handle
246254
self.command_line = "NEB "
247-
if "nimages" in self.specifications:
248-
nimages = self.specifications["nimages"]
255+
self.add_to_block("neb", [f'product "{self.calc.aux_name}.xyz"'])
256+
if "neb" in self.blocks:
257+
for entry in self.blocks["neb"]:
258+
if entry.find("nimages") != -1:
259+
break
260+
else:
261+
self.add_to_block("neb", ["nimages 8"])
249262
else:
250-
nimages = 8
251-
self.add_to_block(
252-
"neb", [f'product "{self.calc.aux_name}.xyz"', f"nimages {nimages}"]
253-
)
263+
self.add_to_block("neb", ["nimages 8"])
254264

255265
method = get_method(self.calc.parameters.method, "orca")
256266
if self.calc.parameters.theory_level not in [
@@ -446,7 +456,11 @@ def create_input_file(self):
446456

447457
self.block_lines += f"""%MaxCore {self.mem_per_core}"""
448458

459+
if len(self.specifications_list) > 0:
460+
self.additional_commands = " ".join(self.specifications_list)
461+
449462
cmd = f"{self.command_line} {self.additional_commands}".replace(" ", " ")
463+
450464
raw = self.TEMPLATE.format(
451465
cmd,
452466
self.calc.charge,

ccinput/tests/test_orca.py

+82-10
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,13 @@ def test_sp_DFT_multiple_specifications(self):
414414
"method": "M06-2X",
415415
"basis_set": "Def2-SVP",
416416
"charge": "-1",
417-
"specifications": "TightSCF GRID6",
417+
"specifications": "TightSCF DEFGRID3",
418418
}
419419

420420
inp = self.generate_calculation(**params)
421421

422422
REF = """
423-
!SP M062X Def2-SVP tightscf grid6
423+
!SP M062X Def2-SVP tightscf defgrid3
424424
*xyz -1 1
425425
Cl 0.0 0.0 0.0
426426
*
@@ -441,13 +441,13 @@ def test_sp_DFT_duplicate_specifications(self):
441441
"method": "M06-2X",
442442
"basis_set": "Def2-SVP",
443443
"charge": "-1",
444-
"specifications": "tightscf TightSCF GRID6",
444+
"specifications": "tightscf TightSCF DEFGRID3",
445445
}
446446

447447
inp = self.generate_calculation(**params)
448448

449449
REF = """
450-
!SP M062X Def2-SVP tightscf grid6
450+
!SP M062X Def2-SVP tightscf defgrid3
451451
*xyz -1 1
452452
Cl 0.0 0.0 0.0
453453
*
@@ -1372,6 +1372,78 @@ def test_ts_DFT_custom_bs_synonym(self):
13721372

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

1375+
def test_ts_bond_following(self):
1376+
params = {
1377+
"nproc": 8,
1378+
"type": "TS Optimisation",
1379+
"file": "mini_ts.xyz",
1380+
"software": "ORCA",
1381+
"charge": "0",
1382+
"method": "B3LYP",
1383+
"basis_set": "6-31+G(d,p)",
1384+
"specifications": "geom(TS_Mode {B 0 108 } end)",
1385+
}
1386+
1387+
inp = self.generate_calculation(**params)
1388+
1389+
# One cannot calculate the Hessian for use in a TS optimization
1390+
# when using xtb as QM engine.
1391+
REF = """
1392+
!OPTTS B3LYP 6-31+G(d,p)
1393+
*xyz 0 1
1394+
N 1.08764072053386 -0.33994563112543 -0.00972525479568
1395+
H 1.99826836912112 0.05502842705407 0.00651240826058
1396+
H 0.59453997172323 -0.48560162159600 0.83949232123172
1397+
H 0.66998093862168 -0.58930117433261 -0.87511947469677
1398+
*
1399+
%geom
1400+
ts_mode {b 0 108 } end
1401+
Calc_Hess true
1402+
end
1403+
%pal
1404+
nprocs 8
1405+
end
1406+
%MaxCore 125
1407+
"""
1408+
1409+
self.assertTrue(self.is_equivalent(REF, inp.input_file))
1410+
1411+
def test_ts_bond_following_alt(self):
1412+
params = {
1413+
"nproc": 8,
1414+
"type": "TS Optimisation",
1415+
"file": "mini_ts.xyz",
1416+
"software": "ORCA",
1417+
"charge": "0",
1418+
"method": "B3LYP",
1419+
"basis_set": "6-31+G(d,p)",
1420+
"specifications": "geom(TS_Mode={B 0 108 } end)",
1421+
}
1422+
1423+
inp = self.generate_calculation(**params)
1424+
1425+
# One cannot calculate the Hessian for use in a TS optimization
1426+
# when using xtb as QM engine.
1427+
REF = """
1428+
!OPTTS B3LYP 6-31+G(d,p)
1429+
*xyz 0 1
1430+
N 1.08764072053386 -0.33994563112543 -0.00972525479568
1431+
H 1.99826836912112 0.05502842705407 0.00651240826058
1432+
H 0.59453997172323 -0.48560162159600 0.83949232123172
1433+
H 0.66998093862168 -0.58930117433261 -0.87511947469677
1434+
*
1435+
%geom
1436+
ts_mode {b 0 108 } end
1437+
Calc_Hess true
1438+
end
1439+
%pal
1440+
nprocs 8
1441+
end
1442+
%MaxCore 125
1443+
"""
1444+
1445+
self.assertTrue(self.is_equivalent(REF, inp.input_file))
1446+
13751447
def test_opt_DFT_custom_bs_ecp(self):
13761448
params = {
13771449
"nproc": 8,
@@ -1677,7 +1749,7 @@ def test_NEB2(self):
16771749
"file": "elimination_substrate.xyz",
16781750
"auxiliary_file": "elimination_product.xyz",
16791751
"software": "ORCA",
1680-
"specifications": "--nimages 12",
1752+
"specifications": "neb(nimages=12)",
16811753
"charge": -1,
16821754
"method": "gfn2-xtb",
16831755
}
@@ -1698,8 +1770,8 @@ def test_NEB2(self):
16981770
H -1.82448 0.94856 3.28105
16991771
*
17001772
%neb
1701-
product "calc2.xyz"
17021773
nimages 12
1774+
product "calc2.xyz"
17031775
end
17041776
%pal
17051777
nprocs 8
@@ -1715,7 +1787,7 @@ def test_NEB_aux_name(self):
17151787
"file": "elimination_substrate.xyz",
17161788
"auxiliary_file": "elimination_product.xyz",
17171789
"software": "ORCA",
1718-
"specifications": "--nimages 12",
1790+
"specifications": "neb(nimages=12)",
17191791
"charge": -1,
17201792
"method": "gfn2-xtb",
17211793
"aux_name": "product",
@@ -1737,8 +1809,8 @@ def test_NEB_aux_name(self):
17371809
H -1.82448 0.94856 3.28105
17381810
*
17391811
%neb
1740-
product "product.xyz"
17411812
nimages 12
1813+
product "product.xyz"
17421814
end
17431815
%pal
17441816
nprocs 8
@@ -1756,7 +1828,7 @@ def test_hirshfeld_pop(self):
17561828
"method": "M06-2X",
17571829
"basis_set": "Def2-SVP",
17581830
"charge": "-1",
1759-
"specifications": "--phirshfeld",
1831+
"specifications": "output(Print[ P_Hirshfeld] 1)",
17601832
}
17611833

17621834
inp = self.generate_calculation(**params)
@@ -1766,7 +1838,7 @@ def test_hirshfeld_pop(self):
17661838
Cl 0.0 0.0 0.0
17671839
*
17681840
%output
1769-
Print[ P_Hirshfeld] 1
1841+
print[ p_hirshfeld] 1
17701842
end
17711843
%pal
17721844
nprocs 8

ccinput/utilities.py

+37
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,40 @@ def check_fragments(counterpoise, fragments, xyz):
480480
raise InvalidParameter(
481481
"Counterpoise keyword must be equal to the total number of fragments"
482482
)
483+
484+
485+
def parse_specifications(specs, add_option_fn, condense=True):
486+
# Could be more sophisticated to catch other incorrect specifications
487+
if specs.count("(") != specs.count(")"):
488+
raise InvalidParameter("Invalid specifications: parenthesis not matching")
489+
490+
_specifications = ""
491+
remove = False
492+
for c in specs:
493+
if c == " " and remove:
494+
if not condense:
495+
_specifications += "&" # Replace spaces temporarily
496+
continue
497+
_specifications += c
498+
if c == "(":
499+
remove = True
500+
elif c == ")":
501+
remove = False
502+
503+
for spec in _specifications.split(" "):
504+
if spec.strip() == "":
505+
continue
506+
if spec.find("(") != -1:
507+
key, options = spec.split("(")
508+
options = options.replace(")", "")
509+
for option in options.split(","):
510+
if option.strip() != "":
511+
add_option_fn(key, option.replace("&", " "))
512+
elif spec.find("=") != -1:
513+
try:
514+
key, option = spec.split("=")
515+
except ValueError:
516+
raise InvalidParameter(f"Invalid specification: {spec}")
517+
add_option_fn(key, option.replace("&", " "))
518+
else:
519+
add_option_fn(spec, "")

0 commit comments

Comments
 (0)