diff --git a/ccinput/packages/nwchem.py b/ccinput/packages/nwchem.py index 7cf2924..382a239 100644 --- a/ccinput/packages/nwchem.py +++ b/ccinput/packages/nwchem.py @@ -7,6 +7,7 @@ get_basis_set, clean_xyz, warn, + parse_specifications, ) from ccinput.constants import ( CalcType, @@ -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: diff --git a/ccinput/packages/orca.py b/ccinput/packages/orca.py index fd8aea1..1fa34a1 100644 --- a/ccinput/packages/orca.py +++ b/ccinput/packages/orca.py @@ -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: diff --git a/ccinput/tests/test_nwchem.py b/ccinput/tests/test_nwchem.py index a41ed45..45c7e5b 100644 --- a/ccinput/tests/test_nwchem.py +++ b/ccinput/tests/test_nwchem.py @@ -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, diff --git a/ccinput/tests/test_orca.py b/ccinput/tests/test_orca.py index 4215a52..8f586f4 100644 --- a/ccinput/tests/test_orca.py +++ b/ccinput/tests/test_orca.py @@ -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, diff --git a/ccinput/utilities.py b/ccinput/utilities.py index b5552a0..6771cb0 100644 --- a/ccinput/utilities.py +++ b/ccinput/utilities.py @@ -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: diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 940525a..5420593 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -246,7 +246,7 @@ Custom keywords to add to the command of the input. !OPT HF Def2-SVP tightscf [...] -With Gaussian 16, this can also add parameters to the calculation keyword: +With Gaussian 16 and ORCA, this can also add parameters to the calculation keyword: .. code-block:: console @@ -265,13 +265,40 @@ With Gaussian 16, this can also add parameters to the calculation keyword: #p opt(maxstep=5) HF/Def2SVP scf(restart) [...] -Note that the specifications are not checked for validity beyond simple syntax checks. This allows you to use all valid keywords of the software, but can also lead to invalid inputs. + $ ccinput orca opt HF --xyz "Cl 0 0 0" -c -1 -bs Def2SVP --specifications "tightscf defgrid3" + !OPT HF Def2-SVP tightscf defgrid3 + [...] -In nwchem, the syntax for specifcations in the following: +The general syntax is ``block(option=value)``, but variations are often correctly parsed: .. code-block:: console - $ ccinput g16 opt HF --xyz "Cl 0 0 0" -c -1 -bs Def2SVP --specifications "scf(maxiter 20);opt(tight)" + $ ccinput nwchem opt M062X --xyz "Cl 0 0 0" -c -1 -bs Def2SVP --specifications "SCF(Tight, direct); opt(maxITer=5, truST=0.2, convggm 5.0d-04)" + [...] + dft + xc m06-2x + mult 1 + tight + direct + end + + driver + maxiter 5 + trust 0.2 + convggm 5.0d-04 + end + [...] + + $ ccinput orca sp HF --xyz "Cl 0 0 0" -c -1 -bs Def2SVP --specifications "output(Print[ P_Hirshfeld] 1)" + [...] + %output + print[ p_hirshfeld] 1 + end + [...] + + +Note that the specifications themselves are not checked for validity beyond simple syntax checks. This allows you to use all valid keywords of the software, but can also lead to invalid inputs. + Constraints (``--constraints, -co``, ``--freeze``, ``--scan``, ``--from``, ``--to``, ``--nsteps``, ``--step``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^