From 00de251d5cc927d1f59829b1059410d79150edc6 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 30 Jan 2020 17:56:16 -0500 Subject: [PATCH 001/102] Formating and added preceding 0 --- qcengine/programs/nwchem/germinate.py | 7 +- qcengine/programs/nwchem/harvester.py | 126 +++++++++++++++++++------ qcengine/programs/nwchem/runner.py | 8 +- qcengine/programs/tests/test_nwchem.py | 14 ++- 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/qcengine/programs/nwchem/germinate.py b/qcengine/programs/nwchem/germinate.py index 0ca466756..e76132ba8 100644 --- a/qcengine/programs/nwchem/germinate.py +++ b/qcengine/programs/nwchem/germinate.py @@ -105,12 +105,7 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict opts = {} # Map the run type to - runtyp = { - "energy": "energy", - "gradient" : "gradient", - "hessian" : "hessian", - "properties": 'property', - }[derint] + runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] # Write out the theory directive if method == "nwchem": diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index 6b5383f07..9caac6990 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -41,7 +41,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_psivar.append(psivar) pass_coord.append(nwcoord) pass_grad.append(nwgrad) - # Determine which segment contained the last geometry retindx = -1 if pass_coord[-1] else -2 @@ -600,7 +599,7 @@ def harvest_outfile_pass(outtext): # print (mobj.group(2)) #error reason psivar['NWCHEM ERROR CODE'] = mobj.group(1) # TODO process errors into error var - + # fmt: on # Get the size of the basis sets, etc @@ -616,62 +615,129 @@ def harvest_outfile_pass(outtext): psivar["N ALPHA ELECTRONS"] = mobj.group(2) psivar["N BETA ELECTRONS"] = mobj.group(3) - if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: - #get HOMO and LUMO energy - mobj = re.search(r"Vector" + r"\s+" + r"%d"%(psivar["N ALPHA ELECTRONS"]) + r"\s+" + r"Occ=" + r".*" + r"\s+" + r"E=" + r"([+-]?\s?\d+[.]\d+)" + r"[D]"+ r"([+-])" + r"[0]" + r"(\d+)", outtext, re.MULTILINE) + # get HOMO and LUMO energy + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"]) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-])" + + r"[0]" + + r"(\d+)", + outtext, + re.MULTILINE, + ) if mobj: if mobj.group(2) == "+": - lumo = float(mobj.group(1)) * (10**(-1 * float(mobj.group(3)))) + lumo = float(mobj.group(1)) * (10 ** (-1 * float(mobj.group(3)))) psivar["HOMO"] = np.array([round(lumo, 10)]) else: - lumo = float(mobj.group(1)) * (10**(-1 * float(mobj.group(3)))) + lumo = float(mobj.group(1)) * (10 ** (-1 * float(mobj.group(3)))) psivar["HOMO"] = np.array([round(lumo, 10)]) - - mobj = re.search(r"Vector" + r"\s+" + r"%d"%(psivar["N ALPHA ELECTRONS"] + 1) + r"\s+" + r"Occ=" + r".*" + r"\s+" + r"E=" + r"([+-]?\s?\d+[.]\d+)" + r"[D]"+ r"([+-])" + r"[0]" + r"(\d+)", outtext, re.MULTILINE) + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-])" + + r"[0]" + + r"(\d+)", + outtext, + re.MULTILINE, + ) if mobj: if mobj.group(2) == "+": - lumo = float(mobj.group(1)) * (10**(-1 * float(mobj.group(3)))) + lumo = float(mobj.group(1)) * (10 ** (-1 * float(mobj.group(3)))) psivar["LUMO"] = np.array([round(lumo, 10)]) else: - lumo = float(mobj.group(1)) * (10**(-1 * float(mobj.group(3)))) + lumo = float(mobj.group(1)) * (10 ** (-1 * float(mobj.group(3)))) psivar["LUMO"] = np.array([round(lumo, 10)]) - mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) if mobj: psivar["N MO"] = mobj.group(2) psivar["N BASIS"] = mobj.group(1) - - #Search for Center of charge + + # Search for Center of charge mobj = re.search( - r"Center of charge \(in au\) is the expansion point" + r"\n" + r"\s+" + r"X\s+=\s+([+-]?\d+[.]\d+)" + r"\s+" + - r"Y\s+=\s+([+-]?\d+[.]\d+)" + r"\s+" + r"Z\s+=\s+([+-]?\d+[.]\d+)", outtext, re.MULTILINE) + r"Center of charge \(in au\) is the expansion point" + + r"\n" + + r"\s+" + + r"X\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Y\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Z\s+=\s+([+-]?\d+[.]\d+)", + outtext, + re.MULTILINE, + ) if mobj: psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - + mobj = re.search( - r"Dipole moment" + r".*?" + r"A\.U\." + r"\s+" + r"DMX\s+([+-]?\d+[.]\d+)\s+" + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" + - r"DMY\s+([+-]?\d+[.]\d+)\s+" + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" + r"DMZ\s+([+-]?\d+[.]\d+)\s+" + - r"DMZEFC\s+[+-]?\d+[.]\d+\s+" + r"\-EFC\-" + r".*?" + r"A\.U\.\s+" + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", outtext, re.MULTILINE) + r"Dipole moment" + + r".*?" + + r"A\.U\." + + r"\s+" + + r"DMX\s+([+-]?\d+[.]\d+)\s+" + + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMY\s+([+-]?\d+[.]\d+)\s+" + + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMZ\s+([+-]?\d+[.]\d+)\s+" + + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" + + r"\-EFC\-" + + r".*?" + + r"A\.U\.\s+" + + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", + outtext, + re.MULTILINE, + ) # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) if mobj: psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - + mobj = re.search( - r"Quadrupole moments in atomic units\s+" + r"Component\s+" + r"Electronic\+nuclear\s+" + r"Point charges\s+" + r"Total\s+" + r"-+\s+" + - r"XX\s+([+-]?\d+[.]\d+)\s+" + r".*\s+.*\s+" + r"YY\s+([+-]?\d+[.]\d+)\s+" + r".*\s+.*\s+" + r"ZZ\s+([+-]?\d+[.]\d+)\s+" + r".*\s+.*\s+" + - r"XY\s+([+-]?\d+[.]\d+)\s+" + r".*\s+.*\s+" + r"XZ\s+([+-]?\d+[.]\d+)\s+" + r".*\s+.*\s+" + r"YZ\s+([+-]?\d+[.]\d+)\s+", outtext, re.MULTILINE) - - if mobj: - psivar["QUADRUPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)]) - + r"Quadrupole moments in atomic units\s+" + + r"Component\s+" + + r"Electronic\+nuclear\s+" + + r"Point charges\s+" + + r"Total\s+" + + r"-+\s+" + + r"XX\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"ZZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YZ\s+([+-]?\d+[.]\d+)\s+", + outtext, + re.MULTILINE, + ) - - + if mobj: + psivar["QUADRUPOLE MOMENT"] = np.array( + [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] + ) # Process CURRENT energies (TODO: needs better way) if "HF TOTAL ENERGY" in psivar: diff --git a/qcengine/programs/nwchem/runner.py b/qcengine/programs/nwchem/runner.py index af24fec6a..c75bc19bf 100644 --- a/qcengine/programs/nwchem/runner.py +++ b/qcengine/programs/nwchem/runner.py @@ -155,9 +155,7 @@ def build_input( opts.update(moldata["keywords"]) # Handle calc type and quantum chemical method - mdccmd, mdcopts = muster_modelchem( - input_model.model.method, input_model.driver, opts.pop("qc_module", False) - ) + mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) opts.update(mdcopts) # Handle basis set @@ -238,8 +236,8 @@ def parse_output( qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats - if (input_model.driver.upper()=="PROPERTIES"): - retres = qcvars[f"CURRENT ENERGY" ] + if input_model.driver.upper() == "PROPERTIES": + retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index e7d3245f5..5edcefbf9 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -98,6 +98,7 @@ def test_gradient(nh2): assert np.allclose(orig_det, shif_det) + @pytest.fixture def h20(): water = """ @@ -107,10 +108,17 @@ def h20(): H 0 1 0 """ return qcel.models.Molecule.from_data(water) + + @using("nwchem") def test_dipole(h20): # Run NH2 - resi = {"molecule": h20, "driver": "properties", "model": {"method": "dft", "basis": "3-21g"},"keywords":{"dft__xc":"b3lyp","property__dipole":True}} + resi = { + "molecule": h20, + "driver": "properties", + "model": {"method": "dft", "basis": "3-21g"}, + "keywords": {"dft__xc": "b3lyp", "property__dipole": True}, + } res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) # Make sure the calculation completed successfully @@ -130,7 +138,7 @@ def test_dipole(h20): assert res["properties"]["calcinfo_nalpha"] == 6 assert res["properties"]["calcinfo_nbasis"] == 13 # Make sure Dipole Moment and center of charge parsed correctly - assert compare_values(.272949872, float(res["extras"]["qcvars"]["TOTAL DIPOLE MOMENT"]), atol=1e-5) + assert compare_values(0.272949872, float(res["extras"]["qcvars"]["TOTAL DIPOLE MOMENT"]), atol=1e-5) assert compare_values(-0.00, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][0]), atol=1e-3) assert compare_values(-0.00, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][1]), atol=1e-3) - assert compare_values(-.272949872, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][2]), atol=1e-5) \ No newline at end of file + assert compare_values(-0.272949872, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][2]), atol=1e-5) From f59dbc9d6f0469256cc7feff6c61ab1765edc400 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:16:39 -0400 Subject: [PATCH 002/102] add madness dir --- qcengine/programs/madness/__init__.py | 1 + qcengine/programs/madness/germinate.py | 189 ++++++ qcengine/programs/madness/harvester.py | 895 +++++++++++++++++++++++++ qcengine/programs/madness/keywords.py | 87 +++ qcengine/programs/madness/runner.py | 270 ++++++++ 5 files changed, 1442 insertions(+) create mode 100644 qcengine/programs/madness/__init__.py create mode 100644 qcengine/programs/madness/germinate.py create mode 100644 qcengine/programs/madness/harvester.py create mode 100644 qcengine/programs/madness/keywords.py create mode 100644 qcengine/programs/madness/runner.py diff --git a/qcengine/programs/madness/__init__.py b/qcengine/programs/madness/__init__.py new file mode 100644 index 000000000..b0ec9fb43 --- /dev/null +++ b/qcengine/programs/madness/__init__.py @@ -0,0 +1 @@ +from .runner import MadnessHarness diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py new file mode 100644 index 000000000..e76132ba8 --- /dev/null +++ b/qcengine/programs/madness/germinate.py @@ -0,0 +1,189 @@ +from typing import Any, Dict, Tuple + +from qcengine.exceptions import InputError + +# List of XC functionals known to NWChem +_xc_functionals = [ + "acm", + "b3lyp", + "beckehandh", + "pbe0", + "becke97", + "becke97-1", + "becke97-2", + "becke97-3", + "becke97-d", + "becke98", + "hcth", + "hcth120", + "hcth147", + "hcth407", + "becke97gga1", + "hcth407p", + "mpw91", + "mpw1k", + "xft97", + "cft97", + "ft97", + "op", + "bop", + "pbeop", + "xpkzb99", + "cpkzb99", + "xtpss03", + "ctpss03", + "xctpssh", + "b1b95", + "bb1k", + "mpw1b95", + "mpwb1k", + "pw6b95", + "pwb6k", + "m05", + "m05-2x", + "vs98", + "m06", + "m06-hf", + "m06-L", + "m06-2x", + "HFexch", + "becke88", + "xperdew91", + "xpbe96", + "gill96", + "lyp", + "perdew81", + "perdew86", + "perdew91", + "cpbe96", + "pw91lda", + "slater", + "vwn_1", + "vwn_2", + "vwn_3", + "vwn_4", + "vwn_5", + "vwn_1_rpa", + "xtpss03", + "ctpss03", + "bc95", + "xpw6b95", + "xpwb6k", + "xm05", + "xm05-2x", + "cpw6b95", + "cpwb6k", + "cm05", + "cm05-2x", + "xvs98", + "cvs98", + "xm06-L", + "xm06-hf", + "xm06", + "xm06-2x", + "cm06-L", + "cm06-hf", + "cm06", + "cm06-2x", +] + + +def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: + """Converts the QC method into NWChem keywords + + Args: + method (str): Name of the QC method to use + derint (str): Index of the run type + use_tce (bool): Whether to use the Tensor Contraction Engine + Returns: + (str): Task command for NWChem + (dict): Any options for NWChem + """ + + # Standardize the method name + method = method.lower() + opts = {} + + # Map the run type to + runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + + # Write out the theory directive + if method == "nwchem": + mdccmd = "" + + elif method in ["scf", "hf"]: + mdccmd = f"task scf {runtyp}\n\n" + + elif method == "mp2": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp2"] = True + else: + mdccmd = f"task mp2 {runtyp}\n\n" + + elif method == "mp3": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp3"] = True + + elif method == "mp4": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp4"] = True + + elif method == "ccd": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccd"] = True + + elif method == "ccsd": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsd"] = True + else: + mdccmd = f"task ccsd {runtyp}\n\n" + + elif method == "ccsdt": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsdt"] = True + else: + mdccmd = f"task ccsdt {runtyp}\n\n" + + elif method == "ccsd(t)": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsd(t)"] = True + else: + mdccmd = f"task ccsd(t) {runtyp}\n\n" + + elif method == "tddft": + mdccmd = f"task tddft {runtyp}\n\n" + + elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: + raise InputError(f'Method "{method}" not yet supported by QCEngine') + + elif method == "tce": + raise InputError( + f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' + ) + + elif method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__"] = "dft" + else: + mdccmd = f"task dft {runtyp}\n\n" + + elif method == "dft": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__"] = "dft" + else: + mdccmd = f"task dft {runtyp}\n\n" + + else: + raise InputError(f"Method not recognized: {method}") + + return mdccmd, opts diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py new file mode 100644 index 000000000..dbcfa9bfa --- /dev/null +++ b/qcengine/programs/madness/harvester.py @@ -0,0 +1,895 @@ +import re +import json +import logging +from decimal import Decimal +from typing import Tuple + +import numpy as np +import qcelemental as qcel +from qcelemental.models import Molecule +from qcelemental.models.results import AtomicResultProperties +from qcelemental.molparse import regex + +from ..util import PreservingDict + +logger = logging.getLogger(__name__) + + +def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: + """Function to read an entire NWChem output file. + + Reads all of the different "line search" segments of a file and returns + values from the last segment for which a geometry was written. + + Args: + outtext (str): Output written to stdout + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (Molecule): Molecule from the last complete step + - (list): Gradient from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Loop over all steps + # TODO (wardlt): Is it only necessary to read the last two steps? + pass_psivar = [] + pass_coord = [] + pass_grad = [] + for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) + pass_psivar.append(psivar) + pass_coord.append(nwcoord) + pass_grad.append(nwgrad) + + # Determine which segment contained the last geometry + retindx = -1 if pass_coord[-1] else -2 + + return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +def harvest_outfile_pass(outtext): + """Function to read NWChem output file *outtext* and parse important + quantum chemical information from it in + + """ + psivar = PreservingDict() + psivar_coord = None + psivar_grad = None + version = "" + error = "" # TODO (wardlt): The error string is never used. + + NUMBER = r"(?x:" + regex.NUMBER + ")" + # fmt: off + + # Process version + mobj = re.search( + r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched version') + version = mobj.group('version') + + # Process SCF + # 1)Fail to converge + mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('failed to converge') + + # 2)Calculation converged + else: + mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched HF') + psivar['HF TOTAL ENERGY'] = mobj.group(1) + + # Process Effective nuclear repulsion energy (a.u.) + mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched NRE') + # logger.debug (mobj.group(1)) + psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) + + # Process DFT dispersion energy (a.u.) + mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched Dispersion') + logger.debug(mobj.group(1)) + psivar['DFT DISPERSION ENERGY'] = mobj.group(1) + + # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) + + mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched DFT') + logger.debug(mobj.group(1)) + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + + # SODFT [for nwchem 6.8+] + mobj = re.search( + r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched DFT') + # print (mobj.group(1)) + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) + + # MCSCF + mobj = re.findall( + r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + + # for mobj_list in mobj: + + if mobj: # Need to change to accommodate find all instances + logger.debug('matched mcscf') # MCSCF energy calculation + psivar['HF TOTAL ENERGY'] = mobj.group(1) + psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) + psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) + psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + # for mobj_list in mobj: + # for i in mobj_list: + # count += 0 + # logger.debug('matched mcscf iteration %i', count) + # psivar['HF TOTAL ENERGY'] = mobj.group(1) + # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) + # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) + # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + + # Process MP2 (Restricted, Unrestricted(RO n/a)) + # 1)SCF-MP2 + mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + + NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 + if mobj: + logger.debug('matched scf-mp2') + psivar['HF TOTAL ENERGY'] = mobj.group(1) + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(5) + # SCS-MP2 + mobj = re.search( + r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE) + if mobj: + logger.debug('matched scs-mp2') + psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) + psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) + + logger.debug(mobj.group(1)) # ess + logger.debug(mobj.group(2)) # fss + logger.debug(mobj.group(3)) # eos + logger.debug(mobj.group(4)) # fos + logger.debug(mobj.group(5)) # scs corl + logger.debug(mobj.group(6)) # scs-mp2 + + # 2) DFT-MP2 + mobj = re.search( + r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + + r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched dft-mp2') + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + + # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) + mobj = re.search( + r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + + r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + + r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug('matched coupled cluster-mp2') + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + + # 4) Direct MP2 + + # 5) RI-MP2 + + # Process calculation through tce [dertype] command + for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: + mobj = re.search( + r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, + re.MULTILINE) + + if mobj: + mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') + logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) + + if mbpt_plain == 'MP2': + psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) + else: + psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) + psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) + #TCE dipole- MBPT(n) + mobj2 = re.search( + r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + + if mobj2: + mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') + print(f'matched tce {mbpt_plain} dipole moment') + #only pulling Debye + psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) + psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) + psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) + + #TCE with () or [] + for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: + mobj = re.search( + r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj: + cc_plain = cc_name.replace('\\', '') + cc_corr = cc_plain.replace('CCSD', '') + logger.debug(f'matched tce cc {cc_plain}') + + psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) + psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) + psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) + #TCE dipole with () or [] + mobj2 = re.search( + r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + + if mobj2: + cc_plain = cc_name.replace('\\', '') + cc_corr = cc_plain.replace('CCSD', '') + print(f'matched tce {cc_plain} dipole moment') + + #only pulling Debye + psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) + psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) + psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) + + #Process other TCE cases + for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: + mobj = re.search( + r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug(f'matched {cc_name}') + logger.debug(mobj) + psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) + psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) + #TCE dipole + mobj2 = re.search( + r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj2: + print(f'matched tce dipole moment') + + #only pulling Debye + psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) + psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) + psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) + + # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command + + mobj = re.search( + r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE | re.DOTALL) + + if mobj: + logger.debug('matched ccsd') + psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) + psivar['CCSD TOTAL ENERGY'] = mobj.group(3) + + mobj = re.search( + r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + + r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + + if mobj: + logger.debug('matched ccsd(t)') + psivar['(T) CORRECTION ENERGY'] = mobj.group(1) + psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] + psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) + + mobj = re.search( + r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + # SCS-CCSD included + if mobj: + logger.debug('matched scs-ccsd') + psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( + Decimal(mobj.group(1)) * Decimal(mobj.group(2))) + psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( + Decimal(mobj.group(4)) * Decimal(mobj.group(3))) + psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) + psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) + psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( + psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) + # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( + # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) + + # Process EOM-[cc_name] #nwchem_tce_dipole = false + # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree + # psivar name might need to be fixed + # each root excitation energy is extracted from the last iteration of right hand side + mobj = re.findall( + r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + + # (..) captures symmetry + r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root + r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree + r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV + outtext, re.MULTILINE | re.DOTALL) + #regex should be more dynamic in finding values, need to revisit + #mobj.group(0) = symmetry value + #mobj.group(1) = cc_name + #mobj.group(2) = root number + #mobj.group(3) = excitation energy (hartree) + #mobj.group(4) = excitation energy (eV) + + if mobj: + print(mobj) + ext_energy = {} # dic + + ext_energy_list = [] + print(f'matched eom-{cc_name}') + for mobj_list in mobj: + logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry + logger.debug(mobj_list) + count = 0 + for line in mobj_list[1].splitlines(): + lline = line.split() + logger.debug(lline[1]) # in hartree + logger.debug(lline[2]) # in eV + count += 1 + + logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) + + ext_energy_list.append(lline[1]) # Collect all excitation energies + + sym = str(mobj_list[0]) + ext_energy.setdefault(sym, []) + ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) + + ext_energy_list.sort(key=float) + + for nroot in range(len(ext_energy_list)): + for k, e_val in ext_energy.items(): + if ext_energy_list[nroot] in e_val: + symm = k + psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ + ext_energy_list[nroot] # in hartree + psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ + psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree + gssym = '' + gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) + + if gs: + logger.debug('matched ground-state symmetry') + psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) + +# Process TDDFT +# 1) Spin allowed + mobj = re.findall( + r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' + #Root | symmetry | a.u. | eV + + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value + + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole + + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople + + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople + + r'\s*$', + outtext, re.MULTILINE) + + if mobj: + logger.debug('matched TDDFT with transition moments') + for mobj_list in mobj: + print (mobj_list) + psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV + psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) + #### temporary psivars #### + #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % + # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. + #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ + # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) + psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] + psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] + psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] + psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] + psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] + psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] + psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] + psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] + psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] + + +# 2) Spin forbidden + mobj = re.findall( + r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' + #Root | symmetry | a.u. | eV + + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value + + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', + outtext, re.MULTILINE) + #mobj.group(0) = Root + #mobj.group(1) = symmetry + #mobj.group(2) a.u. + #mobj.group(3) e.V + #mobj.group(4) Excitation energy + #mobj.group(5) Excited state energy + + if mobj: + logger.debug('matched TDDFT - spin forbidden') + for mobj_list in mobj: + #### temporary psivars #### + psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV + psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) + + #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % + # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. + #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ + # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) + if mobj: + logger.debug('Non-variation initial energy') # prints out energy, 5 counts + + # Process geometry + # 1) CHARGE + # Read charge from SCF module + mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched charge') + out_charge = int(float(mobj.group(1))) + + # Read charge from General information (not scf module) + mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched charge') + out_charge = int(float(mobj.group(1))) + + # 2) MULTIPLICITY + # Read multiplicity from SCF module + mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity') + out_mult = int(mobj.group(1)) + 1 + + # Read multiplicity from SCF module through alpha, beta electrons + mobj = re.search( + r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + + r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity via alpha and beta electrons') + out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 + psivar['N ALPHA ELECTRONS'] = mobj.group(1) + psivar['N BETA ELECTRONS'] = mobj.group(2) + + # Read multiplicity from General information (not scf module) + mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity') + out_mult = int(mobj.group(1)) + + # 3) Initial geometry + mobj = re.search( + r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + + r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + + r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + + r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + + r'\s*' + + r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' + + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched geom') + + # dinky molecule w/ charge and multiplicity + if mobj.group(1) == 'angstroms': + molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult + ) # unit = angstrom + for line in mobj.group(2).splitlines(): + lline = line.split() + molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) + # Jiyoung was collecting charge (-4)? see if this is ok for ghosts + # Tag , X, Y, Z + psivar_coord = Molecule(validate=False, + **qcel.molparse.to_schema(qcel.molparse.from_string( + molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], + dtype=2)) + + else: # unit = a.u. + molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) + for line in mobj.group(2).splitlines(): + lline = line.split() + molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) + # Tag , X, Y, Z + psivar_coord = Molecule(validate=False, + **qcel.molparse.to_schema(qcel.molparse.from_string( + molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], + dtype=2)) + + # Process gradient + mobj = re.search( + r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + + r'atom coordinates gradient' + r'\s*' + r'^\s+' + + r'x y z x y z' + r'\s*' + + r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' + + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug('matched molgrad') + atoms = [] + psivar_grad = [] + for line in mobj.group(1).splitlines(): + lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z + # print (lline) + if lline == []: + pass + else: + atoms.append(lline[1]) # Tag + psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) + + # Process dipole (Properties) + mobj = re.search( + r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + + r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'.*' + + r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + + r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + + r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'.*' + + r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', + outtext, re.MULTILINE) + + if mobj: + logger.debug('matched total dipole') + + # UNIT = DEBYE(S) + psivar['CURRENT DIPOLE X'] = mobj.group(7) + psivar['CURRENT DIPOLE Y'] = mobj.group(8) + psivar['CURRENT DIPOLE Z'] = mobj.group(9) + # total? + + # Process error code + mobj = re.search( + r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + + r'\s*' + r'^\s+' + r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + + r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched error') + # print (mobj.group(1)) #error line number + # print (mobj.group(2)) #error reason + psivar['NWCHEM ERROR CODE'] = mobj.group(1) + # TODO process errors into error var + + # fmt: on + + # Get the size of the basis sets, etc + mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) + if mobj: + psivar["N ATOMS"] = mobj.group(1) + mobj = re.search( + r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", + outtext, + re.MULTILINE, + ) + if mobj: + psivar["N ALPHA ELECTRONS"] = mobj.group(2) + psivar["N BETA ELECTRONS"] = mobj.group(3) + if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: + # get HOMO and LUMO energy + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"]) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-]0\d)", + outtext, + re.MULTILINE, + ) + if mobj: + homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) + psivar["HOMO"] = np.array([round(homo, 10)]) + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-]0\d)", + outtext, + re.MULTILINE, + ) + if mobj: + lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) + psivar["LUMO"] = np.array([round(lumo, 10)]) + + mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) + if mobj: + psivar["N MO"] = mobj.group(2) + psivar["N BASIS"] = mobj.group(1) + + # Search for Center of charge + mobj = re.search( + r"Center of charge \(in au\) is the expansion point" + + r"\n" + + r"\s+" + + r"X\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Y\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Z\s+=\s+([+-]?\d+[.]\d+)", + outtext, + re.MULTILINE, + ) + if mobj: + psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + + mobj = re.search( + r"Dipole moment" + + r".*?" + + r"A\.U\." + + r"\s+" + + r"DMX\s+([+-]?\d+[.]\d+)\s+" + + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMY\s+([+-]?\d+[.]\d+)\s+" + + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMZ\s+([+-]?\d+[.]\d+)\s+" + + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" + + r"\-EFC\-" + + r".*?" + + r"A\.U\.\s+" + + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", + outtext, + re.MULTILINE, + ) + # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) + if mobj: + psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) + + mobj = re.search( + r"Quadrupole moments in atomic units\s+" + + r"Component\s+" + + r"Electronic\+nuclear\s+" + + r"Point charges\s+" + + r"Total\s+" + + r"-+\s+" + + r"XX\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"ZZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YZ\s+([+-]?\d+[.]\d+)\s+", + outtext, + re.MULTILINE, + ) + + if mobj: + psivar["QUADRUPOLE MOMENT"] = np.array( + [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] + ) + + # Process CURRENT energies (TODO: needs better way) + if "HF TOTAL ENERGY" in psivar: + psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] + psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] + psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] + + if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] + if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] + if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] + + if "DFT TOTAL ENERGY" in psivar: + psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] + psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] + + # Process TCE CURRENT energies + # Need to be fixed + # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? + # TODO: CURRENT ENERGY = TCE ENERGY + if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): + psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] + psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] + + if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] + + if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + + if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): + psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] + psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + + return psivar, psivar_coord, psivar_grad, version, error + + +def harvest_hessian(hess: str) -> np.ndarray: + """Parses the contents of the NWChem hess file into a hessian array. + + Args: + hess (str): Contents of the hess file + Returns: + (np.ndarray) Hessian matrix as a 2D array + """ + + # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python + hess_conv = hess.replace("D", "E") + + # Parse all of the float values + hess_tri = [float(x) for x in hess_conv.strip().splitlines()] + + # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) + n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix + + # Add the lower diagonal + hess_arr = np.zeros((n, n)) + hess_arr[np.tril_indices(n)] = hess_tri + + # Transpose and then set the lower diagonal again + hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major + hess_arr[np.tril_indices(n)] = hess_tri + + return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines + + +def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: + """Get named properties out of the general variables extracted out of the result file + + Args: + psivars (PreservingDict): Dictionary of the output results + Returns: + (AtomicResultProperties) Properties in a standard format + """ + # TODO (wardlt): Get more of the named variables out of the NWChem file + + # Initialize the output + output = dict() + + # Extract the Calc Info + output.update( + { + "calcinfo_nbasis": psivars.get("N BASIS", None), + "calcinfo_nmo": psivars.get("N MO", None), + "calcinfo_natom": psivars.get("N ATOMS", None), + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), + "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), + } + ) + + # Get the "canonical" properties + output["return_energy"] = psivars["CURRENT ENERGY"] + output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + + # Get the SCF properties + output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) + output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + + # Get the MP2 properties + output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) + output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) + output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) + output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) + return AtomicResultProperties(**output) + + +def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: + """Parses all the pieces of output from NWChem: the stdout in + *nwout* Scratch files are not yet considered at this moment. + + Args: + in_mol (Molecule): Input molecule + nwout (str): NWChem output molecule + outfiles (dict): Dictionary of outfile files and their contents + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (None): Hessian from the last complete step (Not yet implemented) + - (list): Gradient from the last complete step + - (Molecule): Molecule from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Parse the NWChem output + out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) + + # If available, read higher-accuracy gradients + # These were output using a Python Task in NWChem to read them out of the database + if outfiles.get("nwchem.grad") is not None: + logger.debug("Reading higher-accuracy gradients") + out_grad = json.loads(outfiles.get("nwchem.grad")) + + # If available, read the hessian + out_hess = None + if outfiles.get("nwchem.hess") is not None: + out_hess = harvest_hessian(outfiles.get("nwchem.hess")) + + # Make sure the input and output molecules are the same + if out_mol: + if in_mol: + if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: + raise ValueError( + """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" + % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) + ) + else: + raise ValueError("""No coordinate information extracted from NWChem output.""") + + # If present, align the gradients and hessian with the original molecular coordinates + # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the + # rotated molecule, which we can use to determine how to rotate the gradients/hessian + amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + + mill = data["mill"] # Retrieve tool with alignment routines + + if out_grad is not None: + out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) + if out_hess is not None: + out_hess = mill.align_hessian(np.array(out_hess)) + + return out_psivar, out_hess, out_grad, out_mol, version, error diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py new file mode 100644 index 000000000..1821e98c5 --- /dev/null +++ b/qcengine/programs/madness/keywords.py @@ -0,0 +1,87 @@ +import collections +from typing import Any, Dict, Tuple + + +def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: + """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" + + # Transform string booleans into " " + if val is True: + return keyword.lower(), "" + elif val is False: + return "", "" + + # complete hack + if keyword.upper() == "MEMORY": + return keyword.lower(), f"{val} byte" + + elif isinstance(val, list): + text = " ".join([str(v) for v in val]) + elif isinstance(val, dict): + text = [] + for k, v in val.items(): + merge = [k] + merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) + text.append(" ".join(merge)) + text = " ".join(text) + else: + text = str(val) + + if lop_off: + return keyword[7:].lower(), text + else: + return keyword.lower(), text + + +def format_keywords(keywords: Dict[str, Any]) -> str: + """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" + + def rec_dd(): + return collections.defaultdict(rec_dd) + + grouped_options = rec_dd() + + for group_key, val in keywords.items(): + nesting = group_key.split("__") + if len(nesting) == 1: + key = nesting[0] + grouped_options["aaaglobal"][key] = val + elif len(nesting) == 2: + g1, key = nesting + grouped_options[g1][key] = val + elif len(nesting) == 3: + g1, g2, key = nesting + grouped_options[g1][g2][key] = val + else: + print(nesting) + raise ValueError("Nesting N!") + + grouped_lines = {} + for group, opts in sorted(grouped_options.items()): + lines = [] + group_level_lines = [] + for key, val in grouped_options[group].items(): + if isinstance(val, dict): + g2_level_lines = [] + g2_level_lines.append(key.lower()) + for k2, v2 in val.items(): + line2 = " ".join(format_keyword(k2, v2, lop_off=False)) + g2_level_lines.append(line2) + g2_level_lines = " ".join(g2_level_lines) + lines.append(g2_level_lines) + else: + line = " ".join(format_keyword(key, val, lop_off=False)) + if group.lower() == "basis" and any( + [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] + ): + group_level_lines.append(line) + else: + lines.append(line) + if group == "aaaglobal": + grouped_lines[group] = "\n".join(lines) + "\n" + else: + grouped_lines[group] = ( + f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" + ) + + return "\n".join(grouped_lines.values()) + "\n" diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py new file mode 100644 index 000000000..e86d655c9 --- /dev/null +++ b/qcengine/programs/madness/runner.py @@ -0,0 +1,270 @@ +""" +Calls the Madness moldft executable. +""" +import re +import copy +import logging +import pprint +from decimal import Decimal +from typing import Any, Dict, Optional, Tuple + +import numpy as np +import qcelemental as qcel +from qcelemental.models import AtomicResult, Provenance, AtomicInput +from qcelemental.util import safe_version, which, which_import + +from qcengine.config import TaskConfig, get_config +from qcengine.exceptions import UnknownError + +from ...exceptions import InputError +from ...util import execute, create_mpi_invocation +from ..model import ProgramHarness +from .germinate import muster_modelchem +from .harvester import extract_formatted_properties, harvest +from .keywords import format_keywords + +pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) +logger = logging.getLogger(__name__) + + +class MadnessHarness(ProgramHarness): + """ + +# Notes +# ----- +# * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. + +# """ + +# _defaults = { +# "name": "NWChem", +# "scratch": True, +# "thread_safe": False, +# "thread_parallel": False, +# "node_parallel": True, +# "managed_memory": True, +# } +# # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers +# version_cache: Dict[str, str] = {} + +# class Config(ProgramHarness.Config): +# pass + +# @staticmethod +# def found(raise_error: bool = False) -> bool: +# """Whether NWChem harness is ready for operation, with both the QC program and any particular dependencies found. + +# Parameters +# ---------- +# raise_error: bool +# Passed on to control negative return between False and ModuleNotFoundError raised. + +# Returns +# ------- +# bool +# If both nwchem and its harness dependency networkx are found, returns True. +# If raise_error is False and nwchem or networkx are missing, returns False. +# If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + +# """ +# qc = which( +# "nwchem", +# return_bool=True, +# raise_error=raise_error, +# raise_msg="Please install via http://www.nwchem-sw.org/index.php/Download", +# ) + +# dep = which_import( +# "networkx", +# return_bool=True, +# raise_error=raise_error, +# raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", +# ) + +# return qc and dep + +# def get_version(self) -> str: +# self.found(raise_error=True) + +# # Get the node configuration +# config = get_config() + +# # Run NWChem +# which_prog = which("nwchem") +# if config.use_mpiexec: +# command = create_mpi_invocation(which_prog, config) +# else: +# command = [which_prog] +# command.append("v.nw") + +# if which_prog not in self.version_cache: +# success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) + +# if success: +# for line in output["stdout"].splitlines(): +# if "nwchem branch" in line: +# branch = line.strip().split()[-1] +# if "nwchem revision" in line: +# revision = line.strip().split()[-1] +# self.version_cache[which_prog] = safe_version(branch + "+" + revision) +# else: +# raise UnknownError(output["stderr"]) + +# return self.version_cache[which_prog] + +# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": +# """ +# Runs NWChem in executable mode +# """ +# self.found(raise_error=True) + +# job_inputs = self.build_input(input_model, config) +# success, dexe = self.execute(job_inputs) + +# if "There is an error in the input file" in dexe["stdout"]: +# raise InputError(dexe["stdout"]) +# if "not compiled" in dexe["stdout"]: +# # recoverable with a different compilation with optional modules +# raise InputError(dexe["stdout"]) + +# if success: +# dexe["outfiles"]["stdout"] = dexe["stdout"] +# dexe["outfiles"]["stderr"] = dexe["stderr"] +# return self.parse_output(dexe["outfiles"], input_model) +# else: +# raise UnknownError(dexe["stderr"]) + +# def build_input( +# self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None +# ) -> Dict[str, Any]: +# nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} + +# opts = copy.deepcopy(input_model.keywords) +# opts = {k.lower(): v for k, v in opts.items()} + +# # Handle memory +# # for nwchem, [GiB] --> [B] +# # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' +# memory_size = int(config.memory * (1024 ** 3)) +# if config.use_mpiexec: # It is the memory per MPI rank +# memory_size //= config.nnodes * config.ncores // config.cores_per_rank +# opts["memory"] = memory_size + +# # Handle molecule +# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) +# opts.update(moldata["keywords"]) + +# # Handle calc type and quantum chemical method +# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) +# opts.update(mdcopts) + +# # Handle basis set +# # * for nwchem, still needs sph and ghost +# for el in set(input_model.molecule.symbols): +# opts[f"basis__{el}"] = f"library {input_model.model.basis}" + +# # Log the job settings +# logger.debug("JOB_OPTS") +# logger.debug(pp.pformat(opts)) + +# # Handle conversion from schema (flat key/value) keywords into local format +# optcmd = format_keywords(opts) + +# # Combine the molecule description, options and method command together +# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd + +# # For gradient methods, add a Python command to save the gradients in higher precision +# # Note: The Hessian is already stored in high precision in a file named "*.hess" +# if input_model.driver == "gradient": +# # Get the name of the theory used for computing the gradients +# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) +# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") + +# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) +# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ +# # (not 6 significant figures) +# pycmd = f""" +# python +# grad = rtdb_get('{theory}:gradient') +# if ga_nodeid() == 0: +# import json +# with open('nwchem.grad', 'w') as fp: +# json.dump(grad, fp) +# end + +# task python +# """ +# nwchemrec["infiles"]["nwchem.nw"] += pycmd + +# # Determine the command +# if config.use_mpiexec: +# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) +# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") +# else: +# nwchemrec["command"] = [which("nwchem")] + +# return nwchemrec + +# def execute( +# self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None +# ) -> Tuple[bool, Dict]: + +# success, dexe = execute( +# inputs["command"], +# inputs["infiles"], +# ["nwchem.hess", "nwchem.grad"], +# scratch_messy=False, +# scratch_exist_ok=True, +# scratch_directory=inputs["scratch_directory"], +# ) +# return success, dexe + +# def parse_output( +# self, outfiles: Dict[str, str], input_model: "AtomicInput" +# ) -> "AtomicResult": # lgtm: [py/similar-function] + +# # Get the stdout from the calculation (required) +# stdout = outfiles.pop("stdout") + +# # Read the NWChem stdout file and, if needed, the hess or grad files +# qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + +# if nwgrad is not None: +# qcvars["CURRENT GRADIENT"] = nwgrad + +# if nwhess is not None: +# qcvars["CURRENT HESSIAN"] = nwhess + +# # Normalize the output as a float or list of floats +# if input_model.driver.upper() == "PROPERTIES": +# retres = qcvars[f"CURRENT ENERGY"] +# else: +# retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + +# if isinstance(retres, Decimal): +# retres = float(retres) +# elif isinstance(retres, np.ndarray): +# retres = retres.tolist() + +# # Get the formatted properties +# qcprops = extract_formatted_properties(qcvars) + +# # Format them inout an output +# output_data = { +# "schema_name": "qcschema_output", +# "schema_version": 1, +# "extras": {"outfiles": outfiles, **input_model.extras}, +# "properties": qcprops, +# "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), +# "return_result": retres, +# "stdout": stdout, +# } + +# # got to even out who needs plump/flat/Decimal/float/ndarray/list +# # Decimal --> str preserves precision +# output_data["extras"]["qcvars"] = { +# k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() +# } + +# output_data["success"] = True +# return AtomicResult(**{**input_model.dict(), **output_data}) From f368814e66386d86d6d4d8435ac4d558485405be Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:16:48 -0400 Subject: [PATCH 003/102] init madness dir --- qcengine/programs/madness/germinate.py | 370 ++--- qcengine/programs/madness/harvester.py | 1754 ++++++++++++------------ qcengine/programs/madness/keywords.py | 144 +- 3 files changed, 1134 insertions(+), 1134 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index e76132ba8..88fb672e8 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -2,188 +2,188 @@ from qcengine.exceptions import InputError -# List of XC functionals known to NWChem -_xc_functionals = [ - "acm", - "b3lyp", - "beckehandh", - "pbe0", - "becke97", - "becke97-1", - "becke97-2", - "becke97-3", - "becke97-d", - "becke98", - "hcth", - "hcth120", - "hcth147", - "hcth407", - "becke97gga1", - "hcth407p", - "mpw91", - "mpw1k", - "xft97", - "cft97", - "ft97", - "op", - "bop", - "pbeop", - "xpkzb99", - "cpkzb99", - "xtpss03", - "ctpss03", - "xctpssh", - "b1b95", - "bb1k", - "mpw1b95", - "mpwb1k", - "pw6b95", - "pwb6k", - "m05", - "m05-2x", - "vs98", - "m06", - "m06-hf", - "m06-L", - "m06-2x", - "HFexch", - "becke88", - "xperdew91", - "xpbe96", - "gill96", - "lyp", - "perdew81", - "perdew86", - "perdew91", - "cpbe96", - "pw91lda", - "slater", - "vwn_1", - "vwn_2", - "vwn_3", - "vwn_4", - "vwn_5", - "vwn_1_rpa", - "xtpss03", - "ctpss03", - "bc95", - "xpw6b95", - "xpwb6k", - "xm05", - "xm05-2x", - "cpw6b95", - "cpwb6k", - "cm05", - "cm05-2x", - "xvs98", - "cvs98", - "xm06-L", - "xm06-hf", - "xm06", - "xm06-2x", - "cm06-L", - "cm06-hf", - "cm06", - "cm06-2x", -] - - -def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: - """Converts the QC method into NWChem keywords - - Args: - method (str): Name of the QC method to use - derint (str): Index of the run type - use_tce (bool): Whether to use the Tensor Contraction Engine - Returns: - (str): Task command for NWChem - (dict): Any options for NWChem - """ - - # Standardize the method name - method = method.lower() - opts = {} - - # Map the run type to - runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - - # Write out the theory directive - if method == "nwchem": - mdccmd = "" - - elif method in ["scf", "hf"]: - mdccmd = f"task scf {runtyp}\n\n" - - elif method == "mp2": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp2"] = True - else: - mdccmd = f"task mp2 {runtyp}\n\n" - - elif method == "mp3": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp3"] = True - - elif method == "mp4": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp4"] = True - - elif method == "ccd": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccd"] = True - - elif method == "ccsd": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsd"] = True - else: - mdccmd = f"task ccsd {runtyp}\n\n" - - elif method == "ccsdt": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsdt"] = True - else: - mdccmd = f"task ccsdt {runtyp}\n\n" - - elif method == "ccsd(t)": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsd(t)"] = True - else: - mdccmd = f"task ccsd(t) {runtyp}\n\n" - - elif method == "tddft": - mdccmd = f"task tddft {runtyp}\n\n" - - elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: - raise InputError(f'Method "{method}" not yet supported by QCEngine') - - elif method == "tce": - raise InputError( - f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' - ) - - elif method.split()[0] in _xc_functionals: - opts["dft__xc"] = method - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__"] = "dft" - else: - mdccmd = f"task dft {runtyp}\n\n" - - elif method == "dft": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__"] = "dft" - else: - mdccmd = f"task dft {runtyp}\n\n" - - else: - raise InputError(f"Method not recognized: {method}") - - return mdccmd, opts +# # List of XC functionals known to NWChem +# _xc_functionals = [ +# "acm", +# "b3lyp", +# "beckehandh", +# "pbe0", +# "becke97", +# "becke97-1", +# "becke97-2", +# "becke97-3", +# "becke97-d", +# "becke98", +# "hcth", +# "hcth120", +# "hcth147", +# "hcth407", +# "becke97gga1", +# "hcth407p", +# "mpw91", +# "mpw1k", +# "xft97", +# "cft97", +# "ft97", +# "op", +# "bop", +# "pbeop", +# "xpkzb99", +# "cpkzb99", +# "xtpss03", +# "ctpss03", +# "xctpssh", +# "b1b95", +# "bb1k", +# "mpw1b95", +# "mpwb1k", +# "pw6b95", +# "pwb6k", +# "m05", +# "m05-2x", +# "vs98", +# "m06", +# "m06-hf", +# "m06-L", +# "m06-2x", +# "HFexch", +# "becke88", +# "xperdew91", +# "xpbe96", +# "gill96", +# "lyp", +# "perdew81", +# "perdew86", +# "perdew91", +# "cpbe96", +# "pw91lda", +# "slater", +# "vwn_1", +# "vwn_2", +# "vwn_3", +# "vwn_4", +# "vwn_5", +# "vwn_1_rpa", +# "xtpss03", +# "ctpss03", +# "bc95", +# "xpw6b95", +# "xpwb6k", +# "xm05", +# "xm05-2x", +# "cpw6b95", +# "cpwb6k", +# "cm05", +# "cm05-2x", +# "xvs98", +# "cvs98", +# "xm06-L", +# "xm06-hf", +# "xm06", +# "xm06-2x", +# "cm06-L", +# "cm06-hf", +# "cm06", +# "cm06-2x", +# ] + + +# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +# """Converts the QC method into NWChem keywords + +# Args: +# method (str): Name of the QC method to use +# derint (str): Index of the run type +# use_tce (bool): Whether to use the Tensor Contraction Engine +# Returns: +# (str): Task command for NWChem +# (dict): Any options for NWChem +# """ + +# # Standardize the method name +# method = method.lower() +# opts = {} + +# # Map the run type to +# runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + +# # Write out the theory directive +# if method == "nwchem": +# mdccmd = "" + +# elif method in ["scf", "hf"]: +# mdccmd = f"task scf {runtyp}\n\n" + +# elif method == "mp2": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp2"] = True +# else: +# mdccmd = f"task mp2 {runtyp}\n\n" + +# elif method == "mp3": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp3"] = True + +# elif method == "mp4": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp4"] = True + +# elif method == "ccd": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccd"] = True + +# elif method == "ccsd": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsd"] = True +# else: +# mdccmd = f"task ccsd {runtyp}\n\n" + +# elif method == "ccsdt": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsdt"] = True +# else: +# mdccmd = f"task ccsdt {runtyp}\n\n" + +# elif method == "ccsd(t)": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsd(t)"] = True +# else: +# mdccmd = f"task ccsd(t) {runtyp}\n\n" + +# elif method == "tddft": +# mdccmd = f"task tddft {runtyp}\n\n" + +# elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: +# raise InputError(f'Method "{method}" not yet supported by QCEngine') + +# elif method == "tce": +# raise InputError( +# f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' +# ) + +# elif method.split()[0] in _xc_functionals: +# opts["dft__xc"] = method +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__"] = "dft" +# else: +# mdccmd = f"task dft {runtyp}\n\n" + +# elif method == "dft": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__"] = "dft" +# else: +# mdccmd = f"task dft {runtyp}\n\n" + +# else: +# raise InputError(f"Method not recognized: {method}") + +# # return mdccmd, opts diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index dbcfa9bfa..3040e81b1 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -15,881 +15,881 @@ logger = logging.getLogger(__name__) -def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: - """Function to read an entire NWChem output file. - - Reads all of the different "line search" segments of a file and returns - values from the last segment for which a geometry was written. - - Args: - outtext (str): Output written to stdout - Returns: - - (PreservingDict) Variables extracted from the output file in the last complete step - - (Molecule): Molecule from the last complete step - - (list): Gradient from the last complete step - - (str): Version string - - (str): Error message, if any - """ - - # Loop over all steps - # TODO (wardlt): Is it only necessary to read the last two steps? - pass_psivar = [] - pass_coord = [] - pass_grad = [] - for outpass in re.split(r" Line search:", outtext, re.MULTILINE): - psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) - pass_psivar.append(psivar) - pass_coord.append(nwcoord) - pass_grad.append(nwgrad) - - # Determine which segment contained the last geometry - retindx = -1 if pass_coord[-1] else -2 - - return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error - - -def harvest_outfile_pass(outtext): - """Function to read NWChem output file *outtext* and parse important - quantum chemical information from it in - - """ - psivar = PreservingDict() - psivar_coord = None - psivar_grad = None - version = "" - error = "" # TODO (wardlt): The error string is never used. - - NUMBER = r"(?x:" + regex.NUMBER + ")" - # fmt: off - - # Process version - mobj = re.search( - r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', - outtext, re.MULTILINE) - if mobj: - logger.debug('matched version') - version = mobj.group('version') - - # Process SCF - # 1)Fail to converge - mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('failed to converge') - - # 2)Calculation converged - else: - mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched HF') - psivar['HF TOTAL ENERGY'] = mobj.group(1) - - # Process Effective nuclear repulsion energy (a.u.) - mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj: - logger.debug('matched NRE') - # logger.debug (mobj.group(1)) - psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) - - # Process DFT dispersion energy (a.u.) - mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched Dispersion') - logger.debug(mobj.group(1)) - psivar['DFT DISPERSION ENERGY'] = mobj.group(1) - - # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) - - mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched DFT') - logger.debug(mobj.group(1)) - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - - # SODFT [for nwchem 6.8+] - mobj = re.search( - r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched DFT') - # print (mobj.group(1)) - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) - - # MCSCF - mobj = re.findall( - r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - - # for mobj_list in mobj: - - if mobj: # Need to change to accommodate find all instances - logger.debug('matched mcscf') # MCSCF energy calculation - psivar['HF TOTAL ENERGY'] = mobj.group(1) - psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) - psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) - psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - # for mobj_list in mobj: - # for i in mobj_list: - # count += 0 - # logger.debug('matched mcscf iteration %i', count) - # psivar['HF TOTAL ENERGY'] = mobj.group(1) - # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) - # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) - # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - - # Process MP2 (Restricted, Unrestricted(RO n/a)) - # 1)SCF-MP2 - mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + - NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 - if mobj: - logger.debug('matched scf-mp2') - psivar['HF TOTAL ENERGY'] = mobj.group(1) - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(5) - # SCS-MP2 - mobj = re.search( - r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE) - if mobj: - logger.debug('matched scs-mp2') - psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) - psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) - - logger.debug(mobj.group(1)) # ess - logger.debug(mobj.group(2)) # fss - logger.debug(mobj.group(3)) # eos - logger.debug(mobj.group(4)) # fos - logger.debug(mobj.group(5)) # scs corl - logger.debug(mobj.group(6)) # scs-mp2 - - # 2) DFT-MP2 - mobj = re.search( - r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + - r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched dft-mp2') - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - - # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) - mobj = re.search( - r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + - r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + - r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug('matched coupled cluster-mp2') - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - - # 4) Direct MP2 - - # 5) RI-MP2 - - # Process calculation through tce [dertype] command - for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: - mobj = re.search( - r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, - re.MULTILINE) - - if mobj: - mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') - logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) - - if mbpt_plain == 'MP2': - psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) - else: - psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) - psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) - #TCE dipole- MBPT(n) - mobj2 = re.search( - r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - - if mobj2: - mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') - print(f'matched tce {mbpt_plain} dipole moment') - #only pulling Debye - psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) - psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) - psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) - - #TCE with () or [] - for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: - mobj = re.search( - r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj: - cc_plain = cc_name.replace('\\', '') - cc_corr = cc_plain.replace('CCSD', '') - logger.debug(f'matched tce cc {cc_plain}') - - psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) - psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) - psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) - #TCE dipole with () or [] - mobj2 = re.search( - r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - - if mobj2: - cc_plain = cc_name.replace('\\', '') - cc_corr = cc_plain.replace('CCSD', '') - print(f'matched tce {cc_plain} dipole moment') - - #only pulling Debye - psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) - psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) - psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) +# def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: +# """Function to read an entire NWChem output file. + +# Reads all of the different "line search" segments of a file and returns +# values from the last segment for which a geometry was written. + +# Args: +# outtext (str): Output written to stdout +# Returns: +# - (PreservingDict) Variables extracted from the output file in the last complete step +# - (Molecule): Molecule from the last complete step +# - (list): Gradient from the last complete step +# - (str): Version string +# - (str): Error message, if any +# """ + +# # Loop over all steps +# # TODO (wardlt): Is it only necessary to read the last two steps? +# pass_psivar = [] +# pass_coord = [] +# pass_grad = [] +# for outpass in re.split(r" Line search:", outtext, re.MULTILINE): +# psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) +# pass_psivar.append(psivar) +# pass_coord.append(nwcoord) +# pass_grad.append(nwgrad) + +# # Determine which segment contained the last geometry +# retindx = -1 if pass_coord[-1] else -2 + +# return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +# def harvest_outfile_pass(outtext): +# """Function to read NWChem output file *outtext* and parse important +# quantum chemical information from it in + +# """ +# psivar = PreservingDict() +# psivar_coord = None +# psivar_grad = None +# version = "" +# error = "" # TODO (wardlt): The error string is never used. + +# NUMBER = r"(?x:" + regex.NUMBER + ")" +# # fmt: off + +# # Process version +# mobj = re.search( +# r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched version') +# version = mobj.group('version') + +# # Process SCF +# # 1)Fail to converge +# mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('failed to converge') + +# # 2)Calculation converged +# else: +# mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched HF') +# psivar['HF TOTAL ENERGY'] = mobj.group(1) + +# # Process Effective nuclear repulsion energy (a.u.) +# mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched NRE') +# # logger.debug (mobj.group(1)) +# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) + +# # Process DFT dispersion energy (a.u.) +# mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched Dispersion') +# logger.debug(mobj.group(1)) +# psivar['DFT DISPERSION ENERGY'] = mobj.group(1) + +# # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) + +# mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched DFT') +# logger.debug(mobj.group(1)) +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) + +# # SODFT [for nwchem 6.8+] +# mobj = re.search( +# r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched DFT') +# # print (mobj.group(1)) +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) +# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) + +# # MCSCF +# mobj = re.findall( +# r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + +# # for mobj_list in mobj: + +# if mobj: # Need to change to accommodate find all instances +# logger.debug('matched mcscf') # MCSCF energy calculation +# psivar['HF TOTAL ENERGY'] = mobj.group(1) +# psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) +# psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) +# psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) +# # for mobj_list in mobj: +# # for i in mobj_list: +# # count += 0 +# # logger.debug('matched mcscf iteration %i', count) +# # psivar['HF TOTAL ENERGY'] = mobj.group(1) +# # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) +# # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) +# # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + +# # Process MP2 (Restricted, Unrestricted(RO n/a)) +# # 1)SCF-MP2 +# mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + +# NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 +# if mobj: +# logger.debug('matched scf-mp2') +# psivar['HF TOTAL ENERGY'] = mobj.group(1) +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(5) +# # SCS-MP2 +# mobj = re.search( +# r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE) +# if mobj: +# logger.debug('matched scs-mp2') +# psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) +# psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) + +# logger.debug(mobj.group(1)) # ess +# logger.debug(mobj.group(2)) # fss +# logger.debug(mobj.group(3)) # eos +# logger.debug(mobj.group(4)) # fos +# logger.debug(mobj.group(5)) # scs corl +# logger.debug(mobj.group(6)) # scs-mp2 + +# # 2) DFT-MP2 +# mobj = re.search( +# r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + +# r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched dft-mp2') +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + +# # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) +# mobj = re.search( +# r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + +# r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + +# r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched coupled cluster-mp2') +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + +# # 4) Direct MP2 + +# # 5) RI-MP2 + +# # Process calculation through tce [dertype] command +# for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: +# mobj = re.search( +# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, +# re.MULTILINE) + +# if mobj: +# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') +# logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) + +# if mbpt_plain == 'MP2': +# psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) +# else: +# psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) +# psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) +# #TCE dipole- MBPT(n) +# mobj2 = re.search( +# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj2: +# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') +# print(f'matched tce {mbpt_plain} dipole moment') +# #only pulling Debye +# psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) +# psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) +# psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) + +# #TCE with () or [] +# for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: +# mobj = re.search( +# r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# cc_plain = cc_name.replace('\\', '') +# cc_corr = cc_plain.replace('CCSD', '') +# logger.debug(f'matched tce cc {cc_plain}') + +# psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) +# psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) +# psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) +# #TCE dipole with () or [] +# mobj2 = re.search( +# r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj2: +# cc_plain = cc_name.replace('\\', '') +# cc_corr = cc_plain.replace('CCSD', '') +# print(f'matched tce {cc_plain} dipole moment') + +# #only pulling Debye +# psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) +# psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) +# psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) - #Process other TCE cases - for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: - mobj = re.search( - r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + - r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + - r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug(f'matched {cc_name}') - logger.debug(mobj) - psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) - psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) - #TCE dipole - mobj2 = re.search( - r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj2: - print(f'matched tce dipole moment') - - #only pulling Debye - psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) - psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) - psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) - - # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command - - mobj = re.search( - r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + - r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE | re.DOTALL) - - if mobj: - logger.debug('matched ccsd') - psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) - psivar['CCSD TOTAL ENERGY'] = mobj.group(3) - - mobj = re.search( - r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + - r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - - if mobj: - logger.debug('matched ccsd(t)') - psivar['(T) CORRECTION ENERGY'] = mobj.group(1) - psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] - psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) - - mobj = re.search( - r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' - r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' - r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - # SCS-CCSD included - if mobj: - logger.debug('matched scs-ccsd') - psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( - Decimal(mobj.group(1)) * Decimal(mobj.group(2))) - psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( - Decimal(mobj.group(4)) * Decimal(mobj.group(3))) - psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) - psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) - psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( - psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) - # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( - # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) - - # Process EOM-[cc_name] #nwchem_tce_dipole = false - # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree - # psivar name might need to be fixed - # each root excitation energy is extracted from the last iteration of right hand side - mobj = re.findall( - r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + - # (..) captures symmetry - r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root - r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree - r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV - outtext, re.MULTILINE | re.DOTALL) - #regex should be more dynamic in finding values, need to revisit - #mobj.group(0) = symmetry value - #mobj.group(1) = cc_name - #mobj.group(2) = root number - #mobj.group(3) = excitation energy (hartree) - #mobj.group(4) = excitation energy (eV) - - if mobj: - print(mobj) - ext_energy = {} # dic - - ext_energy_list = [] - print(f'matched eom-{cc_name}') - for mobj_list in mobj: - logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry - logger.debug(mobj_list) - count = 0 - for line in mobj_list[1].splitlines(): - lline = line.split() - logger.debug(lline[1]) # in hartree - logger.debug(lline[2]) # in eV - count += 1 - - logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) - - ext_energy_list.append(lline[1]) # Collect all excitation energies - - sym = str(mobj_list[0]) - ext_energy.setdefault(sym, []) - ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) - - ext_energy_list.sort(key=float) - - for nroot in range(len(ext_energy_list)): - for k, e_val in ext_energy.items(): - if ext_energy_list[nroot] in e_val: - symm = k - psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ - ext_energy_list[nroot] # in hartree - psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ - psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree - gssym = '' - gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) - - if gs: - logger.debug('matched ground-state symmetry') - psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) - -# Process TDDFT -# 1) Spin allowed - mobj = re.findall( - r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' - #Root | symmetry | a.u. | eV - + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value - + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole - + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople - + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople - + r'\s*$', - outtext, re.MULTILINE) - - if mobj: - logger.debug('matched TDDFT with transition moments') - for mobj_list in mobj: - print (mobj_list) - psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV - psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) - #### temporary psivars #### - #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % - # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. - #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ - # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) - psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] - psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] - psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] - psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] - psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] - psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] - psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] - psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] - psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] - - -# 2) Spin forbidden - mobj = re.findall( - r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' - #Root | symmetry | a.u. | eV - + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value - + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', - outtext, re.MULTILINE) - #mobj.group(0) = Root - #mobj.group(1) = symmetry - #mobj.group(2) a.u. - #mobj.group(3) e.V - #mobj.group(4) Excitation energy - #mobj.group(5) Excited state energy - - if mobj: - logger.debug('matched TDDFT - spin forbidden') - for mobj_list in mobj: - #### temporary psivars #### - psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV - psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) - - #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % - # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. - #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ - # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) - if mobj: - logger.debug('Non-variation initial energy') # prints out energy, 5 counts - - # Process geometry - # 1) CHARGE - # Read charge from SCF module - mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched charge') - out_charge = int(float(mobj.group(1))) - - # Read charge from General information (not scf module) - mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched charge') - out_charge = int(float(mobj.group(1))) - - # 2) MULTIPLICITY - # Read multiplicity from SCF module - mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity') - out_mult = int(mobj.group(1)) + 1 - - # Read multiplicity from SCF module through alpha, beta electrons - mobj = re.search( - r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + - r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity via alpha and beta electrons') - out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 - psivar['N ALPHA ELECTRONS'] = mobj.group(1) - psivar['N BETA ELECTRONS'] = mobj.group(2) - - # Read multiplicity from General information (not scf module) - mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity') - out_mult = int(mobj.group(1)) - - # 3) Initial geometry - mobj = re.search( - r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + - r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + - r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + - r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + - r'\s*' + - r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' - + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched geom') - - # dinky molecule w/ charge and multiplicity - if mobj.group(1) == 'angstroms': - molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult - ) # unit = angstrom - for line in mobj.group(2).splitlines(): - lline = line.split() - molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) - # Jiyoung was collecting charge (-4)? see if this is ok for ghosts - # Tag , X, Y, Z - psivar_coord = Molecule(validate=False, - **qcel.molparse.to_schema(qcel.molparse.from_string( - molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], - dtype=2)) - - else: # unit = a.u. - molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) - for line in mobj.group(2).splitlines(): - lline = line.split() - molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) - # Tag , X, Y, Z - psivar_coord = Molecule(validate=False, - **qcel.molparse.to_schema(qcel.molparse.from_string( - molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], - dtype=2)) - - # Process gradient - mobj = re.search( - r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + - r'atom coordinates gradient' + r'\s*' + r'^\s+' + - r'x y z x y z' + r'\s*' + - r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' - + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug('matched molgrad') - atoms = [] - psivar_grad = [] - for line in mobj.group(1).splitlines(): - lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z - # print (lline) - if lline == []: - pass - else: - atoms.append(lline[1]) # Tag - psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) - - # Process dipole (Properties) - mobj = re.search( - r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + - r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'.*' + - r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + - r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + - r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'.*' + - r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', - outtext, re.MULTILINE) - - if mobj: - logger.debug('matched total dipole') - - # UNIT = DEBYE(S) - psivar['CURRENT DIPOLE X'] = mobj.group(7) - psivar['CURRENT DIPOLE Y'] = mobj.group(8) - psivar['CURRENT DIPOLE Z'] = mobj.group(9) - # total? - - # Process error code - mobj = re.search( - r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + - r'\s*' + r'^\s+' - r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' - r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + - r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched error') - # print (mobj.group(1)) #error line number - # print (mobj.group(2)) #error reason - psivar['NWCHEM ERROR CODE'] = mobj.group(1) - # TODO process errors into error var - - # fmt: on - - # Get the size of the basis sets, etc - mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) - if mobj: - psivar["N ATOMS"] = mobj.group(1) - mobj = re.search( - r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", - outtext, - re.MULTILINE, - ) - if mobj: - psivar["N ALPHA ELECTRONS"] = mobj.group(2) - psivar["N BETA ELECTRONS"] = mobj.group(3) - if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: - # get HOMO and LUMO energy - mobj = re.search( - r"Vector" - + r"\s+" - + r"%d" % (psivar["N ALPHA ELECTRONS"]) - + r"\s+" - + r"Occ=" - + r".*" - + r"\s+" - + r"E=" - + r"([+-]?\s?\d+[.]\d+)" - + r"[D]" - + r"([+-]0\d)", - outtext, - re.MULTILINE, - ) - if mobj: - homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) - psivar["HOMO"] = np.array([round(homo, 10)]) - mobj = re.search( - r"Vector" - + r"\s+" - + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) - + r"\s+" - + r"Occ=" - + r".*" - + r"\s+" - + r"E=" - + r"([+-]?\s?\d+[.]\d+)" - + r"[D]" - + r"([+-]0\d)", - outtext, - re.MULTILINE, - ) - if mobj: - lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) - psivar["LUMO"] = np.array([round(lumo, 10)]) - - mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) - if mobj: - psivar["N MO"] = mobj.group(2) - psivar["N BASIS"] = mobj.group(1) - - # Search for Center of charge - mobj = re.search( - r"Center of charge \(in au\) is the expansion point" - + r"\n" - + r"\s+" - + r"X\s+=\s+([+-]?\d+[.]\d+)" - + r"\s+" - + r"Y\s+=\s+([+-]?\d+[.]\d+)" - + r"\s+" - + r"Z\s+=\s+([+-]?\d+[.]\d+)", - outtext, - re.MULTILINE, - ) - if mobj: - psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - - mobj = re.search( - r"Dipole moment" - + r".*?" - + r"A\.U\." - + r"\s+" - + r"DMX\s+([+-]?\d+[.]\d+)\s+" - + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" - + r"DMY\s+([+-]?\d+[.]\d+)\s+" - + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" - + r"DMZ\s+([+-]?\d+[.]\d+)\s+" - + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" - + r"\-EFC\-" - + r".*?" - + r"A\.U\.\s+" - + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", - outtext, - re.MULTILINE, - ) - # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) - if mobj: - psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - - mobj = re.search( - r"Quadrupole moments in atomic units\s+" - + r"Component\s+" - + r"Electronic\+nuclear\s+" - + r"Point charges\s+" - + r"Total\s+" - + r"-+\s+" - + r"XX\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"ZZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YZ\s+([+-]?\d+[.]\d+)\s+", - outtext, - re.MULTILINE, - ) - - if mobj: - psivar["QUADRUPOLE MOMENT"] = np.array( - [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] - ) - - # Process CURRENT energies (TODO: needs better way) - if "HF TOTAL ENERGY" in psivar: - psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] - psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] - psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] - - if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] - if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] - if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] - - if "DFT TOTAL ENERGY" in psivar: - psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] - psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] - - # Process TCE CURRENT energies - # Need to be fixed - # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? - # TODO: CURRENT ENERGY = TCE ENERGY - if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): - psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] - psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] - - if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] - - if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] - - if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): - psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] - psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] - - return psivar, psivar_coord, psivar_grad, version, error - - -def harvest_hessian(hess: str) -> np.ndarray: - """Parses the contents of the NWChem hess file into a hessian array. - - Args: - hess (str): Contents of the hess file - Returns: - (np.ndarray) Hessian matrix as a 2D array - """ - - # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python - hess_conv = hess.replace("D", "E") - - # Parse all of the float values - hess_tri = [float(x) for x in hess_conv.strip().splitlines()] - - # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) - n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix - - # Add the lower diagonal - hess_arr = np.zeros((n, n)) - hess_arr[np.tril_indices(n)] = hess_tri - - # Transpose and then set the lower diagonal again - hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major - hess_arr[np.tril_indices(n)] = hess_tri - - return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines - - -def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: - """Get named properties out of the general variables extracted out of the result file - - Args: - psivars (PreservingDict): Dictionary of the output results - Returns: - (AtomicResultProperties) Properties in a standard format - """ - # TODO (wardlt): Get more of the named variables out of the NWChem file - - # Initialize the output - output = dict() - - # Extract the Calc Info - output.update( - { - "calcinfo_nbasis": psivars.get("N BASIS", None), - "calcinfo_nmo": psivars.get("N MO", None), - "calcinfo_natom": psivars.get("N ATOMS", None), - "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), - "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), - } - ) - - # Get the "canonical" properties - output["return_energy"] = psivars["CURRENT ENERGY"] - output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] - - # Get the SCF properties - output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) - output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) - output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) - output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) - - # Get the MP2 properties - output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) - output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) - output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) - output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) - return AtomicResultProperties(**output) - - -def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: - """Parses all the pieces of output from NWChem: the stdout in - *nwout* Scratch files are not yet considered at this moment. - - Args: - in_mol (Molecule): Input molecule - nwout (str): NWChem output molecule - outfiles (dict): Dictionary of outfile files and their contents - Returns: - - (PreservingDict) Variables extracted from the output file in the last complete step - - (None): Hessian from the last complete step (Not yet implemented) - - (list): Gradient from the last complete step - - (Molecule): Molecule from the last complete step - - (str): Version string - - (str): Error message, if any - """ - - # Parse the NWChem output - out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) - - # If available, read higher-accuracy gradients - # These were output using a Python Task in NWChem to read them out of the database - if outfiles.get("nwchem.grad") is not None: - logger.debug("Reading higher-accuracy gradients") - out_grad = json.loads(outfiles.get("nwchem.grad")) - - # If available, read the hessian - out_hess = None - if outfiles.get("nwchem.hess") is not None: - out_hess = harvest_hessian(outfiles.get("nwchem.hess")) - - # Make sure the input and output molecules are the same - if out_mol: - if in_mol: - if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: - raise ValueError( - """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" - % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) - ) - else: - raise ValueError("""No coordinate information extracted from NWChem output.""") - - # If present, align the gradients and hessian with the original molecular coordinates - # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the - # rotated molecule, which we can use to determine how to rotate the gradients/hessian - amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - - mill = data["mill"] # Retrieve tool with alignment routines - - if out_grad is not None: - out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) - if out_hess is not None: - out_hess = mill.align_hessian(np.array(out_hess)) - - return out_psivar, out_hess, out_grad, out_mol, version, error +# #Process other TCE cases +# for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: +# mobj = re.search( +# r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + +# r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + +# r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug(f'matched {cc_name}') +# logger.debug(mobj) +# psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) +# psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) +# #TCE dipole +# mobj2 = re.search( +# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj2: +# print(f'matched tce dipole moment') + +# #only pulling Debye +# psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) +# psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) +# psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) + +# # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command + +# mobj = re.search( +# r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + +# r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE | re.DOTALL) + +# if mobj: +# logger.debug('matched ccsd') +# psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) +# psivar['CCSD TOTAL ENERGY'] = mobj.group(3) + +# mobj = re.search( +# r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + +# r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + +# if mobj: +# logger.debug('matched ccsd(t)') +# psivar['(T) CORRECTION ENERGY'] = mobj.group(1) +# psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] +# psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) + +# mobj = re.search( +# r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' +# r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' +# r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) +# # SCS-CCSD included +# if mobj: +# logger.debug('matched scs-ccsd') +# psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( +# Decimal(mobj.group(1)) * Decimal(mobj.group(2))) +# psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( +# Decimal(mobj.group(4)) * Decimal(mobj.group(3))) +# psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) +# psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) +# psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( +# psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) +# # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( +# # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) + +# # Process EOM-[cc_name] #nwchem_tce_dipole = false +# # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree +# # psivar name might need to be fixed +# # each root excitation energy is extracted from the last iteration of right hand side +# mobj = re.findall( +# r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + +# # (..) captures symmetry +# r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root +# r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree +# r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV +# outtext, re.MULTILINE | re.DOTALL) +# #regex should be more dynamic in finding values, need to revisit +# #mobj.group(0) = symmetry value +# #mobj.group(1) = cc_name +# #mobj.group(2) = root number +# #mobj.group(3) = excitation energy (hartree) +# #mobj.group(4) = excitation energy (eV) + +# if mobj: +# print(mobj) +# ext_energy = {} # dic + +# ext_energy_list = [] +# print(f'matched eom-{cc_name}') +# for mobj_list in mobj: +# logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry +# logger.debug(mobj_list) +# count = 0 +# for line in mobj_list[1].splitlines(): +# lline = line.split() +# logger.debug(lline[1]) # in hartree +# logger.debug(lline[2]) # in eV +# count += 1 + +# logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) + +# ext_energy_list.append(lline[1]) # Collect all excitation energies + +# sym = str(mobj_list[0]) +# ext_energy.setdefault(sym, []) +# ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) + +# ext_energy_list.sort(key=float) + +# for nroot in range(len(ext_energy_list)): +# for k, e_val in ext_energy.items(): +# if ext_energy_list[nroot] in e_val: +# symm = k +# psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ +# ext_energy_list[nroot] # in hartree +# psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ +# psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree +# gssym = '' +# gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) + +# if gs: +# logger.debug('matched ground-state symmetry') +# psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) + +# # Process TDDFT +# # 1) Spin allowed +# mobj = re.findall( +# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' +# #Root | symmetry | a.u. | eV +# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value +# + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole +# + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople +# + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople +# + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched TDDFT with transition moments') +# for mobj_list in mobj: +# print (mobj_list) +# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV +# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) +# #### temporary psivars #### +# #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % +# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. +# #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ +# # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) +# psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] +# psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] +# psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] +# psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] +# psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] +# psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] +# psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] +# psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] +# psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] + + +# # 2) Spin forbidden +# mobj = re.findall( +# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' +# #Root | symmetry | a.u. | eV +# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value +# + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', +# outtext, re.MULTILINE) +# #mobj.group(0) = Root +# #mobj.group(1) = symmetry +# #mobj.group(2) a.u. +# #mobj.group(3) e.V +# #mobj.group(4) Excitation energy +# #mobj.group(5) Excited state energy + +# if mobj: +# logger.debug('matched TDDFT - spin forbidden') +# for mobj_list in mobj: +# #### temporary psivars #### +# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV +# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) + +# #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % +# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. +# #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ +# # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) +# if mobj: +# logger.debug('Non-variation initial energy') # prints out energy, 5 counts + +# # Process geometry +# # 1) CHARGE +# # Read charge from SCF module +# mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched charge') +# out_charge = int(float(mobj.group(1))) + +# # Read charge from General information (not scf module) +# mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched charge') +# out_charge = int(float(mobj.group(1))) + +# # 2) MULTIPLICITY +# # Read multiplicity from SCF module +# mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity') +# out_mult = int(mobj.group(1)) + 1 + +# # Read multiplicity from SCF module through alpha, beta electrons +# mobj = re.search( +# r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + +# r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity via alpha and beta electrons') +# out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 +# psivar['N ALPHA ELECTRONS'] = mobj.group(1) +# psivar['N BETA ELECTRONS'] = mobj.group(2) + +# # Read multiplicity from General information (not scf module) +# mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity') +# out_mult = int(mobj.group(1)) + +# # 3) Initial geometry +# mobj = re.search( +# r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + +# r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + +# r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + +# r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + +# r'\s*' + +# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' +# + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched geom') + +# # dinky molecule w/ charge and multiplicity +# if mobj.group(1) == 'angstroms': +# molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult +# ) # unit = angstrom +# for line in mobj.group(2).splitlines(): +# lline = line.split() +# molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) +# # Jiyoung was collecting charge (-4)? see if this is ok for ghosts +# # Tag , X, Y, Z +# psivar_coord = Molecule(validate=False, +# **qcel.molparse.to_schema(qcel.molparse.from_string( +# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], +# dtype=2)) + +# else: # unit = a.u. +# molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) +# for line in mobj.group(2).splitlines(): +# lline = line.split() +# molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) +# # Tag , X, Y, Z +# psivar_coord = Molecule(validate=False, +# **qcel.molparse.to_schema(qcel.molparse.from_string( +# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], +# dtype=2)) + +# # Process gradient +# mobj = re.search( +# r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + +# r'atom coordinates gradient' + r'\s*' + r'^\s+' + +# r'x y z x y z' + r'\s*' + +# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' +# + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched molgrad') +# atoms = [] +# psivar_grad = [] +# for line in mobj.group(1).splitlines(): +# lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z +# # print (lline) +# if lline == []: +# pass +# else: +# atoms.append(lline[1]) # Tag +# psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) + +# # Process dipole (Properties) +# mobj = re.search( +# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + +# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'.*' + +# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + +# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + +# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'.*' + +# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched total dipole') + +# # UNIT = DEBYE(S) +# psivar['CURRENT DIPOLE X'] = mobj.group(7) +# psivar['CURRENT DIPOLE Y'] = mobj.group(8) +# psivar['CURRENT DIPOLE Z'] = mobj.group(9) +# # total? + +# # Process error code +# mobj = re.search( +# r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + +# r'\s*' + r'^\s+' +# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' +# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + +# r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched error') +# # print (mobj.group(1)) #error line number +# # print (mobj.group(2)) #error reason +# psivar['NWCHEM ERROR CODE'] = mobj.group(1) +# # TODO process errors into error var + +# # fmt: on + +# # Get the size of the basis sets, etc +# mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["N ATOMS"] = mobj.group(1) +# mobj = re.search( +# r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# psivar["N ALPHA ELECTRONS"] = mobj.group(2) +# psivar["N BETA ELECTRONS"] = mobj.group(3) +# if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: +# # get HOMO and LUMO energy +# mobj = re.search( +# r"Vector" +# + r"\s+" +# + r"%d" % (psivar["N ALPHA ELECTRONS"]) +# + r"\s+" +# + r"Occ=" +# + r".*" +# + r"\s+" +# + r"E=" +# + r"([+-]?\s?\d+[.]\d+)" +# + r"[D]" +# + r"([+-]0\d)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) +# psivar["HOMO"] = np.array([round(homo, 10)]) +# mobj = re.search( +# r"Vector" +# + r"\s+" +# + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) +# + r"\s+" +# + r"Occ=" +# + r".*" +# + r"\s+" +# + r"E=" +# + r"([+-]?\s?\d+[.]\d+)" +# + r"[D]" +# + r"([+-]0\d)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) +# psivar["LUMO"] = np.array([round(lumo, 10)]) + +# mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["N MO"] = mobj.group(2) +# psivar["N BASIS"] = mobj.group(1) + +# # Search for Center of charge +# mobj = re.search( +# r"Center of charge \(in au\) is the expansion point" +# + r"\n" +# + r"\s+" +# + r"X\s+=\s+([+-]?\d+[.]\d+)" +# + r"\s+" +# + r"Y\s+=\s+([+-]?\d+[.]\d+)" +# + r"\s+" +# + r"Z\s+=\s+([+-]?\d+[.]\d+)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + +# mobj = re.search( +# r"Dipole moment" +# + r".*?" +# + r"A\.U\." +# + r"\s+" +# + r"DMX\s+([+-]?\d+[.]\d+)\s+" +# + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" +# + r"DMY\s+([+-]?\d+[.]\d+)\s+" +# + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" +# + r"DMZ\s+([+-]?\d+[.]\d+)\s+" +# + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" +# + r"\-EFC\-" +# + r".*?" +# + r"A\.U\.\s+" +# + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", +# outtext, +# re.MULTILINE, +# ) +# # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) +# psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) + +# mobj = re.search( +# r"Quadrupole moments in atomic units\s+" +# + r"Component\s+" +# + r"Electronic\+nuclear\s+" +# + r"Point charges\s+" +# + r"Total\s+" +# + r"-+\s+" +# + r"XX\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"YY\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"ZZ\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"XY\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"XZ\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"YZ\s+([+-]?\d+[.]\d+)\s+", +# outtext, +# re.MULTILINE, +# ) + +# if mobj: +# psivar["QUADRUPOLE MOMENT"] = np.array( +# [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] +# ) + +# # Process CURRENT energies (TODO: needs better way) +# if "HF TOTAL ENERGY" in psivar: +# psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] +# psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] + +# if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] +# if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] +# if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] + +# if "DFT TOTAL ENERGY" in psivar: +# psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] + +# # Process TCE CURRENT energies +# # Need to be fixed +# # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? +# # TODO: CURRENT ENERGY = TCE ENERGY +# if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): +# psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] +# psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] + +# if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] + +# if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + +# if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): +# psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] +# psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + +# return psivar, psivar_coord, psivar_grad, version, error + + +# def harvest_hessian(hess: str) -> np.ndarray: +# """Parses the contents of the NWChem hess file into a hessian array. + +# Args: +# hess (str): Contents of the hess file +# Returns: +# (np.ndarray) Hessian matrix as a 2D array +# """ + +# # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python +# hess_conv = hess.replace("D", "E") + +# # Parse all of the float values +# hess_tri = [float(x) for x in hess_conv.strip().splitlines()] + +# # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) +# n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix + +# # Add the lower diagonal +# hess_arr = np.zeros((n, n)) +# hess_arr[np.tril_indices(n)] = hess_tri + +# # Transpose and then set the lower diagonal again +# hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major +# hess_arr[np.tril_indices(n)] = hess_tri + +# return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines + + +# def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: +# """Get named properties out of the general variables extracted out of the result file + +# Args: +# psivars (PreservingDict): Dictionary of the output results +# Returns: +# (AtomicResultProperties) Properties in a standard format +# """ +# # TODO (wardlt): Get more of the named variables out of the NWChem file + +# # Initialize the output +# output = dict() + +# # Extract the Calc Info +# output.update( +# { +# "calcinfo_nbasis": psivars.get("N BASIS", None), +# "calcinfo_nmo": psivars.get("N MO", None), +# "calcinfo_natom": psivars.get("N ATOMS", None), +# "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), +# "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), +# } +# ) + +# # Get the "canonical" properties +# output["return_energy"] = psivars["CURRENT ENERGY"] +# output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + +# # Get the SCF properties +# output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) +# output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) +# output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) +# output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + +# # Get the MP2 properties +# output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) +# output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) +# output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) +# output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) +# return AtomicResultProperties(**output) + + +# def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: +# """Parses all the pieces of output from NWChem: the stdout in +# *nwout* Scratch files are not yet considered at this moment. + +# Args: +# in_mol (Molecule): Input molecule +# nwout (str): NWChem output molecule +# outfiles (dict): Dictionary of outfile files and their contents +# Returns: +# - (PreservingDict) Variables extracted from the output file in the last complete step +# - (None): Hessian from the last complete step (Not yet implemented) +# - (list): Gradient from the last complete step +# - (Molecule): Molecule from the last complete step +# - (str): Version string +# - (str): Error message, if any +# """ + +# # Parse the NWChem output +# out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) + +# # If available, read higher-accuracy gradients +# # These were output using a Python Task in NWChem to read them out of the database +# if outfiles.get("nwchem.grad") is not None: +# logger.debug("Reading higher-accuracy gradients") +# out_grad = json.loads(outfiles.get("nwchem.grad")) + +# # If available, read the hessian +# out_hess = None +# if outfiles.get("nwchem.hess") is not None: +# out_hess = harvest_hessian(outfiles.get("nwchem.hess")) + +# # Make sure the input and output molecules are the same +# if out_mol: +# if in_mol: +# if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: +# raise ValueError( +# """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" +# % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) +# ) +# else: +# raise ValueError("""No coordinate information extracted from NWChem output.""") + +# # If present, align the gradients and hessian with the original molecular coordinates +# # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the +# # rotated molecule, which we can use to determine how to rotate the gradients/hessian +# amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + +# mill = data["mill"] # Retrieve tool with alignment routines + +# if out_grad is not None: +# out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) +# if out_hess is not None: +# out_hess = mill.align_hessian(np.array(out_hess)) + +# return out_psivar, out_hess, out_grad, out_mol, version, error diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index 1821e98c5..ec5596a5a 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,86 +2,86 @@ from typing import Any, Dict, Tuple -def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: - """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" +# def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: +# """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" - # Transform string booleans into " " - if val is True: - return keyword.lower(), "" - elif val is False: - return "", "" +# # Transform string booleans into " " +# if val is True: +# return keyword.lower(), "" +# elif val is False: +# return "", "" - # complete hack - if keyword.upper() == "MEMORY": - return keyword.lower(), f"{val} byte" +# # complete hack +# if keyword.upper() == "MEMORY": +# return keyword.lower(), f"{val} byte" - elif isinstance(val, list): - text = " ".join([str(v) for v in val]) - elif isinstance(val, dict): - text = [] - for k, v in val.items(): - merge = [k] - merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) - text.append(" ".join(merge)) - text = " ".join(text) - else: - text = str(val) +# elif isinstance(val, list): +# text = " ".join([str(v) for v in val]) +# elif isinstance(val, dict): +# text = [] +# for k, v in val.items(): +# merge = [k] +# merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) +# text.append(" ".join(merge)) +# text = " ".join(text) +# else: +# text = str(val) - if lop_off: - return keyword[7:].lower(), text - else: - return keyword.lower(), text +# if lop_off: +# return keyword[7:].lower(), text +# else: +# return keyword.lower(), text -def format_keywords(keywords: Dict[str, Any]) -> str: - """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" +# def format_keywords(keywords: Dict[str, Any]) -> str: +# """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" - def rec_dd(): - return collections.defaultdict(rec_dd) +# def rec_dd(): +# return collections.defaultdict(rec_dd) - grouped_options = rec_dd() +# grouped_options = rec_dd() - for group_key, val in keywords.items(): - nesting = group_key.split("__") - if len(nesting) == 1: - key = nesting[0] - grouped_options["aaaglobal"][key] = val - elif len(nesting) == 2: - g1, key = nesting - grouped_options[g1][key] = val - elif len(nesting) == 3: - g1, g2, key = nesting - grouped_options[g1][g2][key] = val - else: - print(nesting) - raise ValueError("Nesting N!") +# for group_key, val in keywords.items(): +# nesting = group_key.split("__") +# if len(nesting) == 1: +# key = nesting[0] +# grouped_options["aaaglobal"][key] = val +# elif len(nesting) == 2: +# g1, key = nesting +# grouped_options[g1][key] = val +# elif len(nesting) == 3: +# g1, g2, key = nesting +# grouped_options[g1][g2][key] = val +# else: +# print(nesting) +# raise ValueError("Nesting N!") - grouped_lines = {} - for group, opts in sorted(grouped_options.items()): - lines = [] - group_level_lines = [] - for key, val in grouped_options[group].items(): - if isinstance(val, dict): - g2_level_lines = [] - g2_level_lines.append(key.lower()) - for k2, v2 in val.items(): - line2 = " ".join(format_keyword(k2, v2, lop_off=False)) - g2_level_lines.append(line2) - g2_level_lines = " ".join(g2_level_lines) - lines.append(g2_level_lines) - else: - line = " ".join(format_keyword(key, val, lop_off=False)) - if group.lower() == "basis" and any( - [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] - ): - group_level_lines.append(line) - else: - lines.append(line) - if group == "aaaglobal": - grouped_lines[group] = "\n".join(lines) + "\n" - else: - grouped_lines[group] = ( - f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" - ) +# grouped_lines = {} +# for group, opts in sorted(grouped_options.items()): +# lines = [] +# group_level_lines = [] +# for key, val in grouped_options[group].items(): +# if isinstance(val, dict): +# g2_level_lines = [] +# g2_level_lines.append(key.lower()) +# for k2, v2 in val.items(): +# line2 = " ".join(format_keyword(k2, v2, lop_off=False)) +# g2_level_lines.append(line2) +# g2_level_lines = " ".join(g2_level_lines) +# lines.append(g2_level_lines) +# else: +# line = " ".join(format_keyword(key, val, lop_off=False)) +# if group.lower() == "basis" and any( +# [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] +# ): +# group_level_lines.append(line) +# else: +# lines.append(line) +# if group == "aaaglobal": +# grouped_lines[group] = "\n".join(lines) + "\n" +# else: +# grouped_lines[group] = ( +# f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" +# ) - return "\n".join(grouped_lines.values()) + "\n" +# return "\n".join(grouped_lines.values()) + "\n" From 8d7ac783b66f54d98d16a98d7af75142f3d8eea8 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:31:14 -0400 Subject: [PATCH 004/102] changin base and test to add madness --- qcengine/programs/base.py | 1 + qcengine/testing.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index 1a49d6db2..282e08141 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -13,6 +13,7 @@ from .mopac import MopacHarness from .mp2d import MP2DHarness from .nwchem import NWChemHarness +from .madness import MadnessHarness from .openmm import OpenMMHarness from .psi4 import Psi4Harness from .qchem import QChemHarness diff --git a/qcengine/testing.py b/qcengine/testing.py index 83a599488..4be81b454 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -153,6 +153,7 @@ def get_job(self): "mopac": is_program_new_enough("mopac", "2016"), "mp2d": which("mp2d", return_bool=True), "nwchem": which("nwchem", return_bool=True), + "madness": which("nwchem", return_bool=True), "psi4": is_program_new_enough("psi4", "1.2"), "psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"), "qcdb": which_import("qcdb", return_bool=True), From eb0bc389f50ba8b0a2e0215d6990f2458411ab81 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 16:54:54 -0400 Subject: [PATCH 005/102] Add madnessHarness to qca --- qcengine/programs/base.py | 1 + qcengine/testing.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index 282e08141..d98b912bb 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -102,6 +102,7 @@ def list_available_programs() -> Set[str]: register_program(GAMESSHarness()) register_program(MolproHarness()) register_program(NWChemHarness()) +register_program(MadnessHarness()) register_program(Psi4Harness()) register_program(QChemHarness()) register_program(TeraChemHarness()) diff --git a/qcengine/testing.py b/qcengine/testing.py index 4be81b454..96f8e4fa7 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -153,7 +153,7 @@ def get_job(self): "mopac": is_program_new_enough("mopac", "2016"), "mp2d": which("mp2d", return_bool=True), "nwchem": which("nwchem", return_bool=True), - "madness": which("nwchem", return_bool=True), + "madness": which("madness", return_bool=True), "psi4": is_program_new_enough("psi4", "1.2"), "psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"), "qcdb": which_import("qcdb", return_bool=True), From e695d7d28c5827f8cb144a2284948a1016c1f0d9 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 18:17:18 -0400 Subject: [PATCH 006/102] first attempt at get_version --- qcengine/programs/madness/runner.py | 171 ++++++++++++++-------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index e86d655c9..348220446 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -19,9 +19,9 @@ from ...exceptions import InputError from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -from .germinate import muster_modelchem -from .harvester import extract_formatted_properties, harvest -from .keywords import format_keywords +#from .germinate import muster_modelchem +#from .harvester import extract_formatted_properties, harvest +#from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -29,90 +29,87 @@ class MadnessHarness(ProgramHarness): """ - -# Notes -# ----- -# * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. - -# """ - -# _defaults = { -# "name": "NWChem", -# "scratch": True, -# "thread_safe": False, -# "thread_parallel": False, -# "node_parallel": True, -# "managed_memory": True, -# } -# # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers -# version_cache: Dict[str, str] = {} - -# class Config(ProgramHarness.Config): -# pass - -# @staticmethod -# def found(raise_error: bool = False) -> bool: -# """Whether NWChem harness is ready for operation, with both the QC program and any particular dependencies found. - -# Parameters -# ---------- -# raise_error: bool -# Passed on to control negative return between False and ModuleNotFoundError raised. - -# Returns -# ------- -# bool -# If both nwchem and its harness dependency networkx are found, returns True. -# If raise_error is False and nwchem or networkx are missing, returns False. -# If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. - -# """ -# qc = which( -# "nwchem", -# return_bool=True, -# raise_error=raise_error, -# raise_msg="Please install via http://www.nwchem-sw.org/index.php/Download", -# ) - -# dep = which_import( -# "networkx", -# return_bool=True, -# raise_error=raise_error, -# raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", -# ) - -# return qc and dep - -# def get_version(self) -> str: -# self.found(raise_error=True) - -# # Get the node configuration -# config = get_config() - -# # Run NWChem -# which_prog = which("nwchem") -# if config.use_mpiexec: -# command = create_mpi_invocation(which_prog, config) -# else: -# command = [which_prog] -# command.append("v.nw") - -# if which_prog not in self.version_cache: -# success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) - -# if success: -# for line in output["stdout"].splitlines(): -# if "nwchem branch" in line: -# branch = line.strip().split()[-1] -# if "nwchem revision" in line: -# revision = line.strip().split()[-1] -# self.version_cache[which_prog] = safe_version(branch + "+" + revision) -# else: -# raise UnknownError(output["stderr"]) - -# return self.version_cache[which_prog] - -# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + Notes + ----- + * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. + """ + + _defaults = { + "name": "Madness", + "scratch": True, + "thread_safe": True, + "thread_parallel": True, + "node_parallel": True, + "managed_memory": True, + } + # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers + version_cache: Dict[str, str] = {} + + class Config(ProgramHarness.Config): + pass + + @staticmethod + def found(raise_error: bool = False) -> bool: + """Whether Madness harness is ready for operation, with both the QC program and any particular dependencies found. + + Parameters + ---------- + raise_error: bool + Passed on to control negative return between False and ModuleNotFoundError raised. + + Returns + ------- + bool + If both nwchem and its harness dependency networkx are found, returns True. + If raise_error is False and nwchem or networkx are missing, returns False. + If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + + """ + qc = which( + "nwchem", + return_bool=True, + raise_error=raise_error, + raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", + ) + # dep = which_import( + # "networkx", + # return_bool=True, + # raise_error=raise_error, + # raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", + # ) + return qc # and dep + + + def get_version(self) -> str: + self.found(raise_error=True) + + # Get the node configuration + config = get_config() + + # Run MADNESS + which_prog = which("madness") + if config.use_mpiexec: + command = create_mpi_invocation(which_prog, config) + else: + command = [which_prog] + command.append("v.nw") + + if which_prog not in self.version_cache: + success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) + + if success: + for line in output["stdout"].splitlines(): + if "nwchem branch" in line: + branch = line.strip().split()[-1] + if "nwchem revision" in line: + revision = line.strip().split()[-1] + self.version_cache[which_prog] = safe_version(branch + "+" + revision) + else: + raise UnknownError(output["stderr"]) + + return self.version_cache[which_prog] + + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": # """ # Runs NWChem in executable mode # """ From fb3570a932b985522bcb737aa4855ca0275c88f7 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 20:50:37 -0400 Subject: [PATCH 007/102] Got a version of get_version that may work --- qcengine/programs/madness/runner.py | 212 ++++++++++++++-------------- 1 file changed, 109 insertions(+), 103 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 348220446..46a15b806 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -19,9 +19,10 @@ from ...exceptions import InputError from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -#from .germinate import muster_modelchem -#from .harvester import extract_formatted_properties, harvest -#from .keywords import format_keywords + +# from .germinate import muster_modelchem +# from .harvester import extract_formatted_properties, harvest +# from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -79,61 +80,66 @@ def found(raise_error: bool = False) -> bool: # ) return qc # and dep - + ## gotta figure out which input file and from where def get_version(self) -> str: - self.found(raise_error=True) - - # Get the node configuration - config = get_config() - - # Run MADNESS - which_prog = which("madness") - if config.use_mpiexec: - command = create_mpi_invocation(which_prog, config) - else: - command = [which_prog] - command.append("v.nw") - - if which_prog not in self.version_cache: - success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) - - if success: - for line in output["stdout"].splitlines(): - if "nwchem branch" in line: - branch = line.strip().split()[-1] - if "nwchem revision" in line: - revision = line.strip().split()[-1] - self.version_cache[which_prog] = safe_version(branch + "+" + revision) - else: - raise UnknownError(output["stderr"]) - - return self.version_cache[which_prog] - - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": -# """ -# Runs NWChem in executable mode -# """ -# self.found(raise_error=True) - -# job_inputs = self.build_input(input_model, config) -# success, dexe = self.execute(job_inputs) - -# if "There is an error in the input file" in dexe["stdout"]: -# raise InputError(dexe["stdout"]) -# if "not compiled" in dexe["stdout"]: -# # recoverable with a different compilation with optional modules -# raise InputError(dexe["stdout"]) - -# if success: -# dexe["outfiles"]["stdout"] = dexe["stdout"] -# dexe["outfiles"]["stderr"] = dexe["stderr"] -# return self.parse_output(dexe["outfiles"], input_model) -# else: -# raise UnknownError(dexe["stderr"]) + self.found(raise_error=True) + + # Get the node configuration + config = get_config() + + # Run MADNESS + which_prog = which("madness") + if config.use_mpiexec: + command = create_mpi_invocation(which_prog, config) + else: + command = [which_prog] + command.append("v.moldft") + + if which_prog not in self.version_cache: + success, output = execute( + command, + { + "v.moldft": "dft\nxc hf\nend\n\ngeometry\n H 0.00000000 0.00000000 -0.36579425\n H 0.00000000 0.00000000 0.36579425\nend\n" + }, + scratch_directory=config.scratch_directory, + ) + + if success: + for line in output["stdout"].splitlines(): + if "multiresolution suite" in line: + version = line.strip().split()[1] + self.version_cache[which_prog] = safe_version(version) + else: + raise UnknownError(output["stderr"]) + + return self.version_cache[which_prog] + + +# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": +# """ +# Runs madness in executable mode +# """ +# self.found(raise_error=True) + +# job_inputs = self.build_input(input_model, config) +# success, dexe = self.execute(job_inputs) + +# if "There is an error in the input file" in dexe["stdout"]: +# raise InputError(dexe["stdout"]) +# if "not compiled" in dexe["stdout"]: +# # recoverable with a different compilation with optional modules +# raise InputError(dexe["stdout"]) + +# if success: +# dexe["outfiles"]["stdout"] = dexe["stdout"] +# dexe["outfiles"]["stderr"] = dexe["stderr"] +# return self.parse_output(dexe["outfiles"], input_model) +# else: +# raise UnknownError(dexe["stderr"]) # def build_input( # self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None -# ) -> Dict[str, Any]: +# ) -> Dict[str, Any]: # nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} # opts = copy.deepcopy(input_model.keywords) @@ -147,60 +153,60 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe # memory_size //= config.nnodes * config.ncores // config.cores_per_rank # opts["memory"] = memory_size -# # Handle molecule -# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) -# opts.update(moldata["keywords"]) +# # Handle molecule +# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) +# opts.update(moldata["keywords"]) -# # Handle calc type and quantum chemical method -# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) -# opts.update(mdcopts) +# # Handle calc type and quantum chemical method +# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) +# opts.update(mdcopts) -# # Handle basis set -# # * for nwchem, still needs sph and ghost -# for el in set(input_model.molecule.symbols): -# opts[f"basis__{el}"] = f"library {input_model.model.basis}" +# # Handle basis set +# # * for nwchem, still needs sph and ghost +# for el in set(input_model.molecule.symbols): +# opts[f"basis__{el}"] = f"library {input_model.model.basis}" -# # Log the job settings -# logger.debug("JOB_OPTS") -# logger.debug(pp.pformat(opts)) +# # Log the job settings +# logger.debug("JOB_OPTS") +# logger.debug(pp.pformat(opts)) # # Handle conversion from schema (flat key/value) keywords into local format -# optcmd = format_keywords(opts) - -# # Combine the molecule description, options and method command together -# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd - -# # For gradient methods, add a Python command to save the gradients in higher precision -# # Note: The Hessian is already stored in high precision in a file named "*.hess" -# if input_model.driver == "gradient": -# # Get the name of the theory used for computing the gradients -# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) -# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") - -# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) -# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ -# # (not 6 significant figures) -# pycmd = f""" -# python -# grad = rtdb_get('{theory}:gradient') -# if ga_nodeid() == 0: -# import json -# with open('nwchem.grad', 'w') as fp: -# json.dump(grad, fp) -# end - -# task python -# """ -# nwchemrec["infiles"]["nwchem.nw"] += pycmd - -# # Determine the command -# if config.use_mpiexec: -# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) -# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") -# else: -# nwchemrec["command"] = [which("nwchem")] - -# return nwchemrec +# optcmd = format_keywords(opts) + +# # Combine the molecule description, options and method command together +# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd + +# # For gradient methods, add a Python command to save the gradients in higher precision +# # Note: The Hessian is already stored in high precision in a file named "*.hess" +# if input_model.driver == "gradient": +# # Get the name of the theory used for computing the gradients +# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) +# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") + +# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) +# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ +# # (not 6 significant figures) +# pycmd = f""" +# python +# grad = rtdb_get('{theory}:gradient') +# if ga_nodeid() == 0: +# import json +# with open('nwchem.grad', 'w') as fp: +# json.dump(grad, fp) +# end + +# task python +# # """ +# nwchemrec["infiles"]["nwchem.nw"] += pycmd + +# # Determine the command +# if config.use_mpiexec: +# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) +# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") +# else: +# nwchemrec["command"] = [which("nwchem")] + +# return nwchemrec # def execute( # self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None From 7d6a24a553828d5ca2f8a8f55eb0edd07ac7476f Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 18:27:03 -0400 Subject: [PATCH 008/102] simple build_input --- qcengine/programs/madness/runner.py | 129 +++++++++------------------- 1 file changed, 40 insertions(+), 89 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 46a15b806..e18e53a79 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -115,7 +115,7 @@ def get_version(self) -> str: return self.version_cache[which_prog] -# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": pass # """ # Runs madness in executable mode # """ @@ -137,94 +137,45 @@ def get_version(self) -> str: # else: # raise UnknownError(dexe["stderr"]) -# def build_input( -# self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None -# ) -> Dict[str, Any]: -# nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} - -# opts = copy.deepcopy(input_model.keywords) -# opts = {k.lower(): v for k, v in opts.items()} - -# # Handle memory -# # for nwchem, [GiB] --> [B] -# # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' -# memory_size = int(config.memory * (1024 ** 3)) -# if config.use_mpiexec: # It is the memory per MPI rank -# memory_size //= config.nnodes * config.ncores // config.cores_per_rank -# opts["memory"] = memory_size - -# # Handle molecule -# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) -# opts.update(moldata["keywords"]) - -# # Handle calc type and quantum chemical method -# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) -# opts.update(mdcopts) - -# # Handle basis set -# # * for nwchem, still needs sph and ghost -# for el in set(input_model.molecule.symbols): -# opts[f"basis__{el}"] = f"library {input_model.model.basis}" - -# # Log the job settings -# logger.debug("JOB_OPTS") -# logger.debug(pp.pformat(opts)) - -# # Handle conversion from schema (flat key/value) keywords into local format -# optcmd = format_keywords(opts) - -# # Combine the molecule description, options and method command together -# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd - -# # For gradient methods, add a Python command to save the gradients in higher precision -# # Note: The Hessian is already stored in high precision in a file named "*.hess" -# if input_model.driver == "gradient": -# # Get the name of the theory used for computing the gradients -# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) -# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") - -# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) -# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ -# # (not 6 significant figures) -# pycmd = f""" -# python -# grad = rtdb_get('{theory}:gradient') -# if ga_nodeid() == 0: -# import json -# with open('nwchem.grad', 'w') as fp: -# json.dump(grad, fp) -# end - -# task python -# # """ -# nwchemrec["infiles"]["nwchem.nw"] += pycmd - -# # Determine the command -# if config.use_mpiexec: -# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) -# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") -# else: -# nwchemrec["command"] = [which("nwchem")] - -# return nwchemrec - -# def execute( -# self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None -# ) -> Tuple[bool, Dict]: - -# success, dexe = execute( -# inputs["command"], -# inputs["infiles"], -# ["nwchem.hess", "nwchem.grad"], -# scratch_messy=False, -# scratch_exist_ok=True, -# scratch_directory=inputs["scratch_directory"], -# ) -# return success, dexe - -# def parse_output( -# self, outfiles: Dict[str, str], input_model: "AtomicInput" -# ) -> "AtomicResult": # lgtm: [py/similar-function] + def build_input( + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + ) -> Dict[str, Any]: + # + madnessrec={"infiles":{},"scratch_directory":config.scratch_directory} + + opts=copy.deepcopy(input_model.keywords) + opts = {k.lower(): v for k, v in opts.items()} + + + molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) + + opts.update(moldata["keywords"]) + optcmd="dft\n xc hf \nend\n" + madnessrec["infiles"]["mad_input"]=optcmd+molcmd + ## Determine the command + # Determine the command + madnessrec["command"] = [which("madness")] + + return madnessrec + + + def execute( + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + ) -> Tuple[bool, Dict]: + + success, dexe = execute( + inputs["command"], + inputs["infiles"], + ["nwchem.hess", "nwchem.grad"], + scratch_messy=False, + scratch_exist_ok=True, + scratch_directory=inputs["scratch_directory"], + ) + return success, dexe + + def parse_output( + self, outfiles: Dict[str, str], input_model: "AtomicInput" + ) -> "AtomicResult": pass # lgtm: [py/similar-function] # # Get the stdout from the calculation (required) # stdout = outfiles.pop("stdout") From b83169ee82eee77aaf3704133fe34a43aba96dad Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 20:37:41 -0400 Subject: [PATCH 009/102] simple execuate function --- qcengine/programs/madness/runner.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index e18e53a79..3ecf20128 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -151,7 +151,7 @@ def build_input( opts.update(moldata["keywords"]) optcmd="dft\n xc hf \nend\n" - madnessrec["infiles"]["mad_input"]=optcmd+molcmd + madnessrec["infiles"]["input"]=optcmd+molcmd ## Determine the command # Determine the command madnessrec["command"] = [which("madness")] @@ -160,16 +160,11 @@ def build_input( def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None - ) -> Tuple[bool, Dict]: - + self,inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + ) -> Tuple[bool, Dict]: success, dexe = execute( inputs["command"], inputs["infiles"], - ["nwchem.hess", "nwchem.grad"], - scratch_messy=False, - scratch_exist_ok=True, - scratch_directory=inputs["scratch_directory"], ) return success, dexe From 3a005430a7d7de25b8ba7d4589de40a0eb94723b Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 21:24:23 -0400 Subject: [PATCH 010/102] add madness pytest dft w/ xc hf --- .../programs/tests/test_standard_suite_hf.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e1aeeda49..b5428952e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -155,3 +155,25 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) + +@pytest.mark.parametrize( + "program,keywords", + [ + pytest.param("madness", {"dft__xc__hf": True } ), + ], +) +def test_mad_hf(program, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "dft", "keywords": keywords}} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + + assert res["driver"] == "energy" + assert "provenance" in res + assert res["success"] is True + + # aug-cc-pvdz + scf_tot = -76.06718642 + + atol = 1.0e-6 + assert compare_values(scf_tot, res["return_result"], atol=atol) + From 61cfa4a24b4ad3ce925748e4e11f82a193bfec71 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 26 Apr 2020 19:52:29 -0400 Subject: [PATCH 011/102] updating --- qcengine/programs/madness/germinate.py | 313 ++++++++++--------------- qcengine/programs/madness/keywords.py | 146 ++++++------ qcengine/programs/madness/runner.py | 29 ++- 3 files changed, 228 insertions(+), 260 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 88fb672e8..fce6b2cca 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -2,188 +2,131 @@ from qcengine.exceptions import InputError -# # List of XC functionals known to NWChem -# _xc_functionals = [ -# "acm", -# "b3lyp", -# "beckehandh", -# "pbe0", -# "becke97", -# "becke97-1", -# "becke97-2", -# "becke97-3", -# "becke97-d", -# "becke98", -# "hcth", -# "hcth120", -# "hcth147", -# "hcth407", -# "becke97gga1", -# "hcth407p", -# "mpw91", -# "mpw1k", -# "xft97", -# "cft97", -# "ft97", -# "op", -# "bop", -# "pbeop", -# "xpkzb99", -# "cpkzb99", -# "xtpss03", -# "ctpss03", -# "xctpssh", -# "b1b95", -# "bb1k", -# "mpw1b95", -# "mpwb1k", -# "pw6b95", -# "pwb6k", -# "m05", -# "m05-2x", -# "vs98", -# "m06", -# "m06-hf", -# "m06-L", -# "m06-2x", -# "HFexch", -# "becke88", -# "xperdew91", -# "xpbe96", -# "gill96", -# "lyp", -# "perdew81", -# "perdew86", -# "perdew91", -# "cpbe96", -# "pw91lda", -# "slater", -# "vwn_1", -# "vwn_2", -# "vwn_3", -# "vwn_4", -# "vwn_5", -# "vwn_1_rpa", -# "xtpss03", -# "ctpss03", -# "bc95", -# "xpw6b95", -# "xpwb6k", -# "xm05", -# "xm05-2x", -# "cpw6b95", -# "cpwb6k", -# "cm05", -# "cm05-2x", -# "xvs98", -# "cvs98", -# "xm06-L", -# "xm06-hf", -# "xm06", -# "xm06-2x", -# "cm06-L", -# "cm06-hf", -# "cm06", -# "cm06-2x", -# ] - - -# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -# """Converts the QC method into NWChem keywords - -# Args: -# method (str): Name of the QC method to use -# derint (str): Index of the run type -# use_tce (bool): Whether to use the Tensor Contraction Engine -# Returns: -# (str): Task command for NWChem -# (dict): Any options for NWChem -# """ - -# # Standardize the method name -# method = method.lower() -# opts = {} - -# # Map the run type to -# runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - -# # Write out the theory directive -# if method == "nwchem": -# mdccmd = "" - -# elif method in ["scf", "hf"]: -# mdccmd = f"task scf {runtyp}\n\n" - -# elif method == "mp2": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp2"] = True -# else: -# mdccmd = f"task mp2 {runtyp}\n\n" - -# elif method == "mp3": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp3"] = True - -# elif method == "mp4": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp4"] = True - -# elif method == "ccd": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccd"] = True - -# elif method == "ccsd": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsd"] = True -# else: -# mdccmd = f"task ccsd {runtyp}\n\n" - -# elif method == "ccsdt": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsdt"] = True -# else: -# mdccmd = f"task ccsdt {runtyp}\n\n" - -# elif method == "ccsd(t)": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsd(t)"] = True -# else: -# mdccmd = f"task ccsd(t) {runtyp}\n\n" - -# elif method == "tddft": -# mdccmd = f"task tddft {runtyp}\n\n" - -# elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: -# raise InputError(f'Method "{method}" not yet supported by QCEngine') - -# elif method == "tce": -# raise InputError( -# f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' -# ) - -# elif method.split()[0] in _xc_functionals: -# opts["dft__xc"] = method -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__"] = "dft" -# else: -# mdccmd = f"task dft {runtyp}\n\n" - -# elif method == "dft": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__"] = "dft" -# else: -# mdccmd = f"task dft {runtyp}\n\n" - -# else: -# raise InputError(f"Method not recognized: {method}") - -# # return mdccmd, opts +# List of XC functionals known to NWChem +_xc_functionals = [ + "hf", + "acm", + "b3lyp", + "beckehandh", + "pbe0", + "becke97", + "becke97-1", + "becke97-2", + "becke97-3", + "becke97-d", + "becke98", + "hcth", + "hcth120", + "hcth147", + "hcth407", + "becke97gga1", + "hcth407p", + "mpw91", + "mpw1k", + "xft97", + "cft97", + "ft97", + "op", + "bop", + "pbeop", + "xpkzb99", + "cpkzb99", + "xtpss03", + "ctpss03", + "xctpssh", + "b1b95", + "bb1k", + "mpw1b95", + "mpwb1k", + "pw6b95", + "pwb6k", + "m05", + "m05-2x", + "vs98", + "m06", + "m06-hf", + "m06-L", + "m06-2x", + "HFexch", + "becke88", + "xperdew91", + "xpbe96", + "gill96", + "lyp", + "perdew81", + "perdew86", + "perdew91", + "cpbe96", + "pw91lda", + "slater", + "vwn_1", + "vwn_2", + "vwn_3", + "vwn_4", + "vwn_5", + "vwn_1_rpa", + "xtpss03", + "ctpss03", + "bc95", + "xpw6b95", + "xpwb6k", + "xm05", + "xm05-2x", + "cpw6b95", + "cpwb6k", + "cm05", + "cm05-2x", + "xvs98", + "cvs98", + "xm06-L", + "xm06-hf", + "xm06", + "xm06-2x", + "cm06-L", + "cm06-hf", + "cm06", + "cm06-2x", +] + + +def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: + """Converts the QC method into NWChem keywords + + Args: + method (str): Name of the QC method to use + derint (str): Index of the run type + use_tce (bool): Whether to use the Tensor Contraction Engine + Returns: + (str): Task command for NWChem + (dict): Any options for NWChem + """ + + # Standardize the method name + method = method.lower() + opts = {} + + # Map the run type to + #runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + #runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] + + # Write out the theory directive + + mdccmd = f""## we don't need this right now + ## in the future when we link other exec this will change + ## all we have to do is add options to the dft block in order to change the run type + ## default in energy + # do nothing + if method =="optimization": + opts["dft__gopts"]= True + elif method == "response": + opts["dft__response"]= True + elif method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + else: + raise InputError(f"Method not recognized: {method}") + + + + + return mdccmd, opts +# # # # \ No newline at end of file diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index ec5596a5a..4b168f2ff 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,86 +2,88 @@ from typing import Any, Dict, Tuple -# def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: -# """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" -# # Transform string booleans into " " -# if val is True: -# return keyword.lower(), "" -# elif val is False: -# return "", "" -# # complete hack -# if keyword.upper() == "MEMORY": -# return keyword.lower(), f"{val} byte" +def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: + """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" -# elif isinstance(val, list): -# text = " ".join([str(v) for v in val]) -# elif isinstance(val, dict): -# text = [] -# for k, v in val.items(): -# merge = [k] -# merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) -# text.append(" ".join(merge)) -# text = " ".join(text) -# else: -# text = str(val) + # Transform string booleans into " " + if val is True: + return keyword.lower(), "true" + elif val is False: + return keyword.lower(), "false" -# if lop_off: -# return keyword[7:].lower(), text -# else: -# return keyword.lower(), text + # complete hack + #if keyword.upper() == "MEMORY": + # return keyword.lower(), f"{val} byte" + elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play + text = " ".join([str(v) for v in val]) + elif isinstance(val, dict): # val is a dict... text is list + text = [] + for k, v in val.items(): + merge = [k] + merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) + text.append(" ".join(merge)) + text = " ".join(text) + else: + text = str(val) -# def format_keywords(keywords: Dict[str, Any]) -> str: -# """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" + if lop_off: + return keyword[7:].lower(), text + else: + return keyword.lower(), text -# def rec_dd(): -# return collections.defaultdict(rec_dd) -# grouped_options = rec_dd() +def format_keywords(keywords: Dict[str, Any]) -> str: + """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" -# for group_key, val in keywords.items(): -# nesting = group_key.split("__") -# if len(nesting) == 1: -# key = nesting[0] -# grouped_options["aaaglobal"][key] = val -# elif len(nesting) == 2: -# g1, key = nesting -# grouped_options[g1][key] = val -# elif len(nesting) == 3: -# g1, g2, key = nesting -# grouped_options[g1][g2][key] = val -# else: -# print(nesting) -# raise ValueError("Nesting N!") + def rec_dd(): + return collections.defaultdict(rec_dd) -# grouped_lines = {} -# for group, opts in sorted(grouped_options.items()): -# lines = [] -# group_level_lines = [] -# for key, val in grouped_options[group].items(): -# if isinstance(val, dict): -# g2_level_lines = [] -# g2_level_lines.append(key.lower()) -# for k2, v2 in val.items(): -# line2 = " ".join(format_keyword(k2, v2, lop_off=False)) -# g2_level_lines.append(line2) -# g2_level_lines = " ".join(g2_level_lines) -# lines.append(g2_level_lines) -# else: -# line = " ".join(format_keyword(key, val, lop_off=False)) -# if group.lower() == "basis" and any( -# [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] -# ): -# group_level_lines.append(line) -# else: -# lines.append(line) -# if group == "aaaglobal": -# grouped_lines[group] = "\n".join(lines) + "\n" -# else: -# grouped_lines[group] = ( -# f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" -# ) + grouped_options = rec_dd() -# return "\n".join(grouped_lines.values()) + "\n" + for group_key, val in keywords.items(): + nesting = group_key.split("__") + if len(nesting) == 1: + key = nesting[0] + grouped_options["aaaglobal"][key] = val + elif len(nesting) == 2: + g1, key = nesting + grouped_options[g1][key] = val + elif len(nesting) == 3: + g1, g2, key = nesting + grouped_options[g1][g2][key] = val + else: + print(nesting) + raise ValueError("Nesting N!") + + grouped_lines = {} + for group, opts in sorted(grouped_options.items()): + lines = [] + group_level_lines = [] + for key, val in grouped_options[group].items(): + if isinstance(val, dict): + g2_level_lines = [] + g2_level_lines.append(key.lower()) + for k2, v2 in val.items(): + line2 = " ".join(format_keyword(k2, v2, lop_off=False)) + g2_level_lines.append(line2) + g2_level_lines = " ".join(g2_level_lines) + lines.append(g2_level_lines) + else: + line = " ".join(format_keyword(key, val, lop_off=False)) + if group.lower() == "basis" and any( + [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] + ): + group_level_lines.append(line) + else: + lines.append(line) + if group == "aaaglobal": + grouped_lines[group] = "\n".join(lines) + "\n" + else: + grouped_lines[group] = ( + f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" + ) + + return "\n".join(grouped_lines.values()) + "\n" diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 3ecf20128..f397b3e97 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -146,11 +146,34 @@ def build_input( opts=copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - + # Handle memory (ROBERT) Look in keywords as well + # for nwchem, [GiB] --> [B] + # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' + #memory_size = int(config.memory * (1024 ** 3)) + #if config.use_mpiexec: # It is the memory per MPI rank + # memory_size //= config.nnodes * config.ncores // config.cores_per_rank + #opts["memory"] = memory_size + + # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - opts.update(moldata["keywords"]) - optcmd="dft\n xc hf \nend\n" + + ## Handle Calc Type (ROBERT) + mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) + opts.update(mdcopts) + + ## Handle the basis set (ROBERT) the question is what value of k + + # Log the job settings (LORI) Not sure if i need this + logger.debug("JOB_OPTS") + logger.debug(pp.pformat(opts)) + + # Handle conversion from schema (flat key/value) keywords into local format + optcmd = format_keywords(opts) + + #optcmd="dft\n xc hf \nend\n" + + madnessrec["infiles"]["input"]=optcmd+molcmd ## Determine the command # Determine the command From 4a05965d33eaf01cdff62c89305d315d4b0f61a0 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 01:10:56 -0400 Subject: [PATCH 012/102] got variables for scf calculation --- qcengine/programs/madness/harvester.py | 714 +++++-------------------- 1 file changed, 134 insertions(+), 580 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 3040e81b1..2db119cf5 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -15,494 +15,80 @@ logger = logging.getLogger(__name__) -# def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: -# """Function to read an entire NWChem output file. - -# Reads all of the different "line search" segments of a file and returns -# values from the last segment for which a geometry was written. - -# Args: -# outtext (str): Output written to stdout -# Returns: -# - (PreservingDict) Variables extracted from the output file in the last complete step -# - (Molecule): Molecule from the last complete step -# - (list): Gradient from the last complete step -# - (str): Version string -# - (str): Error message, if any -# """ - -# # Loop over all steps -# # TODO (wardlt): Is it only necessary to read the last two steps? -# pass_psivar = [] -# pass_coord = [] -# pass_grad = [] -# for outpass in re.split(r" Line search:", outtext, re.MULTILINE): -# psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) -# pass_psivar.append(psivar) -# pass_coord.append(nwcoord) -# pass_grad.append(nwgrad) - -# # Determine which segment contained the last geometry -# retindx = -1 if pass_coord[-1] else -2 - -# return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error - - -# def harvest_outfile_pass(outtext): -# """Function to read NWChem output file *outtext* and parse important -# quantum chemical information from it in - -# """ -# psivar = PreservingDict() -# psivar_coord = None -# psivar_grad = None -# version = "" -# error = "" # TODO (wardlt): The error string is never used. - -# NUMBER = r"(?x:" + regex.NUMBER + ")" -# # fmt: off - -# # Process version -# mobj = re.search( -# r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched version') -# version = mobj.group('version') - -# # Process SCF -# # 1)Fail to converge -# mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('failed to converge') - -# # 2)Calculation converged -# else: -# mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched HF') -# psivar['HF TOTAL ENERGY'] = mobj.group(1) - -# # Process Effective nuclear repulsion energy (a.u.) -# mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched NRE') -# # logger.debug (mobj.group(1)) -# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) - -# # Process DFT dispersion energy (a.u.) -# mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched Dispersion') -# logger.debug(mobj.group(1)) -# psivar['DFT DISPERSION ENERGY'] = mobj.group(1) - -# # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) - -# mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched DFT') -# logger.debug(mobj.group(1)) -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) - -# # SODFT [for nwchem 6.8+] -# mobj = re.search( -# r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched DFT') -# # print (mobj.group(1)) -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) -# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) - -# # MCSCF -# mobj = re.findall( -# r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - -# # for mobj_list in mobj: - -# if mobj: # Need to change to accommodate find all instances -# logger.debug('matched mcscf') # MCSCF energy calculation -# psivar['HF TOTAL ENERGY'] = mobj.group(1) -# psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) -# psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) -# psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) -# # for mobj_list in mobj: -# # for i in mobj_list: -# # count += 0 -# # logger.debug('matched mcscf iteration %i', count) -# # psivar['HF TOTAL ENERGY'] = mobj.group(1) -# # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) -# # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) -# # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - -# # Process MP2 (Restricted, Unrestricted(RO n/a)) -# # 1)SCF-MP2 -# mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + -# NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 -# if mobj: -# logger.debug('matched scf-mp2') -# psivar['HF TOTAL ENERGY'] = mobj.group(1) -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(5) -# # SCS-MP2 -# mobj = re.search( -# r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE) -# if mobj: -# logger.debug('matched scs-mp2') -# psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) -# psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) - -# logger.debug(mobj.group(1)) # ess -# logger.debug(mobj.group(2)) # fss -# logger.debug(mobj.group(3)) # eos -# logger.debug(mobj.group(4)) # fos -# logger.debug(mobj.group(5)) # scs corl -# logger.debug(mobj.group(6)) # scs-mp2 - -# # 2) DFT-MP2 -# mobj = re.search( -# r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + -# r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched dft-mp2') -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - -# # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) -# mobj = re.search( -# r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + -# r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + -# r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched coupled cluster-mp2') -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - -# # 4) Direct MP2 - -# # 5) RI-MP2 - -# # Process calculation through tce [dertype] command -# for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: -# mobj = re.search( -# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, -# re.MULTILINE) - -# if mobj: -# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') -# logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) - -# if mbpt_plain == 'MP2': -# psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) -# else: -# psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) -# psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) -# #TCE dipole- MBPT(n) -# mobj2 = re.search( -# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj2: -# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') -# print(f'matched tce {mbpt_plain} dipole moment') -# #only pulling Debye -# psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) -# psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) -# psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) - -# #TCE with () or [] -# for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: -# mobj = re.search( -# r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# cc_plain = cc_name.replace('\\', '') -# cc_corr = cc_plain.replace('CCSD', '') -# logger.debug(f'matched tce cc {cc_plain}') - -# psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) -# psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) -# psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) -# #TCE dipole with () or [] -# mobj2 = re.search( -# r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj2: -# cc_plain = cc_name.replace('\\', '') -# cc_corr = cc_plain.replace('CCSD', '') -# print(f'matched tce {cc_plain} dipole moment') - -# #only pulling Debye -# psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) -# psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) -# psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) - -# #Process other TCE cases -# for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: -# mobj = re.search( -# r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + -# r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + -# r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug(f'matched {cc_name}') -# logger.debug(mobj) -# psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) -# psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) -# #TCE dipole -# mobj2 = re.search( -# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj2: -# print(f'matched tce dipole moment') - -# #only pulling Debye -# psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) -# psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) -# psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) - -# # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command - -# mobj = re.search( -# r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + -# r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE | re.DOTALL) - -# if mobj: -# logger.debug('matched ccsd') -# psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) -# psivar['CCSD TOTAL ENERGY'] = mobj.group(3) - -# mobj = re.search( -# r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + -# r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - -# if mobj: -# logger.debug('matched ccsd(t)') -# psivar['(T) CORRECTION ENERGY'] = mobj.group(1) -# psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] -# psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) - -# mobj = re.search( -# r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' -# r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' -# r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) -# # SCS-CCSD included -# if mobj: -# logger.debug('matched scs-ccsd') -# psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( -# Decimal(mobj.group(1)) * Decimal(mobj.group(2))) -# psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( -# Decimal(mobj.group(4)) * Decimal(mobj.group(3))) -# psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) -# psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) -# psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( -# psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) -# # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( -# # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) - -# # Process EOM-[cc_name] #nwchem_tce_dipole = false -# # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree -# # psivar name might need to be fixed -# # each root excitation energy is extracted from the last iteration of right hand side -# mobj = re.findall( -# r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + -# # (..) captures symmetry -# r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root -# r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree -# r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV -# outtext, re.MULTILINE | re.DOTALL) -# #regex should be more dynamic in finding values, need to revisit -# #mobj.group(0) = symmetry value -# #mobj.group(1) = cc_name -# #mobj.group(2) = root number -# #mobj.group(3) = excitation energy (hartree) -# #mobj.group(4) = excitation energy (eV) - -# if mobj: -# print(mobj) -# ext_energy = {} # dic - -# ext_energy_list = [] -# print(f'matched eom-{cc_name}') -# for mobj_list in mobj: -# logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry -# logger.debug(mobj_list) -# count = 0 -# for line in mobj_list[1].splitlines(): -# lline = line.split() -# logger.debug(lline[1]) # in hartree -# logger.debug(lline[2]) # in eV -# count += 1 - -# logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) - -# ext_energy_list.append(lline[1]) # Collect all excitation energies - -# sym = str(mobj_list[0]) -# ext_energy.setdefault(sym, []) -# ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) - -# ext_energy_list.sort(key=float) - -# for nroot in range(len(ext_energy_list)): -# for k, e_val in ext_energy.items(): -# if ext_energy_list[nroot] in e_val: -# symm = k -# psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ -# ext_energy_list[nroot] # in hartree -# psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ -# psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree -# gssym = '' -# gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) - -# if gs: -# logger.debug('matched ground-state symmetry') -# psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) - -# # Process TDDFT -# # 1) Spin allowed -# mobj = re.findall( -# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' -# #Root | symmetry | a.u. | eV -# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value -# + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole -# + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople -# + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople -# + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched TDDFT with transition moments') -# for mobj_list in mobj: -# print (mobj_list) -# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV -# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) -# #### temporary psivars #### -# #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % -# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. -# #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ -# # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) -# psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] -# psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] -# psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] -# psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] -# psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] -# psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] -# psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] -# psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] -# psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] - - -# # 2) Spin forbidden -# mobj = re.findall( -# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' -# #Root | symmetry | a.u. | eV -# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value -# + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', -# outtext, re.MULTILINE) -# #mobj.group(0) = Root -# #mobj.group(1) = symmetry -# #mobj.group(2) a.u. -# #mobj.group(3) e.V -# #mobj.group(4) Excitation energy -# #mobj.group(5) Excited state energy - -# if mobj: -# logger.debug('matched TDDFT - spin forbidden') -# for mobj_list in mobj: -# #### temporary psivars #### -# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV -# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) - -# #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % -# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. -# #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ -# # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) -# if mobj: -# logger.debug('Non-variation initial energy') # prints out energy, 5 counts - -# # Process geometry -# # 1) CHARGE -# # Read charge from SCF module -# mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched charge') -# out_charge = int(float(mobj.group(1))) - -# # Read charge from General information (not scf module) -# mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched charge') -# out_charge = int(float(mobj.group(1))) - -# # 2) MULTIPLICITY -# # Read multiplicity from SCF module -# mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity') -# out_mult = int(mobj.group(1)) + 1 - -# # Read multiplicity from SCF module through alpha, beta electrons -# mobj = re.search( -# r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + -# r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity via alpha and beta electrons') -# out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 -# psivar['N ALPHA ELECTRONS'] = mobj.group(1) -# psivar['N BETA ELECTRONS'] = mobj.group(2) - -# # Read multiplicity from General information (not scf module) -# mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity') -# out_mult = int(mobj.group(1)) - -# # 3) Initial geometry +def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: + """Function to read an entire NWChem output file. + + Reads all of the different "line search" segments of a file and returns + values from the last segment for which a geometry was written. + + Args: + outtext (str): Output written to stdout + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (Molecule): Molecule from the last complete step + - (list): Gradient from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Loop over all steps + # TODO (wardlt): Is it only necessary to read the last two steps? + pass_psivar = [] + pass_coord = [] + pass_grad = [] + for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) + pass_psivar.append(psivar)## all the variables extracted + pass_coord.append(madcoord) + pass_grad.append(madgrad) + + # Determine which segment contained the last geometry + retindx = -1 if pass_coord[-1] else -2 + + return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +def harvest_outfile_pass(outtext): + """Function to read Madness output file *outtext* and parse important + quantum chemical information from it in + + """ + psivar = PreservingDict() + psivar_coord = None + psivar_grad = None + version = "" + error = "" # TODO (wardlt): The error string is never used. + + NUMBER = r"(?x:" + regex.NUMBER + ")" + # fmt: off + + # Process version + mobj = re.search( + r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' +r'\s'+ r'multiresolution suite'+r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched version') + version = mobj.group(1) + + # Process SCF + # 1)Fail to converge (TODO Robert ask for failed convergence) + mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('failed to converge') + + # 2)Calculation converged + else: + OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + optDict=dict(zip(OPTIONS,PSIVAR)) + + for var,VAR in optDict.items(): + mobj = re.search(r'^\s+' + var + r'\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched SCF')## not sure what this means + psivar[VAR] = mobj.group(1) + + # 3) Initial geometry # mobj = re.search( # r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + # r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + @@ -730,45 +316,13 @@ # psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] # psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] -# if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] -# if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] -# if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] - -# if "DFT TOTAL ENERGY" in psivar: -# psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] - -# # Process TCE CURRENT energies -# # Need to be fixed -# # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? -# # TODO: CURRENT ENERGY = TCE ENERGY -# if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): -# psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] -# psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] - -# if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] - -# if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] -# if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): -# psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] -# psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + return psivar, psivar_coord, psivar_grad, version, error -# return psivar, psivar_coord, psivar_grad, version, error - -# def harvest_hessian(hess: str) -> np.ndarray: -# """Parses the contents of the NWChem hess file into a hessian array. +def harvest_hessian(hess: str) -> np.ndarray: pass + """Parses the contents of the NWChem hess file into a hessian array. # Args: # hess (str): Contents of the hess file @@ -776,7 +330,7 @@ # (np.ndarray) Hessian matrix as a 2D array # """ -# # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python + # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python # hess_conv = hess.replace("D", "E") # # Parse all of the float values @@ -838,58 +392,58 @@ # return AtomicResultProperties(**output) -# def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: -# """Parses all the pieces of output from NWChem: the stdout in -# *nwout* Scratch files are not yet considered at this moment. - -# Args: -# in_mol (Molecule): Input molecule -# nwout (str): NWChem output molecule -# outfiles (dict): Dictionary of outfile files and their contents -# Returns: -# - (PreservingDict) Variables extracted from the output file in the last complete step -# - (None): Hessian from the last complete step (Not yet implemented) -# - (list): Gradient from the last complete step -# - (Molecule): Molecule from the last complete step -# - (str): Version string -# - (str): Error message, if any -# """ - -# # Parse the NWChem output -# out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) - -# # If available, read higher-accuracy gradients -# # These were output using a Python Task in NWChem to read them out of the database -# if outfiles.get("nwchem.grad") is not None: -# logger.debug("Reading higher-accuracy gradients") -# out_grad = json.loads(outfiles.get("nwchem.grad")) - -# # If available, read the hessian -# out_hess = None -# if outfiles.get("nwchem.hess") is not None: -# out_hess = harvest_hessian(outfiles.get("nwchem.hess")) - -# # Make sure the input and output molecules are the same -# if out_mol: -# if in_mol: -# if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: -# raise ValueError( -# """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" -# % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) -# ) -# else: -# raise ValueError("""No coordinate information extracted from NWChem output.""") - -# # If present, align the gradients and hessian with the original molecular coordinates -# # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the -# # rotated molecule, which we can use to determine how to rotate the gradients/hessian -# amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - -# mill = data["mill"] # Retrieve tool with alignment routines - -# if out_grad is not None: -# out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) -# if out_hess is not None: -# out_hess = mill.align_hessian(np.array(out_hess)) - -# return out_psivar, out_hess, out_grad, out_mol, version, error +def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: + """Parses all the pieces of output from NWChem: the stdout in + *nwout* Scratch files are not yet considered at this moment. + + Args: + in_mol (Molecule): Input molecule + madout (str): Madness output molecule + outfiles (dict): Dictionary of outfile files and their contents + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (None): Hessian from the last complete step (Not yet implemented) + - (None): Gradient from the last complete step (Not yet implemented) + - (Molecule): Molecule from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Parse the Madness output + out_psivar, out_mol, out_grad, version, error = harvest_output(madout) + + # If available, read higher-accuracy gradients + # These were output using a Python Task in NWChem to read them out of the database + if outfiles.get("mad.grad") is not None: + logger.debug("Reading higher-accuracy gradients") + out_grad = json.loads(outfiles.get("mad.grad")) + + # If available, read the hessian + out_hess = None + if outfiles.get("mad.hess") is not None: + out_hess = harvest_hessian(outfiles.get("mad.hess")) + + # Make sure the input and output molecules are the same + if out_mol: + if in_mol: + if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: + raise ValueError( + """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" + % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) + ) + else: + raise ValueError("""No coordinate information extracted from Madness output.""") + + # If present, align the gradients and hessian with the original molecular coordinates + # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the + # rotated molecule, which we can use to determine how to rotate the gradients/hessian + amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + + mill = data["mill"] # Retrieve tool with alignment routines + + if out_grad is not None: + out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) + if out_hess is not None: + out_hess = mill.align_hessian(np.array(out_hess)) + + return out_psivar, out_hess, out_grad, out_mol, version, error From 82352ff80b086dbc73d05e19480e7c729a6562c3 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:27:58 -0400 Subject: [PATCH 013/102] add gopts method to germinate --- qcengine/programs/madness/germinate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index fce6b2cca..69f5e1433 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -117,7 +117,7 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict ## default in energy # do nothing if method =="optimization": - opts["dft__gopts"]= True + opts["dft__gopt"]= True elif method == "response": opts["dft__response"]= True elif method.split()[0] in _xc_functionals: From 18b721a41beb83b3fa9f094e2c38249fa211dbc6 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:28:32 -0400 Subject: [PATCH 014/102] added h20MAD for debuggin I think I can get rid of this --- .../programs/tests/test_standard_suite_hf.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index b5428952e..c4b94b94d 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -17,6 +17,17 @@ def h2o(): """ return qcel.models.Molecule.from_data(smol) +@pytest.fixture +def h2oMAD(): + smol = """ + H 0.000000000000 1.431430901356 0.984293362719 + O 0.000000000000 0.000000000000 -0.124038860300 + H 0.000000000000 -1.431430901356 0.984293362719 + units au +""" + return qcel.models.Molecule.from_data(smol) + + @pytest.fixture def nh2(): @@ -156,24 +167,26 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) + @pytest.mark.parametrize( - "program,keywords", - [ - pytest.param("madness", {"dft__xc__hf": True } ), + "program,basis,keywords", [ + pytest.param("madness", None, {"dft__aobasis":"sto-3g","dft__econv": 1.0000e-05}), + ], ) -def test_mad_hf(program, keywords, h2o): - resi = {"molecule": h2o, "driver": "energy", "model": {"method": "dft", "keywords": keywords}} +def test_mad_hf(program, basis, keywords, h2oMAD): + resi = {"molecule": h2oMAD, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + print(res["stdout"]) assert res["driver"] == "energy" assert "provenance" in res assert res["success"] is True - # aug-cc-pvdz - scf_tot = -76.06718642 + # k=7 + scf_tot = -76.06718632 - atol = 1.0e-6 + atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) - From 43ec24ade5e4a2a745841eeb800f34bbf4769da9 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:30:30 -0400 Subject: [PATCH 015/102] pytest passed madness with h20() --- qcengine/programs/tests/test_standard_suite_hf.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index c4b94b94d..e7976b35e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -17,15 +17,6 @@ def h2o(): """ return qcel.models.Molecule.from_data(smol) -@pytest.fixture -def h2oMAD(): - smol = """ - H 0.000000000000 1.431430901356 0.984293362719 - O 0.000000000000 0.000000000000 -0.124038860300 - H 0.000000000000 -1.431430901356 0.984293362719 - units au -""" - return qcel.models.Molecule.from_data(smol) @@ -174,8 +165,8 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): ], ) -def test_mad_hf(program, basis, keywords, h2oMAD): - resi = {"molecule": h2oMAD, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} +def test_mad_hf(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} res = qcng.compute(resi, program, raise_error=True, return_dict=True) From 0e7668cce13cfcf3b6744731077b1b9c149cd667 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:31:56 -0400 Subject: [PATCH 016/102] split output by Converged! STILL Need to read format stuff --- qcengine/programs/madness/harvester.py | 335 ++++--------------------- 1 file changed, 54 insertions(+), 281 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 2db119cf5..213119734 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -16,7 +16,7 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: - """Function to read an entire NWChem output file. + """Function to read an entire MADNESS output file. Reads all of the different "line search" segments of a file and returns values from the last segment for which a geometry was written. @@ -36,15 +36,14 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_psivar = [] pass_coord = [] pass_grad = [] - for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + for outpass in re.split(r"Converged!", outtext, re.MULTILINE): psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) pass_psivar.append(psivar)## all the variables extracted pass_coord.append(madcoord) pass_grad.append(madgrad) # Determine which segment contained the last geometry - retindx = -1 if pass_coord[-1] else -2 - + retindx = -1 #if pass_coord[-1] else -2 return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error @@ -87,242 +86,20 @@ def harvest_outfile_pass(outtext): if mobj: logger.debug('matched SCF')## not sure what this means psivar[VAR] = mobj.group(1) + # Other options + + + # Process CURRENT energies (TODO: needs better way) + if "TOTAL SCF ENERGY" in psivar: + psivar["CURRENT REFERENCE ENERGY"] = psivar["TOTAL SCF ENERGY"] + psivar["CURRENT ENERGY"] = psivar["TOTAL SCF ENERGY"] - # 3) Initial geometry -# mobj = re.search( -# r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + -# r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + -# r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + -# r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + -# r'\s*' + -# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' -# + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched geom') - -# # dinky molecule w/ charge and multiplicity -# if mobj.group(1) == 'angstroms': -# molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult -# ) # unit = angstrom -# for line in mobj.group(2).splitlines(): -# lline = line.split() -# molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) -# # Jiyoung was collecting charge (-4)? see if this is ok for ghosts -# # Tag , X, Y, Z -# psivar_coord = Molecule(validate=False, -# **qcel.molparse.to_schema(qcel.molparse.from_string( -# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], -# dtype=2)) - -# else: # unit = a.u. -# molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) -# for line in mobj.group(2).splitlines(): -# lline = line.split() -# molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) -# # Tag , X, Y, Z -# psivar_coord = Molecule(validate=False, -# **qcel.molparse.to_schema(qcel.molparse.from_string( -# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], -# dtype=2)) - -# # Process gradient -# mobj = re.search( -# r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + -# r'atom coordinates gradient' + r'\s*' + r'^\s+' + -# r'x y z x y z' + r'\s*' + -# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' -# + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched molgrad') -# atoms = [] -# psivar_grad = [] -# for line in mobj.group(1).splitlines(): -# lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z -# # print (lline) -# if lline == []: -# pass -# else: -# atoms.append(lline[1]) # Tag -# psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) - -# # Process dipole (Properties) -# mobj = re.search( -# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + -# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'.*' + -# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + -# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + -# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'.*' + -# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched total dipole') - -# # UNIT = DEBYE(S) -# psivar['CURRENT DIPOLE X'] = mobj.group(7) -# psivar['CURRENT DIPOLE Y'] = mobj.group(8) -# psivar['CURRENT DIPOLE Z'] = mobj.group(9) -# # total? - -# # Process error code -# mobj = re.search( -# r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + -# r'\s*' + r'^\s+' -# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' -# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + -# r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched error') -# # print (mobj.group(1)) #error line number -# # print (mobj.group(2)) #error reason -# psivar['NWCHEM ERROR CODE'] = mobj.group(1) -# # TODO process errors into error var - -# # fmt: on - -# # Get the size of the basis sets, etc -# mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["N ATOMS"] = mobj.group(1) -# mobj = re.search( -# r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# psivar["N ALPHA ELECTRONS"] = mobj.group(2) -# psivar["N BETA ELECTRONS"] = mobj.group(3) -# if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: -# # get HOMO and LUMO energy -# mobj = re.search( -# r"Vector" -# + r"\s+" -# + r"%d" % (psivar["N ALPHA ELECTRONS"]) -# + r"\s+" -# + r"Occ=" -# + r".*" -# + r"\s+" -# + r"E=" -# + r"([+-]?\s?\d+[.]\d+)" -# + r"[D]" -# + r"([+-]0\d)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) -# psivar["HOMO"] = np.array([round(homo, 10)]) -# mobj = re.search( -# r"Vector" -# + r"\s+" -# + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) -# + r"\s+" -# + r"Occ=" -# + r".*" -# + r"\s+" -# + r"E=" -# + r"([+-]?\s?\d+[.]\d+)" -# + r"[D]" -# + r"([+-]0\d)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) -# psivar["LUMO"] = np.array([round(lumo, 10)]) - -# mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["N MO"] = mobj.group(2) -# psivar["N BASIS"] = mobj.group(1) - -# # Search for Center of charge -# mobj = re.search( -# r"Center of charge \(in au\) is the expansion point" -# + r"\n" -# + r"\s+" -# + r"X\s+=\s+([+-]?\d+[.]\d+)" -# + r"\s+" -# + r"Y\s+=\s+([+-]?\d+[.]\d+)" -# + r"\s+" -# + r"Z\s+=\s+([+-]?\d+[.]\d+)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - -# mobj = re.search( -# r"Dipole moment" -# + r".*?" -# + r"A\.U\." -# + r"\s+" -# + r"DMX\s+([+-]?\d+[.]\d+)\s+" -# + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" -# + r"DMY\s+([+-]?\d+[.]\d+)\s+" -# + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" -# + r"DMZ\s+([+-]?\d+[.]\d+)\s+" -# + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" -# + r"\-EFC\-" -# + r".*?" -# + r"A\.U\.\s+" -# + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", -# outtext, -# re.MULTILINE, -# ) -# # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) -# psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - -# mobj = re.search( -# r"Quadrupole moments in atomic units\s+" -# + r"Component\s+" -# + r"Electronic\+nuclear\s+" -# + r"Point charges\s+" -# + r"Total\s+" -# + r"-+\s+" -# + r"XX\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"YY\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"ZZ\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"XY\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"XZ\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"YZ\s+([+-]?\d+[.]\d+)\s+", -# outtext, -# re.MULTILINE, -# ) - -# if mobj: -# psivar["QUADRUPOLE MOMENT"] = np.array( -# [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] -# ) - -# # Process CURRENT energies (TODO: needs better way) -# if "HF TOTAL ENERGY" in psivar: -# psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] -# psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] - - psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] return psivar, psivar_coord, psivar_grad, version, error def harvest_hessian(hess: str) -> np.ndarray: pass - """Parses the contents of the NWChem hess file into a hessian array. +# """Parses the contents of the NWChem hess file into a hessian array. # Args: # hess (str): Contents of the hess file @@ -350,49 +127,44 @@ def harvest_hessian(hess: str) -> np.ndarray: pass # return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines -# def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: -# """Get named properties out of the general variables extracted out of the result file +def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: + """Get named properties out of the general variables extracted out of the result file -# Args: -# psivars (PreservingDict): Dictionary of the output results -# Returns: -# (AtomicResultProperties) Properties in a standard format -# """ -# # TODO (wardlt): Get more of the named variables out of the NWChem file - -# # Initialize the output -# output = dict() - -# # Extract the Calc Info -# output.update( -# { -# "calcinfo_nbasis": psivars.get("N BASIS", None), -# "calcinfo_nmo": psivars.get("N MO", None), -# "calcinfo_natom": psivars.get("N ATOMS", None), -# "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), -# "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), -# } -# ) - -# # Get the "canonical" properties -# output["return_energy"] = psivars["CURRENT ENERGY"] -# output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] - -# # Get the SCF properties -# output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) -# output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) -# output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) -# output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) - -# # Get the MP2 properties -# output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) -# output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) -# output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) -# output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) -# return AtomicResultProperties(**output) - - -def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: + Args: + psivars (PreservingDict): Dictionary of the output results + Returns: + (AtomicResultProperties) Properties in a standard format + """ + # TODO (wardlt): Get more of the named variables out of the NWChem file + + # Initialize the output + output = dict() + + # Extract the Calc Info + output.update( + { + "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness + "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals + "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) + "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), + } + ) + + # Get the "canonical" properties + output["return_energy"] = psivars["CURRENT ENERGY"] + output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + + # Get the SCF properties + output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + + return AtomicResultProperties(**output) + + +def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: """Parses all the pieces of output from NWChem: the stdout in *nwout* Scratch files are not yet considered at this moment. @@ -411,7 +183,8 @@ def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, N # Parse the Madness output out_psivar, out_mol, out_grad, version, error = harvest_output(madout) - + + # If available, read higher-accuracy gradients # These were output using a Python Task in NWChem to read them out of the database if outfiles.get("mad.grad") is not None: @@ -431,15 +204,15 @@ def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, N """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) - else: - raise ValueError("""No coordinate information extracted from Madness output.""") + #else: + # raise ValueError("""No coordinate information extracted from Madness output.""") # If present, align the gradients and hessian with the original molecular coordinates # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the # rotated molecule, which we can use to determine how to rotate the gradients/hessian - amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + #amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - mill = data["mill"] # Retrieve tool with alignment routines + # mill = data["mill"] # Retrieve tool with alignment routines if out_grad is not None: out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) From 98e0f4c23019e461cdc3dd7fbd2db2b0dba5dbd7 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:32:57 -0400 Subject: [PATCH 017/102] wrote compute, fix mol.keywords, wrote parse_output --- qcengine/programs/madness/runner.py | 152 ++++++++++++++-------------- 1 file changed, 75 insertions(+), 77 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index f397b3e97..773fb96ab 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -20,9 +20,9 @@ from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -# from .germinate import muster_modelchem -# from .harvester import extract_formatted_properties, harvest -# from .keywords import format_keywords +from .germinate import muster_modelchem +from .harvester import extract_formatted_properties, harvest +from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -115,27 +115,27 @@ def get_version(self) -> str: return self.version_cache[which_prog] - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": pass -# """ -# Runs madness in executable mode -# """ -# self.found(raise_error=True) + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + """ + Runs madness in executable mode + """ + self.found(raise_error=True) -# job_inputs = self.build_input(input_model, config) -# success, dexe = self.execute(job_inputs) + job_inputs = self.build_input(input_model, config) + success, dexe = self.execute(job_inputs) -# if "There is an error in the input file" in dexe["stdout"]: -# raise InputError(dexe["stdout"]) -# if "not compiled" in dexe["stdout"]: -# # recoverable with a different compilation with optional modules -# raise InputError(dexe["stdout"]) + if "There is an error in the input file" in dexe["stdout"]: + raise InputError(dexe["stdout"]) + if "not compiled" in dexe["stdout"]: + # recoverable with a different compilation with optional modules + raise InputError(dexe["stdout"]) -# if success: -# dexe["outfiles"]["stdout"] = dexe["stdout"] -# dexe["outfiles"]["stderr"] = dexe["stderr"] -# return self.parse_output(dexe["outfiles"], input_model) -# else: -# raise UnknownError(dexe["stderr"]) + if success: + dexe["outfiles"]["stdout"] = dexe["stdout"] + dexe["outfiles"]["stderr"] = dexe["stderr"] + return self.parse_output(dexe["outfiles"], input_model) + else: + raise UnknownError(dexe["stderr"]) def build_input( self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None @@ -146,17 +146,13 @@ def build_input( opts=copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - # Handle memory (ROBERT) Look in keywords as well - # for nwchem, [GiB] --> [B] - # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' - #memory_size = int(config.memory * (1024 ** 3)) - #if config.use_mpiexec: # It is the memory per MPI rank - # memory_size //= config.nnodes * config.ncores // config.cores_per_rank - #opts["memory"] = memory_size # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - opts.update(moldata["keywords"]) + molData={} + for k,v in moldata["keywords"].items(): + molData['dft__'+k]=v + opts.update(molData) ## Handle Calc Type (ROBERT) mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) @@ -178,7 +174,7 @@ def build_input( ## Determine the command # Determine the command madnessrec["command"] = [which("madness")] - + print(madnessrec["infiles"]["input"]) return madnessrec @@ -193,50 +189,52 @@ def execute( def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" - ) -> "AtomicResult": pass # lgtm: [py/similar-function] - -# # Get the stdout from the calculation (required) -# stdout = outfiles.pop("stdout") - -# # Read the NWChem stdout file and, if needed, the hess or grad files -# qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) - -# if nwgrad is not None: -# qcvars["CURRENT GRADIENT"] = nwgrad - -# if nwhess is not None: -# qcvars["CURRENT HESSIAN"] = nwhess - -# # Normalize the output as a float or list of floats -# if input_model.driver.upper() == "PROPERTIES": -# retres = qcvars[f"CURRENT ENERGY"] -# else: -# retres = qcvars[f"CURRENT {input_model.driver.upper()}"] - -# if isinstance(retres, Decimal): -# retres = float(retres) -# elif isinstance(retres, np.ndarray): -# retres = retres.tolist() - -# # Get the formatted properties -# qcprops = extract_formatted_properties(qcvars) - -# # Format them inout an output -# output_data = { -# "schema_name": "qcschema_output", -# "schema_version": 1, -# "extras": {"outfiles": outfiles, **input_model.extras}, -# "properties": qcprops, -# "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), -# "return_result": retres, -# "stdout": stdout, -# } - -# # got to even out who needs plump/flat/Decimal/float/ndarray/list -# # Decimal --> str preserves precision -# output_data["extras"]["qcvars"] = { -# k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() -# } - -# output_data["success"] = True -# return AtomicResult(**{**input_model.dict(), **output_data}) + ) -> "AtomicResult": # lgtm: [py/similar-function] + + + # Get the stdout from the calculation (required) + stdout = outfiles.pop("stdout") + + # Read the NWChem stdout file and, if needed, the hess or grad files + qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + + if nwgrad is not None: + qcvars["CURRENT GRADIENT"] = nwgrad + + if nwhess is not None: + qcvars["CURRENT HESSIAN"] = nwhess + + # Normalize the output as a float or list of floats + if input_model.driver.upper() == "PROPERTIES": + retres = qcvars[f"CURRENT ENERGY"] + else: + print(qcvars) + retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + + if isinstance(retres, Decimal): + retres = float(retres) + elif isinstance(retres, np.ndarray): + retres = retres.tolist() + + # Get the formatted properties + qcprops = extract_formatted_properties(qcvars) + + # Format them inout an output + output_data = { + "schema_name": "qcschema_output", + "schema_version": 1, + "extras": {"outfiles": outfiles, **input_model.extras}, + "properties": qcprops, + "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), + "return_result": retres, + "stdout": stdout, + } + + # got to even out who needs plump/flat/Decimal/float/ndarray/list + # Decimal --> str preserves precision + output_data["extras"]["qcvars"] = { + k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() + } + + output_data["success"] = True + return AtomicResult(**{**input_model.dict(), **output_data}) From 70b0c15665cdd83e7334b69ab55f617f8f2a54aa Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 23:05:30 -0400 Subject: [PATCH 018/102] make format --- qcengine/programs/madness/germinate.py | 43 +++++----- qcengine/programs/madness/keywords.py | 12 ++- qcengine/programs/madness/runner.py | 86 +++++++++---------- .../programs/tests/test_standard_suite_hf.py | 8 +- 4 files changed, 66 insertions(+), 83 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 69f5e1433..87c9418a0 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -4,7 +4,7 @@ # List of XC functionals known to NWChem _xc_functionals = [ - "hf", + "hf", "acm", "b3lyp", "beckehandh", @@ -90,7 +90,7 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: - """Converts the QC method into NWChem keywords + """Converts the QC method into NWChem keywords Args: method (str): Name of the QC method to use @@ -101,32 +101,31 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict (dict): Any options for NWChem """ - # Standardize the method name - method = method.lower() - opts = {} + # Standardize the method name + method = method.lower() + opts = {} # Map the run type to - #runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - #runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] + # runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + # runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] # Write out the theory directive - mdccmd = f""## we don't need this right now - ## in the future when we link other exec this will change - ## all we have to do is add options to the dft block in order to change the run type - ## default in energy - # do nothing - if method =="optimization": - opts["dft__gopt"]= True - elif method == "response": - opts["dft__response"]= True - elif method.split()[0] in _xc_functionals: + mdccmd = f"" ## we don't need this right now + ## in the future when we link other exec this will change + ## all we have to do is add options to the dft block in order to change the run type + ## default in energy + # do nothing + if method == "optimization": + opts["dft__gopt"] = True + elif method == "response": + opts["dft__response"] = True + elif method.split()[0] in _xc_functionals: opts["dft__xc"] = method - else: - raise InputError(f"Method not recognized: {method}") + else: + raise InputError(f"Method not recognized: {method}") + return mdccmd, opts - - return mdccmd, opts -# # # # \ No newline at end of file +# # # # diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index 4b168f2ff..6059b582a 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,24 +2,22 @@ from typing import Any, Dict, Tuple - - def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" - # Transform string booleans into " " + # Transform string booleans into " " if val is True: return keyword.lower(), "true" elif val is False: return keyword.lower(), "false" - # complete hack - #if keyword.upper() == "MEMORY": + # complete hack + # if keyword.upper() == "MEMORY": # return keyword.lower(), f"{val} byte" - elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play + elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play text = " ".join([str(v) for v in val]) - elif isinstance(val, dict): # val is a dict... text is list + elif isinstance(val, dict): # val is a dict... text is list text = [] for k, v in val.items(): merge = [k] diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 773fb96ab..eccff8dd9 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -114,8 +114,7 @@ def get_version(self) -> str: return self.version_cache[which_prog] - - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs madness in executable mode """ @@ -125,9 +124,9 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe success, dexe = self.execute(job_inputs) if "There is an error in the input file" in dexe["stdout"]: - raise InputError(dexe["stdout"]) + raise InputError(dexe["stdout"]) if "not compiled" in dexe["stdout"]: - # recoverable with a different compilation with optional modules + # recoverable with a different compilation with optional modules raise InputError(dexe["stdout"]) if success: @@ -138,102 +137,95 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None - ) -> Dict[str, Any]: - # - madnessrec={"infiles":{},"scratch_directory":config.scratch_directory} + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + ) -> Dict[str, Any]: + # + madnessrec = {"infiles": {}, "scratch_directory": config.scratch_directory} - opts=copy.deepcopy(input_model.keywords) + opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - molData={} - for k,v in moldata["keywords"].items(): - molData['dft__'+k]=v + molData = {} + for k, v in moldata["keywords"].items(): + molData["dft__" + k] = v opts.update(molData) - + ## Handle Calc Type (ROBERT) mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) opts.update(mdcopts) - ## Handle the basis set (ROBERT) the question is what value of k + ## Handle the basis set (ROBERT) the question is what value of k # Log the job settings (LORI) Not sure if i need this logger.debug("JOB_OPTS") logger.debug(pp.pformat(opts)) - + # Handle conversion from schema (flat key/value) keywords into local format optcmd = format_keywords(opts) - #optcmd="dft\n xc hf \nend\n" + # optcmd="dft\n xc hf \nend\n" - - madnessrec["infiles"]["input"]=optcmd+molcmd + madnessrec["infiles"]["input"] = optcmd + molcmd ## Determine the command - # Determine the command + # Determine the command madnessrec["command"] = [which("madness")] print(madnessrec["infiles"]["input"]) return madnessrec - def execute( - self,inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute( - inputs["command"], - inputs["infiles"], - ) + success, dexe = execute(inputs["command"], inputs["infiles"],) return success, dexe def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] - - # Get the stdout from the calculation (required) + # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") # Read the NWChem stdout file and, if needed, the hess or grad files qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) if nwgrad is not None: - qcvars["CURRENT GRADIENT"] = nwgrad + qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: - qcvars["CURRENT HESSIAN"] = nwhess + qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": - retres = qcvars[f"CURRENT ENERGY"] + retres = qcvars[f"CURRENT ENERGY"] else: - print(qcvars) - retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + print(qcvars) + retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): - retres = float(retres) + retres = float(retres) elif isinstance(retres, np.ndarray): - retres = retres.tolist() + retres = retres.tolist() - # Get the formatted properties + # Get the formatted properties qcprops = extract_formatted_properties(qcvars) - # Format them inout an output + # Format them inout an output output_data = { - "schema_name": "qcschema_output", - "schema_version": 1, - "extras": {"outfiles": outfiles, **input_model.extras}, - "properties": qcprops, - "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), - "return_result": retres, - "stdout": stdout, + "schema_name": "qcschema_output", + "schema_version": 1, + "extras": {"outfiles": outfiles, **input_model.extras}, + "properties": qcprops, + "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), + "return_result": retres, + "stdout": stdout, } - # got to even out who needs plump/flat/Decimal/float/ndarray/list - # Decimal --> str preserves precision + # got to even out who needs plump/flat/Decimal/float/ndarray/list + # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { - k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() + k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data["success"] = True diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e7976b35e..92fa36c6f 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -18,8 +18,6 @@ def h2o(): return qcel.models.Molecule.from_data(smol) - - @pytest.fixture def nh2(): smol = """ @@ -160,15 +158,11 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [ - pytest.param("madness", None, {"dft__aobasis":"sto-3g","dft__econv": 1.0000e-05}), - - ], + "program,basis,keywords", [pytest.param("madness", None, {"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} - res = qcng.compute(resi, program, raise_error=True, return_dict=True) print(res["stdout"]) From 61bbf8edccfe796b9718a18362bcd6463cfd9fed Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:38:17 -0400 Subject: [PATCH 019/102] alphabetize --- qcengine/programs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index d98b912bb..24cf1db8f 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -100,9 +100,9 @@ def list_available_programs() -> Set[str]: register_program(CFOURHarness()) register_program(EntosHarness()) register_program(GAMESSHarness()) +register_program(MadnessHarness()) register_program(MolproHarness()) register_program(NWChemHarness()) -register_program(MadnessHarness()) register_program(Psi4Harness()) register_program(QChemHarness()) register_program(TeraChemHarness()) From 2c28fb6aff3a79f5d05088732ff285a02d220185 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:41:14 -0400 Subject: [PATCH 020/102] which madness and use helium for command --- qcengine/programs/madness/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index eccff8dd9..7dd7dd7ec 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -67,7 +67,7 @@ def found(raise_error: bool = False) -> bool: """ qc = which( - "nwchem", + "madness", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", @@ -99,7 +99,7 @@ def get_version(self) -> str: success, output = execute( command, { - "v.moldft": "dft\nxc hf\nend\n\ngeometry\n H 0.00000000 0.00000000 -0.36579425\n H 0.00000000 0.00000000 0.36579425\nend\n" + "v.moldft": "dft\nxc hf\nend\n\ngeometry\n He 0.00000000 0.00000000 0.00000000 \n end\n" }, scratch_directory=config.scratch_directory, ) From b9d24a225c998a62b62ed1c41f95df4cd40644ce Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:46:35 -0400 Subject: [PATCH 021/102] add scratch_directory to input for execute --- qcengine/programs/madness/runner.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 7dd7dd7ec..4d4491e56 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -177,7 +177,12 @@ def build_input( def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute(inputs["command"], inputs["infiles"],) + success, dexe = execute( + inputs["command"], + inputs["infiles"], + scratch_directory=inputs["scratch_directory"], + + ) return success, dexe def parse_output( From b1b05fc290add4e010733f5f421942180c20cfb5 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:50:38 -0400 Subject: [PATCH 022/102] the quadrupole work should have been in a different branch --- qcengine/programs/nwchem/harvester.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index dbcfa9bfa..9d3e9ec57 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -697,32 +697,7 @@ def harvest_outfile_pass(outtext): psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - mobj = re.search( - r"Quadrupole moments in atomic units\s+" - + r"Component\s+" - + r"Electronic\+nuclear\s+" - + r"Point charges\s+" - + r"Total\s+" - + r"-+\s+" - + r"XX\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"ZZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YZ\s+([+-]?\d+[.]\d+)\s+", - outtext, - re.MULTILINE, - ) - - if mobj: - psivar["QUADRUPOLE MOMENT"] = np.array( - [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] - ) + # Process CURRENT energies (TODO: needs better way) if "HF TOTAL ENERGY" in psivar: From 2e6c2fc0766f0c262d533b4c3cd9ed0e91e8186f Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:00:02 -0400 Subject: [PATCH 023/102] added k=7 to keywords of test. k=7 is a default but its okay to define --- qcengine/programs/tests/test_standard_suite_hf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 92fa36c6f..e39c9d0ed 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -158,7 +158,7 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [pytest.param("madness", None, {"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + "program,basis,keywords", [pytest.param("madness", None, {"dft__k":7,"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} From 7c4f2d88a3bede543506e8d61b1e075cf61cc34c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:28:49 -0400 Subject: [PATCH 024/102] limit to collecting qc vars in results.py --- qcengine/programs/madness/harvester.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 213119734..d3379db6e 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -32,7 +32,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s """ # Loop over all steps - # TODO (wardlt): Is it only necessary to read the last two steps? pass_psivar = [] pass_coord = [] pass_grad = [] @@ -77,8 +76,10 @@ def harvest_outfile_pass(outtext): # 2)Calculation converged else: - OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] - PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + OPTIONS=[r'exchange-correlation',r'nuclear-repulsion',r'total'] + PSIVAR=['EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + #OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + #PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] optDict=dict(zip(OPTIONS,PSIVAR)) for var,VAR in optDict.items(): From 031226580465cb3ca9edf9da9d2f3a1ba7e58d20 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:34:11 -0400 Subject: [PATCH 025/102] return scf_exchange_correlation --- qcengine/programs/madness/harvester.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index d3379db6e..43bce8f35 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -158,6 +158,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION") #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) From 4eb4a75f131e90495e0fc3f6bf4d12b6ad858ad1 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:44:35 -0400 Subject: [PATCH 026/102] Add none --- qcengine/programs/madness/harvester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 43bce8f35..2256a8e94 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -158,7 +158,8 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION") + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION",None) + # TODO AdrianH right madness to output these variables #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) From 8e73a4f688a1a5732dcfad686dea37ad335ea685 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:45:25 -0400 Subject: [PATCH 027/102] make format --- qcengine/programs/madness/runner.py | 7 +------ qcengine/programs/nwchem/harvester.py | 2 -- qcengine/programs/tests/test_standard_suite_hf.py | 3 ++- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 4d4491e56..9a3ee7e6c 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -177,12 +177,7 @@ def build_input( def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute( - inputs["command"], - inputs["infiles"], - scratch_directory=inputs["scratch_directory"], - - ) + success, dexe = execute(inputs["command"], inputs["infiles"], scratch_directory=inputs["scratch_directory"],) return success, dexe def parse_output( diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index 9d3e9ec57..a3c275ff7 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -697,8 +697,6 @@ def harvest_outfile_pass(outtext): psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - - # Process CURRENT energies (TODO: needs better way) if "HF TOTAL ENERGY" in psivar: psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e39c9d0ed..0c5b6929e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -158,7 +158,8 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [pytest.param("madness", None, {"dft__k":7,"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + "program,basis,keywords", + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} From 379c96f38084458b48c3e29743ab8e86de2b1204 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Tue, 5 May 2020 09:40:32 -0400 Subject: [PATCH 028/102] move madness test into separate file --- qcengine/programs/tests/test_madness.py | 40 +++++++++++++++++++ .../programs/tests/test_standard_suite_hf.py | 19 --------- 2 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 qcengine/programs/tests/test_madness.py diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py new file mode 100644 index 000000000..027f01e4d --- /dev/null +++ b/qcengine/programs/tests/test_madness.py @@ -0,0 +1,40 @@ +import pytest +import qcelemental as qcel +from qcelemental.testing import compare_values + +import qcengine as qcng +from qcengine.testing import using + + +@pytest.fixture +def h2o(): + smol = """ + # R=0.958 A=104.5 + H 0.000000000000 1.431430901356 0.984293362719 + O 0.000000000000 0.000000000000 -0.124038860300 + H 0.000000000000 -1.431430901356 0.984293362719 + units au +""" + return qcel.models.Molecule.from_data(smol) + + + +@pytest.mark.parametrize( + "program,basis,keywords", + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], +) +def test_mad_hf(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + print(res["stdout"]) + + assert res["driver"] == "energy" + assert "provenance" in res + assert res["success"] is True + + # k=7 + scf_tot = -76.06718632 + + atol = 1.0e-5 + assert compare_values(scf_tot, res["return_result"], atol=atol) \ No newline at end of file diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 0c5b6929e..c782d5291 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -157,22 +157,3 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): assert compare_values(scf_tot, res["return_result"], atol=atol) -@pytest.mark.parametrize( - "program,basis,keywords", - [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], -) -def test_mad_hf(program, basis, keywords, h2o): - resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} - - res = qcng.compute(resi, program, raise_error=True, return_dict=True) - print(res["stdout"]) - - assert res["driver"] == "energy" - assert "provenance" in res - assert res["success"] is True - - # k=7 - scf_tot = -76.06718632 - - atol = 1.0e-5 - assert compare_values(scf_tot, res["return_result"], atol=atol) From dfe6afdb90691f40afbbb40dce33271cdb576476 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Wed, 6 May 2020 17:14:48 -0400 Subject: [PATCH 029/102] got rid of stuff from nwchem --- qcengine/programs/madness/germinate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 87c9418a0..48df723a1 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -89,7 +89,8 @@ ] -def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +#def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem(method: str, ) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into NWChem keywords Args: From 5829ad49a59f6db71e3989b51c91803235b71c59 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Wed, 6 May 2020 17:17:51 -0400 Subject: [PATCH 030/102] format --- qcengine/programs/madness/germinate.py | 4 +- qcengine/programs/madness/harvester.py | 65 ++++++++++--------- qcengine/programs/tests/test_madness.py | 3 +- .../programs/tests/test_standard_suite_hf.py | 2 - 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 48df723a1..183f2448c 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -89,8 +89,8 @@ ] -#def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -def muster_modelchem(method: str, ) -> Tuple[str, Dict[str, Any]]: +# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem(method: str,) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into NWChem keywords Args: diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 2256a8e94..b455d977f 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -37,12 +37,12 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_grad = [] for outpass in re.split(r"Converged!", outtext, re.MULTILINE): psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) - pass_psivar.append(psivar)## all the variables extracted + pass_psivar.append(psivar) ## all the variables extracted pass_coord.append(madcoord) pass_grad.append(madgrad) # Determine which segment contained the last geometry - retindx = -1 #if pass_coord[-1] else -2 + retindx = -1 # if pass_coord[-1] else -2 return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error @@ -64,42 +64,46 @@ def harvest_outfile_pass(outtext): mobj = re.search( r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' +r'\s'+ r'multiresolution suite'+r'\s*$', outtext, re.MULTILINE) + # fmt: on if mobj: - logger.debug('matched version') + logger.debug("matched version") version = mobj.group(1) # Process SCF # 1)Fail to converge (TODO Robert ask for failed convergence) + # fmt: off mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + # fmt: on if mobj: - logger.debug('failed to converge') + logger.debug("failed to converge") # 2)Calculation converged else: - OPTIONS=[r'exchange-correlation',r'nuclear-repulsion',r'total'] - PSIVAR=['EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] - #OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] - #PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] - optDict=dict(zip(OPTIONS,PSIVAR)) - - for var,VAR in optDict.items(): - mobj = re.search(r'^\s+' + var + r'\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + OPTIONS = [r"exchange-correlation", r"nuclear-repulsion", r"total"] + PSIVAR = ["EXCHANGE-CORRELATION", "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] + # OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + # PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + optDict = dict(zip(OPTIONS, PSIVAR)) + + for var, VAR in optDict.items(): + mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + r"s*$", outtext, re.MULTILINE) if mobj: - logger.debug('matched SCF')## not sure what this means + logger.debug("matched SCF") ## not sure what this means psivar[VAR] = mobj.group(1) # Other options - # Process CURRENT energies (TODO: needs better way) if "TOTAL SCF ENERGY" in psivar: psivar["CURRENT REFERENCE ENERGY"] = psivar["TOTAL SCF ENERGY"] psivar["CURRENT ENERGY"] = psivar["TOTAL SCF ENERGY"] - return psivar, psivar_coord, psivar_grad, version, error -def harvest_hessian(hess: str) -> np.ndarray: pass +def harvest_hessian(hess: str) -> np.ndarray: + pass + + # """Parses the contents of the NWChem hess file into a hessian array. # Args: @@ -108,7 +112,7 @@ def harvest_hessian(hess: str) -> np.ndarray: pass # (np.ndarray) Hessian matrix as a 2D array # """ - # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python +# Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python # hess_conv = hess.replace("D", "E") # # Parse all of the float values @@ -144,10 +148,10 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Extract the Calc Info output.update( { - "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness - "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals - "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out - "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) + "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness + "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals + "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), } ) @@ -158,11 +162,11 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION",None) - # TODO AdrianH right madness to output these variables - #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) - #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) - #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) + # TODO AdrianH right madness to output these variables + # output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + # output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + # output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) return AtomicResultProperties(**output) @@ -186,8 +190,7 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, # Parse the Madness output out_psivar, out_mol, out_grad, version, error = harvest_output(madout) - - + # If available, read higher-accuracy gradients # These were output using a Python Task in NWChem to read them out of the database if outfiles.get("mad.grad") is not None: @@ -207,15 +210,15 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) - #else: + # else: # raise ValueError("""No coordinate information extracted from Madness output.""") # If present, align the gradients and hessian with the original molecular coordinates # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the # rotated molecule, which we can use to determine how to rotate the gradients/hessian - #amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + # amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - # mill = data["mill"] # Retrieve tool with alignment routines + # mill = data["mill"] # Retrieve tool with alignment routines if out_grad is not None: out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 027f01e4d..f5d5d8ad5 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -18,7 +18,6 @@ def h2o(): return qcel.models.Molecule.from_data(smol) - @pytest.mark.parametrize( "program,basis,keywords", [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], @@ -37,4 +36,4 @@ def test_mad_hf(program, basis, keywords, h2o): scf_tot = -76.06718632 atol = 1.0e-5 - assert compare_values(scf_tot, res["return_result"], atol=atol) \ No newline at end of file + assert compare_values(scf_tot, res["return_result"], atol=atol) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index c782d5291..e1aeeda49 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -155,5 +155,3 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) - - From d6a83d6909a9f30fb2ee467dd3dcaffdbfeaf8c9 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:16:39 -0400 Subject: [PATCH 031/102] add madness dir --- qcengine/programs/madness/__init__.py | 1 + qcengine/programs/madness/germinate.py | 189 ++++++ qcengine/programs/madness/harvester.py | 895 +++++++++++++++++++++++++ qcengine/programs/madness/keywords.py | 87 +++ qcengine/programs/madness/runner.py | 270 ++++++++ 5 files changed, 1442 insertions(+) create mode 100644 qcengine/programs/madness/__init__.py create mode 100644 qcengine/programs/madness/germinate.py create mode 100644 qcengine/programs/madness/harvester.py create mode 100644 qcengine/programs/madness/keywords.py create mode 100644 qcengine/programs/madness/runner.py diff --git a/qcengine/programs/madness/__init__.py b/qcengine/programs/madness/__init__.py new file mode 100644 index 000000000..b0ec9fb43 --- /dev/null +++ b/qcengine/programs/madness/__init__.py @@ -0,0 +1 @@ +from .runner import MadnessHarness diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py new file mode 100644 index 000000000..e76132ba8 --- /dev/null +++ b/qcengine/programs/madness/germinate.py @@ -0,0 +1,189 @@ +from typing import Any, Dict, Tuple + +from qcengine.exceptions import InputError + +# List of XC functionals known to NWChem +_xc_functionals = [ + "acm", + "b3lyp", + "beckehandh", + "pbe0", + "becke97", + "becke97-1", + "becke97-2", + "becke97-3", + "becke97-d", + "becke98", + "hcth", + "hcth120", + "hcth147", + "hcth407", + "becke97gga1", + "hcth407p", + "mpw91", + "mpw1k", + "xft97", + "cft97", + "ft97", + "op", + "bop", + "pbeop", + "xpkzb99", + "cpkzb99", + "xtpss03", + "ctpss03", + "xctpssh", + "b1b95", + "bb1k", + "mpw1b95", + "mpwb1k", + "pw6b95", + "pwb6k", + "m05", + "m05-2x", + "vs98", + "m06", + "m06-hf", + "m06-L", + "m06-2x", + "HFexch", + "becke88", + "xperdew91", + "xpbe96", + "gill96", + "lyp", + "perdew81", + "perdew86", + "perdew91", + "cpbe96", + "pw91lda", + "slater", + "vwn_1", + "vwn_2", + "vwn_3", + "vwn_4", + "vwn_5", + "vwn_1_rpa", + "xtpss03", + "ctpss03", + "bc95", + "xpw6b95", + "xpwb6k", + "xm05", + "xm05-2x", + "cpw6b95", + "cpwb6k", + "cm05", + "cm05-2x", + "xvs98", + "cvs98", + "xm06-L", + "xm06-hf", + "xm06", + "xm06-2x", + "cm06-L", + "cm06-hf", + "cm06", + "cm06-2x", +] + + +def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: + """Converts the QC method into NWChem keywords + + Args: + method (str): Name of the QC method to use + derint (str): Index of the run type + use_tce (bool): Whether to use the Tensor Contraction Engine + Returns: + (str): Task command for NWChem + (dict): Any options for NWChem + """ + + # Standardize the method name + method = method.lower() + opts = {} + + # Map the run type to + runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + + # Write out the theory directive + if method == "nwchem": + mdccmd = "" + + elif method in ["scf", "hf"]: + mdccmd = f"task scf {runtyp}\n\n" + + elif method == "mp2": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp2"] = True + else: + mdccmd = f"task mp2 {runtyp}\n\n" + + elif method == "mp3": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp3"] = True + + elif method == "mp4": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__mp4"] = True + + elif method == "ccd": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccd"] = True + + elif method == "ccsd": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsd"] = True + else: + mdccmd = f"task ccsd {runtyp}\n\n" + + elif method == "ccsdt": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsdt"] = True + else: + mdccmd = f"task ccsdt {runtyp}\n\n" + + elif method == "ccsd(t)": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__ccsd(t)"] = True + else: + mdccmd = f"task ccsd(t) {runtyp}\n\n" + + elif method == "tddft": + mdccmd = f"task tddft {runtyp}\n\n" + + elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: + raise InputError(f'Method "{method}" not yet supported by QCEngine') + + elif method == "tce": + raise InputError( + f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' + ) + + elif method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__"] = "dft" + else: + mdccmd = f"task dft {runtyp}\n\n" + + elif method == "dft": + if use_tce: + mdccmd = f"task tce {runtyp}\n\n" + opts["tce__"] = "dft" + else: + mdccmd = f"task dft {runtyp}\n\n" + + else: + raise InputError(f"Method not recognized: {method}") + + return mdccmd, opts diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py new file mode 100644 index 000000000..dbcfa9bfa --- /dev/null +++ b/qcengine/programs/madness/harvester.py @@ -0,0 +1,895 @@ +import re +import json +import logging +from decimal import Decimal +from typing import Tuple + +import numpy as np +import qcelemental as qcel +from qcelemental.models import Molecule +from qcelemental.models.results import AtomicResultProperties +from qcelemental.molparse import regex + +from ..util import PreservingDict + +logger = logging.getLogger(__name__) + + +def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: + """Function to read an entire NWChem output file. + + Reads all of the different "line search" segments of a file and returns + values from the last segment for which a geometry was written. + + Args: + outtext (str): Output written to stdout + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (Molecule): Molecule from the last complete step + - (list): Gradient from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Loop over all steps + # TODO (wardlt): Is it only necessary to read the last two steps? + pass_psivar = [] + pass_coord = [] + pass_grad = [] + for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) + pass_psivar.append(psivar) + pass_coord.append(nwcoord) + pass_grad.append(nwgrad) + + # Determine which segment contained the last geometry + retindx = -1 if pass_coord[-1] else -2 + + return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +def harvest_outfile_pass(outtext): + """Function to read NWChem output file *outtext* and parse important + quantum chemical information from it in + + """ + psivar = PreservingDict() + psivar_coord = None + psivar_grad = None + version = "" + error = "" # TODO (wardlt): The error string is never used. + + NUMBER = r"(?x:" + regex.NUMBER + ")" + # fmt: off + + # Process version + mobj = re.search( + r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched version') + version = mobj.group('version') + + # Process SCF + # 1)Fail to converge + mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('failed to converge') + + # 2)Calculation converged + else: + mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched HF') + psivar['HF TOTAL ENERGY'] = mobj.group(1) + + # Process Effective nuclear repulsion energy (a.u.) + mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched NRE') + # logger.debug (mobj.group(1)) + psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) + + # Process DFT dispersion energy (a.u.) + mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched Dispersion') + logger.debug(mobj.group(1)) + psivar['DFT DISPERSION ENERGY'] = mobj.group(1) + + # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) + + mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched DFT') + logger.debug(mobj.group(1)) + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + + # SODFT [for nwchem 6.8+] + mobj = re.search( + r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched DFT') + # print (mobj.group(1)) + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) + + # MCSCF + mobj = re.findall( + r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + + # for mobj_list in mobj: + + if mobj: # Need to change to accommodate find all instances + logger.debug('matched mcscf') # MCSCF energy calculation + psivar['HF TOTAL ENERGY'] = mobj.group(1) + psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) + psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) + psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + # for mobj_list in mobj: + # for i in mobj_list: + # count += 0 + # logger.debug('matched mcscf iteration %i', count) + # psivar['HF TOTAL ENERGY'] = mobj.group(1) + # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) + # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) + # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + + # Process MP2 (Restricted, Unrestricted(RO n/a)) + # 1)SCF-MP2 + mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + + NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 + if mobj: + logger.debug('matched scf-mp2') + psivar['HF TOTAL ENERGY'] = mobj.group(1) + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(5) + # SCS-MP2 + mobj = re.search( + r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE) + if mobj: + logger.debug('matched scs-mp2') + psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) + psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) + + logger.debug(mobj.group(1)) # ess + logger.debug(mobj.group(2)) # fss + logger.debug(mobj.group(3)) # eos + logger.debug(mobj.group(4)) # fos + logger.debug(mobj.group(5)) # scs corl + logger.debug(mobj.group(6)) # scs-mp2 + + # 2) DFT-MP2 + mobj = re.search( + r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + + r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched dft-mp2') + psivar['DFT TOTAL ENERGY'] = mobj.group(1) + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + + # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) + mobj = re.search( + r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + + r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + + r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug('matched coupled cluster-mp2') + psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) + psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + + # 4) Direct MP2 + + # 5) RI-MP2 + + # Process calculation through tce [dertype] command + for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: + mobj = re.search( + r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, + re.MULTILINE) + + if mobj: + mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') + logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) + + if mbpt_plain == 'MP2': + psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) + else: + psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) + psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) + #TCE dipole- MBPT(n) + mobj2 = re.search( + r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + + if mobj2: + mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') + print(f'matched tce {mbpt_plain} dipole moment') + #only pulling Debye + psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) + psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) + psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) + + #TCE with () or [] + for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: + mobj = re.search( + r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + + r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj: + cc_plain = cc_name.replace('\\', '') + cc_corr = cc_plain.replace('CCSD', '') + logger.debug(f'matched tce cc {cc_plain}') + + psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) + psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) + psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) + #TCE dipole with () or [] + mobj2 = re.search( + r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + + if mobj2: + cc_plain = cc_name.replace('\\', '') + cc_corr = cc_plain.replace('CCSD', '') + print(f'matched tce {cc_plain} dipole moment') + + #only pulling Debye + psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) + psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) + psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) + + #Process other TCE cases + for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: + mobj = re.search( + r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug(f'matched {cc_name}') + logger.debug(mobj) + psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) + psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) + #TCE dipole + mobj2 = re.search( + r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + + r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', + outtext, re.MULTILINE) + if mobj2: + print(f'matched tce dipole moment') + + #only pulling Debye + psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) + psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) + psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) + + # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command + + mobj = re.search( + r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + + NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE | re.DOTALL) + + if mobj: + logger.debug('matched ccsd') + psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) + psivar['CCSD TOTAL ENERGY'] = mobj.group(3) + + mobj = re.search( + r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + + r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + + r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + + if mobj: + logger.debug('matched ccsd(t)') + psivar['(T) CORRECTION ENERGY'] = mobj.group(1) + psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] + psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) + + mobj = re.search( + r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + + r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + # SCS-CCSD included + if mobj: + logger.debug('matched scs-ccsd') + psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( + Decimal(mobj.group(1)) * Decimal(mobj.group(2))) + psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( + Decimal(mobj.group(4)) * Decimal(mobj.group(3))) + psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) + psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) + psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( + psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) + # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( + # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) + + # Process EOM-[cc_name] #nwchem_tce_dipole = false + # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree + # psivar name might need to be fixed + # each root excitation energy is extracted from the last iteration of right hand side + mobj = re.findall( + r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + + # (..) captures symmetry + r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root + r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree + r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV + outtext, re.MULTILINE | re.DOTALL) + #regex should be more dynamic in finding values, need to revisit + #mobj.group(0) = symmetry value + #mobj.group(1) = cc_name + #mobj.group(2) = root number + #mobj.group(3) = excitation energy (hartree) + #mobj.group(4) = excitation energy (eV) + + if mobj: + print(mobj) + ext_energy = {} # dic + + ext_energy_list = [] + print(f'matched eom-{cc_name}') + for mobj_list in mobj: + logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry + logger.debug(mobj_list) + count = 0 + for line in mobj_list[1].splitlines(): + lline = line.split() + logger.debug(lline[1]) # in hartree + logger.debug(lline[2]) # in eV + count += 1 + + logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) + + ext_energy_list.append(lline[1]) # Collect all excitation energies + + sym = str(mobj_list[0]) + ext_energy.setdefault(sym, []) + ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) + + ext_energy_list.sort(key=float) + + for nroot in range(len(ext_energy_list)): + for k, e_val in ext_energy.items(): + if ext_energy_list[nroot] in e_val: + symm = k + psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ + ext_energy_list[nroot] # in hartree + psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ + psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree + gssym = '' + gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) + + if gs: + logger.debug('matched ground-state symmetry') + psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) + +# Process TDDFT +# 1) Spin allowed + mobj = re.findall( + r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' + #Root | symmetry | a.u. | eV + + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value + + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole + + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople + + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople + + r'\s*$', + outtext, re.MULTILINE) + + if mobj: + logger.debug('matched TDDFT with transition moments') + for mobj_list in mobj: + print (mobj_list) + psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV + psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) + #### temporary psivars #### + #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % + # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. + #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ + # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) + psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] + psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] + psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] + psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] + psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] + psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] + psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] + psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] + psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] + + +# 2) Spin forbidden + mobj = re.findall( + r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' + #Root | symmetry | a.u. | eV + + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value + + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', + outtext, re.MULTILINE) + #mobj.group(0) = Root + #mobj.group(1) = symmetry + #mobj.group(2) a.u. + #mobj.group(3) e.V + #mobj.group(4) Excitation energy + #mobj.group(5) Excited state energy + + if mobj: + logger.debug('matched TDDFT - spin forbidden') + for mobj_list in mobj: + #### temporary psivars #### + psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV + psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) + + #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % + # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. + #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ + # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) + if mobj: + logger.debug('Non-variation initial energy') # prints out energy, 5 counts + + # Process geometry + # 1) CHARGE + # Read charge from SCF module + mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched charge') + out_charge = int(float(mobj.group(1))) + + # Read charge from General information (not scf module) + mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched charge') + out_charge = int(float(mobj.group(1))) + + # 2) MULTIPLICITY + # Read multiplicity from SCF module + mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity') + out_mult = int(mobj.group(1)) + 1 + + # Read multiplicity from SCF module through alpha, beta electrons + mobj = re.search( + r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + + r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity via alpha and beta electrons') + out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 + psivar['N ALPHA ELECTRONS'] = mobj.group(1) + psivar['N BETA ELECTRONS'] = mobj.group(2) + + # Read multiplicity from General information (not scf module) + mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, + re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched multiplicity') + out_mult = int(mobj.group(1)) + + # 3) Initial geometry + mobj = re.search( + r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + + r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + + r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + + r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + + r'\s*' + + r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' + + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + + if mobj: + logger.debug('matched geom') + + # dinky molecule w/ charge and multiplicity + if mobj.group(1) == 'angstroms': + molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult + ) # unit = angstrom + for line in mobj.group(2).splitlines(): + lline = line.split() + molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) + # Jiyoung was collecting charge (-4)? see if this is ok for ghosts + # Tag , X, Y, Z + psivar_coord = Molecule(validate=False, + **qcel.molparse.to_schema(qcel.molparse.from_string( + molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], + dtype=2)) + + else: # unit = a.u. + molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) + for line in mobj.group(2).splitlines(): + lline = line.split() + molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) + # Tag , X, Y, Z + psivar_coord = Molecule(validate=False, + **qcel.molparse.to_schema(qcel.molparse.from_string( + molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], + dtype=2)) + + # Process gradient + mobj = re.search( + r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + + r'atom coordinates gradient' + r'\s*' + r'^\s+' + + r'x y z x y z' + r'\s*' + + r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' + + r'\s*$', outtext, re.MULTILINE) + + if mobj: + logger.debug('matched molgrad') + atoms = [] + psivar_grad = [] + for line in mobj.group(1).splitlines(): + lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z + # print (lline) + if lline == []: + pass + else: + atoms.append(lline[1]) # Tag + psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) + + # Process dipole (Properties) + mobj = re.search( + r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + + r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'.*' + + r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + + r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + + r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + + r'^\s+' + r'.*' + + r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', + outtext, re.MULTILINE) + + if mobj: + logger.debug('matched total dipole') + + # UNIT = DEBYE(S) + psivar['CURRENT DIPOLE X'] = mobj.group(7) + psivar['CURRENT DIPOLE Y'] = mobj.group(8) + psivar['CURRENT DIPOLE Z'] = mobj.group(9) + # total? + + # Process error code + mobj = re.search( + r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + + r'\s*' + r'^\s+' + r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + + r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched error') + # print (mobj.group(1)) #error line number + # print (mobj.group(2)) #error reason + psivar['NWCHEM ERROR CODE'] = mobj.group(1) + # TODO process errors into error var + + # fmt: on + + # Get the size of the basis sets, etc + mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) + if mobj: + psivar["N ATOMS"] = mobj.group(1) + mobj = re.search( + r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", + outtext, + re.MULTILINE, + ) + if mobj: + psivar["N ALPHA ELECTRONS"] = mobj.group(2) + psivar["N BETA ELECTRONS"] = mobj.group(3) + if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: + # get HOMO and LUMO energy + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"]) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-]0\d)", + outtext, + re.MULTILINE, + ) + if mobj: + homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) + psivar["HOMO"] = np.array([round(homo, 10)]) + mobj = re.search( + r"Vector" + + r"\s+" + + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) + + r"\s+" + + r"Occ=" + + r".*" + + r"\s+" + + r"E=" + + r"([+-]?\s?\d+[.]\d+)" + + r"[D]" + + r"([+-]0\d)", + outtext, + re.MULTILINE, + ) + if mobj: + lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) + psivar["LUMO"] = np.array([round(lumo, 10)]) + + mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) + if mobj: + psivar["N MO"] = mobj.group(2) + psivar["N BASIS"] = mobj.group(1) + + # Search for Center of charge + mobj = re.search( + r"Center of charge \(in au\) is the expansion point" + + r"\n" + + r"\s+" + + r"X\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Y\s+=\s+([+-]?\d+[.]\d+)" + + r"\s+" + + r"Z\s+=\s+([+-]?\d+[.]\d+)", + outtext, + re.MULTILINE, + ) + if mobj: + psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + + mobj = re.search( + r"Dipole moment" + + r".*?" + + r"A\.U\." + + r"\s+" + + r"DMX\s+([+-]?\d+[.]\d+)\s+" + + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMY\s+([+-]?\d+[.]\d+)\s+" + + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" + + r"DMZ\s+([+-]?\d+[.]\d+)\s+" + + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" + + r"\-EFC\-" + + r".*?" + + r"A\.U\.\s+" + + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", + outtext, + re.MULTILINE, + ) + # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) + if mobj: + psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) + + mobj = re.search( + r"Quadrupole moments in atomic units\s+" + + r"Component\s+" + + r"Electronic\+nuclear\s+" + + r"Point charges\s+" + + r"Total\s+" + + r"-+\s+" + + r"XX\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"ZZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XY\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"XZ\s+([+-]?\d+[.]\d+)\s+" + + r".*\s+.*\s+" + + r"YZ\s+([+-]?\d+[.]\d+)\s+", + outtext, + re.MULTILINE, + ) + + if mobj: + psivar["QUADRUPOLE MOMENT"] = np.array( + [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] + ) + + # Process CURRENT energies (TODO: needs better way) + if "HF TOTAL ENERGY" in psivar: + psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] + psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] + psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] + + if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] + if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] + if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] + + if "DFT TOTAL ENERGY" in psivar: + psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] + psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] + + # Process TCE CURRENT energies + # Need to be fixed + # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? + # TODO: CURRENT ENERGY = TCE ENERGY + if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): + psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] + psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] + + if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] + + if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: + psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] + psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + + if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): + psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] + psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + + return psivar, psivar_coord, psivar_grad, version, error + + +def harvest_hessian(hess: str) -> np.ndarray: + """Parses the contents of the NWChem hess file into a hessian array. + + Args: + hess (str): Contents of the hess file + Returns: + (np.ndarray) Hessian matrix as a 2D array + """ + + # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python + hess_conv = hess.replace("D", "E") + + # Parse all of the float values + hess_tri = [float(x) for x in hess_conv.strip().splitlines()] + + # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) + n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix + + # Add the lower diagonal + hess_arr = np.zeros((n, n)) + hess_arr[np.tril_indices(n)] = hess_tri + + # Transpose and then set the lower diagonal again + hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major + hess_arr[np.tril_indices(n)] = hess_tri + + return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines + + +def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: + """Get named properties out of the general variables extracted out of the result file + + Args: + psivars (PreservingDict): Dictionary of the output results + Returns: + (AtomicResultProperties) Properties in a standard format + """ + # TODO (wardlt): Get more of the named variables out of the NWChem file + + # Initialize the output + output = dict() + + # Extract the Calc Info + output.update( + { + "calcinfo_nbasis": psivars.get("N BASIS", None), + "calcinfo_nmo": psivars.get("N MO", None), + "calcinfo_natom": psivars.get("N ATOMS", None), + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), + "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), + } + ) + + # Get the "canonical" properties + output["return_energy"] = psivars["CURRENT ENERGY"] + output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + + # Get the SCF properties + output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) + output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + + # Get the MP2 properties + output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) + output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) + output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) + output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) + return AtomicResultProperties(**output) + + +def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: + """Parses all the pieces of output from NWChem: the stdout in + *nwout* Scratch files are not yet considered at this moment. + + Args: + in_mol (Molecule): Input molecule + nwout (str): NWChem output molecule + outfiles (dict): Dictionary of outfile files and their contents + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (None): Hessian from the last complete step (Not yet implemented) + - (list): Gradient from the last complete step + - (Molecule): Molecule from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Parse the NWChem output + out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) + + # If available, read higher-accuracy gradients + # These were output using a Python Task in NWChem to read them out of the database + if outfiles.get("nwchem.grad") is not None: + logger.debug("Reading higher-accuracy gradients") + out_grad = json.loads(outfiles.get("nwchem.grad")) + + # If available, read the hessian + out_hess = None + if outfiles.get("nwchem.hess") is not None: + out_hess = harvest_hessian(outfiles.get("nwchem.hess")) + + # Make sure the input and output molecules are the same + if out_mol: + if in_mol: + if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: + raise ValueError( + """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" + % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) + ) + else: + raise ValueError("""No coordinate information extracted from NWChem output.""") + + # If present, align the gradients and hessian with the original molecular coordinates + # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the + # rotated molecule, which we can use to determine how to rotate the gradients/hessian + amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + + mill = data["mill"] # Retrieve tool with alignment routines + + if out_grad is not None: + out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) + if out_hess is not None: + out_hess = mill.align_hessian(np.array(out_hess)) + + return out_psivar, out_hess, out_grad, out_mol, version, error diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py new file mode 100644 index 000000000..1821e98c5 --- /dev/null +++ b/qcengine/programs/madness/keywords.py @@ -0,0 +1,87 @@ +import collections +from typing import Any, Dict, Tuple + + +def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: + """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" + + # Transform string booleans into " " + if val is True: + return keyword.lower(), "" + elif val is False: + return "", "" + + # complete hack + if keyword.upper() == "MEMORY": + return keyword.lower(), f"{val} byte" + + elif isinstance(val, list): + text = " ".join([str(v) for v in val]) + elif isinstance(val, dict): + text = [] + for k, v in val.items(): + merge = [k] + merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) + text.append(" ".join(merge)) + text = " ".join(text) + else: + text = str(val) + + if lop_off: + return keyword[7:].lower(), text + else: + return keyword.lower(), text + + +def format_keywords(keywords: Dict[str, Any]) -> str: + """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" + + def rec_dd(): + return collections.defaultdict(rec_dd) + + grouped_options = rec_dd() + + for group_key, val in keywords.items(): + nesting = group_key.split("__") + if len(nesting) == 1: + key = nesting[0] + grouped_options["aaaglobal"][key] = val + elif len(nesting) == 2: + g1, key = nesting + grouped_options[g1][key] = val + elif len(nesting) == 3: + g1, g2, key = nesting + grouped_options[g1][g2][key] = val + else: + print(nesting) + raise ValueError("Nesting N!") + + grouped_lines = {} + for group, opts in sorted(grouped_options.items()): + lines = [] + group_level_lines = [] + for key, val in grouped_options[group].items(): + if isinstance(val, dict): + g2_level_lines = [] + g2_level_lines.append(key.lower()) + for k2, v2 in val.items(): + line2 = " ".join(format_keyword(k2, v2, lop_off=False)) + g2_level_lines.append(line2) + g2_level_lines = " ".join(g2_level_lines) + lines.append(g2_level_lines) + else: + line = " ".join(format_keyword(key, val, lop_off=False)) + if group.lower() == "basis" and any( + [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] + ): + group_level_lines.append(line) + else: + lines.append(line) + if group == "aaaglobal": + grouped_lines[group] = "\n".join(lines) + "\n" + else: + grouped_lines[group] = ( + f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" + ) + + return "\n".join(grouped_lines.values()) + "\n" diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py new file mode 100644 index 000000000..e86d655c9 --- /dev/null +++ b/qcengine/programs/madness/runner.py @@ -0,0 +1,270 @@ +""" +Calls the Madness moldft executable. +""" +import re +import copy +import logging +import pprint +from decimal import Decimal +from typing import Any, Dict, Optional, Tuple + +import numpy as np +import qcelemental as qcel +from qcelemental.models import AtomicResult, Provenance, AtomicInput +from qcelemental.util import safe_version, which, which_import + +from qcengine.config import TaskConfig, get_config +from qcengine.exceptions import UnknownError + +from ...exceptions import InputError +from ...util import execute, create_mpi_invocation +from ..model import ProgramHarness +from .germinate import muster_modelchem +from .harvester import extract_formatted_properties, harvest +from .keywords import format_keywords + +pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) +logger = logging.getLogger(__name__) + + +class MadnessHarness(ProgramHarness): + """ + +# Notes +# ----- +# * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. + +# """ + +# _defaults = { +# "name": "NWChem", +# "scratch": True, +# "thread_safe": False, +# "thread_parallel": False, +# "node_parallel": True, +# "managed_memory": True, +# } +# # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers +# version_cache: Dict[str, str] = {} + +# class Config(ProgramHarness.Config): +# pass + +# @staticmethod +# def found(raise_error: bool = False) -> bool: +# """Whether NWChem harness is ready for operation, with both the QC program and any particular dependencies found. + +# Parameters +# ---------- +# raise_error: bool +# Passed on to control negative return between False and ModuleNotFoundError raised. + +# Returns +# ------- +# bool +# If both nwchem and its harness dependency networkx are found, returns True. +# If raise_error is False and nwchem or networkx are missing, returns False. +# If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + +# """ +# qc = which( +# "nwchem", +# return_bool=True, +# raise_error=raise_error, +# raise_msg="Please install via http://www.nwchem-sw.org/index.php/Download", +# ) + +# dep = which_import( +# "networkx", +# return_bool=True, +# raise_error=raise_error, +# raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", +# ) + +# return qc and dep + +# def get_version(self) -> str: +# self.found(raise_error=True) + +# # Get the node configuration +# config = get_config() + +# # Run NWChem +# which_prog = which("nwchem") +# if config.use_mpiexec: +# command = create_mpi_invocation(which_prog, config) +# else: +# command = [which_prog] +# command.append("v.nw") + +# if which_prog not in self.version_cache: +# success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) + +# if success: +# for line in output["stdout"].splitlines(): +# if "nwchem branch" in line: +# branch = line.strip().split()[-1] +# if "nwchem revision" in line: +# revision = line.strip().split()[-1] +# self.version_cache[which_prog] = safe_version(branch + "+" + revision) +# else: +# raise UnknownError(output["stderr"]) + +# return self.version_cache[which_prog] + +# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": +# """ +# Runs NWChem in executable mode +# """ +# self.found(raise_error=True) + +# job_inputs = self.build_input(input_model, config) +# success, dexe = self.execute(job_inputs) + +# if "There is an error in the input file" in dexe["stdout"]: +# raise InputError(dexe["stdout"]) +# if "not compiled" in dexe["stdout"]: +# # recoverable with a different compilation with optional modules +# raise InputError(dexe["stdout"]) + +# if success: +# dexe["outfiles"]["stdout"] = dexe["stdout"] +# dexe["outfiles"]["stderr"] = dexe["stderr"] +# return self.parse_output(dexe["outfiles"], input_model) +# else: +# raise UnknownError(dexe["stderr"]) + +# def build_input( +# self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None +# ) -> Dict[str, Any]: +# nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} + +# opts = copy.deepcopy(input_model.keywords) +# opts = {k.lower(): v for k, v in opts.items()} + +# # Handle memory +# # for nwchem, [GiB] --> [B] +# # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' +# memory_size = int(config.memory * (1024 ** 3)) +# if config.use_mpiexec: # It is the memory per MPI rank +# memory_size //= config.nnodes * config.ncores // config.cores_per_rank +# opts["memory"] = memory_size + +# # Handle molecule +# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) +# opts.update(moldata["keywords"]) + +# # Handle calc type and quantum chemical method +# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) +# opts.update(mdcopts) + +# # Handle basis set +# # * for nwchem, still needs sph and ghost +# for el in set(input_model.molecule.symbols): +# opts[f"basis__{el}"] = f"library {input_model.model.basis}" + +# # Log the job settings +# logger.debug("JOB_OPTS") +# logger.debug(pp.pformat(opts)) + +# # Handle conversion from schema (flat key/value) keywords into local format +# optcmd = format_keywords(opts) + +# # Combine the molecule description, options and method command together +# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd + +# # For gradient methods, add a Python command to save the gradients in higher precision +# # Note: The Hessian is already stored in high precision in a file named "*.hess" +# if input_model.driver == "gradient": +# # Get the name of the theory used for computing the gradients +# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) +# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") + +# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) +# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ +# # (not 6 significant figures) +# pycmd = f""" +# python +# grad = rtdb_get('{theory}:gradient') +# if ga_nodeid() == 0: +# import json +# with open('nwchem.grad', 'w') as fp: +# json.dump(grad, fp) +# end + +# task python +# """ +# nwchemrec["infiles"]["nwchem.nw"] += pycmd + +# # Determine the command +# if config.use_mpiexec: +# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) +# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") +# else: +# nwchemrec["command"] = [which("nwchem")] + +# return nwchemrec + +# def execute( +# self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None +# ) -> Tuple[bool, Dict]: + +# success, dexe = execute( +# inputs["command"], +# inputs["infiles"], +# ["nwchem.hess", "nwchem.grad"], +# scratch_messy=False, +# scratch_exist_ok=True, +# scratch_directory=inputs["scratch_directory"], +# ) +# return success, dexe + +# def parse_output( +# self, outfiles: Dict[str, str], input_model: "AtomicInput" +# ) -> "AtomicResult": # lgtm: [py/similar-function] + +# # Get the stdout from the calculation (required) +# stdout = outfiles.pop("stdout") + +# # Read the NWChem stdout file and, if needed, the hess or grad files +# qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + +# if nwgrad is not None: +# qcvars["CURRENT GRADIENT"] = nwgrad + +# if nwhess is not None: +# qcvars["CURRENT HESSIAN"] = nwhess + +# # Normalize the output as a float or list of floats +# if input_model.driver.upper() == "PROPERTIES": +# retres = qcvars[f"CURRENT ENERGY"] +# else: +# retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + +# if isinstance(retres, Decimal): +# retres = float(retres) +# elif isinstance(retres, np.ndarray): +# retres = retres.tolist() + +# # Get the formatted properties +# qcprops = extract_formatted_properties(qcvars) + +# # Format them inout an output +# output_data = { +# "schema_name": "qcschema_output", +# "schema_version": 1, +# "extras": {"outfiles": outfiles, **input_model.extras}, +# "properties": qcprops, +# "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), +# "return_result": retres, +# "stdout": stdout, +# } + +# # got to even out who needs plump/flat/Decimal/float/ndarray/list +# # Decimal --> str preserves precision +# output_data["extras"]["qcvars"] = { +# k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() +# } + +# output_data["success"] = True +# return AtomicResult(**{**input_model.dict(), **output_data}) From f16796d600358696811819a62a5dc3781fbab121 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:16:48 -0400 Subject: [PATCH 032/102] init madness dir --- qcengine/programs/madness/germinate.py | 370 ++--- qcengine/programs/madness/harvester.py | 1754 ++++++++++++------------ qcengine/programs/madness/keywords.py | 144 +- 3 files changed, 1134 insertions(+), 1134 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index e76132ba8..88fb672e8 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -2,188 +2,188 @@ from qcengine.exceptions import InputError -# List of XC functionals known to NWChem -_xc_functionals = [ - "acm", - "b3lyp", - "beckehandh", - "pbe0", - "becke97", - "becke97-1", - "becke97-2", - "becke97-3", - "becke97-d", - "becke98", - "hcth", - "hcth120", - "hcth147", - "hcth407", - "becke97gga1", - "hcth407p", - "mpw91", - "mpw1k", - "xft97", - "cft97", - "ft97", - "op", - "bop", - "pbeop", - "xpkzb99", - "cpkzb99", - "xtpss03", - "ctpss03", - "xctpssh", - "b1b95", - "bb1k", - "mpw1b95", - "mpwb1k", - "pw6b95", - "pwb6k", - "m05", - "m05-2x", - "vs98", - "m06", - "m06-hf", - "m06-L", - "m06-2x", - "HFexch", - "becke88", - "xperdew91", - "xpbe96", - "gill96", - "lyp", - "perdew81", - "perdew86", - "perdew91", - "cpbe96", - "pw91lda", - "slater", - "vwn_1", - "vwn_2", - "vwn_3", - "vwn_4", - "vwn_5", - "vwn_1_rpa", - "xtpss03", - "ctpss03", - "bc95", - "xpw6b95", - "xpwb6k", - "xm05", - "xm05-2x", - "cpw6b95", - "cpwb6k", - "cm05", - "cm05-2x", - "xvs98", - "cvs98", - "xm06-L", - "xm06-hf", - "xm06", - "xm06-2x", - "cm06-L", - "cm06-hf", - "cm06", - "cm06-2x", -] - - -def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: - """Converts the QC method into NWChem keywords - - Args: - method (str): Name of the QC method to use - derint (str): Index of the run type - use_tce (bool): Whether to use the Tensor Contraction Engine - Returns: - (str): Task command for NWChem - (dict): Any options for NWChem - """ - - # Standardize the method name - method = method.lower() - opts = {} - - # Map the run type to - runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - - # Write out the theory directive - if method == "nwchem": - mdccmd = "" - - elif method in ["scf", "hf"]: - mdccmd = f"task scf {runtyp}\n\n" - - elif method == "mp2": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp2"] = True - else: - mdccmd = f"task mp2 {runtyp}\n\n" - - elif method == "mp3": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp3"] = True - - elif method == "mp4": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__mp4"] = True - - elif method == "ccd": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccd"] = True - - elif method == "ccsd": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsd"] = True - else: - mdccmd = f"task ccsd {runtyp}\n\n" - - elif method == "ccsdt": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsdt"] = True - else: - mdccmd = f"task ccsdt {runtyp}\n\n" - - elif method == "ccsd(t)": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__ccsd(t)"] = True - else: - mdccmd = f"task ccsd(t) {runtyp}\n\n" - - elif method == "tddft": - mdccmd = f"task tddft {runtyp}\n\n" - - elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: - raise InputError(f'Method "{method}" not yet supported by QCEngine') - - elif method == "tce": - raise InputError( - f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' - ) - - elif method.split()[0] in _xc_functionals: - opts["dft__xc"] = method - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__"] = "dft" - else: - mdccmd = f"task dft {runtyp}\n\n" - - elif method == "dft": - if use_tce: - mdccmd = f"task tce {runtyp}\n\n" - opts["tce__"] = "dft" - else: - mdccmd = f"task dft {runtyp}\n\n" - - else: - raise InputError(f"Method not recognized: {method}") - - return mdccmd, opts +# # List of XC functionals known to NWChem +# _xc_functionals = [ +# "acm", +# "b3lyp", +# "beckehandh", +# "pbe0", +# "becke97", +# "becke97-1", +# "becke97-2", +# "becke97-3", +# "becke97-d", +# "becke98", +# "hcth", +# "hcth120", +# "hcth147", +# "hcth407", +# "becke97gga1", +# "hcth407p", +# "mpw91", +# "mpw1k", +# "xft97", +# "cft97", +# "ft97", +# "op", +# "bop", +# "pbeop", +# "xpkzb99", +# "cpkzb99", +# "xtpss03", +# "ctpss03", +# "xctpssh", +# "b1b95", +# "bb1k", +# "mpw1b95", +# "mpwb1k", +# "pw6b95", +# "pwb6k", +# "m05", +# "m05-2x", +# "vs98", +# "m06", +# "m06-hf", +# "m06-L", +# "m06-2x", +# "HFexch", +# "becke88", +# "xperdew91", +# "xpbe96", +# "gill96", +# "lyp", +# "perdew81", +# "perdew86", +# "perdew91", +# "cpbe96", +# "pw91lda", +# "slater", +# "vwn_1", +# "vwn_2", +# "vwn_3", +# "vwn_4", +# "vwn_5", +# "vwn_1_rpa", +# "xtpss03", +# "ctpss03", +# "bc95", +# "xpw6b95", +# "xpwb6k", +# "xm05", +# "xm05-2x", +# "cpw6b95", +# "cpwb6k", +# "cm05", +# "cm05-2x", +# "xvs98", +# "cvs98", +# "xm06-L", +# "xm06-hf", +# "xm06", +# "xm06-2x", +# "cm06-L", +# "cm06-hf", +# "cm06", +# "cm06-2x", +# ] + + +# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +# """Converts the QC method into NWChem keywords + +# Args: +# method (str): Name of the QC method to use +# derint (str): Index of the run type +# use_tce (bool): Whether to use the Tensor Contraction Engine +# Returns: +# (str): Task command for NWChem +# (dict): Any options for NWChem +# """ + +# # Standardize the method name +# method = method.lower() +# opts = {} + +# # Map the run type to +# runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + +# # Write out the theory directive +# if method == "nwchem": +# mdccmd = "" + +# elif method in ["scf", "hf"]: +# mdccmd = f"task scf {runtyp}\n\n" + +# elif method == "mp2": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp2"] = True +# else: +# mdccmd = f"task mp2 {runtyp}\n\n" + +# elif method == "mp3": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp3"] = True + +# elif method == "mp4": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__mp4"] = True + +# elif method == "ccd": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccd"] = True + +# elif method == "ccsd": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsd"] = True +# else: +# mdccmd = f"task ccsd {runtyp}\n\n" + +# elif method == "ccsdt": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsdt"] = True +# else: +# mdccmd = f"task ccsdt {runtyp}\n\n" + +# elif method == "ccsd(t)": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__ccsd(t)"] = True +# else: +# mdccmd = f"task ccsd(t) {runtyp}\n\n" + +# elif method == "tddft": +# mdccmd = f"task tddft {runtyp}\n\n" + +# elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: +# raise InputError(f'Method "{method}" not yet supported by QCEngine') + +# elif method == "tce": +# raise InputError( +# f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' +# ) + +# elif method.split()[0] in _xc_functionals: +# opts["dft__xc"] = method +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__"] = "dft" +# else: +# mdccmd = f"task dft {runtyp}\n\n" + +# elif method == "dft": +# if use_tce: +# mdccmd = f"task tce {runtyp}\n\n" +# opts["tce__"] = "dft" +# else: +# mdccmd = f"task dft {runtyp}\n\n" + +# else: +# raise InputError(f"Method not recognized: {method}") + +# # return mdccmd, opts diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index dbcfa9bfa..3040e81b1 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -15,881 +15,881 @@ logger = logging.getLogger(__name__) -def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: - """Function to read an entire NWChem output file. - - Reads all of the different "line search" segments of a file and returns - values from the last segment for which a geometry was written. - - Args: - outtext (str): Output written to stdout - Returns: - - (PreservingDict) Variables extracted from the output file in the last complete step - - (Molecule): Molecule from the last complete step - - (list): Gradient from the last complete step - - (str): Version string - - (str): Error message, if any - """ - - # Loop over all steps - # TODO (wardlt): Is it only necessary to read the last two steps? - pass_psivar = [] - pass_coord = [] - pass_grad = [] - for outpass in re.split(r" Line search:", outtext, re.MULTILINE): - psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) - pass_psivar.append(psivar) - pass_coord.append(nwcoord) - pass_grad.append(nwgrad) - - # Determine which segment contained the last geometry - retindx = -1 if pass_coord[-1] else -2 - - return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error - - -def harvest_outfile_pass(outtext): - """Function to read NWChem output file *outtext* and parse important - quantum chemical information from it in - - """ - psivar = PreservingDict() - psivar_coord = None - psivar_grad = None - version = "" - error = "" # TODO (wardlt): The error string is never used. - - NUMBER = r"(?x:" + regex.NUMBER + ")" - # fmt: off - - # Process version - mobj = re.search( - r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', - outtext, re.MULTILINE) - if mobj: - logger.debug('matched version') - version = mobj.group('version') - - # Process SCF - # 1)Fail to converge - mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('failed to converge') - - # 2)Calculation converged - else: - mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched HF') - psivar['HF TOTAL ENERGY'] = mobj.group(1) - - # Process Effective nuclear repulsion energy (a.u.) - mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj: - logger.debug('matched NRE') - # logger.debug (mobj.group(1)) - psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) - - # Process DFT dispersion energy (a.u.) - mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched Dispersion') - logger.debug(mobj.group(1)) - psivar['DFT DISPERSION ENERGY'] = mobj.group(1) - - # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) - - mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched DFT') - logger.debug(mobj.group(1)) - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - - # SODFT [for nwchem 6.8+] - mobj = re.search( - r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched DFT') - # print (mobj.group(1)) - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) - - # MCSCF - mobj = re.findall( - r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - - # for mobj_list in mobj: - - if mobj: # Need to change to accommodate find all instances - logger.debug('matched mcscf') # MCSCF energy calculation - psivar['HF TOTAL ENERGY'] = mobj.group(1) - psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) - psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) - psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - # for mobj_list in mobj: - # for i in mobj_list: - # count += 0 - # logger.debug('matched mcscf iteration %i', count) - # psivar['HF TOTAL ENERGY'] = mobj.group(1) - # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) - # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) - # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - - # Process MP2 (Restricted, Unrestricted(RO n/a)) - # 1)SCF-MP2 - mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + - NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 - if mobj: - logger.debug('matched scf-mp2') - psivar['HF TOTAL ENERGY'] = mobj.group(1) - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(5) - # SCS-MP2 - mobj = re.search( - r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE) - if mobj: - logger.debug('matched scs-mp2') - psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) - psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) - - logger.debug(mobj.group(1)) # ess - logger.debug(mobj.group(2)) # fss - logger.debug(mobj.group(3)) # eos - logger.debug(mobj.group(4)) # fos - logger.debug(mobj.group(5)) # scs corl - logger.debug(mobj.group(6)) # scs-mp2 - - # 2) DFT-MP2 - mobj = re.search( - r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + - r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched dft-mp2') - psivar['DFT TOTAL ENERGY'] = mobj.group(1) - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - - # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) - mobj = re.search( - r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + - r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + - r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug('matched coupled cluster-mp2') - psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) - psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - - # 4) Direct MP2 - - # 5) RI-MP2 - - # Process calculation through tce [dertype] command - for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: - mobj = re.search( - r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, - re.MULTILINE) - - if mobj: - mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') - logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) - - if mbpt_plain == 'MP2': - psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) - else: - psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) - psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) - #TCE dipole- MBPT(n) - mobj2 = re.search( - r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - - if mobj2: - mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') - print(f'matched tce {mbpt_plain} dipole moment') - #only pulling Debye - psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) - psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) - psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) - - #TCE with () or [] - for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: - mobj = re.search( - r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + - r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj: - cc_plain = cc_name.replace('\\', '') - cc_corr = cc_plain.replace('CCSD', '') - logger.debug(f'matched tce cc {cc_plain}') - - psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) - psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) - psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) - #TCE dipole with () or [] - mobj2 = re.search( - r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - - if mobj2: - cc_plain = cc_name.replace('\\', '') - cc_corr = cc_plain.replace('CCSD', '') - print(f'matched tce {cc_plain} dipole moment') - - #only pulling Debye - psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) - psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) - psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) +# def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: +# """Function to read an entire NWChem output file. + +# Reads all of the different "line search" segments of a file and returns +# values from the last segment for which a geometry was written. + +# Args: +# outtext (str): Output written to stdout +# Returns: +# - (PreservingDict) Variables extracted from the output file in the last complete step +# - (Molecule): Molecule from the last complete step +# - (list): Gradient from the last complete step +# - (str): Version string +# - (str): Error message, if any +# """ + +# # Loop over all steps +# # TODO (wardlt): Is it only necessary to read the last two steps? +# pass_psivar = [] +# pass_coord = [] +# pass_grad = [] +# for outpass in re.split(r" Line search:", outtext, re.MULTILINE): +# psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) +# pass_psivar.append(psivar) +# pass_coord.append(nwcoord) +# pass_grad.append(nwgrad) + +# # Determine which segment contained the last geometry +# retindx = -1 if pass_coord[-1] else -2 + +# return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +# def harvest_outfile_pass(outtext): +# """Function to read NWChem output file *outtext* and parse important +# quantum chemical information from it in + +# """ +# psivar = PreservingDict() +# psivar_coord = None +# psivar_grad = None +# version = "" +# error = "" # TODO (wardlt): The error string is never used. + +# NUMBER = r"(?x:" + regex.NUMBER + ")" +# # fmt: off + +# # Process version +# mobj = re.search( +# r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched version') +# version = mobj.group('version') + +# # Process SCF +# # 1)Fail to converge +# mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('failed to converge') + +# # 2)Calculation converged +# else: +# mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched HF') +# psivar['HF TOTAL ENERGY'] = mobj.group(1) + +# # Process Effective nuclear repulsion energy (a.u.) +# mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched NRE') +# # logger.debug (mobj.group(1)) +# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) + +# # Process DFT dispersion energy (a.u.) +# mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched Dispersion') +# logger.debug(mobj.group(1)) +# psivar['DFT DISPERSION ENERGY'] = mobj.group(1) + +# # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) + +# mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched DFT') +# logger.debug(mobj.group(1)) +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) + +# # SODFT [for nwchem 6.8+] +# mobj = re.search( +# r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched DFT') +# # print (mobj.group(1)) +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) +# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) + +# # MCSCF +# mobj = re.findall( +# r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + +# # for mobj_list in mobj: + +# if mobj: # Need to change to accommodate find all instances +# logger.debug('matched mcscf') # MCSCF energy calculation +# psivar['HF TOTAL ENERGY'] = mobj.group(1) +# psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) +# psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) +# psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) +# # for mobj_list in mobj: +# # for i in mobj_list: +# # count += 0 +# # logger.debug('matched mcscf iteration %i', count) +# # psivar['HF TOTAL ENERGY'] = mobj.group(1) +# # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) +# # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) +# # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) + +# # Process MP2 (Restricted, Unrestricted(RO n/a)) +# # 1)SCF-MP2 +# mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + +# NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 +# if mobj: +# logger.debug('matched scf-mp2') +# psivar['HF TOTAL ENERGY'] = mobj.group(1) +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(5) +# # SCS-MP2 +# mobj = re.search( +# r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE) +# if mobj: +# logger.debug('matched scs-mp2') +# psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) +# psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) + +# logger.debug(mobj.group(1)) # ess +# logger.debug(mobj.group(2)) # fss +# logger.debug(mobj.group(3)) # eos +# logger.debug(mobj.group(4)) # fos +# logger.debug(mobj.group(5)) # scs corl +# logger.debug(mobj.group(6)) # scs-mp2 + +# # 2) DFT-MP2 +# mobj = re.search( +# r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + +# r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched dft-mp2') +# psivar['DFT TOTAL ENERGY'] = mobj.group(1) +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + +# # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) +# mobj = re.search( +# r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + +# r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + +# r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched coupled cluster-mp2') +# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) +# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) + +# # 4) Direct MP2 + +# # 5) RI-MP2 + +# # Process calculation through tce [dertype] command +# for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: +# mobj = re.search( +# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, +# re.MULTILINE) + +# if mobj: +# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') +# logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) + +# if mbpt_plain == 'MP2': +# psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) +# else: +# psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) +# psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) +# #TCE dipole- MBPT(n) +# mobj2 = re.search( +# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj2: +# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') +# print(f'matched tce {mbpt_plain} dipole moment') +# #only pulling Debye +# psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) +# psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) +# psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) + +# #TCE with () or [] +# for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: +# mobj = re.search( +# r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + +# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj: +# cc_plain = cc_name.replace('\\', '') +# cc_corr = cc_plain.replace('CCSD', '') +# logger.debug(f'matched tce cc {cc_plain}') + +# psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) +# psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) +# psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) +# #TCE dipole with () or [] +# mobj2 = re.search( +# r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj2: +# cc_plain = cc_name.replace('\\', '') +# cc_corr = cc_plain.replace('CCSD', '') +# print(f'matched tce {cc_plain} dipole moment') + +# #only pulling Debye +# psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) +# psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) +# psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) - #Process other TCE cases - for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: - mobj = re.search( - r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + - r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + - r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug(f'matched {cc_name}') - logger.debug(mobj) - psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) - psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) - #TCE dipole - mobj2 = re.search( - r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + - r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', - outtext, re.MULTILINE) - if mobj2: - print(f'matched tce dipole moment') - - #only pulling Debye - psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) - psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) - psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) - - # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command - - mobj = re.search( - r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + - r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + - NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE | re.DOTALL) - - if mobj: - logger.debug('matched ccsd') - psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) - psivar['CCSD TOTAL ENERGY'] = mobj.group(3) - - mobj = re.search( - r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + - r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + - r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - - if mobj: - logger.debug('matched ccsd(t)') - psivar['(T) CORRECTION ENERGY'] = mobj.group(1) - psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] - psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) - - mobj = re.search( - r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + - r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' - r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + - r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' - r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + - r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - # SCS-CCSD included - if mobj: - logger.debug('matched scs-ccsd') - psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( - Decimal(mobj.group(1)) * Decimal(mobj.group(2))) - psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( - Decimal(mobj.group(4)) * Decimal(mobj.group(3))) - psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) - psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) - psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( - psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) - # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( - # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) - - # Process EOM-[cc_name] #nwchem_tce_dipole = false - # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree - # psivar name might need to be fixed - # each root excitation energy is extracted from the last iteration of right hand side - mobj = re.findall( - r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + - # (..) captures symmetry - r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root - r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree - r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV - outtext, re.MULTILINE | re.DOTALL) - #regex should be more dynamic in finding values, need to revisit - #mobj.group(0) = symmetry value - #mobj.group(1) = cc_name - #mobj.group(2) = root number - #mobj.group(3) = excitation energy (hartree) - #mobj.group(4) = excitation energy (eV) - - if mobj: - print(mobj) - ext_energy = {} # dic - - ext_energy_list = [] - print(f'matched eom-{cc_name}') - for mobj_list in mobj: - logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry - logger.debug(mobj_list) - count = 0 - for line in mobj_list[1].splitlines(): - lline = line.split() - logger.debug(lline[1]) # in hartree - logger.debug(lline[2]) # in eV - count += 1 - - logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) - - ext_energy_list.append(lline[1]) # Collect all excitation energies - - sym = str(mobj_list[0]) - ext_energy.setdefault(sym, []) - ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) - - ext_energy_list.sort(key=float) - - for nroot in range(len(ext_energy_list)): - for k, e_val in ext_energy.items(): - if ext_energy_list[nroot] in e_val: - symm = k - psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ - ext_energy_list[nroot] # in hartree - psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ - psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree - gssym = '' - gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) - - if gs: - logger.debug('matched ground-state symmetry') - psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) - -# Process TDDFT -# 1) Spin allowed - mobj = re.findall( - r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' - #Root | symmetry | a.u. | eV - + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value - + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole - + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople - + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople - + r'\s*$', - outtext, re.MULTILINE) - - if mobj: - logger.debug('matched TDDFT with transition moments') - for mobj_list in mobj: - print (mobj_list) - psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV - psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) - #### temporary psivars #### - #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % - # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. - #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ - # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) - psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] - psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] - psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] - psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] - psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] - psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] - psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] - psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] - psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] - - -# 2) Spin forbidden - mobj = re.findall( - r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' - #Root | symmetry | a.u. | eV - + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value - + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', - outtext, re.MULTILINE) - #mobj.group(0) = Root - #mobj.group(1) = symmetry - #mobj.group(2) a.u. - #mobj.group(3) e.V - #mobj.group(4) Excitation energy - #mobj.group(5) Excited state energy - - if mobj: - logger.debug('matched TDDFT - spin forbidden') - for mobj_list in mobj: - #### temporary psivars #### - psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV - psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) - - #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % - # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. - #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ - # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) - if mobj: - logger.debug('Non-variation initial energy') # prints out energy, 5 counts - - # Process geometry - # 1) CHARGE - # Read charge from SCF module - mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched charge') - out_charge = int(float(mobj.group(1))) - - # Read charge from General information (not scf module) - mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched charge') - out_charge = int(float(mobj.group(1))) - - # 2) MULTIPLICITY - # Read multiplicity from SCF module - mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity') - out_mult = int(mobj.group(1)) + 1 - - # Read multiplicity from SCF module through alpha, beta electrons - mobj = re.search( - r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + - r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity via alpha and beta electrons') - out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 - psivar['N ALPHA ELECTRONS'] = mobj.group(1) - psivar['N BETA ELECTRONS'] = mobj.group(2) - - # Read multiplicity from General information (not scf module) - mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, - re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched multiplicity') - out_mult = int(mobj.group(1)) - - # 3) Initial geometry - mobj = re.search( - r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + - r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + - r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + - r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + - r'\s*' + - r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' - + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - - if mobj: - logger.debug('matched geom') - - # dinky molecule w/ charge and multiplicity - if mobj.group(1) == 'angstroms': - molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult - ) # unit = angstrom - for line in mobj.group(2).splitlines(): - lline = line.split() - molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) - # Jiyoung was collecting charge (-4)? see if this is ok for ghosts - # Tag , X, Y, Z - psivar_coord = Molecule(validate=False, - **qcel.molparse.to_schema(qcel.molparse.from_string( - molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], - dtype=2)) - - else: # unit = a.u. - molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) - for line in mobj.group(2).splitlines(): - lline = line.split() - molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) - # Tag , X, Y, Z - psivar_coord = Molecule(validate=False, - **qcel.molparse.to_schema(qcel.molparse.from_string( - molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], - dtype=2)) - - # Process gradient - mobj = re.search( - r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + - r'atom coordinates gradient' + r'\s*' + r'^\s+' + - r'x y z x y z' + r'\s*' + - r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' - + r'\s*$', outtext, re.MULTILINE) - - if mobj: - logger.debug('matched molgrad') - atoms = [] - psivar_grad = [] - for line in mobj.group(1).splitlines(): - lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z - # print (lline) - if lline == []: - pass - else: - atoms.append(lline[1]) # Tag - psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) - - # Process dipole (Properties) - mobj = re.search( - r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + - r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'.*' + - r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + - r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + - r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + - r'^\s+' + r'.*' + - r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', - outtext, re.MULTILINE) - - if mobj: - logger.debug('matched total dipole') - - # UNIT = DEBYE(S) - psivar['CURRENT DIPOLE X'] = mobj.group(7) - psivar['CURRENT DIPOLE Y'] = mobj.group(8) - psivar['CURRENT DIPOLE Z'] = mobj.group(9) - # total? - - # Process error code - mobj = re.search( - r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + - r'\s*' + r'^\s+' - r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' - r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + - r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) - if mobj: - logger.debug('matched error') - # print (mobj.group(1)) #error line number - # print (mobj.group(2)) #error reason - psivar['NWCHEM ERROR CODE'] = mobj.group(1) - # TODO process errors into error var - - # fmt: on - - # Get the size of the basis sets, etc - mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) - if mobj: - psivar["N ATOMS"] = mobj.group(1) - mobj = re.search( - r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", - outtext, - re.MULTILINE, - ) - if mobj: - psivar["N ALPHA ELECTRONS"] = mobj.group(2) - psivar["N BETA ELECTRONS"] = mobj.group(3) - if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: - # get HOMO and LUMO energy - mobj = re.search( - r"Vector" - + r"\s+" - + r"%d" % (psivar["N ALPHA ELECTRONS"]) - + r"\s+" - + r"Occ=" - + r".*" - + r"\s+" - + r"E=" - + r"([+-]?\s?\d+[.]\d+)" - + r"[D]" - + r"([+-]0\d)", - outtext, - re.MULTILINE, - ) - if mobj: - homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) - psivar["HOMO"] = np.array([round(homo, 10)]) - mobj = re.search( - r"Vector" - + r"\s+" - + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) - + r"\s+" - + r"Occ=" - + r".*" - + r"\s+" - + r"E=" - + r"([+-]?\s?\d+[.]\d+)" - + r"[D]" - + r"([+-]0\d)", - outtext, - re.MULTILINE, - ) - if mobj: - lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) - psivar["LUMO"] = np.array([round(lumo, 10)]) - - mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) - if mobj: - psivar["N MO"] = mobj.group(2) - psivar["N BASIS"] = mobj.group(1) - - # Search for Center of charge - mobj = re.search( - r"Center of charge \(in au\) is the expansion point" - + r"\n" - + r"\s+" - + r"X\s+=\s+([+-]?\d+[.]\d+)" - + r"\s+" - + r"Y\s+=\s+([+-]?\d+[.]\d+)" - + r"\s+" - + r"Z\s+=\s+([+-]?\d+[.]\d+)", - outtext, - re.MULTILINE, - ) - if mobj: - psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - - mobj = re.search( - r"Dipole moment" - + r".*?" - + r"A\.U\." - + r"\s+" - + r"DMX\s+([+-]?\d+[.]\d+)\s+" - + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" - + r"DMY\s+([+-]?\d+[.]\d+)\s+" - + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" - + r"DMZ\s+([+-]?\d+[.]\d+)\s+" - + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" - + r"\-EFC\-" - + r".*?" - + r"A\.U\.\s+" - + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", - outtext, - re.MULTILINE, - ) - # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) - if mobj: - psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - - mobj = re.search( - r"Quadrupole moments in atomic units\s+" - + r"Component\s+" - + r"Electronic\+nuclear\s+" - + r"Point charges\s+" - + r"Total\s+" - + r"-+\s+" - + r"XX\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"ZZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XY\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"XZ\s+([+-]?\d+[.]\d+)\s+" - + r".*\s+.*\s+" - + r"YZ\s+([+-]?\d+[.]\d+)\s+", - outtext, - re.MULTILINE, - ) - - if mobj: - psivar["QUADRUPOLE MOMENT"] = np.array( - [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] - ) - - # Process CURRENT energies (TODO: needs better way) - if "HF TOTAL ENERGY" in psivar: - psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] - psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] - psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] - - if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] - if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] - if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] - - if "DFT TOTAL ENERGY" in psivar: - psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] - psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] - - # Process TCE CURRENT energies - # Need to be fixed - # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? - # TODO: CURRENT ENERGY = TCE ENERGY - if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): - psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] - psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] - - if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] - - if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: - psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] - psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] - - if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): - psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] - psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] - - return psivar, psivar_coord, psivar_grad, version, error - - -def harvest_hessian(hess: str) -> np.ndarray: - """Parses the contents of the NWChem hess file into a hessian array. - - Args: - hess (str): Contents of the hess file - Returns: - (np.ndarray) Hessian matrix as a 2D array - """ - - # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python - hess_conv = hess.replace("D", "E") - - # Parse all of the float values - hess_tri = [float(x) for x in hess_conv.strip().splitlines()] - - # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) - n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix - - # Add the lower diagonal - hess_arr = np.zeros((n, n)) - hess_arr[np.tril_indices(n)] = hess_tri - - # Transpose and then set the lower diagonal again - hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major - hess_arr[np.tril_indices(n)] = hess_tri - - return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines - - -def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: - """Get named properties out of the general variables extracted out of the result file - - Args: - psivars (PreservingDict): Dictionary of the output results - Returns: - (AtomicResultProperties) Properties in a standard format - """ - # TODO (wardlt): Get more of the named variables out of the NWChem file - - # Initialize the output - output = dict() - - # Extract the Calc Info - output.update( - { - "calcinfo_nbasis": psivars.get("N BASIS", None), - "calcinfo_nmo": psivars.get("N MO", None), - "calcinfo_natom": psivars.get("N ATOMS", None), - "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), - "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), - } - ) - - # Get the "canonical" properties - output["return_energy"] = psivars["CURRENT ENERGY"] - output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] - - # Get the SCF properties - output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) - output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) - output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) - output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) - - # Get the MP2 properties - output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) - output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) - output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) - output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) - return AtomicResultProperties(**output) - - -def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: - """Parses all the pieces of output from NWChem: the stdout in - *nwout* Scratch files are not yet considered at this moment. - - Args: - in_mol (Molecule): Input molecule - nwout (str): NWChem output molecule - outfiles (dict): Dictionary of outfile files and their contents - Returns: - - (PreservingDict) Variables extracted from the output file in the last complete step - - (None): Hessian from the last complete step (Not yet implemented) - - (list): Gradient from the last complete step - - (Molecule): Molecule from the last complete step - - (str): Version string - - (str): Error message, if any - """ - - # Parse the NWChem output - out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) - - # If available, read higher-accuracy gradients - # These were output using a Python Task in NWChem to read them out of the database - if outfiles.get("nwchem.grad") is not None: - logger.debug("Reading higher-accuracy gradients") - out_grad = json.loads(outfiles.get("nwchem.grad")) - - # If available, read the hessian - out_hess = None - if outfiles.get("nwchem.hess") is not None: - out_hess = harvest_hessian(outfiles.get("nwchem.hess")) - - # Make sure the input and output molecules are the same - if out_mol: - if in_mol: - if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: - raise ValueError( - """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" - % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) - ) - else: - raise ValueError("""No coordinate information extracted from NWChem output.""") - - # If present, align the gradients and hessian with the original molecular coordinates - # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the - # rotated molecule, which we can use to determine how to rotate the gradients/hessian - amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - - mill = data["mill"] # Retrieve tool with alignment routines - - if out_grad is not None: - out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) - if out_hess is not None: - out_hess = mill.align_hessian(np.array(out_hess)) - - return out_psivar, out_hess, out_grad, out_mol, version, error +# #Process other TCE cases +# for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: +# mobj = re.search( +# r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + +# r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + +# r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug(f'matched {cc_name}') +# logger.debug(mobj) +# psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) +# psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) +# #TCE dipole +# mobj2 = re.search( +# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + +# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', +# outtext, re.MULTILINE) +# if mobj2: +# print(f'matched tce dipole moment') + +# #only pulling Debye +# psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) +# psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) +# psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) + +# # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command + +# mobj = re.search( +# r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + +# r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + +# NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE | re.DOTALL) + +# if mobj: +# logger.debug('matched ccsd') +# psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) +# psivar['CCSD TOTAL ENERGY'] = mobj.group(3) + +# mobj = re.search( +# r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + +# r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + +# r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) + +# if mobj: +# logger.debug('matched ccsd(t)') +# psivar['(T) CORRECTION ENERGY'] = mobj.group(1) +# psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] +# psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) + +# mobj = re.search( +# r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + +# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' +# r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + +# r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' +# r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + +# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) +# # SCS-CCSD included +# if mobj: +# logger.debug('matched scs-ccsd') +# psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( +# Decimal(mobj.group(1)) * Decimal(mobj.group(2))) +# psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( +# Decimal(mobj.group(4)) * Decimal(mobj.group(3))) +# psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) +# psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) +# psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( +# psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) +# # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( +# # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) + +# # Process EOM-[cc_name] #nwchem_tce_dipole = false +# # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree +# # psivar name might need to be fixed +# # each root excitation energy is extracted from the last iteration of right hand side +# mobj = re.findall( +# r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + +# # (..) captures symmetry +# r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root +# r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree +# r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV +# outtext, re.MULTILINE | re.DOTALL) +# #regex should be more dynamic in finding values, need to revisit +# #mobj.group(0) = symmetry value +# #mobj.group(1) = cc_name +# #mobj.group(2) = root number +# #mobj.group(3) = excitation energy (hartree) +# #mobj.group(4) = excitation energy (eV) + +# if mobj: +# print(mobj) +# ext_energy = {} # dic + +# ext_energy_list = [] +# print(f'matched eom-{cc_name}') +# for mobj_list in mobj: +# logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry +# logger.debug(mobj_list) +# count = 0 +# for line in mobj_list[1].splitlines(): +# lline = line.split() +# logger.debug(lline[1]) # in hartree +# logger.debug(lline[2]) # in eV +# count += 1 + +# logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) + +# ext_energy_list.append(lline[1]) # Collect all excitation energies + +# sym = str(mobj_list[0]) +# ext_energy.setdefault(sym, []) +# ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) + +# ext_energy_list.sort(key=float) + +# for nroot in range(len(ext_energy_list)): +# for k, e_val in ext_energy.items(): +# if ext_energy_list[nroot] in e_val: +# symm = k +# psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ +# ext_energy_list[nroot] # in hartree +# psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ +# psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree +# gssym = '' +# gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) + +# if gs: +# logger.debug('matched ground-state symmetry') +# psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) + +# # Process TDDFT +# # 1) Spin allowed +# mobj = re.findall( +# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' +# #Root | symmetry | a.u. | eV +# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value +# + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole +# + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople +# + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople +# + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched TDDFT with transition moments') +# for mobj_list in mobj: +# print (mobj_list) +# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV +# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) +# #### temporary psivars #### +# #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % +# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. +# #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ +# # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) +# psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] +# psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] +# psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] +# psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] +# psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] +# psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] +# psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] +# psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] +# psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] + + +# # 2) Spin forbidden +# mobj = re.findall( +# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' +# #Root | symmetry | a.u. | eV +# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value +# + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', +# outtext, re.MULTILINE) +# #mobj.group(0) = Root +# #mobj.group(1) = symmetry +# #mobj.group(2) a.u. +# #mobj.group(3) e.V +# #mobj.group(4) Excitation energy +# #mobj.group(5) Excited state energy + +# if mobj: +# logger.debug('matched TDDFT - spin forbidden') +# for mobj_list in mobj: +# #### temporary psivars #### +# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV +# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) + +# #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % +# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. +# #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ +# # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) +# if mobj: +# logger.debug('Non-variation initial energy') # prints out energy, 5 counts + +# # Process geometry +# # 1) CHARGE +# # Read charge from SCF module +# mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched charge') +# out_charge = int(float(mobj.group(1))) + +# # Read charge from General information (not scf module) +# mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched charge') +# out_charge = int(float(mobj.group(1))) + +# # 2) MULTIPLICITY +# # Read multiplicity from SCF module +# mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity') +# out_mult = int(mobj.group(1)) + 1 + +# # Read multiplicity from SCF module through alpha, beta electrons +# mobj = re.search( +# r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + +# r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity via alpha and beta electrons') +# out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 +# psivar['N ALPHA ELECTRONS'] = mobj.group(1) +# psivar['N BETA ELECTRONS'] = mobj.group(2) + +# # Read multiplicity from General information (not scf module) +# mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, +# re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched multiplicity') +# out_mult = int(mobj.group(1)) + +# # 3) Initial geometry +# mobj = re.search( +# r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + +# r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + +# r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + +# r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + +# r'\s*' + +# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' +# + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) + +# if mobj: +# logger.debug('matched geom') + +# # dinky molecule w/ charge and multiplicity +# if mobj.group(1) == 'angstroms': +# molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult +# ) # unit = angstrom +# for line in mobj.group(2).splitlines(): +# lline = line.split() +# molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) +# # Jiyoung was collecting charge (-4)? see if this is ok for ghosts +# # Tag , X, Y, Z +# psivar_coord = Molecule(validate=False, +# **qcel.molparse.to_schema(qcel.molparse.from_string( +# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], +# dtype=2)) + +# else: # unit = a.u. +# molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) +# for line in mobj.group(2).splitlines(): +# lline = line.split() +# molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) +# # Tag , X, Y, Z +# psivar_coord = Molecule(validate=False, +# **qcel.molparse.to_schema(qcel.molparse.from_string( +# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], +# dtype=2)) + +# # Process gradient +# mobj = re.search( +# r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + +# r'atom coordinates gradient' + r'\s*' + r'^\s+' + +# r'x y z x y z' + r'\s*' + +# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' +# + r'\s*$', outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched molgrad') +# atoms = [] +# psivar_grad = [] +# for line in mobj.group(1).splitlines(): +# lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z +# # print (lline) +# if lline == []: +# pass +# else: +# atoms.append(lline[1]) # Tag +# psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) + +# # Process dipole (Properties) +# mobj = re.search( +# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + +# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'.*' + +# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + +# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + +# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + +# r'^\s+' + r'.*' + +# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', +# outtext, re.MULTILINE) + +# if mobj: +# logger.debug('matched total dipole') + +# # UNIT = DEBYE(S) +# psivar['CURRENT DIPOLE X'] = mobj.group(7) +# psivar['CURRENT DIPOLE Y'] = mobj.group(8) +# psivar['CURRENT DIPOLE Z'] = mobj.group(9) +# # total? + +# # Process error code +# mobj = re.search( +# r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + +# r'\s*' + r'^\s+' +# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' +# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + +# r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) +# if mobj: +# logger.debug('matched error') +# # print (mobj.group(1)) #error line number +# # print (mobj.group(2)) #error reason +# psivar['NWCHEM ERROR CODE'] = mobj.group(1) +# # TODO process errors into error var + +# # fmt: on + +# # Get the size of the basis sets, etc +# mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["N ATOMS"] = mobj.group(1) +# mobj = re.search( +# r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# psivar["N ALPHA ELECTRONS"] = mobj.group(2) +# psivar["N BETA ELECTRONS"] = mobj.group(3) +# if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: +# # get HOMO and LUMO energy +# mobj = re.search( +# r"Vector" +# + r"\s+" +# + r"%d" % (psivar["N ALPHA ELECTRONS"]) +# + r"\s+" +# + r"Occ=" +# + r".*" +# + r"\s+" +# + r"E=" +# + r"([+-]?\s?\d+[.]\d+)" +# + r"[D]" +# + r"([+-]0\d)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) +# psivar["HOMO"] = np.array([round(homo, 10)]) +# mobj = re.search( +# r"Vector" +# + r"\s+" +# + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) +# + r"\s+" +# + r"Occ=" +# + r".*" +# + r"\s+" +# + r"E=" +# + r"([+-]?\s?\d+[.]\d+)" +# + r"[D]" +# + r"([+-]0\d)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) +# psivar["LUMO"] = np.array([round(lumo, 10)]) + +# mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["N MO"] = mobj.group(2) +# psivar["N BASIS"] = mobj.group(1) + +# # Search for Center of charge +# mobj = re.search( +# r"Center of charge \(in au\) is the expansion point" +# + r"\n" +# + r"\s+" +# + r"X\s+=\s+([+-]?\d+[.]\d+)" +# + r"\s+" +# + r"Y\s+=\s+([+-]?\d+[.]\d+)" +# + r"\s+" +# + r"Z\s+=\s+([+-]?\d+[.]\d+)", +# outtext, +# re.MULTILINE, +# ) +# if mobj: +# psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) + +# mobj = re.search( +# r"Dipole moment" +# + r".*?" +# + r"A\.U\." +# + r"\s+" +# + r"DMX\s+([+-]?\d+[.]\d+)\s+" +# + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" +# + r"DMY\s+([+-]?\d+[.]\d+)\s+" +# + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" +# + r"DMZ\s+([+-]?\d+[.]\d+)\s+" +# + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" +# + r"\-EFC\-" +# + r".*?" +# + r"A\.U\.\s+" +# + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", +# outtext, +# re.MULTILINE, +# ) +# # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) +# if mobj: +# psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) +# psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) + +# mobj = re.search( +# r"Quadrupole moments in atomic units\s+" +# + r"Component\s+" +# + r"Electronic\+nuclear\s+" +# + r"Point charges\s+" +# + r"Total\s+" +# + r"-+\s+" +# + r"XX\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"YY\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"ZZ\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"XY\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"XZ\s+([+-]?\d+[.]\d+)\s+" +# + r".*\s+.*\s+" +# + r"YZ\s+([+-]?\d+[.]\d+)\s+", +# outtext, +# re.MULTILINE, +# ) + +# if mobj: +# psivar["QUADRUPOLE MOMENT"] = np.array( +# [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] +# ) + +# # Process CURRENT energies (TODO: needs better way) +# if "HF TOTAL ENERGY" in psivar: +# psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] +# psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] + +# if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] +# if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] +# if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] + +# if "DFT TOTAL ENERGY" in psivar: +# psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] + +# # Process TCE CURRENT energies +# # Need to be fixed +# # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? +# # TODO: CURRENT ENERGY = TCE ENERGY +# if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): +# psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] +# psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] + +# if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] + +# if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: +# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] +# psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + +# if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): +# psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] +# psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + +# return psivar, psivar_coord, psivar_grad, version, error + + +# def harvest_hessian(hess: str) -> np.ndarray: +# """Parses the contents of the NWChem hess file into a hessian array. + +# Args: +# hess (str): Contents of the hess file +# Returns: +# (np.ndarray) Hessian matrix as a 2D array +# """ + +# # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python +# hess_conv = hess.replace("D", "E") + +# # Parse all of the float values +# hess_tri = [float(x) for x in hess_conv.strip().splitlines()] + +# # The value in the Hessian matrix is the lower triangle printed row-wise (e.g., 0,0 -> 1,0 -> 1,1 -> ...) +# n = int(np.sqrt(8 * len(hess_tri) + 1) - 1) // 2 # Size of the 2D matrix + +# # Add the lower diagonal +# hess_arr = np.zeros((n, n)) +# hess_arr[np.tril_indices(n)] = hess_tri + +# # Transpose and then set the lower diagonal again +# hess_arr = np.transpose(hess_arr) # Numpy implementations might only change the ordering to column-major +# hess_arr[np.tril_indices(n)] = hess_tri + +# return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines + + +# def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: +# """Get named properties out of the general variables extracted out of the result file + +# Args: +# psivars (PreservingDict): Dictionary of the output results +# Returns: +# (AtomicResultProperties) Properties in a standard format +# """ +# # TODO (wardlt): Get more of the named variables out of the NWChem file + +# # Initialize the output +# output = dict() + +# # Extract the Calc Info +# output.update( +# { +# "calcinfo_nbasis": psivars.get("N BASIS", None), +# "calcinfo_nmo": psivars.get("N MO", None), +# "calcinfo_natom": psivars.get("N ATOMS", None), +# "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), +# "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), +# } +# ) + +# # Get the "canonical" properties +# output["return_energy"] = psivars["CURRENT ENERGY"] +# output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + +# # Get the SCF properties +# output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) +# output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) +# output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) +# output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + +# # Get the MP2 properties +# output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) +# output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) +# output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) +# output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) +# return AtomicResultProperties(**output) + + +# def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: +# """Parses all the pieces of output from NWChem: the stdout in +# *nwout* Scratch files are not yet considered at this moment. + +# Args: +# in_mol (Molecule): Input molecule +# nwout (str): NWChem output molecule +# outfiles (dict): Dictionary of outfile files and their contents +# Returns: +# - (PreservingDict) Variables extracted from the output file in the last complete step +# - (None): Hessian from the last complete step (Not yet implemented) +# - (list): Gradient from the last complete step +# - (Molecule): Molecule from the last complete step +# - (str): Version string +# - (str): Error message, if any +# """ + +# # Parse the NWChem output +# out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) + +# # If available, read higher-accuracy gradients +# # These were output using a Python Task in NWChem to read them out of the database +# if outfiles.get("nwchem.grad") is not None: +# logger.debug("Reading higher-accuracy gradients") +# out_grad = json.loads(outfiles.get("nwchem.grad")) + +# # If available, read the hessian +# out_hess = None +# if outfiles.get("nwchem.hess") is not None: +# out_hess = harvest_hessian(outfiles.get("nwchem.hess")) + +# # Make sure the input and output molecules are the same +# if out_mol: +# if in_mol: +# if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: +# raise ValueError( +# """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" +# % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) +# ) +# else: +# raise ValueError("""No coordinate information extracted from NWChem output.""") + +# # If present, align the gradients and hessian with the original molecular coordinates +# # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the +# # rotated molecule, which we can use to determine how to rotate the gradients/hessian +# amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + +# mill = data["mill"] # Retrieve tool with alignment routines + +# if out_grad is not None: +# out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) +# if out_hess is not None: +# out_hess = mill.align_hessian(np.array(out_hess)) + +# return out_psivar, out_hess, out_grad, out_mol, version, error diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index 1821e98c5..ec5596a5a 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,86 +2,86 @@ from typing import Any, Dict, Tuple -def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: - """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" +# def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: +# """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" - # Transform string booleans into " " - if val is True: - return keyword.lower(), "" - elif val is False: - return "", "" +# # Transform string booleans into " " +# if val is True: +# return keyword.lower(), "" +# elif val is False: +# return "", "" - # complete hack - if keyword.upper() == "MEMORY": - return keyword.lower(), f"{val} byte" +# # complete hack +# if keyword.upper() == "MEMORY": +# return keyword.lower(), f"{val} byte" - elif isinstance(val, list): - text = " ".join([str(v) for v in val]) - elif isinstance(val, dict): - text = [] - for k, v in val.items(): - merge = [k] - merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) - text.append(" ".join(merge)) - text = " ".join(text) - else: - text = str(val) +# elif isinstance(val, list): +# text = " ".join([str(v) for v in val]) +# elif isinstance(val, dict): +# text = [] +# for k, v in val.items(): +# merge = [k] +# merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) +# text.append(" ".join(merge)) +# text = " ".join(text) +# else: +# text = str(val) - if lop_off: - return keyword[7:].lower(), text - else: - return keyword.lower(), text +# if lop_off: +# return keyword[7:].lower(), text +# else: +# return keyword.lower(), text -def format_keywords(keywords: Dict[str, Any]) -> str: - """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" +# def format_keywords(keywords: Dict[str, Any]) -> str: +# """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" - def rec_dd(): - return collections.defaultdict(rec_dd) +# def rec_dd(): +# return collections.defaultdict(rec_dd) - grouped_options = rec_dd() +# grouped_options = rec_dd() - for group_key, val in keywords.items(): - nesting = group_key.split("__") - if len(nesting) == 1: - key = nesting[0] - grouped_options["aaaglobal"][key] = val - elif len(nesting) == 2: - g1, key = nesting - grouped_options[g1][key] = val - elif len(nesting) == 3: - g1, g2, key = nesting - grouped_options[g1][g2][key] = val - else: - print(nesting) - raise ValueError("Nesting N!") +# for group_key, val in keywords.items(): +# nesting = group_key.split("__") +# if len(nesting) == 1: +# key = nesting[0] +# grouped_options["aaaglobal"][key] = val +# elif len(nesting) == 2: +# g1, key = nesting +# grouped_options[g1][key] = val +# elif len(nesting) == 3: +# g1, g2, key = nesting +# grouped_options[g1][g2][key] = val +# else: +# print(nesting) +# raise ValueError("Nesting N!") - grouped_lines = {} - for group, opts in sorted(grouped_options.items()): - lines = [] - group_level_lines = [] - for key, val in grouped_options[group].items(): - if isinstance(val, dict): - g2_level_lines = [] - g2_level_lines.append(key.lower()) - for k2, v2 in val.items(): - line2 = " ".join(format_keyword(k2, v2, lop_off=False)) - g2_level_lines.append(line2) - g2_level_lines = " ".join(g2_level_lines) - lines.append(g2_level_lines) - else: - line = " ".join(format_keyword(key, val, lop_off=False)) - if group.lower() == "basis" and any( - [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] - ): - group_level_lines.append(line) - else: - lines.append(line) - if group == "aaaglobal": - grouped_lines[group] = "\n".join(lines) + "\n" - else: - grouped_lines[group] = ( - f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" - ) +# grouped_lines = {} +# for group, opts in sorted(grouped_options.items()): +# lines = [] +# group_level_lines = [] +# for key, val in grouped_options[group].items(): +# if isinstance(val, dict): +# g2_level_lines = [] +# g2_level_lines.append(key.lower()) +# for k2, v2 in val.items(): +# line2 = " ".join(format_keyword(k2, v2, lop_off=False)) +# g2_level_lines.append(line2) +# g2_level_lines = " ".join(g2_level_lines) +# lines.append(g2_level_lines) +# else: +# line = " ".join(format_keyword(key, val, lop_off=False)) +# if group.lower() == "basis" and any( +# [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] +# ): +# group_level_lines.append(line) +# else: +# lines.append(line) +# if group == "aaaglobal": +# grouped_lines[group] = "\n".join(lines) + "\n" +# else: +# grouped_lines[group] = ( +# f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" +# ) - return "\n".join(grouped_lines.values()) + "\n" +# return "\n".join(grouped_lines.values()) + "\n" From bfa4c5555828602cb959a52d3f8ad5f04a13bde3 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 23 Apr 2020 15:31:14 -0400 Subject: [PATCH 033/102] changin base and test to add madness --- qcengine/programs/base.py | 1 + qcengine/testing.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index 1a49d6db2..282e08141 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -13,6 +13,7 @@ from .mopac import MopacHarness from .mp2d import MP2DHarness from .nwchem import NWChemHarness +from .madness import MadnessHarness from .openmm import OpenMMHarness from .psi4 import Psi4Harness from .qchem import QChemHarness diff --git a/qcengine/testing.py b/qcengine/testing.py index 83a599488..4be81b454 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -153,6 +153,7 @@ def get_job(self): "mopac": is_program_new_enough("mopac", "2016"), "mp2d": which("mp2d", return_bool=True), "nwchem": which("nwchem", return_bool=True), + "madness": which("nwchem", return_bool=True), "psi4": is_program_new_enough("psi4", "1.2"), "psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"), "qcdb": which_import("qcdb", return_bool=True), From 4c306e82065db00e2970f8c8cdb71864323a3e42 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 16:54:54 -0400 Subject: [PATCH 034/102] Add madnessHarness to qca --- qcengine/programs/base.py | 1 + qcengine/testing.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index 282e08141..d98b912bb 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -102,6 +102,7 @@ def list_available_programs() -> Set[str]: register_program(GAMESSHarness()) register_program(MolproHarness()) register_program(NWChemHarness()) +register_program(MadnessHarness()) register_program(Psi4Harness()) register_program(QChemHarness()) register_program(TeraChemHarness()) diff --git a/qcengine/testing.py b/qcengine/testing.py index 4be81b454..96f8e4fa7 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -153,7 +153,7 @@ def get_job(self): "mopac": is_program_new_enough("mopac", "2016"), "mp2d": which("mp2d", return_bool=True), "nwchem": which("nwchem", return_bool=True), - "madness": which("nwchem", return_bool=True), + "madness": which("madness", return_bool=True), "psi4": is_program_new_enough("psi4", "1.2"), "psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"), "qcdb": which_import("qcdb", return_bool=True), From 739c5b551e063db63984c1da51a3818aea023578 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 18:17:18 -0400 Subject: [PATCH 035/102] first attempt at get_version --- qcengine/programs/madness/runner.py | 171 ++++++++++++++-------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index e86d655c9..348220446 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -19,9 +19,9 @@ from ...exceptions import InputError from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -from .germinate import muster_modelchem -from .harvester import extract_formatted_properties, harvest -from .keywords import format_keywords +#from .germinate import muster_modelchem +#from .harvester import extract_formatted_properties, harvest +#from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -29,90 +29,87 @@ class MadnessHarness(ProgramHarness): """ - -# Notes -# ----- -# * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. - -# """ - -# _defaults = { -# "name": "NWChem", -# "scratch": True, -# "thread_safe": False, -# "thread_parallel": False, -# "node_parallel": True, -# "managed_memory": True, -# } -# # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers -# version_cache: Dict[str, str] = {} - -# class Config(ProgramHarness.Config): -# pass - -# @staticmethod -# def found(raise_error: bool = False) -> bool: -# """Whether NWChem harness is ready for operation, with both the QC program and any particular dependencies found. - -# Parameters -# ---------- -# raise_error: bool -# Passed on to control negative return between False and ModuleNotFoundError raised. - -# Returns -# ------- -# bool -# If both nwchem and its harness dependency networkx are found, returns True. -# If raise_error is False and nwchem or networkx are missing, returns False. -# If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. - -# """ -# qc = which( -# "nwchem", -# return_bool=True, -# raise_error=raise_error, -# raise_msg="Please install via http://www.nwchem-sw.org/index.php/Download", -# ) - -# dep = which_import( -# "networkx", -# return_bool=True, -# raise_error=raise_error, -# raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", -# ) - -# return qc and dep - -# def get_version(self) -> str: -# self.found(raise_error=True) - -# # Get the node configuration -# config = get_config() - -# # Run NWChem -# which_prog = which("nwchem") -# if config.use_mpiexec: -# command = create_mpi_invocation(which_prog, config) -# else: -# command = [which_prog] -# command.append("v.nw") - -# if which_prog not in self.version_cache: -# success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) - -# if success: -# for line in output["stdout"].splitlines(): -# if "nwchem branch" in line: -# branch = line.strip().split()[-1] -# if "nwchem revision" in line: -# revision = line.strip().split()[-1] -# self.version_cache[which_prog] = safe_version(branch + "+" + revision) -# else: -# raise UnknownError(output["stderr"]) - -# return self.version_cache[which_prog] - -# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + Notes + ----- + * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. + """ + + _defaults = { + "name": "Madness", + "scratch": True, + "thread_safe": True, + "thread_parallel": True, + "node_parallel": True, + "managed_memory": True, + } + # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers + version_cache: Dict[str, str] = {} + + class Config(ProgramHarness.Config): + pass + + @staticmethod + def found(raise_error: bool = False) -> bool: + """Whether Madness harness is ready for operation, with both the QC program and any particular dependencies found. + + Parameters + ---------- + raise_error: bool + Passed on to control negative return between False and ModuleNotFoundError raised. + + Returns + ------- + bool + If both nwchem and its harness dependency networkx are found, returns True. + If raise_error is False and nwchem or networkx are missing, returns False. + If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + + """ + qc = which( + "nwchem", + return_bool=True, + raise_error=raise_error, + raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", + ) + # dep = which_import( + # "networkx", + # return_bool=True, + # raise_error=raise_error, + # raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", + # ) + return qc # and dep + + + def get_version(self) -> str: + self.found(raise_error=True) + + # Get the node configuration + config = get_config() + + # Run MADNESS + which_prog = which("madness") + if config.use_mpiexec: + command = create_mpi_invocation(which_prog, config) + else: + command = [which_prog] + command.append("v.nw") + + if which_prog not in self.version_cache: + success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) + + if success: + for line in output["stdout"].splitlines(): + if "nwchem branch" in line: + branch = line.strip().split()[-1] + if "nwchem revision" in line: + revision = line.strip().split()[-1] + self.version_cache[which_prog] = safe_version(branch + "+" + revision) + else: + raise UnknownError(output["stderr"]) + + return self.version_cache[which_prog] + + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": # """ # Runs NWChem in executable mode # """ From e2d83beaadd537a72c2bd9ba1c0e943fba4a573b Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Thu, 23 Apr 2020 20:50:37 -0400 Subject: [PATCH 036/102] Got a version of get_version that may work --- qcengine/programs/madness/runner.py | 212 ++++++++++++++-------------- 1 file changed, 109 insertions(+), 103 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 348220446..46a15b806 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -19,9 +19,10 @@ from ...exceptions import InputError from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -#from .germinate import muster_modelchem -#from .harvester import extract_formatted_properties, harvest -#from .keywords import format_keywords + +# from .germinate import muster_modelchem +# from .harvester import extract_formatted_properties, harvest +# from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -79,61 +80,66 @@ def found(raise_error: bool = False) -> bool: # ) return qc # and dep - + ## gotta figure out which input file and from where def get_version(self) -> str: - self.found(raise_error=True) - - # Get the node configuration - config = get_config() - - # Run MADNESS - which_prog = which("madness") - if config.use_mpiexec: - command = create_mpi_invocation(which_prog, config) - else: - command = [which_prog] - command.append("v.nw") - - if which_prog not in self.version_cache: - success, output = execute(command, {"v.nw": ""}, scratch_directory=config.scratch_directory) - - if success: - for line in output["stdout"].splitlines(): - if "nwchem branch" in line: - branch = line.strip().split()[-1] - if "nwchem revision" in line: - revision = line.strip().split()[-1] - self.version_cache[which_prog] = safe_version(branch + "+" + revision) - else: - raise UnknownError(output["stderr"]) - - return self.version_cache[which_prog] - - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": -# """ -# Runs NWChem in executable mode -# """ -# self.found(raise_error=True) - -# job_inputs = self.build_input(input_model, config) -# success, dexe = self.execute(job_inputs) - -# if "There is an error in the input file" in dexe["stdout"]: -# raise InputError(dexe["stdout"]) -# if "not compiled" in dexe["stdout"]: -# # recoverable with a different compilation with optional modules -# raise InputError(dexe["stdout"]) - -# if success: -# dexe["outfiles"]["stdout"] = dexe["stdout"] -# dexe["outfiles"]["stderr"] = dexe["stderr"] -# return self.parse_output(dexe["outfiles"], input_model) -# else: -# raise UnknownError(dexe["stderr"]) + self.found(raise_error=True) + + # Get the node configuration + config = get_config() + + # Run MADNESS + which_prog = which("madness") + if config.use_mpiexec: + command = create_mpi_invocation(which_prog, config) + else: + command = [which_prog] + command.append("v.moldft") + + if which_prog not in self.version_cache: + success, output = execute( + command, + { + "v.moldft": "dft\nxc hf\nend\n\ngeometry\n H 0.00000000 0.00000000 -0.36579425\n H 0.00000000 0.00000000 0.36579425\nend\n" + }, + scratch_directory=config.scratch_directory, + ) + + if success: + for line in output["stdout"].splitlines(): + if "multiresolution suite" in line: + version = line.strip().split()[1] + self.version_cache[which_prog] = safe_version(version) + else: + raise UnknownError(output["stderr"]) + + return self.version_cache[which_prog] + + +# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": +# """ +# Runs madness in executable mode +# """ +# self.found(raise_error=True) + +# job_inputs = self.build_input(input_model, config) +# success, dexe = self.execute(job_inputs) + +# if "There is an error in the input file" in dexe["stdout"]: +# raise InputError(dexe["stdout"]) +# if "not compiled" in dexe["stdout"]: +# # recoverable with a different compilation with optional modules +# raise InputError(dexe["stdout"]) + +# if success: +# dexe["outfiles"]["stdout"] = dexe["stdout"] +# dexe["outfiles"]["stderr"] = dexe["stderr"] +# return self.parse_output(dexe["outfiles"], input_model) +# else: +# raise UnknownError(dexe["stderr"]) # def build_input( # self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None -# ) -> Dict[str, Any]: +# ) -> Dict[str, Any]: # nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} # opts = copy.deepcopy(input_model.keywords) @@ -147,60 +153,60 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe # memory_size //= config.nnodes * config.ncores // config.cores_per_rank # opts["memory"] = memory_size -# # Handle molecule -# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) -# opts.update(moldata["keywords"]) +# # Handle molecule +# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) +# opts.update(moldata["keywords"]) -# # Handle calc type and quantum chemical method -# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) -# opts.update(mdcopts) +# # Handle calc type and quantum chemical method +# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) +# opts.update(mdcopts) -# # Handle basis set -# # * for nwchem, still needs sph and ghost -# for el in set(input_model.molecule.symbols): -# opts[f"basis__{el}"] = f"library {input_model.model.basis}" +# # Handle basis set +# # * for nwchem, still needs sph and ghost +# for el in set(input_model.molecule.symbols): +# opts[f"basis__{el}"] = f"library {input_model.model.basis}" -# # Log the job settings -# logger.debug("JOB_OPTS") -# logger.debug(pp.pformat(opts)) +# # Log the job settings +# logger.debug("JOB_OPTS") +# logger.debug(pp.pformat(opts)) # # Handle conversion from schema (flat key/value) keywords into local format -# optcmd = format_keywords(opts) - -# # Combine the molecule description, options and method command together -# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd - -# # For gradient methods, add a Python command to save the gradients in higher precision -# # Note: The Hessian is already stored in high precision in a file named "*.hess" -# if input_model.driver == "gradient": -# # Get the name of the theory used for computing the gradients -# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) -# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") - -# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) -# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ -# # (not 6 significant figures) -# pycmd = f""" -# python -# grad = rtdb_get('{theory}:gradient') -# if ga_nodeid() == 0: -# import json -# with open('nwchem.grad', 'w') as fp: -# json.dump(grad, fp) -# end - -# task python -# """ -# nwchemrec["infiles"]["nwchem.nw"] += pycmd - -# # Determine the command -# if config.use_mpiexec: -# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) -# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") -# else: -# nwchemrec["command"] = [which("nwchem")] - -# return nwchemrec +# optcmd = format_keywords(opts) + +# # Combine the molecule description, options and method command together +# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd + +# # For gradient methods, add a Python command to save the gradients in higher precision +# # Note: The Hessian is already stored in high precision in a file named "*.hess" +# if input_model.driver == "gradient": +# # Get the name of the theory used for computing the gradients +# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) +# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") + +# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) +# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ +# # (not 6 significant figures) +# pycmd = f""" +# python +# grad = rtdb_get('{theory}:gradient') +# if ga_nodeid() == 0: +# import json +# with open('nwchem.grad', 'w') as fp: +# json.dump(grad, fp) +# end + +# task python +# # """ +# nwchemrec["infiles"]["nwchem.nw"] += pycmd + +# # Determine the command +# if config.use_mpiexec: +# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) +# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") +# else: +# nwchemrec["command"] = [which("nwchem")] + +# return nwchemrec # def execute( # self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None From 9d4c5f01e7e07892a177ca8cd9b8728c2020996d Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 18:27:03 -0400 Subject: [PATCH 037/102] simple build_input --- qcengine/programs/madness/runner.py | 129 +++++++++------------------- 1 file changed, 40 insertions(+), 89 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 46a15b806..e18e53a79 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -115,7 +115,7 @@ def get_version(self) -> str: return self.version_cache[which_prog] -# def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": pass # """ # Runs madness in executable mode # """ @@ -137,94 +137,45 @@ def get_version(self) -> str: # else: # raise UnknownError(dexe["stderr"]) -# def build_input( -# self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None -# ) -> Dict[str, Any]: -# nwchemrec = {"infiles": {}, "scratch_directory": config.scratch_directory} - -# opts = copy.deepcopy(input_model.keywords) -# opts = {k.lower(): v for k, v in opts.items()} - -# # Handle memory -# # for nwchem, [GiB] --> [B] -# # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' -# memory_size = int(config.memory * (1024 ** 3)) -# if config.use_mpiexec: # It is the memory per MPI rank -# memory_size //= config.nnodes * config.ncores // config.cores_per_rank -# opts["memory"] = memory_size - -# # Handle molecule -# molcmd, moldata = input_model.molecule.to_string(dtype="nwchem", units="Bohr", return_data=True) -# opts.update(moldata["keywords"]) - -# # Handle calc type and quantum chemical method -# mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) -# opts.update(mdcopts) - -# # Handle basis set -# # * for nwchem, still needs sph and ghost -# for el in set(input_model.molecule.symbols): -# opts[f"basis__{el}"] = f"library {input_model.model.basis}" - -# # Log the job settings -# logger.debug("JOB_OPTS") -# logger.debug(pp.pformat(opts)) - -# # Handle conversion from schema (flat key/value) keywords into local format -# optcmd = format_keywords(opts) - -# # Combine the molecule description, options and method command together -# nwchemrec["infiles"]["nwchem.nw"] = "echo\n" + molcmd + optcmd + mdccmd - -# # For gradient methods, add a Python command to save the gradients in higher precision -# # Note: The Hessian is already stored in high precision in a file named "*.hess" -# if input_model.driver == "gradient": -# # Get the name of the theory used for computing the gradients -# theory = re.search("^task (\w+) ", mdccmd, re.MULTILINE).group(1) -# logger.debug(f"Adding a Python task to retrieve gradients. Theory: {theory}") - -# # Create a Python function to get the gradient from NWChem's checkpoint file (rtdb) -# # and save it to a JSON file. The stdout for NWChem only prints 6 _decimal places_ -# # (not 6 significant figures) -# pycmd = f""" -# python -# grad = rtdb_get('{theory}:gradient') -# if ga_nodeid() == 0: -# import json -# with open('nwchem.grad', 'w') as fp: -# json.dump(grad, fp) -# end - -# task python -# # """ -# nwchemrec["infiles"]["nwchem.nw"] += pycmd - -# # Determine the command -# if config.use_mpiexec: -# nwchemrec["command"] = create_mpi_invocation(which("nwchem"), config) -# logger.info(f"Launching with mpiexec: {' '.join(nwchemrec['command'])}") -# else: -# nwchemrec["command"] = [which("nwchem")] - -# return nwchemrec - -# def execute( -# self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None -# ) -> Tuple[bool, Dict]: - -# success, dexe = execute( -# inputs["command"], -# inputs["infiles"], -# ["nwchem.hess", "nwchem.grad"], -# scratch_messy=False, -# scratch_exist_ok=True, -# scratch_directory=inputs["scratch_directory"], -# ) -# return success, dexe - -# def parse_output( -# self, outfiles: Dict[str, str], input_model: "AtomicInput" -# ) -> "AtomicResult": # lgtm: [py/similar-function] + def build_input( + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + ) -> Dict[str, Any]: + # + madnessrec={"infiles":{},"scratch_directory":config.scratch_directory} + + opts=copy.deepcopy(input_model.keywords) + opts = {k.lower(): v for k, v in opts.items()} + + + molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) + + opts.update(moldata["keywords"]) + optcmd="dft\n xc hf \nend\n" + madnessrec["infiles"]["mad_input"]=optcmd+molcmd + ## Determine the command + # Determine the command + madnessrec["command"] = [which("madness")] + + return madnessrec + + + def execute( + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + ) -> Tuple[bool, Dict]: + + success, dexe = execute( + inputs["command"], + inputs["infiles"], + ["nwchem.hess", "nwchem.grad"], + scratch_messy=False, + scratch_exist_ok=True, + scratch_directory=inputs["scratch_directory"], + ) + return success, dexe + + def parse_output( + self, outfiles: Dict[str, str], input_model: "AtomicInput" + ) -> "AtomicResult": pass # lgtm: [py/similar-function] # # Get the stdout from the calculation (required) # stdout = outfiles.pop("stdout") From 103c031de918fe14eb4d1f649cdd76e3f09d1c5c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 20:37:41 -0400 Subject: [PATCH 038/102] simple execuate function --- qcengine/programs/madness/runner.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index e18e53a79..3ecf20128 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -151,7 +151,7 @@ def build_input( opts.update(moldata["keywords"]) optcmd="dft\n xc hf \nend\n" - madnessrec["infiles"]["mad_input"]=optcmd+molcmd + madnessrec["infiles"]["input"]=optcmd+molcmd ## Determine the command # Determine the command madnessrec["command"] = [which("madness")] @@ -160,16 +160,11 @@ def build_input( def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None - ) -> Tuple[bool, Dict]: - + self,inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + ) -> Tuple[bool, Dict]: success, dexe = execute( inputs["command"], inputs["infiles"], - ["nwchem.hess", "nwchem.grad"], - scratch_messy=False, - scratch_exist_ok=True, - scratch_directory=inputs["scratch_directory"], ) return success, dexe From e9d8f3cc4bd7560e54b24734bbb516edf57cd321 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Fri, 24 Apr 2020 21:24:23 -0400 Subject: [PATCH 039/102] add madness pytest dft w/ xc hf --- .../programs/tests/test_standard_suite_hf.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e1aeeda49..b5428952e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -155,3 +155,25 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) + +@pytest.mark.parametrize( + "program,keywords", + [ + pytest.param("madness", {"dft__xc__hf": True } ), + ], +) +def test_mad_hf(program, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "dft", "keywords": keywords}} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + + assert res["driver"] == "energy" + assert "provenance" in res + assert res["success"] is True + + # aug-cc-pvdz + scf_tot = -76.06718642 + + atol = 1.0e-6 + assert compare_values(scf_tot, res["return_result"], atol=atol) + From 1373f7179f7952d4564801c4d41072e4c56dc68c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 26 Apr 2020 19:52:29 -0400 Subject: [PATCH 040/102] updating --- qcengine/programs/madness/germinate.py | 313 ++++++++++--------------- qcengine/programs/madness/keywords.py | 146 ++++++------ qcengine/programs/madness/runner.py | 29 ++- 3 files changed, 228 insertions(+), 260 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 88fb672e8..fce6b2cca 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -2,188 +2,131 @@ from qcengine.exceptions import InputError -# # List of XC functionals known to NWChem -# _xc_functionals = [ -# "acm", -# "b3lyp", -# "beckehandh", -# "pbe0", -# "becke97", -# "becke97-1", -# "becke97-2", -# "becke97-3", -# "becke97-d", -# "becke98", -# "hcth", -# "hcth120", -# "hcth147", -# "hcth407", -# "becke97gga1", -# "hcth407p", -# "mpw91", -# "mpw1k", -# "xft97", -# "cft97", -# "ft97", -# "op", -# "bop", -# "pbeop", -# "xpkzb99", -# "cpkzb99", -# "xtpss03", -# "ctpss03", -# "xctpssh", -# "b1b95", -# "bb1k", -# "mpw1b95", -# "mpwb1k", -# "pw6b95", -# "pwb6k", -# "m05", -# "m05-2x", -# "vs98", -# "m06", -# "m06-hf", -# "m06-L", -# "m06-2x", -# "HFexch", -# "becke88", -# "xperdew91", -# "xpbe96", -# "gill96", -# "lyp", -# "perdew81", -# "perdew86", -# "perdew91", -# "cpbe96", -# "pw91lda", -# "slater", -# "vwn_1", -# "vwn_2", -# "vwn_3", -# "vwn_4", -# "vwn_5", -# "vwn_1_rpa", -# "xtpss03", -# "ctpss03", -# "bc95", -# "xpw6b95", -# "xpwb6k", -# "xm05", -# "xm05-2x", -# "cpw6b95", -# "cpwb6k", -# "cm05", -# "cm05-2x", -# "xvs98", -# "cvs98", -# "xm06-L", -# "xm06-hf", -# "xm06", -# "xm06-2x", -# "cm06-L", -# "cm06-hf", -# "cm06", -# "cm06-2x", -# ] - - -# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -# """Converts the QC method into NWChem keywords - -# Args: -# method (str): Name of the QC method to use -# derint (str): Index of the run type -# use_tce (bool): Whether to use the Tensor Contraction Engine -# Returns: -# (str): Task command for NWChem -# (dict): Any options for NWChem -# """ - -# # Standardize the method name -# method = method.lower() -# opts = {} - -# # Map the run type to -# runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - -# # Write out the theory directive -# if method == "nwchem": -# mdccmd = "" - -# elif method in ["scf", "hf"]: -# mdccmd = f"task scf {runtyp}\n\n" - -# elif method == "mp2": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp2"] = True -# else: -# mdccmd = f"task mp2 {runtyp}\n\n" - -# elif method == "mp3": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp3"] = True - -# elif method == "mp4": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__mp4"] = True - -# elif method == "ccd": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccd"] = True - -# elif method == "ccsd": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsd"] = True -# else: -# mdccmd = f"task ccsd {runtyp}\n\n" - -# elif method == "ccsdt": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsdt"] = True -# else: -# mdccmd = f"task ccsdt {runtyp}\n\n" - -# elif method == "ccsd(t)": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__ccsd(t)"] = True -# else: -# mdccmd = f"task ccsd(t) {runtyp}\n\n" - -# elif method == "tddft": -# mdccmd = f"task tddft {runtyp}\n\n" - -# elif method in ["sodft", "direct_mp2", "rimp2", "mcscf", "selci", "md", "pspw", "band"]: -# raise InputError(f'Method "{method}" not yet supported by QCEngine') - -# elif method == "tce": -# raise InputError( -# f"Do not specify TCE as a method. Instead specify the desired method " f'as a keyword and "qc_module=True".' -# ) - -# elif method.split()[0] in _xc_functionals: -# opts["dft__xc"] = method -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__"] = "dft" -# else: -# mdccmd = f"task dft {runtyp}\n\n" - -# elif method == "dft": -# if use_tce: -# mdccmd = f"task tce {runtyp}\n\n" -# opts["tce__"] = "dft" -# else: -# mdccmd = f"task dft {runtyp}\n\n" - -# else: -# raise InputError(f"Method not recognized: {method}") - -# # return mdccmd, opts +# List of XC functionals known to NWChem +_xc_functionals = [ + "hf", + "acm", + "b3lyp", + "beckehandh", + "pbe0", + "becke97", + "becke97-1", + "becke97-2", + "becke97-3", + "becke97-d", + "becke98", + "hcth", + "hcth120", + "hcth147", + "hcth407", + "becke97gga1", + "hcth407p", + "mpw91", + "mpw1k", + "xft97", + "cft97", + "ft97", + "op", + "bop", + "pbeop", + "xpkzb99", + "cpkzb99", + "xtpss03", + "ctpss03", + "xctpssh", + "b1b95", + "bb1k", + "mpw1b95", + "mpwb1k", + "pw6b95", + "pwb6k", + "m05", + "m05-2x", + "vs98", + "m06", + "m06-hf", + "m06-L", + "m06-2x", + "HFexch", + "becke88", + "xperdew91", + "xpbe96", + "gill96", + "lyp", + "perdew81", + "perdew86", + "perdew91", + "cpbe96", + "pw91lda", + "slater", + "vwn_1", + "vwn_2", + "vwn_3", + "vwn_4", + "vwn_5", + "vwn_1_rpa", + "xtpss03", + "ctpss03", + "bc95", + "xpw6b95", + "xpwb6k", + "xm05", + "xm05-2x", + "cpw6b95", + "cpwb6k", + "cm05", + "cm05-2x", + "xvs98", + "cvs98", + "xm06-L", + "xm06-hf", + "xm06", + "xm06-2x", + "cm06-L", + "cm06-hf", + "cm06", + "cm06-2x", +] + + +def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: + """Converts the QC method into NWChem keywords + + Args: + method (str): Name of the QC method to use + derint (str): Index of the run type + use_tce (bool): Whether to use the Tensor Contraction Engine + Returns: + (str): Task command for NWChem + (dict): Any options for NWChem + """ + + # Standardize the method name + method = method.lower() + opts = {} + + # Map the run type to + #runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + #runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] + + # Write out the theory directive + + mdccmd = f""## we don't need this right now + ## in the future when we link other exec this will change + ## all we have to do is add options to the dft block in order to change the run type + ## default in energy + # do nothing + if method =="optimization": + opts["dft__gopts"]= True + elif method == "response": + opts["dft__response"]= True + elif method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + else: + raise InputError(f"Method not recognized: {method}") + + + + + return mdccmd, opts +# # # # \ No newline at end of file diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index ec5596a5a..4b168f2ff 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,86 +2,88 @@ from typing import Any, Dict, Tuple -# def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: -# """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" -# # Transform string booleans into " " -# if val is True: -# return keyword.lower(), "" -# elif val is False: -# return "", "" -# # complete hack -# if keyword.upper() == "MEMORY": -# return keyword.lower(), f"{val} byte" +def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: + """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" -# elif isinstance(val, list): -# text = " ".join([str(v) for v in val]) -# elif isinstance(val, dict): -# text = [] -# for k, v in val.items(): -# merge = [k] -# merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) -# text.append(" ".join(merge)) -# text = " ".join(text) -# else: -# text = str(val) + # Transform string booleans into " " + if val is True: + return keyword.lower(), "true" + elif val is False: + return keyword.lower(), "false" -# if lop_off: -# return keyword[7:].lower(), text -# else: -# return keyword.lower(), text + # complete hack + #if keyword.upper() == "MEMORY": + # return keyword.lower(), f"{val} byte" + elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play + text = " ".join([str(v) for v in val]) + elif isinstance(val, dict): # val is a dict... text is list + text = [] + for k, v in val.items(): + merge = [k] + merge.extend(str(v) if isinstance(v, (int, float)) else list(map(str, v))) + text.append(" ".join(merge)) + text = " ".join(text) + else: + text = str(val) -# def format_keywords(keywords: Dict[str, Any]) -> str: -# """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" + if lop_off: + return keyword[7:].lower(), text + else: + return keyword.lower(), text -# def rec_dd(): -# return collections.defaultdict(rec_dd) -# grouped_options = rec_dd() +def format_keywords(keywords: Dict[str, Any]) -> str: + """From NWCHEM-directed, non-default `keywords` dictionary, write a NWCHEM deck.""" -# for group_key, val in keywords.items(): -# nesting = group_key.split("__") -# if len(nesting) == 1: -# key = nesting[0] -# grouped_options["aaaglobal"][key] = val -# elif len(nesting) == 2: -# g1, key = nesting -# grouped_options[g1][key] = val -# elif len(nesting) == 3: -# g1, g2, key = nesting -# grouped_options[g1][g2][key] = val -# else: -# print(nesting) -# raise ValueError("Nesting N!") + def rec_dd(): + return collections.defaultdict(rec_dd) -# grouped_lines = {} -# for group, opts in sorted(grouped_options.items()): -# lines = [] -# group_level_lines = [] -# for key, val in grouped_options[group].items(): -# if isinstance(val, dict): -# g2_level_lines = [] -# g2_level_lines.append(key.lower()) -# for k2, v2 in val.items(): -# line2 = " ".join(format_keyword(k2, v2, lop_off=False)) -# g2_level_lines.append(line2) -# g2_level_lines = " ".join(g2_level_lines) -# lines.append(g2_level_lines) -# else: -# line = " ".join(format_keyword(key, val, lop_off=False)) -# if group.lower() == "basis" and any( -# [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] -# ): -# group_level_lines.append(line) -# else: -# lines.append(line) -# if group == "aaaglobal": -# grouped_lines[group] = "\n".join(lines) + "\n" -# else: -# grouped_lines[group] = ( -# f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" -# ) + grouped_options = rec_dd() -# return "\n".join(grouped_lines.values()) + "\n" + for group_key, val in keywords.items(): + nesting = group_key.split("__") + if len(nesting) == 1: + key = nesting[0] + grouped_options["aaaglobal"][key] = val + elif len(nesting) == 2: + g1, key = nesting + grouped_options[g1][key] = val + elif len(nesting) == 3: + g1, g2, key = nesting + grouped_options[g1][g2][key] = val + else: + print(nesting) + raise ValueError("Nesting N!") + + grouped_lines = {} + for group, opts in sorted(grouped_options.items()): + lines = [] + group_level_lines = [] + for key, val in grouped_options[group].items(): + if isinstance(val, dict): + g2_level_lines = [] + g2_level_lines.append(key.lower()) + for k2, v2 in val.items(): + line2 = " ".join(format_keyword(k2, v2, lop_off=False)) + g2_level_lines.append(line2) + g2_level_lines = " ".join(g2_level_lines) + lines.append(g2_level_lines) + else: + line = " ".join(format_keyword(key, val, lop_off=False)) + if group.lower() == "basis" and any( + [word in line for word in ["spherical", "cartesian", "print", "noprint", "rel"]] + ): + group_level_lines.append(line) + else: + lines.append(line) + if group == "aaaglobal": + grouped_lines[group] = "\n".join(lines) + "\n" + else: + grouped_lines[group] = ( + f"{group.lower()} " + " ".join(group_level_lines) + "\n " + "\n ".join(lines) + "\nend\n" + ) + + return "\n".join(grouped_lines.values()) + "\n" diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 3ecf20128..f397b3e97 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -146,11 +146,34 @@ def build_input( opts=copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - + # Handle memory (ROBERT) Look in keywords as well + # for nwchem, [GiB] --> [B] + # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' + #memory_size = int(config.memory * (1024 ** 3)) + #if config.use_mpiexec: # It is the memory per MPI rank + # memory_size //= config.nnodes * config.ncores // config.cores_per_rank + #opts["memory"] = memory_size + + # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - opts.update(moldata["keywords"]) - optcmd="dft\n xc hf \nend\n" + + ## Handle Calc Type (ROBERT) + mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) + opts.update(mdcopts) + + ## Handle the basis set (ROBERT) the question is what value of k + + # Log the job settings (LORI) Not sure if i need this + logger.debug("JOB_OPTS") + logger.debug(pp.pformat(opts)) + + # Handle conversion from schema (flat key/value) keywords into local format + optcmd = format_keywords(opts) + + #optcmd="dft\n xc hf \nend\n" + + madnessrec["infiles"]["input"]=optcmd+molcmd ## Determine the command # Determine the command From 0cba478a2adf506c6361486a1c228abef3be9bf7 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 01:10:56 -0400 Subject: [PATCH 041/102] got variables for scf calculation --- qcengine/programs/madness/harvester.py | 714 +++++-------------------- 1 file changed, 134 insertions(+), 580 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 3040e81b1..2db119cf5 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -15,494 +15,80 @@ logger = logging.getLogger(__name__) -# def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: -# """Function to read an entire NWChem output file. - -# Reads all of the different "line search" segments of a file and returns -# values from the last segment for which a geometry was written. - -# Args: -# outtext (str): Output written to stdout -# Returns: -# - (PreservingDict) Variables extracted from the output file in the last complete step -# - (Molecule): Molecule from the last complete step -# - (list): Gradient from the last complete step -# - (str): Version string -# - (str): Error message, if any -# """ - -# # Loop over all steps -# # TODO (wardlt): Is it only necessary to read the last two steps? -# pass_psivar = [] -# pass_coord = [] -# pass_grad = [] -# for outpass in re.split(r" Line search:", outtext, re.MULTILINE): -# psivar, nwcoord, nwgrad, version, error = harvest_outfile_pass(outpass) -# pass_psivar.append(psivar) -# pass_coord.append(nwcoord) -# pass_grad.append(nwgrad) - -# # Determine which segment contained the last geometry -# retindx = -1 if pass_coord[-1] else -2 - -# return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error - - -# def harvest_outfile_pass(outtext): -# """Function to read NWChem output file *outtext* and parse important -# quantum chemical information from it in - -# """ -# psivar = PreservingDict() -# psivar_coord = None -# psivar_grad = None -# version = "" -# error = "" # TODO (wardlt): The error string is never used. - -# NUMBER = r"(?x:" + regex.NUMBER + ")" -# # fmt: off - -# # Process version -# mobj = re.search( -# r'^\s+' + r'Northwest Computational Chemistry Package (NWChem)' + r'\s+' + r'(?:\d+.\d+)' + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched version') -# version = mobj.group('version') - -# # Process SCF -# # 1)Fail to converge -# mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('failed to converge') - -# # 2)Calculation converged -# else: -# mobj = re.search(r'^\s+' + r'(?:Total SCF energy)' + r'\s+=\s*' + NUMBER + r's*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched HF') -# psivar['HF TOTAL ENERGY'] = mobj.group(1) - -# # Process Effective nuclear repulsion energy (a.u.) -# mobj = re.search(r'^\s+' + r'Effective nuclear repulsion energy \(a\.u\.\)' + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched NRE') -# # logger.debug (mobj.group(1)) -# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(1) - -# # Process DFT dispersion energy (a.u.) -# mobj = re.search(r'^\s+' + r'(?:Dispersion correction)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched Dispersion') -# logger.debug(mobj.group(1)) -# psivar['DFT DISPERSION ENERGY'] = mobj.group(1) - -# # Process DFT (RDFT, RODFT,UDFT, SODFT [SODFT for nwchem versions before nwchem 6.8]) - -# mobj = re.search(r'^\s+' + r'(?:Total DFT energy)' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched DFT') -# logger.debug(mobj.group(1)) -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) - -# # SODFT [for nwchem 6.8+] -# mobj = re.search( -# r'^\s+' + r'Total SO-DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Nuclear repulsion energy' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched DFT') -# # print (mobj.group(1)) -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) -# psivar['NUCLEAR REPULSION ENERGY'] = mobj.group(2) - -# # MCSCF -# mobj = re.findall( -# r'^\s+' + r'Total SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'One-electron energy' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Two-electron energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Total MCSCF energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - -# # for mobj_list in mobj: - -# if mobj: # Need to change to accommodate find all instances -# logger.debug('matched mcscf') # MCSCF energy calculation -# psivar['HF TOTAL ENERGY'] = mobj.group(1) -# psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) -# psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) -# psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) -# # for mobj_list in mobj: -# # for i in mobj_list: -# # count += 0 -# # logger.debug('matched mcscf iteration %i', count) -# # psivar['HF TOTAL ENERGY'] = mobj.group(1) -# # psivar['ONE-ELECTRON ENERGY'] = mobj.group(2) -# # psivar['TWO-ELECTRON ENERGY'] = mobj.group(3) -# # psivar['MCSCF TOTAL ENERGY'] = mobj.group(4) - -# # Process MP2 (Restricted, Unrestricted(RO n/a)) -# # 1)SCF-MP2 -# mobj = re.search(r'^\s+' + r'SCF energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Correlation energy' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Singlet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Triplet pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy' + r'\s+' + -# NUMBER + r'\s*$', outtext, re.MULTILINE) # MP2 -# if mobj: -# logger.debug('matched scf-mp2') -# psivar['HF TOTAL ENERGY'] = mobj.group(1) -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(5) -# # SCS-MP2 -# mobj = re.search( -# r'^\s+' + r'Same spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Opposite spin pairs' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Opposite spin scaling fact.' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'SCS-MP2 correlation energy' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total SCS-MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE) -# if mobj: -# logger.debug('matched scs-mp2') -# psivar['MP2 SAME-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(1)) * Decimal(mobj.group(2)) -# psivar['MP2 OPPOSITE-SPIN CORRELATION ENERGY'] = Decimal(mobj.group(3)) * Decimal(mobj.group(4)) - -# logger.debug(mobj.group(1)) # ess -# logger.debug(mobj.group(2)) # fss -# logger.debug(mobj.group(3)) # eos -# logger.debug(mobj.group(4)) # fos -# logger.debug(mobj.group(5)) # scs corl -# logger.debug(mobj.group(6)) # scs-mp2 - -# # 2) DFT-MP2 -# mobj = re.search( -# r'^\s+' + r'DFT energy' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Unscaled MP2 energy' + r'\s+' + NUMBER + -# r'\s*' + r'^\s+' + r'Total DFT+MP2 energy' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched dft-mp2') -# psivar['DFT TOTAL ENERGY'] = mobj.group(1) -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - -# # 3) MP2 with CCSD or CCSD(T) calculation (through CCSD(T) directive) -# mobj = re.search( -# r'^\s+' + r'MP2 Energy \(coupled cluster initial guess\)' + r'\s*' + r'^\s+' + -# r'------------------------------------------' + r'\s*' + r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + -# r'\s*' + r'^\s+' + r'MP2 Corr\. energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Total MP2 energy:' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched coupled cluster-mp2') -# psivar['MP2 CORRELATION ENERGY'] = mobj.group(2) -# psivar['MP2 TOTAL ENERGY'] = mobj.group(3) - -# # 4) Direct MP2 - -# # 5) RI-MP2 - -# # Process calculation through tce [dertype] command -# for cc_name in [r'MBPT\(2\)', r'MBPT\(3\)', r'MBPT\(4\)']: -# mobj = re.search( -# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, -# re.MULTILINE) - -# if mobj: -# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') -# logger.debug(f'matched tce mbpt {mbpt_plain}', mobj.groups()) - -# if mbpt_plain == 'MP2': -# psivar[f'{mbpt_plain} CORRELATION ENERGY'] = mobj.group(1) -# else: -# psivar[f'{mbpt_plain} CORRECTION ENERGY'] = mobj.group(1) -# psivar[f'{mbpt_plain} TOTAL ENERGY'] = mobj.group(2) -# #TCE dipole- MBPT(n) -# mobj2 = re.search( -# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj2: -# mbpt_plain = cc_name.replace('\\', '').replace('MBPT', 'MP').replace('(', '').replace(')', '') -# print(f'matched tce {mbpt_plain} dipole moment') -# #only pulling Debye -# psivar[f'{mbpt_plain} DIPOLE X'] = mobj2.group(2) -# psivar[f'{mbpt_plain} DIPOLE Y'] = mobj2.group(4) -# psivar[f'{mbpt_plain} DIPOLE Z'] = mobj2.group(6) - -# #TCE with () or [] -# for cc_name in [r'CCSD\(T\)', r'CCSD\[T\]', r'CCSD\(2\)_T', r'CCSD\(2\)', r'CCSDT\(2\)_Q', r'CR-CCSD\[T\]', r'CR-CCSD\(T\)', r'LR-CCSD\(T\)', r'LR-CCSD\(TQ\)-1', r'CREOMSD\(T\)']: -# mobj = re.search( -# r'^\s+' + cc_name + r'\s+' + r'correction energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + -# r'^\s+' + cc_name + r'\s+' + r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj: -# cc_plain = cc_name.replace('\\', '') -# cc_corr = cc_plain.replace('CCSD', '') -# logger.debug(f'matched tce cc {cc_plain}') - -# psivar[f'{cc_corr} CORRECTION ENERGY'] = mobj.group(1) -# psivar[f'{cc_plain} CORRELATION ENERGY'] = mobj.group(2) -# psivar[f'{cc_plain} TOTAL ENERGY'] = mobj.group(3) -# #TCE dipole with () or [] -# mobj2 = re.search( -# r'^\s+' + cc_name + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj2: -# cc_plain = cc_name.replace('\\', '') -# cc_corr = cc_plain.replace('CCSD', '') -# print(f'matched tce {cc_plain} dipole moment') - -# #only pulling Debye -# psivar[f'{cc_plain} DIPOLE X'] = mobj2.group(2) -# psivar[f'{cc_plain} DIPOLE Y'] = mobj2.group(4) -# psivar[f'{cc_plain} DIPOLE Z'] = mobj2.group(6) - -# #Process other TCE cases -# for cc_name in [r'CISD', r'CISDT', r'CISDTQ', r'CCD', r'CC2', r'CCSD', r'CCSDT', r'CCSDTQ', r'LCCSD', r'LCCD', r'CCSDTA']: -# mobj = re.search( -# r'^\s+' + r'Iterations converged' + r'\s*' + r'^\s+' + cc_name + r'\s+' + -# r'correlation energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*' + r'^\s+' + cc_name + r'\s+' + -# r'total energy / hartree' + r'\s+=\s*' + NUMBER + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug(f'matched {cc_name}') -# logger.debug(mobj) -# psivar[f'{cc_name} CORRELATION ENERGY'] = mobj.group(1) -# psivar[f'{cc_name} TOTAL ENERGY'] = mobj.group(2) -# #TCE dipole -# mobj2 = re.search( -# r'^\s+' + r'dipole moments / hartree & Debye' + r'\s*' + -# r'^\s+' + r'X' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Y' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Z' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total' + r'\s+' + NUMBER + r'\s+' + NUMBER + r'\s*$', -# outtext, re.MULTILINE) -# if mobj2: -# print(f'matched tce dipole moment') - -# #only pulling Debye -# psivar[f'CURRENT DIPOLE X'] = mobj2.group(2) -# psivar[f'CURRENT DIPOLE Y'] = mobj2.group(4) -# psivar[f'CURRENT DIPOLE Z'] = mobj2.group(6) - -# # Process CCSD/CCSD(T) using nwchem CCSD/CCSD(T) [dertype] command - -# mobj = re.search( -# r'^\s+' + r'-----------' + r'\s*' + r'^\s+' + r'CCSD Energy' + r'\s*' + r'^\s+' + r'-----------' + r'\s*' + -# r'^\s+' + r'Reference energy:' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'CCSD corr\. energy:' + r'\s+' + -# NUMBER + r'\s*' + r'^\s+' + r'Total CCSD energy:' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE | re.DOTALL) - -# if mobj: -# logger.debug('matched ccsd') -# psivar['CCSD CORRELATION ENERGY'] = mobj.group(2) -# psivar['CCSD TOTAL ENERGY'] = mobj.group(3) - -# mobj = re.search( -# r'^\s+' + r'--------------' + r'\s*' + r'^\s+' + r'CCSD\(T\) Energy' + r'\s*' + r'^\s+' + -# r'--------------' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'\(T\) corr\. energy:' + r'\s+' + NUMBER + r'\s*' + -# r'^\s+' + r'Total CCSD\(T\) energy:' + r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) - -# if mobj: -# logger.debug('matched ccsd(t)') -# psivar['(T) CORRECTION ENERGY'] = mobj.group(1) -# psivar['CCSD(T) CORRELATION ENERGY'] = Decimal(mobj.group(2)) - psivar['HF TOTAL ENERGY'] -# psivar['CCSD(T) TOTAL ENERGY'] = mobj.group(2) - -# mobj = re.search( -# r'^\s+' + r'Spin Component Scaled (SCS) CCSD' + r'\s*' + r'(?:.*?)' + r'^\s+' + r'Same spin contribution' + -# r'\s+' + NUMBER + r'\s*' + r'^\s+' + r'Same spin scaling factor' + r'\s+' + NUMBER + r'\s*' -# r'^\s+' + r'Opposite spin contribution' + r'\s+' + NUMBER + r'\s*' + r'^\s+' + -# r'Opposite spin scaling factor' + r'\s+' + NUMBER + r'\s*' -# r'\^s+' + r'SCS-CCSD correlation energy' + r'\s+' + NUMBER + r'\s*' + r'\^s+' + r'Total SCS-CCSD energy' + -# r'\s+' + NUMBER + r'\s*$', outtext, re.MULTILINE | re.DOTALL) -# # SCS-CCSD included -# if mobj: -# logger.debug('matched scs-ccsd') -# psivar['CCSD SAME-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD SAME-SPIN CORRELATION ENERGY'] = ( -# Decimal(mobj.group(1)) * Decimal(mobj.group(2))) -# psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = psivar['SCS-CCSD OPPOSITE-SPIN CORRELATION ENERGY'] = ( -# Decimal(mobj.group(4)) * Decimal(mobj.group(3))) -# psivar['SCS-CCSD CORRELATION ENERGY'] = mobj.group(5) -# psivar['SCS-CCSD TOTAL ENERGY'] = mobj.group(6) -# psivar['CUSTOM SCS-CCSD CORRELATION ENERGY'] = 0.5 * (float( -# psivar['CCSD SAME-SPIN CORRELATION ENERGY']) + float(psivar['CCSD OPPOSITE-SPIN CORRELATION ENERGY'])) -# # psivar['CUSTOM SCS-CCSD TOTAL ENERGY'] = float(mobj.group(6)) + float( -# # psivar['CUSTOM SCS-CCSD CORRERLATION ENERGY']) - -# # Process EOM-[cc_name] #nwchem_tce_dipole = false -# # Parsed information: each symmetry, root excitation energy in eV and total energy in hartree -# # psivar name might need to be fixed -# # each root excitation energy is extracted from the last iteration of right hand side -# mobj = re.findall( -# r'^\s+(?:Excited-state calculation \( )(.*)\s+(?:symmetry\))\s+(?:.*\n)*^\s+EOM-' + cc_name + -# # (..) captures symmetry -# r'right-hand side iterations\s+(?:.*\n)*(?:Excited state root)\s+' + NUMBER + #root -# r'\s*(?:Excitation energy / hartree)\s+.\s+' + NUMBER + #excitation energy hartree -# r'\s*(?:/ eV)\s+.\s+' + NUMBER + r'\s*$', #excitation energy eV -# outtext, re.MULTILINE | re.DOTALL) -# #regex should be more dynamic in finding values, need to revisit -# #mobj.group(0) = symmetry value -# #mobj.group(1) = cc_name -# #mobj.group(2) = root number -# #mobj.group(3) = excitation energy (hartree) -# #mobj.group(4) = excitation energy (eV) - -# if mobj: -# print(mobj) -# ext_energy = {} # dic - -# ext_energy_list = [] -# print(f'matched eom-{cc_name}') -# for mobj_list in mobj: -# logger.debug('matched EOM-%s - %s symmetry' % (cc_name, mobj_list[0])) # cc_name, symmetry -# logger.debug(mobj_list) -# count = 0 -# for line in mobj_list[1].splitlines(): -# lline = line.split() -# logger.debug(lline[1]) # in hartree -# logger.debug(lline[2]) # in eV -# count += 1 - -# logger.debug('matched excitation energy #%d - %s symmetry' % (count, mobj_list[0])) - -# ext_energy_list.append(lline[1]) # Collect all excitation energies - -# sym = str(mobj_list[0]) -# ext_energy.setdefault(sym, []) -# ext_energy[sym].append(lline[1]) # Dictionary: symmetries(key), energies(value) - -# ext_energy_list.sort(key=float) - -# for nroot in range(len(ext_energy_list)): -# for k, e_val in ext_energy.items(): -# if ext_energy_list[nroot] in e_val: -# symm = k -# psivar['EOM-%s ROOT 0 -> ROOT %d EXCITATION ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ -# ext_energy_list[nroot] # in hartree -# psivar['EOM-%s ROOT 0 -> ROOT %d TOTAL ENERGY - %s SYMMETRY' %(cc_name, nroot+1, symm)] = \ -# psivar['%s TOTAL ENERGY' %(cc_name)] + Decimal(ext_energy_list[nroot]) # in hartree -# gssym = '' -# gs = re.search(r'^\s+' + r'Ground-state symmetry is' + gssym + r'\s*$', outtext, re.MULTILINE) - -# if gs: -# logger.debug('matched ground-state symmetry') -# psivar['GROUND-STATE SYMMETRY'] = gssym.group(1) - -# # Process TDDFT -# # 1) Spin allowed -# mobj = re.findall( -# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' -# #Root | symmetry | a.u. | eV -# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value -# + r'\s+(?:.*\n)\s+Transition Moments\s+X\s+'+ NUMBER + r'\s+Y\s+'+ NUMBER+ r'\s+Z\s+'+ NUMBER #dipole -# + r'\s+Transition Moments\s+XX\s+'+ NUMBER + r'\s+XY\s+'+ NUMBER+ r'\s+XZ\s+'+ NUMBER #quadrople -# + r'\s+Transition Moments\s+YY\s+'+ NUMBER + r'\s+YZ\s+'+ NUMBER+ r'\s+ZZ\s+'+ NUMBER #quadrople -# + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched TDDFT with transition moments') -# for mobj_list in mobj: -# print (mobj_list) -# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = mobj_list[2] #in eV -# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[1])] = psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[2]) -# #### temporary psivars #### -# #psivar['TDDFT ROOT %d %s %s EXCITATION ENERGY' % -# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. -# #psivar ['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0],mobj_list[1],mobj_list[2])] = \ -# # psivar ['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) -# psivar['TDDFT ROOT %s DIPOLE X' % (mobj_list[0])] = mobj_list[5] -# psivar['TDDFT ROOT %s DIPOLE Y' % (mobj_list[0])] = mobj_list[6] -# psivar['TDDFT ROOT %s DIPOLE Z' % (mobj_list[0])] = mobj_list[7] -# psivar['TDDFT ROOT %s QUADRUPOLE XX' % (mobj_list[0])] = mobj_list[8] -# psivar['TDDFT ROOT %s QUADRUPOLE XY' % (mobj_list[0])] = mobj_list[9] -# psivar['TDDFT ROOT %s QUADRUPOLE XZ' % (mobj_list[0])] = mobj_list[10] -# psivar['TDDFT ROOT %s QUADRUPOLE YY' % (mobj_list[0])] = mobj_list[11] -# psivar['TDDFT ROOT %s QUADRUPOLE YZ' % (mobj_list[0])] = mobj_list[12] -# psivar['TDDFT ROOT %s QUADRUPOLE ZZ' % (mobj_list[0])] = mobj_list[13] - - -# # 2) Spin forbidden -# mobj = re.findall( -# r'^\s+(?:Root)\s+(\d+)\s+(.*?)\s+' + NUMBER + r'\s(?:a\.u\.)\s+' + NUMBER + r'\s+(?:\w+)' -# #Root | symmetry | a.u. | eV -# + r'\s+(?:.\w+.\s+.\s+\d+.\d+)' #s2 value -# + r'\s+Transition Moments\s+(?:Spin forbidden)' + r'\s*$', -# outtext, re.MULTILINE) -# #mobj.group(0) = Root -# #mobj.group(1) = symmetry -# #mobj.group(2) a.u. -# #mobj.group(3) e.V -# #mobj.group(4) Excitation energy -# #mobj.group(5) Excited state energy - -# if mobj: -# logger.debug('matched TDDFT - spin forbidden') -# for mobj_list in mobj: -# #### temporary psivars #### -# psivar['TDDFT ROOT %s EXCITATION ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = mobj_list[4] #in eV -# psivar['TDDFT ROOT %s EXCITED STATE ENERGY - %s SYMMETRY' % (mobj_list[0], mobj_list[2])] = psivar['DFT TOTAL ENERGY'] + qcel.constants.converstion_factor("eV", "hartree")*Decimal(mobj_list[4]) - -# #psivar['TDDFT ROOT %s %s %s EXCITATION ENERGY' % -# # (mobj_list[0], mobj_list[1], mobj_list[2])] = mobj_list[3] # in a.u. -# #psivar['TDDFT ROOT %s %s %s EXCITED STATE ENERGY' %(mobj_list[0], mobj_list[1], mobj_list[2])] = \ -# # psivar['DFT TOTAL ENERGY'] + Decimal(mobj_list[3]) -# if mobj: -# logger.debug('Non-variation initial energy') # prints out energy, 5 counts - -# # Process geometry -# # 1) CHARGE -# # Read charge from SCF module -# mobj = re.search(r'^\s+' + r'charge =' + r'\s+' + NUMBER + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched charge') -# out_charge = int(float(mobj.group(1))) - -# # Read charge from General information (not scf module) -# mobj = re.search(r'^\s+' + r'Charge :' + r'\s+' + r'(-?\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched charge') -# out_charge = int(float(mobj.group(1))) - -# # 2) MULTIPLICITY -# # Read multiplicity from SCF module -# mobj = re.search(r'^\s+' + r'open shells =' + r'\s+' + r'(\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity') -# out_mult = int(mobj.group(1)) + 1 - -# # Read multiplicity from SCF module through alpha, beta electrons -# mobj = re.search( -# r'^\s+' + r'alpha electrons =' + r'\s+' + r'(\d+)' + r'\s*' + r'^\s+' + r'beta electrons =' + r'\s+' + -# r'(\d+)' + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity via alpha and beta electrons') -# out_mult = int(mobj.group(1)) - int(mobj.group(2)) + 1 # nopen + 1 -# psivar['N ALPHA ELECTRONS'] = mobj.group(1) -# psivar['N BETA ELECTRONS'] = mobj.group(2) - -# # Read multiplicity from General information (not scf module) -# mobj = re.search(r'^\s+' + r'Spin multiplicity:' + r'\s+' + r'(\d+)' + r'\s*$', outtext, -# re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched multiplicity') -# out_mult = int(mobj.group(1)) - -# # 3) Initial geometry +def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: + """Function to read an entire NWChem output file. + + Reads all of the different "line search" segments of a file and returns + values from the last segment for which a geometry was written. + + Args: + outtext (str): Output written to stdout + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (Molecule): Molecule from the last complete step + - (list): Gradient from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Loop over all steps + # TODO (wardlt): Is it only necessary to read the last two steps? + pass_psivar = [] + pass_coord = [] + pass_grad = [] + for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) + pass_psivar.append(psivar)## all the variables extracted + pass_coord.append(madcoord) + pass_grad.append(madgrad) + + # Determine which segment contained the last geometry + retindx = -1 if pass_coord[-1] else -2 + + return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error + + +def harvest_outfile_pass(outtext): + """Function to read Madness output file *outtext* and parse important + quantum chemical information from it in + + """ + psivar = PreservingDict() + psivar_coord = None + psivar_grad = None + version = "" + error = "" # TODO (wardlt): The error string is never used. + + NUMBER = r"(?x:" + regex.NUMBER + ")" + # fmt: off + + # Process version + mobj = re.search( + r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' +r'\s'+ r'multiresolution suite'+r'\s*$', + outtext, re.MULTILINE) + if mobj: + logger.debug('matched version') + version = mobj.group(1) + + # Process SCF + # 1)Fail to converge (TODO Robert ask for failed convergence) + mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + if mobj: + logger.debug('failed to converge') + + # 2)Calculation converged + else: + OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + optDict=dict(zip(OPTIONS,PSIVAR)) + + for var,VAR in optDict.items(): + mobj = re.search(r'^\s+' + var + r'\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + if mobj: + logger.debug('matched SCF')## not sure what this means + psivar[VAR] = mobj.group(1) + + # 3) Initial geometry # mobj = re.search( # r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + # r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + @@ -730,45 +316,13 @@ # psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] # psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] -# if "MP2 TOTAL ENERGY" in psivar and "MP2 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP2 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP2 TOTAL ENERGY"] -# if "MP3 TOTAL ENERGY" in psivar and "MP3 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP3 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP3 TOTAL ENERGY"] -# if "MP4 TOTAL ENERGY" in psivar and "MP4 CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["MP4 CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["MP4 TOTAL ENERGY"] - -# if "DFT TOTAL ENERGY" in psivar: -# psivar["CURRENT REFERENCE ENERGY"] = psivar["DFT TOTAL ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["DFT TOTAL ENERGY"] - -# # Process TCE CURRENT energies -# # Need to be fixed -# # HOW TO KNOW options['NWCHEM']['NWCHEM_TCE']['value']? -# # TODO: CURRENT ENERGY = TCE ENERGY -# if "%s TOTAL ENERGY" % (cc_name) in psivar and ("%s CORRELATION ENERGY" % (cc_name) in psivar): -# psivar["CURRENT CORRELATION ENERGY"] = psivar["%s CORRELATION ENERGY" % (cc_name)] -# psivar["CURRENT ENERGY"] = psivar["%s TOTAL ENERGY" % (cc_name)] - -# if "CCSD TOTAL ENERGY" in psivar and "CCSD CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["CCSD TOTAL ENERGY"] - -# if "CCSD(T) TOTAL ENERGY" in psivar and "CCSD(T) CORRELATION ENERGY" in psivar: -# psivar["CURRENT CORRELATION ENERGY"] = psivar["CCSD(T) CORRELATION ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["CCSD(T) TOTAL ENERGY"] + psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] -# if ("EOM-%s TOTAL ENERGY" % (cc_name) in psivar) and ("%s EXCITATION ENERGY" % (cc_name) in psivar): -# psivar["CURRENT ENERGY"] = psivar["EOM-%s TOTAL ENERGY" % (cc_name)] -# psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] + return psivar, psivar_coord, psivar_grad, version, error -# return psivar, psivar_coord, psivar_grad, version, error - -# def harvest_hessian(hess: str) -> np.ndarray: -# """Parses the contents of the NWChem hess file into a hessian array. +def harvest_hessian(hess: str) -> np.ndarray: pass + """Parses the contents of the NWChem hess file into a hessian array. # Args: # hess (str): Contents of the hess file @@ -776,7 +330,7 @@ # (np.ndarray) Hessian matrix as a 2D array # """ -# # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python + # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python # hess_conv = hess.replace("D", "E") # # Parse all of the float values @@ -838,58 +392,58 @@ # return AtomicResultProperties(**output) -# def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, list, Molecule, str, str]: -# """Parses all the pieces of output from NWChem: the stdout in -# *nwout* Scratch files are not yet considered at this moment. - -# Args: -# in_mol (Molecule): Input molecule -# nwout (str): NWChem output molecule -# outfiles (dict): Dictionary of outfile files and their contents -# Returns: -# - (PreservingDict) Variables extracted from the output file in the last complete step -# - (None): Hessian from the last complete step (Not yet implemented) -# - (list): Gradient from the last complete step -# - (Molecule): Molecule from the last complete step -# - (str): Version string -# - (str): Error message, if any -# """ - -# # Parse the NWChem output -# out_psivar, out_mol, out_grad, version, error = harvest_output(nwout) - -# # If available, read higher-accuracy gradients -# # These were output using a Python Task in NWChem to read them out of the database -# if outfiles.get("nwchem.grad") is not None: -# logger.debug("Reading higher-accuracy gradients") -# out_grad = json.loads(outfiles.get("nwchem.grad")) - -# # If available, read the hessian -# out_hess = None -# if outfiles.get("nwchem.hess") is not None: -# out_hess = harvest_hessian(outfiles.get("nwchem.hess")) - -# # Make sure the input and output molecules are the same -# if out_mol: -# if in_mol: -# if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: -# raise ValueError( -# """NWChem outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" -# % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) -# ) -# else: -# raise ValueError("""No coordinate information extracted from NWChem output.""") - -# # If present, align the gradients and hessian with the original molecular coordinates -# # NWChem rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the -# # rotated molecule, which we can use to determine how to rotate the gradients/hessian -# amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - -# mill = data["mill"] # Retrieve tool with alignment routines - -# if out_grad is not None: -# out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) -# if out_hess is not None: -# out_hess = mill.align_hessian(np.array(out_hess)) - -# return out_psivar, out_hess, out_grad, out_mol, version, error +def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: + """Parses all the pieces of output from NWChem: the stdout in + *nwout* Scratch files are not yet considered at this moment. + + Args: + in_mol (Molecule): Input molecule + madout (str): Madness output molecule + outfiles (dict): Dictionary of outfile files and their contents + Returns: + - (PreservingDict) Variables extracted from the output file in the last complete step + - (None): Hessian from the last complete step (Not yet implemented) + - (None): Gradient from the last complete step (Not yet implemented) + - (Molecule): Molecule from the last complete step + - (str): Version string + - (str): Error message, if any + """ + + # Parse the Madness output + out_psivar, out_mol, out_grad, version, error = harvest_output(madout) + + # If available, read higher-accuracy gradients + # These were output using a Python Task in NWChem to read them out of the database + if outfiles.get("mad.grad") is not None: + logger.debug("Reading higher-accuracy gradients") + out_grad = json.loads(outfiles.get("mad.grad")) + + # If available, read the hessian + out_hess = None + if outfiles.get("mad.hess") is not None: + out_hess = harvest_hessian(outfiles.get("mad.hess")) + + # Make sure the input and output molecules are the same + if out_mol: + if in_mol: + if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: + raise ValueError( + """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" + % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) + ) + else: + raise ValueError("""No coordinate information extracted from Madness output.""") + + # If present, align the gradients and hessian with the original molecular coordinates + # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the + # rotated molecule, which we can use to determine how to rotate the gradients/hessian + amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + + mill = data["mill"] # Retrieve tool with alignment routines + + if out_grad is not None: + out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) + if out_hess is not None: + out_hess = mill.align_hessian(np.array(out_hess)) + + return out_psivar, out_hess, out_grad, out_mol, version, error From 8b20a74bb7f231f5d2e7ecccf0cee73f07d86833 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:27:58 -0400 Subject: [PATCH 042/102] add gopts method to germinate --- qcengine/programs/madness/germinate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index fce6b2cca..69f5e1433 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -117,7 +117,7 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict ## default in energy # do nothing if method =="optimization": - opts["dft__gopts"]= True + opts["dft__gopt"]= True elif method == "response": opts["dft__response"]= True elif method.split()[0] in _xc_functionals: From 41e014927c10f20c062669b39a3cda4ee38b8eab Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:28:32 -0400 Subject: [PATCH 043/102] added h20MAD for debuggin I think I can get rid of this --- .../programs/tests/test_standard_suite_hf.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index b5428952e..c4b94b94d 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -17,6 +17,17 @@ def h2o(): """ return qcel.models.Molecule.from_data(smol) +@pytest.fixture +def h2oMAD(): + smol = """ + H 0.000000000000 1.431430901356 0.984293362719 + O 0.000000000000 0.000000000000 -0.124038860300 + H 0.000000000000 -1.431430901356 0.984293362719 + units au +""" + return qcel.models.Molecule.from_data(smol) + + @pytest.fixture def nh2(): @@ -156,24 +167,26 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) + @pytest.mark.parametrize( - "program,keywords", - [ - pytest.param("madness", {"dft__xc__hf": True } ), + "program,basis,keywords", [ + pytest.param("madness", None, {"dft__aobasis":"sto-3g","dft__econv": 1.0000e-05}), + ], ) -def test_mad_hf(program, keywords, h2o): - resi = {"molecule": h2o, "driver": "energy", "model": {"method": "dft", "keywords": keywords}} +def test_mad_hf(program, basis, keywords, h2oMAD): + resi = {"molecule": h2oMAD, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + print(res["stdout"]) assert res["driver"] == "energy" assert "provenance" in res assert res["success"] is True - # aug-cc-pvdz - scf_tot = -76.06718642 + # k=7 + scf_tot = -76.06718632 - atol = 1.0e-6 + atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) - From 0574ffe9a11d251de6eeeba9c252d19bbb638371 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:30:30 -0400 Subject: [PATCH 044/102] pytest passed madness with h20() --- qcengine/programs/tests/test_standard_suite_hf.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index c4b94b94d..e7976b35e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -17,15 +17,6 @@ def h2o(): """ return qcel.models.Molecule.from_data(smol) -@pytest.fixture -def h2oMAD(): - smol = """ - H 0.000000000000 1.431430901356 0.984293362719 - O 0.000000000000 0.000000000000 -0.124038860300 - H 0.000000000000 -1.431430901356 0.984293362719 - units au -""" - return qcel.models.Molecule.from_data(smol) @@ -174,8 +165,8 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): ], ) -def test_mad_hf(program, basis, keywords, h2oMAD): - resi = {"molecule": h2oMAD, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} +def test_mad_hf(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} res = qcng.compute(resi, program, raise_error=True, return_dict=True) From 56ffcf5bdaf28e27608fa1ac9509821e03e44c10 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:31:56 -0400 Subject: [PATCH 045/102] split output by Converged! STILL Need to read format stuff --- qcengine/programs/madness/harvester.py | 335 ++++--------------------- 1 file changed, 54 insertions(+), 281 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 2db119cf5..213119734 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -16,7 +16,7 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: - """Function to read an entire NWChem output file. + """Function to read an entire MADNESS output file. Reads all of the different "line search" segments of a file and returns values from the last segment for which a geometry was written. @@ -36,15 +36,14 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_psivar = [] pass_coord = [] pass_grad = [] - for outpass in re.split(r" Line search:", outtext, re.MULTILINE): + for outpass in re.split(r"Converged!", outtext, re.MULTILINE): psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) pass_psivar.append(psivar)## all the variables extracted pass_coord.append(madcoord) pass_grad.append(madgrad) # Determine which segment contained the last geometry - retindx = -1 if pass_coord[-1] else -2 - + retindx = -1 #if pass_coord[-1] else -2 return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error @@ -87,242 +86,20 @@ def harvest_outfile_pass(outtext): if mobj: logger.debug('matched SCF')## not sure what this means psivar[VAR] = mobj.group(1) + # Other options + + + # Process CURRENT energies (TODO: needs better way) + if "TOTAL SCF ENERGY" in psivar: + psivar["CURRENT REFERENCE ENERGY"] = psivar["TOTAL SCF ENERGY"] + psivar["CURRENT ENERGY"] = psivar["TOTAL SCF ENERGY"] - # 3) Initial geometry -# mobj = re.search( -# r'^\s+' + r'Geometry' + r'.*' + r'\s*' + r'^\s+' + r'(?:-+)\s*' + r'\s+' + r'\n' + r'^\s' + -# r'Output coordinates in ' + r'(.*?)' + r'\s' + r'\(scale by' + r'.*' + r'\s' + r'to convert to a\.u\.\)' + -# r'\s+' + r'\n' + r'^\s+' + r'No\.\ Tag Charge X Y Z' + -# r'\s*' + r'^\s+' + r'---- ---------------- ---------- -------------- -------------- --------------' + -# r'\s*' + -# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-z]*)+\s+\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' -# + r'\s*$', outtext, re.MULTILINE | re.IGNORECASE) - -# if mobj: -# logger.debug('matched geom') - -# # dinky molecule w/ charge and multiplicity -# if mobj.group(1) == 'angstroms': -# molxyz = '%d \n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult -# ) # unit = angstrom -# for line in mobj.group(2).splitlines(): -# lline = line.split() -# molxyz += '%s %16s %16s %16s\n' % (lline[-5], lline[-3], lline[-2], lline[-1]) -# # Jiyoung was collecting charge (-4)? see if this is ok for ghosts -# # Tag , X, Y, Z -# psivar_coord = Molecule(validate=False, -# **qcel.molparse.to_schema(qcel.molparse.from_string( -# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], -# dtype=2)) - -# else: # unit = a.u. -# molxyz = '%d au\n%d %d tag\n' % (len(mobj.group(2).splitlines()), out_charge, out_mult) -# for line in mobj.group(2).splitlines(): -# lline = line.split() -# molxyz += '%s %16s %16s %16s\n' % (int(float(lline[-4])), lline[-3], lline[-2], lline[-1]) -# # Tag , X, Y, Z -# psivar_coord = Molecule(validate=False, -# **qcel.molparse.to_schema(qcel.molparse.from_string( -# molxyz, dtype='xyz+', fix_com=True, fix_orientation=True)["qm"], -# dtype=2)) - -# # Process gradient -# mobj = re.search( -# r'^\s+' + r'.*' + r'ENERGY GRADIENTS' + r'\s*' + r'\s+' + r'\n' + r'^\s+' + -# r'atom coordinates gradient' + r'\s*' + r'^\s+' + -# r'x y z x y z' + r'\s*' + -# r'((?:\s+([1-9][0-9]*)+\s+([A-Z][a-x]*)+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s+[-+]?\d+\.\d+\s*\n)+)' -# + r'\s*$', outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched molgrad') -# atoms = [] -# psivar_grad = [] -# for line in mobj.group(1).splitlines(): -# lline = line.split() # Num, Tag, coord x, coord y, coord z, grad x, grad y, grad z -# # print (lline) -# if lline == []: -# pass -# else: -# atoms.append(lline[1]) # Tag -# psivar_grad.append([float(lline[-3]), float(lline[-2]), float(lline[-1])]) - -# # Process dipole (Properties) -# mobj = re.search( -# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + -# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'.*' + -# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s+' + r'A\.U\.' + r'\s*' + -# r'^\s+' + r'Dipole moment' + r'\s+' + NUMBER + r'\s' + r'Debye\(s\)' + r'\s*' + -# r'^\s+' + r'DMX' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMY' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'DMZ' + r'\s+' + NUMBER + r'.*' + -# r'^\s+' + r'.*' + -# r'^\s+' + r'Total dipole' + r'\s+' + NUMBER + r'\s' + r'DEBYE\(S\)' + r'\s*$', -# outtext, re.MULTILINE) - -# if mobj: -# logger.debug('matched total dipole') - -# # UNIT = DEBYE(S) -# psivar['CURRENT DIPOLE X'] = mobj.group(7) -# psivar['CURRENT DIPOLE Y'] = mobj.group(8) -# psivar['CURRENT DIPOLE Z'] = mobj.group(9) -# # total? - -# # Process error code -# mobj = re.search( -# r'^\s+' + r'current input line \:' + r'\s*' + r'^\s+' + r'([1-9][0-9]*)' + r'\:' + r'\s+' + r'(.*)' + -# r'\s*' + r'^\s+' -# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' -# r'------------------------------------------------------------------------' + r'\s*' + r'^\s+' + -# r'There is an error in the input file' + r'\s*$', outtext, re.MULTILINE) -# if mobj: -# logger.debug('matched error') -# # print (mobj.group(1)) #error line number -# # print (mobj.group(2)) #error reason -# psivar['NWCHEM ERROR CODE'] = mobj.group(1) -# # TODO process errors into error var - -# # fmt: on - -# # Get the size of the basis sets, etc -# mobj = re.search(r"No. of atoms\s+:\s+(\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["N ATOMS"] = mobj.group(1) -# mobj = re.search( -# r"No. of electrons\s+:\s+(\d+)\s+Alpha electrons\s+:\s+(\d+)\s+Beta electrons\s+:\s+(\d+)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# psivar["N ALPHA ELECTRONS"] = mobj.group(2) -# psivar["N BETA ELECTRONS"] = mobj.group(3) -# if psivar["N ALPHA ELECTRONS"] == psivar["N BETA ELECTRONS"]: -# # get HOMO and LUMO energy -# mobj = re.search( -# r"Vector" -# + r"\s+" -# + r"%d" % (psivar["N ALPHA ELECTRONS"]) -# + r"\s+" -# + r"Occ=" -# + r".*" -# + r"\s+" -# + r"E=" -# + r"([+-]?\s?\d+[.]\d+)" -# + r"[D]" -# + r"([+-]0\d)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# homo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) -# psivar["HOMO"] = np.array([round(homo, 10)]) -# mobj = re.search( -# r"Vector" -# + r"\s+" -# + r"%d" % (psivar["N ALPHA ELECTRONS"] + 1) -# + r"\s+" -# + r"Occ=" -# + r".*" -# + r"\s+" -# + r"E=" -# + r"([+-]?\s?\d+[.]\d+)" -# + r"[D]" -# + r"([+-]0\d)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# lumo = float(mobj.group(1)) * (10 ** (int(mobj.group(2)))) -# psivar["LUMO"] = np.array([round(lumo, 10)]) - -# mobj = re.search(r"AO basis - number of functions:\s+(\d+)\s+number of shells:\s+(\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["N MO"] = mobj.group(2) -# psivar["N BASIS"] = mobj.group(1) - -# # Search for Center of charge -# mobj = re.search( -# r"Center of charge \(in au\) is the expansion point" -# + r"\n" -# + r"\s+" -# + r"X\s+=\s+([+-]?\d+[.]\d+)" -# + r"\s+" -# + r"Y\s+=\s+([+-]?\d+[.]\d+)" -# + r"\s+" -# + r"Z\s+=\s+([+-]?\d+[.]\d+)", -# outtext, -# re.MULTILINE, -# ) -# if mobj: -# psivar["CENTER OF CHARGE"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) - -# mobj = re.search( -# r"Dipole moment" -# + r".*?" -# + r"A\.U\." -# + r"\s+" -# + r"DMX\s+([+-]?\d+[.]\d+)\s+" -# + r"DMXEFC\s+[+-]?\d+[.]\d+\s+" -# + r"DMY\s+([+-]?\d+[.]\d+)\s+" -# + r"DMYEFC\s+[+-]?\d+[.]\d+\s+" -# + r"DMZ\s+([+-]?\d+[.]\d+)\s+" -# + r"DMZEFC\s+[+-]?\d+[.]\d+\s+" -# + r"\-EFC\-" -# + r".*?" -# + r"A\.U\.\s+" -# + r"Total dipole\s+([+-]?\d+[.]\d+\s+)", -# outtext, -# re.MULTILINE, -# ) -# # + r"DMY\s+" + r"([+-]?\d+[.]\d+)", outtext, re.MULTILINE) -# if mobj: -# psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) -# psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - -# mobj = re.search( -# r"Quadrupole moments in atomic units\s+" -# + r"Component\s+" -# + r"Electronic\+nuclear\s+" -# + r"Point charges\s+" -# + r"Total\s+" -# + r"-+\s+" -# + r"XX\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"YY\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"ZZ\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"XY\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"XZ\s+([+-]?\d+[.]\d+)\s+" -# + r".*\s+.*\s+" -# + r"YZ\s+([+-]?\d+[.]\d+)\s+", -# outtext, -# re.MULTILINE, -# ) - -# if mobj: -# psivar["QUADRUPOLE MOMENT"] = np.array( -# [mobj.group(1), mobj.group(2), mobj.group(3), mobj.group(4), mobj.group(5), mobj.group(6)] -# ) - -# # Process CURRENT energies (TODO: needs better way) -# if "HF TOTAL ENERGY" in psivar: -# psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] -# psivar["CURRENT REFERENCE ENERGY"] = psivar["HF TOTAL ENERGY"] -# psivar["CURRENT ENERGY"] = psivar["HF TOTAL ENERGY"] - - psivar["CURRENT EXCITATION ENERGY"] = psivar["%s EXCITATION ENERGY" % (cc_name)] return psivar, psivar_coord, psivar_grad, version, error def harvest_hessian(hess: str) -> np.ndarray: pass - """Parses the contents of the NWChem hess file into a hessian array. +# """Parses the contents of the NWChem hess file into a hessian array. # Args: # hess (str): Contents of the hess file @@ -350,49 +127,44 @@ def harvest_hessian(hess: str) -> np.ndarray: pass # return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines -# def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: -# """Get named properties out of the general variables extracted out of the result file +def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: + """Get named properties out of the general variables extracted out of the result file -# Args: -# psivars (PreservingDict): Dictionary of the output results -# Returns: -# (AtomicResultProperties) Properties in a standard format -# """ -# # TODO (wardlt): Get more of the named variables out of the NWChem file - -# # Initialize the output -# output = dict() - -# # Extract the Calc Info -# output.update( -# { -# "calcinfo_nbasis": psivars.get("N BASIS", None), -# "calcinfo_nmo": psivars.get("N MO", None), -# "calcinfo_natom": psivars.get("N ATOMS", None), -# "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), -# "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), -# } -# ) - -# # Get the "canonical" properties -# output["return_energy"] = psivars["CURRENT ENERGY"] -# output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] - -# # Get the SCF properties -# output["scf_total_energy"] = psivars.get("HF TOTAL ENERGY", None) -# output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) -# output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) -# output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) - -# # Get the MP2 properties -# output["mp2_total_correlation_energy"] = psivars.get("MP2 CORRELATION ENERGY", None) -# output["mp2_total_energy"] = psivars.get("MP2 TOTAL ENERGY", None) -# output["mp2_same_spin_correlation_energy"] = psivars.get("MP2 SAME-SPIN CORRELATION ENERGY", None) -# output["mp2_opposite_spin_correlation_energy"] = psivars.get("MP2 OPPOSITE-SPIN CORRELATION ENERGY", None) -# return AtomicResultProperties(**output) - - -def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: + Args: + psivars (PreservingDict): Dictionary of the output results + Returns: + (AtomicResultProperties) Properties in a standard format + """ + # TODO (wardlt): Get more of the named variables out of the NWChem file + + # Initialize the output + output = dict() + + # Extract the Calc Info + output.update( + { + "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness + "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals + "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) + "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), + } + ) + + # Get the "canonical" properties + output["return_energy"] = psivars["CURRENT ENERGY"] + output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + + # Get the SCF properties + output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + + return AtomicResultProperties(**output) + + +def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: """Parses all the pieces of output from NWChem: the stdout in *nwout* Scratch files are not yet considered at this moment. @@ -411,7 +183,8 @@ def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, N # Parse the Madness output out_psivar, out_mol, out_grad, version, error = harvest_output(madout) - + + # If available, read higher-accuracy gradients # These were output using a Python Task in NWChem to read them out of the database if outfiles.get("mad.grad") is not None: @@ -431,15 +204,15 @@ def harvest(in_mol: Molecule, nwout: str, **outfiles) -> Tuple[PreservingDict, N """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) - else: - raise ValueError("""No coordinate information extracted from Madness output.""") + #else: + # raise ValueError("""No coordinate information extracted from Madness output.""") # If present, align the gradients and hessian with the original molecular coordinates # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the # rotated molecule, which we can use to determine how to rotate the gradients/hessian - amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + #amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - mill = data["mill"] # Retrieve tool with alignment routines + # mill = data["mill"] # Retrieve tool with alignment routines if out_grad is not None: out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) From b6c21cb227fd1fd58b8142e9cb399a47fb029352 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 17:32:57 -0400 Subject: [PATCH 046/102] wrote compute, fix mol.keywords, wrote parse_output --- qcengine/programs/madness/runner.py | 152 ++++++++++++++-------------- 1 file changed, 75 insertions(+), 77 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index f397b3e97..773fb96ab 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -20,9 +20,9 @@ from ...util import execute, create_mpi_invocation from ..model import ProgramHarness -# from .germinate import muster_modelchem -# from .harvester import extract_formatted_properties, harvest -# from .keywords import format_keywords +from .germinate import muster_modelchem +from .harvester import extract_formatted_properties, harvest +from .keywords import format_keywords pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -115,27 +115,27 @@ def get_version(self) -> str: return self.version_cache[which_prog] - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": pass -# """ -# Runs madness in executable mode -# """ -# self.found(raise_error=True) + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + """ + Runs madness in executable mode + """ + self.found(raise_error=True) -# job_inputs = self.build_input(input_model, config) -# success, dexe = self.execute(job_inputs) + job_inputs = self.build_input(input_model, config) + success, dexe = self.execute(job_inputs) -# if "There is an error in the input file" in dexe["stdout"]: -# raise InputError(dexe["stdout"]) -# if "not compiled" in dexe["stdout"]: -# # recoverable with a different compilation with optional modules -# raise InputError(dexe["stdout"]) + if "There is an error in the input file" in dexe["stdout"]: + raise InputError(dexe["stdout"]) + if "not compiled" in dexe["stdout"]: + # recoverable with a different compilation with optional modules + raise InputError(dexe["stdout"]) -# if success: -# dexe["outfiles"]["stdout"] = dexe["stdout"] -# dexe["outfiles"]["stderr"] = dexe["stderr"] -# return self.parse_output(dexe["outfiles"], input_model) -# else: -# raise UnknownError(dexe["stderr"]) + if success: + dexe["outfiles"]["stdout"] = dexe["stdout"] + dexe["outfiles"]["stderr"] = dexe["stderr"] + return self.parse_output(dexe["outfiles"], input_model) + else: + raise UnknownError(dexe["stderr"]) def build_input( self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None @@ -146,17 +146,13 @@ def build_input( opts=copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - # Handle memory (ROBERT) Look in keywords as well - # for nwchem, [GiB] --> [B] - # someday, replace with this: opts['memory'] = str(int(config.memory * (1024**3) / 1e6)) + ' mb' - #memory_size = int(config.memory * (1024 ** 3)) - #if config.use_mpiexec: # It is the memory per MPI rank - # memory_size //= config.nnodes * config.ncores // config.cores_per_rank - #opts["memory"] = memory_size # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - opts.update(moldata["keywords"]) + molData={} + for k,v in moldata["keywords"].items(): + molData['dft__'+k]=v + opts.update(molData) ## Handle Calc Type (ROBERT) mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) @@ -178,7 +174,7 @@ def build_input( ## Determine the command # Determine the command madnessrec["command"] = [which("madness")] - + print(madnessrec["infiles"]["input"]) return madnessrec @@ -193,50 +189,52 @@ def execute( def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" - ) -> "AtomicResult": pass # lgtm: [py/similar-function] - -# # Get the stdout from the calculation (required) -# stdout = outfiles.pop("stdout") - -# # Read the NWChem stdout file and, if needed, the hess or grad files -# qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) - -# if nwgrad is not None: -# qcvars["CURRENT GRADIENT"] = nwgrad - -# if nwhess is not None: -# qcvars["CURRENT HESSIAN"] = nwhess - -# # Normalize the output as a float or list of floats -# if input_model.driver.upper() == "PROPERTIES": -# retres = qcvars[f"CURRENT ENERGY"] -# else: -# retres = qcvars[f"CURRENT {input_model.driver.upper()}"] - -# if isinstance(retres, Decimal): -# retres = float(retres) -# elif isinstance(retres, np.ndarray): -# retres = retres.tolist() - -# # Get the formatted properties -# qcprops = extract_formatted_properties(qcvars) - -# # Format them inout an output -# output_data = { -# "schema_name": "qcschema_output", -# "schema_version": 1, -# "extras": {"outfiles": outfiles, **input_model.extras}, -# "properties": qcprops, -# "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), -# "return_result": retres, -# "stdout": stdout, -# } - -# # got to even out who needs plump/flat/Decimal/float/ndarray/list -# # Decimal --> str preserves precision -# output_data["extras"]["qcvars"] = { -# k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() -# } - -# output_data["success"] = True -# return AtomicResult(**{**input_model.dict(), **output_data}) + ) -> "AtomicResult": # lgtm: [py/similar-function] + + + # Get the stdout from the calculation (required) + stdout = outfiles.pop("stdout") + + # Read the NWChem stdout file and, if needed, the hess or grad files + qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + + if nwgrad is not None: + qcvars["CURRENT GRADIENT"] = nwgrad + + if nwhess is not None: + qcvars["CURRENT HESSIAN"] = nwhess + + # Normalize the output as a float or list of floats + if input_model.driver.upper() == "PROPERTIES": + retres = qcvars[f"CURRENT ENERGY"] + else: + print(qcvars) + retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + + if isinstance(retres, Decimal): + retres = float(retres) + elif isinstance(retres, np.ndarray): + retres = retres.tolist() + + # Get the formatted properties + qcprops = extract_formatted_properties(qcvars) + + # Format them inout an output + output_data = { + "schema_name": "qcschema_output", + "schema_version": 1, + "extras": {"outfiles": outfiles, **input_model.extras}, + "properties": qcprops, + "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), + "return_result": retres, + "stdout": stdout, + } + + # got to even out who needs plump/flat/Decimal/float/ndarray/list + # Decimal --> str preserves precision + output_data["extras"]["qcvars"] = { + k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() + } + + output_data["success"] = True + return AtomicResult(**{**input_model.dict(), **output_data}) From 8b93e470c017412b95df8bccde575b3a76045649 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 27 Apr 2020 23:05:30 -0400 Subject: [PATCH 047/102] make format --- qcengine/programs/madness/germinate.py | 43 +++++----- qcengine/programs/madness/keywords.py | 12 ++- qcengine/programs/madness/runner.py | 86 +++++++++---------- .../programs/tests/test_standard_suite_hf.py | 8 +- 4 files changed, 66 insertions(+), 83 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 69f5e1433..87c9418a0 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -4,7 +4,7 @@ # List of XC functionals known to NWChem _xc_functionals = [ - "hf", + "hf", "acm", "b3lyp", "beckehandh", @@ -90,7 +90,7 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: - """Converts the QC method into NWChem keywords + """Converts the QC method into NWChem keywords Args: method (str): Name of the QC method to use @@ -101,32 +101,31 @@ def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict (dict): Any options for NWChem """ - # Standardize the method name - method = method.lower() - opts = {} + # Standardize the method name + method = method.lower() + opts = {} # Map the run type to - #runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - #runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] + # runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] + # runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] # Write out the theory directive - mdccmd = f""## we don't need this right now - ## in the future when we link other exec this will change - ## all we have to do is add options to the dft block in order to change the run type - ## default in energy - # do nothing - if method =="optimization": - opts["dft__gopt"]= True - elif method == "response": - opts["dft__response"]= True - elif method.split()[0] in _xc_functionals: + mdccmd = f"" ## we don't need this right now + ## in the future when we link other exec this will change + ## all we have to do is add options to the dft block in order to change the run type + ## default in energy + # do nothing + if method == "optimization": + opts["dft__gopt"] = True + elif method == "response": + opts["dft__response"] = True + elif method.split()[0] in _xc_functionals: opts["dft__xc"] = method - else: - raise InputError(f"Method not recognized: {method}") + else: + raise InputError(f"Method not recognized: {method}") + return mdccmd, opts - - return mdccmd, opts -# # # # \ No newline at end of file +# # # # diff --git a/qcengine/programs/madness/keywords.py b/qcengine/programs/madness/keywords.py index 4b168f2ff..6059b582a 100644 --- a/qcengine/programs/madness/keywords.py +++ b/qcengine/programs/madness/keywords.py @@ -2,24 +2,22 @@ from typing import Any, Dict, Tuple - - def format_keyword(keyword: str, val: Any, lop_off: bool = True) -> Tuple[str, str]: """Function to reformat value `val` for `keyword` from python into nwchem-speak.""" - # Transform string booleans into " " + # Transform string booleans into " " if val is True: return keyword.lower(), "true" elif val is False: return keyword.lower(), "false" - # complete hack - #if keyword.upper() == "MEMORY": + # complete hack + # if keyword.upper() == "MEMORY": # return keyword.lower(), f"{val} byte" - elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play + elif isinstance(val, list): # if it is a list... join the list into a string ??? when is this in play text = " ".join([str(v) for v in val]) - elif isinstance(val, dict): # val is a dict... text is list + elif isinstance(val, dict): # val is a dict... text is list text = [] for k, v in val.items(): merge = [k] diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 773fb96ab..eccff8dd9 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -114,8 +114,7 @@ def get_version(self) -> str: return self.version_cache[which_prog] - - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs madness in executable mode """ @@ -125,9 +124,9 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe success, dexe = self.execute(job_inputs) if "There is an error in the input file" in dexe["stdout"]: - raise InputError(dexe["stdout"]) + raise InputError(dexe["stdout"]) if "not compiled" in dexe["stdout"]: - # recoverable with a different compilation with optional modules + # recoverable with a different compilation with optional modules raise InputError(dexe["stdout"]) if success: @@ -138,102 +137,95 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None - ) -> Dict[str, Any]: - # - madnessrec={"infiles":{},"scratch_directory":config.scratch_directory} + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + ) -> Dict[str, Any]: + # + madnessrec = {"infiles": {}, "scratch_directory": config.scratch_directory} - opts=copy.deepcopy(input_model.keywords) + opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - molData={} - for k,v in moldata["keywords"].items(): - molData['dft__'+k]=v + molData = {} + for k, v in moldata["keywords"].items(): + molData["dft__" + k] = v opts.update(molData) - + ## Handle Calc Type (ROBERT) mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) opts.update(mdcopts) - ## Handle the basis set (ROBERT) the question is what value of k + ## Handle the basis set (ROBERT) the question is what value of k # Log the job settings (LORI) Not sure if i need this logger.debug("JOB_OPTS") logger.debug(pp.pformat(opts)) - + # Handle conversion from schema (flat key/value) keywords into local format optcmd = format_keywords(opts) - #optcmd="dft\n xc hf \nend\n" + # optcmd="dft\n xc hf \nend\n" - - madnessrec["infiles"]["input"]=optcmd+molcmd + madnessrec["infiles"]["input"] = optcmd + molcmd ## Determine the command - # Determine the command + # Determine the command madnessrec["command"] = [which("madness")] print(madnessrec["infiles"]["input"]) return madnessrec - def execute( - self,inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute( - inputs["command"], - inputs["infiles"], - ) + success, dexe = execute(inputs["command"], inputs["infiles"],) return success, dexe def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] - - # Get the stdout from the calculation (required) + # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") # Read the NWChem stdout file and, if needed, the hess or grad files qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) if nwgrad is not None: - qcvars["CURRENT GRADIENT"] = nwgrad + qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: - qcvars["CURRENT HESSIAN"] = nwhess + qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": - retres = qcvars[f"CURRENT ENERGY"] + retres = qcvars[f"CURRENT ENERGY"] else: - print(qcvars) - retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + print(qcvars) + retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): - retres = float(retres) + retres = float(retres) elif isinstance(retres, np.ndarray): - retres = retres.tolist() + retres = retres.tolist() - # Get the formatted properties + # Get the formatted properties qcprops = extract_formatted_properties(qcvars) - # Format them inout an output + # Format them inout an output output_data = { - "schema_name": "qcschema_output", - "schema_version": 1, - "extras": {"outfiles": outfiles, **input_model.extras}, - "properties": qcprops, - "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), - "return_result": retres, - "stdout": stdout, + "schema_name": "qcschema_output", + "schema_version": 1, + "extras": {"outfiles": outfiles, **input_model.extras}, + "properties": qcprops, + "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), + "return_result": retres, + "stdout": stdout, } - # got to even out who needs plump/flat/Decimal/float/ndarray/list - # Decimal --> str preserves precision + # got to even out who needs plump/flat/Decimal/float/ndarray/list + # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { - k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() + k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data["success"] = True diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e7976b35e..92fa36c6f 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -18,8 +18,6 @@ def h2o(): return qcel.models.Molecule.from_data(smol) - - @pytest.fixture def nh2(): smol = """ @@ -160,15 +158,11 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [ - pytest.param("madness", None, {"dft__aobasis":"sto-3g","dft__econv": 1.0000e-05}), - - ], + "program,basis,keywords", [pytest.param("madness", None, {"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} - res = qcng.compute(resi, program, raise_error=True, return_dict=True) print(res["stdout"]) From 9d8c950d551a2478566e0abc61c9f89f6c2dfd6c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:38:17 -0400 Subject: [PATCH 048/102] alphabetize --- qcengine/programs/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index d98b912bb..24cf1db8f 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -100,9 +100,9 @@ def list_available_programs() -> Set[str]: register_program(CFOURHarness()) register_program(EntosHarness()) register_program(GAMESSHarness()) +register_program(MadnessHarness()) register_program(MolproHarness()) register_program(NWChemHarness()) -register_program(MadnessHarness()) register_program(Psi4Harness()) register_program(QChemHarness()) register_program(TeraChemHarness()) From f5bb2d81b0fd3f2607e9c8693a21fef3e7799835 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:41:14 -0400 Subject: [PATCH 049/102] which madness and use helium for command --- qcengine/programs/madness/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index eccff8dd9..7dd7dd7ec 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -67,7 +67,7 @@ def found(raise_error: bool = False) -> bool: """ qc = which( - "nwchem", + "madness", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", @@ -99,7 +99,7 @@ def get_version(self) -> str: success, output = execute( command, { - "v.moldft": "dft\nxc hf\nend\n\ngeometry\n H 0.00000000 0.00000000 -0.36579425\n H 0.00000000 0.00000000 0.36579425\nend\n" + "v.moldft": "dft\nxc hf\nend\n\ngeometry\n He 0.00000000 0.00000000 0.00000000 \n end\n" }, scratch_directory=config.scratch_directory, ) From acc9bda8b8b7e08501acd72cd447824c969617b0 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:46:35 -0400 Subject: [PATCH 050/102] add scratch_directory to input for execute --- qcengine/programs/madness/runner.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 7dd7dd7ec..4d4491e56 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -177,7 +177,12 @@ def build_input( def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute(inputs["command"], inputs["infiles"],) + success, dexe = execute( + inputs["command"], + inputs["infiles"], + scratch_directory=inputs["scratch_directory"], + + ) return success, dexe def parse_output( From 98f59bea9afeb5012b69d407b7de02e2d6385c68 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 11:50:38 -0400 Subject: [PATCH 051/102] the quadrupole work should have been in a different branch --- qcengine/programs/nwchem/harvester.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index fe83ae728..9d3e9ec57 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -696,6 +696,9 @@ def harvest_outfile_pass(outtext): if mobj: psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) + + + # Process CURRENT energies (TODO: needs better way) if "HF TOTAL ENERGY" in psivar: psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] From 38aa76250560ebb7a61e754d96bed9856d6fb591 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:00:02 -0400 Subject: [PATCH 052/102] added k=7 to keywords of test. k=7 is a default but its okay to define --- qcengine/programs/tests/test_standard_suite_hf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 92fa36c6f..e39c9d0ed 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -158,7 +158,7 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [pytest.param("madness", None, {"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + "program,basis,keywords", [pytest.param("madness", None, {"dft__k":7,"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} From 19f002ebe741395675f5a6cb423cdd0b61abf934 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:28:49 -0400 Subject: [PATCH 053/102] limit to collecting qc vars in results.py --- qcengine/programs/madness/harvester.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 213119734..d3379db6e 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -32,7 +32,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s """ # Loop over all steps - # TODO (wardlt): Is it only necessary to read the last two steps? pass_psivar = [] pass_coord = [] pass_grad = [] @@ -77,8 +76,10 @@ def harvest_outfile_pass(outtext): # 2)Calculation converged else: - OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] - PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + OPTIONS=[r'exchange-correlation',r'nuclear-repulsion',r'total'] + PSIVAR=['EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + #OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + #PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] optDict=dict(zip(OPTIONS,PSIVAR)) for var,VAR in optDict.items(): From eb21253aa4dd73e58f283e99baa31d7484ce11bc Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:34:11 -0400 Subject: [PATCH 054/102] return scf_exchange_correlation --- qcengine/programs/madness/harvester.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index d3379db6e..43bce8f35 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -158,6 +158,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION") #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) From 32fce6582f30c2a6c136ab0ecd1bf353fa12919c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:44:35 -0400 Subject: [PATCH 055/102] Add none --- qcengine/programs/madness/harvester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 43bce8f35..2256a8e94 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -158,7 +158,8 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION") + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION",None) + # TODO AdrianH right madness to output these variables #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) From eabe89d00dca0ee2857088ba2d4037a7c0a48379 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 4 May 2020 12:45:25 -0400 Subject: [PATCH 056/102] make format --- qcengine/programs/madness/runner.py | 7 +------ qcengine/programs/nwchem/harvester.py | 2 -- qcengine/programs/tests/test_standard_suite_hf.py | 3 ++- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 4d4491e56..9a3ee7e6c 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -177,12 +177,7 @@ def build_input( def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute( - inputs["command"], - inputs["infiles"], - scratch_directory=inputs["scratch_directory"], - - ) + success, dexe = execute(inputs["command"], inputs["infiles"], scratch_directory=inputs["scratch_directory"],) return success, dexe def parse_output( diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index 9d3e9ec57..a3c275ff7 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -697,8 +697,6 @@ def harvest_outfile_pass(outtext): psivar["DIPOLE MOMENT"] = np.array([mobj.group(1), mobj.group(2), mobj.group(3)]) psivar["TOTAL DIPOLE MOMENT"] = mobj.group(4) - - # Process CURRENT energies (TODO: needs better way) if "HF TOTAL ENERGY" in psivar: psivar["SCF TOTAL ENERGY"] = psivar["HF TOTAL ENERGY"] diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index e39c9d0ed..0c5b6929e 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -158,7 +158,8 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): @pytest.mark.parametrize( - "program,basis,keywords", [pytest.param("madness", None, {"dft__k":7,"dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + "program,basis,keywords", + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} From f18a0deb39e5eda677a288c7215406e1c9eca758 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Tue, 5 May 2020 09:40:32 -0400 Subject: [PATCH 057/102] move madness test into separate file --- qcengine/programs/tests/test_madness.py | 40 +++++++++++++++++++ .../programs/tests/test_standard_suite_hf.py | 19 --------- 2 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 qcengine/programs/tests/test_madness.py diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py new file mode 100644 index 000000000..027f01e4d --- /dev/null +++ b/qcengine/programs/tests/test_madness.py @@ -0,0 +1,40 @@ +import pytest +import qcelemental as qcel +from qcelemental.testing import compare_values + +import qcengine as qcng +from qcengine.testing import using + + +@pytest.fixture +def h2o(): + smol = """ + # R=0.958 A=104.5 + H 0.000000000000 1.431430901356 0.984293362719 + O 0.000000000000 0.000000000000 -0.124038860300 + H 0.000000000000 -1.431430901356 0.984293362719 + units au +""" + return qcel.models.Molecule.from_data(smol) + + + +@pytest.mark.parametrize( + "program,basis,keywords", + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], +) +def test_mad_hf(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + print(res["stdout"]) + + assert res["driver"] == "energy" + assert "provenance" in res + assert res["success"] is True + + # k=7 + scf_tot = -76.06718632 + + atol = 1.0e-5 + assert compare_values(scf_tot, res["return_result"], atol=atol) \ No newline at end of file diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 0c5b6929e..c782d5291 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -157,22 +157,3 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): assert compare_values(scf_tot, res["return_result"], atol=atol) -@pytest.mark.parametrize( - "program,basis,keywords", - [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], -) -def test_mad_hf(program, basis, keywords, h2o): - resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} - - res = qcng.compute(resi, program, raise_error=True, return_dict=True) - print(res["stdout"]) - - assert res["driver"] == "energy" - assert "provenance" in res - assert res["success"] is True - - # k=7 - scf_tot = -76.06718632 - - atol = 1.0e-5 - assert compare_values(scf_tot, res["return_result"], atol=atol) From f4f89f9e1e82d1b2fa29292fc0fc057096454a38 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Wed, 6 May 2020 17:14:48 -0400 Subject: [PATCH 058/102] got rid of stuff from nwchem --- qcengine/programs/madness/germinate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 87c9418a0..48df723a1 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -89,7 +89,8 @@ ] -def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +#def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem(method: str, ) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into NWChem keywords Args: From a30be6995954046b60f827bad13f32189cb12097 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Wed, 6 May 2020 17:17:51 -0400 Subject: [PATCH 059/102] format --- qcengine/programs/madness/germinate.py | 4 +- qcengine/programs/madness/harvester.py | 65 ++++++++++--------- qcengine/programs/tests/test_madness.py | 3 +- .../programs/tests/test_standard_suite_hf.py | 2 - 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 48df723a1..183f2448c 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -89,8 +89,8 @@ ] -#def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -def muster_modelchem(method: str, ) -> Tuple[str, Dict[str, Any]]: +# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem(method: str,) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into NWChem keywords Args: diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 2256a8e94..b455d977f 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -37,12 +37,12 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_grad = [] for outpass in re.split(r"Converged!", outtext, re.MULTILINE): psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) - pass_psivar.append(psivar)## all the variables extracted + pass_psivar.append(psivar) ## all the variables extracted pass_coord.append(madcoord) pass_grad.append(madgrad) # Determine which segment contained the last geometry - retindx = -1 #if pass_coord[-1] else -2 + retindx = -1 # if pass_coord[-1] else -2 return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error @@ -64,42 +64,46 @@ def harvest_outfile_pass(outtext): mobj = re.search( r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' +r'\s'+ r'multiresolution suite'+r'\s*$', outtext, re.MULTILINE) + # fmt: on if mobj: - logger.debug('matched version') + logger.debug("matched version") version = mobj.group(1) # Process SCF # 1)Fail to converge (TODO Robert ask for failed convergence) + # fmt: off mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + # fmt: on if mobj: - logger.debug('failed to converge') + logger.debug("failed to converge") # 2)Calculation converged else: - OPTIONS=[r'exchange-correlation',r'nuclear-repulsion',r'total'] - PSIVAR=['EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] - #OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] - #PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] - optDict=dict(zip(OPTIONS,PSIVAR)) - - for var,VAR in optDict.items(): - mobj = re.search(r'^\s+' + var + r'\s*' + NUMBER + r's*$', outtext, re.MULTILINE) + OPTIONS = [r"exchange-correlation", r"nuclear-repulsion", r"total"] + PSIVAR = ["EXCHANGE-CORRELATION", "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] + # OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] + # PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] + optDict = dict(zip(OPTIONS, PSIVAR)) + + for var, VAR in optDict.items(): + mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + r"s*$", outtext, re.MULTILINE) if mobj: - logger.debug('matched SCF')## not sure what this means + logger.debug("matched SCF") ## not sure what this means psivar[VAR] = mobj.group(1) # Other options - # Process CURRENT energies (TODO: needs better way) if "TOTAL SCF ENERGY" in psivar: psivar["CURRENT REFERENCE ENERGY"] = psivar["TOTAL SCF ENERGY"] psivar["CURRENT ENERGY"] = psivar["TOTAL SCF ENERGY"] - return psivar, psivar_coord, psivar_grad, version, error -def harvest_hessian(hess: str) -> np.ndarray: pass +def harvest_hessian(hess: str) -> np.ndarray: + pass + + # """Parses the contents of the NWChem hess file into a hessian array. # Args: @@ -108,7 +112,7 @@ def harvest_hessian(hess: str) -> np.ndarray: pass # (np.ndarray) Hessian matrix as a 2D array # """ - # Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python +# Change the "D[+-]" notation of Fortran output to "E[+-]" used by Python # hess_conv = hess.replace("D", "E") # # Parse all of the float values @@ -144,10 +148,10 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Extract the Calc Info output.update( { - "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness - "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals - "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out - "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) + "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness + "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals + "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), } ) @@ -158,11 +162,11 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Get the SCF properties output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION",None) - # TODO AdrianH right madness to output these variables - #output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) - #output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) - #output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) + output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) + # TODO AdrianH right madness to output these variables + # output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) + # output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) + # output["scf_dispersion_correction_energy"] = psivars.get("DFT DISPERSION ENERGY", None) return AtomicResultProperties(**output) @@ -186,8 +190,7 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, # Parse the Madness output out_psivar, out_mol, out_grad, version, error = harvest_output(madout) - - + # If available, read higher-accuracy gradients # These were output using a Python Task in NWChem to read them out of the database if outfiles.get("mad.grad") is not None: @@ -207,15 +210,15 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) - #else: + # else: # raise ValueError("""No coordinate information extracted from Madness output.""") # If present, align the gradients and hessian with the original molecular coordinates # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the # rotated molecule, which we can use to determine how to rotate the gradients/hessian - #amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) + # amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - # mill = data["mill"] # Retrieve tool with alignment routines + # mill = data["mill"] # Retrieve tool with alignment routines if out_grad is not None: out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 027f01e4d..f5d5d8ad5 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -18,7 +18,6 @@ def h2o(): return qcel.models.Molecule.from_data(smol) - @pytest.mark.parametrize( "program,basis,keywords", [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], @@ -37,4 +36,4 @@ def test_mad_hf(program, basis, keywords, h2o): scf_tot = -76.06718632 atol = 1.0e-5 - assert compare_values(scf_tot, res["return_result"], atol=atol) \ No newline at end of file + assert compare_values(scf_tot, res["return_result"], atol=atol) diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index c782d5291..e1aeeda49 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -155,5 +155,3 @@ def test_sp_hf_rohf(program, basis, keywords, nh2): atol = 1.0e-6 assert compare_values(scf_tot, res["return_result"], atol=atol) - - From 1d33d2bf132b569047ff66d58cdfbcc292ef7ea3 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 11 May 2020 18:57:50 -0400 Subject: [PATCH 060/102] get rid of extra arguments --- qcengine/programs/madness/germinate.py | 2 +- qcengine/programs/madness/runner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 183f2448c..1366f511b 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -91,7 +91,7 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: def muster_modelchem(method: str,) -> Tuple[str, Dict[str, Any]]: - """Converts the QC method into NWChem keywords + """Converts the QC method into MADNESS keywords Args: method (str): Name of the QC method to use diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 9a3ee7e6c..2c3378bae 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -153,7 +153,7 @@ def build_input( opts.update(molData) ## Handle Calc Type (ROBERT) - mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver, opts.pop("qc_module", False)) + mdccmd, mdcopts = muster_modelchem(input_model.model.method) opts.update(mdcopts) ## Handle the basis set (ROBERT) the question is what value of k From 20be271b091c209d05a2b2d41f1ffd12d836aa65 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 11 May 2020 20:23:47 -0400 Subject: [PATCH 061/102] make format --- qcengine/programs/tests/test_nwchem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index e742a58f9..99b03c281 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -142,4 +142,3 @@ def test_dipole(h20): assert compare_values(-0.00, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][0]), atol=1e-3) assert compare_values(-0.00, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][1]), atol=1e-3) assert compare_values(-0.272949872, float(res["extras"]["qcvars"]["DIPOLE MOMENT"][2]), atol=1e-5) - From 0357d4020f8d4014e29153e07b3b4e853694fcf2 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 18 May 2020 09:08:16 -0400 Subject: [PATCH 062/102] remove some imports and format --- qcengine/programs/madness/germinate.py | 3 --- qcengine/programs/madness/harvester.py | 6 ++++-- qcengine/programs/madness/runner.py | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 1366f511b..17ab918df 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -127,6 +127,3 @@ def muster_modelchem(method: str,) -> Tuple[str, Dict[str, Any]]: raise InputError(f"Method not recognized: {method}") return mdccmd, opts - - -# # # # diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index b455d977f..19f1be85c 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -1,11 +1,13 @@ import re import json import logging -from decimal import Decimal + +# from decimal import Decimal from typing import Tuple import numpy as np -import qcelemental as qcel + +# import qcelemental as qcel from qcelemental.models import Molecule from qcelemental.models.results import AtomicResultProperties from qcelemental.molparse import regex diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 2c3378bae..87e95bddf 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -1,7 +1,7 @@ """ Calls the Madness moldft executable. """ -import re +# import re import copy import logging import pprint @@ -11,7 +11,7 @@ import numpy as np import qcelemental as qcel from qcelemental.models import AtomicResult, Provenance, AtomicInput -from qcelemental.util import safe_version, which, which_import +from qcelemental.util import safe_version, which from qcengine.config import TaskConfig, get_config from qcengine.exceptions import UnknownError From 82d110b21888e6aa55a823d7807edb15a981fc68 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 25 Jun 2020 07:57:41 -0400 Subject: [PATCH 063/102] add @using("madness") to madness test --- qcengine/programs/tests/test_madness.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index f5d5d8ad5..71cfeb0b0 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -18,10 +18,12 @@ def h2o(): return qcel.models.Molecule.from_data(smol) +@using("madness") @pytest.mark.parametrize( "program,basis,keywords", [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) +@using("madness") def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} From 100656e8e5bd7de3bedda5abecd907af3ec1f798 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 17:16:15 -0400 Subject: [PATCH 064/102] qca looks for moldft executable for scf calculations --- qcengine/programs/madness/runner.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 87e95bddf..c1d8d2ece 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -67,7 +67,7 @@ def found(raise_error: bool = False) -> bool: """ qc = which( - "madness", + "moldft", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", @@ -171,7 +171,7 @@ def build_input( ## Determine the command # Determine the command madnessrec["command"] = [which("madness")] - print(madnessrec["infiles"]["input"]) + # print(madnessrec["infiles"]["input"]) return madnessrec def execute( @@ -187,14 +187,14 @@ def parse_output( # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") - # Read the NWChem stdout file and, if needed, the hess or grad files - qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + # Read the MADNESj stdout file and, if needed, the hess or grad files + qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) - if nwgrad is not None: - qcvars["CURRENT GRADIENT"] = nwgrad + if madgrad is not None: + qcvars["CURRENT GRADIENT"] = madgrad - if nwhess is not None: - qcvars["CURRENT HESSIAN"] = nwhess + if madhess is not None: + qcvars["CURRENT HESSIAN"] = madhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": From a7d86eba538d3141c86d3e7df4e27e60b1488bac Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 17:40:56 -0400 Subject: [PATCH 065/102] debugging madness harvester...split by "Converged!" --- qcengine/programs/madness/harvester.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 19f1be85c..ea360c994 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -37,7 +37,14 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_psivar = [] pass_coord = [] pass_grad = [] + # Write now we split at Converge + counter = 1 for outpass in re.split(r"Converged!", outtext, re.MULTILINE): + # for outpass in re.split(r"Iteration" + r"\s*" + r"[0 - 256]", outtext, re.MULTILINE): + + print("This is outpass ", counter) + print(outpass) + counter = counter + 1 psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) pass_psivar.append(psivar) ## all the variables extracted pass_coord.append(madcoord) @@ -45,6 +52,13 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s # Determine which segment contained the last geometry retindx = -1 # if pass_coord[-1] else -2 + for qvars in pass_psivar: + if "TOTAL SCF ENERGY" in qvars.keys(): + print(qvars["TOTAL SCF ENERGY"]) + else: + print("empty") + + print(pass_psivar) return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error @@ -209,7 +223,7 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, if in_mol: if abs(out_mol.nuclear_repulsion_energy() - in_mol.nuclear_repulsion_energy()) > 1.0e-3: raise ValueError( - """Madness outfile (NRE: %f) inconsistent with Psi4 input (NRE: %f).""" + """Madness outfile (NRE: %f) inconsistent with MADNESS input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) # else: @@ -222,9 +236,9 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, # mill = data["mill"] # Retrieve tool with alignment routines - if out_grad is not None: - out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) - if out_hess is not None: - out_hess = mill.align_hessian(np.array(out_hess)) + # if out_grad is not None: + # out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) + # if out_hess is not None: + # out_hess = mill.align_hessian(np.array(out_hess)) return out_psivar, out_hess, out_grad, out_mol, version, error From 5a10bf5c2b499475310a7dffe3df963274cda315 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 18:48:03 -0400 Subject: [PATCH 066/102] trying to debug harvester...Converged! --- qcengine/programs/madness/harvester.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index ea360c994..216ad6f2a 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -58,7 +58,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s else: print("empty") - print(pass_psivar) return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error From e937e62372da024809f414d76fbe9acc7eee49f8 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 18:48:58 -0400 Subject: [PATCH 067/102] using moldft executable...it's easier for now --- qcengine/programs/madness/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index c1d8d2ece..36da7fdd4 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -170,7 +170,7 @@ def build_input( madnessrec["infiles"]["input"] = optcmd + molcmd ## Determine the command # Determine the command - madnessrec["command"] = [which("madness")] + madnessrec["command"] = [which("moldft")] # print(madnessrec["infiles"]["input"]) return madnessrec From c6323135787d720f63e9ba4347dadb4c9bb87b1c Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 19:19:14 -0400 Subject: [PATCH 068/102] change test values to reflect changes in master madness --- qcengine/programs/tests/test_madness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index f5d5d8ad5..d61a93405 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -33,7 +33,7 @@ def test_mad_hf(program, basis, keywords, h2o): assert res["success"] is True # k=7 - scf_tot = -76.06718632 + scf_tot = -76.06720262 atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) From afd940bd78741eaceeec82754c21ef96db8b1a33 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Sun, 28 Jun 2020 19:35:05 -0400 Subject: [PATCH 069/102] Getting rid of debugging print statemtments --- qcengine/programs/madness/harvester.py | 7 ------- qcengine/programs/tests/test_madness.py | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 216ad6f2a..5e2c0e851 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -42,8 +42,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s for outpass in re.split(r"Converged!", outtext, re.MULTILINE): # for outpass in re.split(r"Iteration" + r"\s*" + r"[0 - 256]", outtext, re.MULTILINE): - print("This is outpass ", counter) - print(outpass) counter = counter + 1 psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) pass_psivar.append(psivar) ## all the variables extracted @@ -52,11 +50,6 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s # Determine which segment contained the last geometry retindx = -1 # if pass_coord[-1] else -2 - for qvars in pass_psivar: - if "TOTAL SCF ENERGY" in qvars.keys(): - print(qvars["TOTAL SCF ENERGY"]) - else: - print("empty") return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 184d69ecf..1657dcc05 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -28,7 +28,7 @@ def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} res = qcng.compute(resi, program, raise_error=True, return_dict=True) - print(res["stdout"]) + # print(res["stdout"]) assert res["driver"] == "energy" assert "provenance" in res From f8e70ca95b900e41a3efd3255e19adb0b8cc359b Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 17 Aug 2020 16:57:01 -0400 Subject: [PATCH 070/102] formatting --- qcengine/programs/tests/test_nwchem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 4f2c19a66..09ad21e67 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -271,4 +271,3 @@ def test_autoz_error(): ) assert "insufficient internal variables" not in result.error.error_message # Ok if it crashes for other reasons - From f250cfff86211ecb33a636327f3bbe57fe8808df Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Mon, 17 Aug 2020 20:08:38 -0400 Subject: [PATCH 071/102] comments- don't use mpi command as default --- qcengine/programs/madness/runner.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 36da7fdd4..a529e63c6 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -32,7 +32,6 @@ class MadnessHarness(ProgramHarness): """ Notes ----- - * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. """ _defaults = { @@ -61,9 +60,8 @@ def found(raise_error: bool = False) -> bool: Returns ------- bool - If both nwchem and its harness dependency networkx are found, returns True. - If raise_error is False and nwchem or networkx are missing, returns False. - If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + If Madness is found returns True. + If raise_error is False and Madness are missing, returns False. """ qc = which( @@ -85,14 +83,18 @@ def get_version(self) -> str: self.found(raise_error=True) # Get the node configuration + # We don't always need mpi to run madness...Instead we use threads and define + # MAD_NUM_THREADS=NUM... a call to moldft or madness will use the environment variable config = get_config() # Run MADNESS - which_prog = which("madness") - if config.use_mpiexec: - command = create_mpi_invocation(which_prog, config) - else: - command = [which_prog] + which_prog = which("moldft") + # we get the mpi comand but we don't use it..(Ask Robert about running madness with MPI) + # if config.use_mpiexec: + # command = create_mpi_invocation(which_prog, config) + # else: + # command = [which_prog] + command = [which_prog] command.append("v.moldft") if which_prog not in self.version_cache: @@ -170,6 +172,7 @@ def build_input( madnessrec["infiles"]["input"] = optcmd + molcmd ## Determine the command # Determine the command + # (TODO ) how can i be smart about choosing MAD_NUM_THREADS for a specific calculation madnessrec["command"] = [which("moldft")] # print(madnessrec["infiles"]["input"]) return madnessrec From 53fc940cbe02c460378f0f94060e7259adc178c5 Mon Sep 17 00:00:00 2001 From: ahurta92 Date: Tue, 18 Aug 2020 14:27:22 -0400 Subject: [PATCH 072/102] add lda to list of functionals --- qcengine/programs/madness/germinate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 17ab918df..25168deca 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -5,6 +5,7 @@ # List of XC functionals known to NWChem _xc_functionals = [ "hf", + "lda", "acm", "b3lyp", "beckehandh", From 69c3cd0a546330105ddc54ea1a9e761b5e277135 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 17 Nov 2021 15:27:56 -0500 Subject: [PATCH 073/102] black formatting --- devtools/scripts/conda_env.py | 8 +- docs/source/conf.py | 89 +++++----- examples/terachem_pbs.py | 2 +- qcengine/programs/madness/germinate.py | 20 ++- qcengine/programs/madness/runner.py | 20 ++- qcengine/programs/tests/test_madness.py | 4 +- versioneer.py | 227 +++++++++++++----------- 7 files changed, 201 insertions(+), 169 deletions(-) diff --git a/devtools/scripts/conda_env.py b/devtools/scripts/conda_env.py index 53332f0fe..8cfe8641e 100644 --- a/devtools/scripts/conda_env.py +++ b/devtools/scripts/conda_env.py @@ -4,10 +4,10 @@ import subprocess as sp # Args -parser = argparse.ArgumentParser(description='Creates a conda environment from file for a given Python version.') -parser.add_argument('-n', '--name', type=str, nargs=1, help='The name of the created Python environment') -parser.add_argument('-p', '--python', type=str, nargs=1, help='The version of the created Python environment') -parser.add_argument('conda_file', nargs='*', help='The file for the created Python environment') +parser = argparse.ArgumentParser(description="Creates a conda environment from file for a given Python version.") +parser.add_argument("-n", "--name", type=str, nargs=1, help="The name of the created Python environment") +parser.add_argument("-p", "--python", type=str, nargs=1, help="The version of the created Python environment") +parser.add_argument("conda_file", nargs="*", help="The file for the created Python environment") args = parser.parse_args() diff --git a/docs/source/conf.py b/docs/source/conf.py index 27c45e2c7..082654952 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,7 +10,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath("../..")) import qcengine # -- Path setup -------------------------------------------------------------- @@ -26,9 +26,9 @@ # -- Project information ----------------------------------------------------- -project = 'QCEngine' -copyright = f'2018-{datetime.datetime.today().year}, The Molecular Sciences Software Institute' -author = 'The QCArchive Development Team' +project = "QCEngine" +copyright = f"2018-{datetime.datetime.today().year}, The Molecular Sciences Software Institute" +author = "The QCArchive Development Team" # The short X.Y version version = qcengine.__version__ @@ -46,18 +46,18 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.intersphinx', - 'sphinx.ext.extlinks', - 'sphinx_automodapi.automodapi', - 'sphinx_automodapi.automodsumm', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", + "sphinx.ext.extlinks", + "sphinx_automodapi.automodapi", + "sphinx_automodapi.automodsumm", ] napoleon_google_docstring = False @@ -65,16 +65,16 @@ napoleon_use_ivar = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -89,7 +89,7 @@ exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'default' +pygments_style = "default" # -- Options for HTML output ------------------------------------------------- @@ -99,9 +99,10 @@ # try: import qcarchive_sphinx_theme - html_theme = 'qcarchive_sphinx_theme' + + html_theme = "qcarchive_sphinx_theme" except ModuleNotFoundError: - html_theme = 'sphinx_rtd_theme' + html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -112,7 +113,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -128,7 +129,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'QCEnginedoc' +htmlhelp_basename = "QCEnginedoc" # -- Options for LaTeX output ------------------------------------------------ @@ -137,15 +138,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -155,8 +153,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'QCEngine.tex', 'QCEngine Documentation', - 'The QCArchive Development Team', 'manual'), + (master_doc, "QCEngine.tex", "QCEngine Documentation", "The QCArchive Development Team", "manual"), ] @@ -164,10 +161,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'qcengine', 'QCEngine Documentation', - [author], 1) -] +man_pages = [(master_doc, "qcengine", "QCEngine Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -176,28 +170,35 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'QCEngine', 'QCEngine Documentation', - author, 'QCEngine', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "QCEngine", + "QCEngine Documentation", + author, + "QCEngine", + "One line description of project.", + "Miscellaneous", + ), ] # -- Extension configuration ------------------------------------------------- extlinks = { - 'issue': ('https://github.com/MolSSI/QCEngine/issues/%s', 'GH#'), - 'pr': ('https://github.com/MolSSI/QCEngine/pull/%s', 'GH#') + "issue": ("https://github.com/MolSSI/QCEngine/issues/%s", "GH#"), + "pr": ("https://github.com/MolSSI/QCEngine/pull/%s", "GH#"), } # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None), - 'numpy': ('https://docs.scipy.org/doc/numpy/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), - 'matplotlib': ('https://matplotlib.org/', None), - 'qcelemental': ('https://qcelemental.readthedocs.io/en/latest/', None) - } +intersphinx_mapping = { + "python": ("https://docs.python.org/3.7", None), + "numpy": ("https://docs.scipy.org/doc/numpy/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/reference/", None), + "matplotlib": ("https://matplotlib.org/", None), + "qcelemental": ("https://qcelemental.readthedocs.io/en/latest/", None), +} # -- Options for todo extension ---------------------------------------------- diff --git a/examples/terachem_pbs.py b/examples/terachem_pbs.py index a2e041d65..d0c343f50 100644 --- a/examples/terachem_pbs.py +++ b/examples/terachem_pbs.py @@ -22,4 +22,4 @@ model={"method": "pbe0", "basis": "6-31g"}, ) ret = prog.compute(inp) -print(ret) \ No newline at end of file +print(ret) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 17ab918df..1c3b31957 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -90,17 +90,19 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -def muster_modelchem(method: str,) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem( + method: str, +) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords - Args: - method (str): Name of the QC method to use - derint (str): Index of the run type - use_tce (bool): Whether to use the Tensor Contraction Engine - Returns: - (str): Task command for NWChem - (dict): Any options for NWChem - """ + Args: + method (str): Name of the QC method to use + derint (str): Index of the run type + use_tce (bool): Whether to use the Tensor Contraction Engine + Returns: + (str): Task command for NWChem + (dict): Any options for NWChem + """ # Standardize the method name method = method.lower() diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 36da7fdd4..cabd22348 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -30,10 +30,10 @@ class MadnessHarness(ProgramHarness): """ - Notes - ----- - * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. - """ + Notes + ----- + * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. + """ _defaults = { "name": "Madness", @@ -65,7 +65,7 @@ def found(raise_error: bool = False) -> bool: If raise_error is False and nwchem or networkx are missing, returns False. If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. - """ + """ qc = which( "moldft", return_bool=True, @@ -116,8 +116,8 @@ def get_version(self) -> str: def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ - Runs madness in executable mode - """ + Runs madness in executable mode + """ self.found(raise_error=True) job_inputs = self.build_input(input_model, config) @@ -177,7 +177,11 @@ def build_input( def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute(inputs["command"], inputs["infiles"], scratch_directory=inputs["scratch_directory"],) + success, dexe = execute( + inputs["command"], + inputs["infiles"], + scratch_directory=inputs["scratch_directory"], + ) return success, dexe def parse_output( diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 1657dcc05..246369085 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -21,7 +21,9 @@ def h2o(): @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + [ + pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}), + ], ) @using("madness") def test_mad_hf(program, basis, keywords, h2o): diff --git a/versioneer.py b/versioneer.py index 64fea1c89..a1eca36d8 100644 --- a/versioneer.py +++ b/versioneer.py @@ -1,4 +1,3 @@ - # Version: 0.18 """The Versioneer - like a rocketeer, but for versions. @@ -277,6 +276,7 @@ """ from __future__ import print_function + try: import configparser except ImportError: @@ -308,11 +308,13 @@ def get_root(): setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): - err = ("Versioneer was unable to run the project root directory. " - "Versioneer requires setup.py to be executed from " - "its immediate directory (like 'python setup.py COMMAND'), " - "or in a way that lets it use sys.argv[0] to find the root " - "(like 'python path/to/setup.py COMMAND').") + err = ( + "Versioneer was unable to run the project root directory. " + "Versioneer requires setup.py to be executed from " + "its immediate directory (like 'python setup.py COMMAND'), " + "or in a way that lets it use sys.argv[0] to find the root " + "(like 'python path/to/setup.py COMMAND')." + ) raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools @@ -325,8 +327,7 @@ def get_root(): me_dir = os.path.normcase(os.path.splitext(me)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir: - print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(me), versioneer_py)) + print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py)) except NameError: pass return root @@ -348,6 +349,7 @@ def get(parser, name): if parser.has_option("versioneer", name): return parser.get("versioneer", name) return None + cfg = VersioneerConfig() cfg.VCS = VCS cfg.style = get(parser, "style") or "" @@ -372,17 +374,18 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None @@ -390,10 +393,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + p = subprocess.Popen( + [c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None) + ) break except EnvironmentError: e = sys.exc_info()[1] @@ -418,7 +420,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, return stdout, p.returncode -LONG_VERSION_PY['git'] = ''' +LONG_VERSION_PY[ + "git" +] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -993,7 +997,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -1002,7 +1006,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = set([r for r in refs if re.search(r"\d", r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -1010,19 +1014,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None, + "date": date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags", + "date": None, + } @register_vcs_handler("git", "pieces_from_vcs") @@ -1037,8 +1048,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -1046,10 +1056,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = run_command( + GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -1072,17 +1081,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -1091,10 +1099,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -1105,13 +1112,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -1167,16 +1172,19 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + "version": dirname[len(parentdir_prefix) :], + "full-revisionid": None, + "dirty": False, + "error": None, + "date": None, + } else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -1205,11 +1213,9 @@ def versions_from_file(filename): contents = f.read() except EnvironmentError: raise NotThisMethod("unable to read _version.py") - mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) + mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: - mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", - contents, re.M | re.S) + mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) @@ -1218,8 +1224,7 @@ def versions_from_file(filename): def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) - contents = json.dumps(versions, sort_keys=True, - indent=1, separators=(",", ": ")) + contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) @@ -1251,8 +1256,7 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -1366,11 +1370,13 @@ def render_git_describe_long(pieces): def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None, + } if not style or style == "default": style = "pep440" # the default @@ -1390,9 +1396,13 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None, + "date": pieces.get("date"), + } class VersioneerBadRootError(Exception): @@ -1415,8 +1425,7 @@ def get_versions(verbose=False): handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose - assert cfg.versionfile_source is not None, \ - "please set versioneer.versionfile_source" + assert cfg.versionfile_source is not None, "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) @@ -1470,9 +1479,13 @@ def get_versions(verbose=False): if verbose: print("unable to compute version") - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, "error": "unable to compute version", - "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", + "date": None, + } def get_version(): @@ -1521,6 +1534,7 @@ def run(self): print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) + cmds["version"] = cmd_version # we override "build_py" in both distutils and setuptools @@ -1553,14 +1567,15 @@ def run(self): # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: - target_versionfile = os.path.join(self.build_lib, - cfg.versionfile_build) + target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) + cmds["build_py"] = cmd_build_py if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe + # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ @@ -1581,17 +1596,21 @@ def run(self): os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) + f.write( + LONG + % { + "DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + } + ) + cmds["build_exe"] = cmd_build_exe del cmds["build_py"] - if 'py2exe' in sys.modules: # py2exe enabled? + if "py2exe" in sys.modules: # py2exe enabled? try: from py2exe.distutils_buildexe import py2exe as _py2exe # py3 except ImportError: @@ -1610,13 +1629,17 @@ def run(self): os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % - {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) + f.write( + LONG + % { + "DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + } + ) + cmds["py2exe"] = cmd_py2exe # we override different "sdist" commands for both environments @@ -1643,8 +1666,8 @@ def make_release_tree(self, base_dir, files): # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) - write_to_version_file(target_versionfile, - self._versioneer_generated_versions) + write_to_version_file(target_versionfile, self._versioneer_generated_versions) + cmds["sdist"] = cmd_sdist return cmds @@ -1699,11 +1722,9 @@ def do_setup(): root = get_root() try: cfg = get_config_from_root(root) - except (EnvironmentError, configparser.NoSectionError, - configparser.NoOptionError) as e: + except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (EnvironmentError, configparser.NoSectionError)): - print("Adding sample versioneer config to setup.cfg", - file=sys.stderr) + print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) @@ -1712,15 +1733,18 @@ def do_setup(): print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] - f.write(LONG % {"DOLLAR": "$", - "STYLE": cfg.style, - "TAG_PREFIX": cfg.tag_prefix, - "PARENTDIR_PREFIX": cfg.parentdir_prefix, - "VERSIONFILE_SOURCE": cfg.versionfile_source, - }) - - ipy = os.path.join(os.path.dirname(cfg.versionfile_source), - "__init__.py") + f.write( + LONG + % { + "DOLLAR": "$", + "STYLE": cfg.style, + "TAG_PREFIX": cfg.tag_prefix, + "PARENTDIR_PREFIX": cfg.parentdir_prefix, + "VERSIONFILE_SOURCE": cfg.versionfile_source, + } + ) + + ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: @@ -1762,8 +1786,7 @@ def do_setup(): else: print(" 'versioneer.py' already in MANIFEST.in") if cfg.versionfile_source not in simple_includes: - print(" appending versionfile_source ('%s') to MANIFEST.in" % - cfg.versionfile_source) + print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source) with open(manifest_in, "a") as f: f.write("include %s\n" % cfg.versionfile_source) else: From 8479cf843db167a15a90605b693e507208bed66f Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 5 Jan 2022 18:36:13 -0500 Subject: [PATCH 074/102] Get runner.py to read moldft using new format --- qcengine/programs/madness/runner.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index cabd22348..9d4aa947b 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -36,7 +36,7 @@ class MadnessHarness(ProgramHarness): """ _defaults = { - "name": "Madness", + "name": "madness", "scratch": True, "thread_safe": True, "thread_parallel": True, @@ -67,7 +67,7 @@ def found(raise_error: bool = False) -> bool: """ qc = which( - "moldft", + "madness", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", @@ -89,6 +89,7 @@ def get_version(self) -> str: # Run MADNESS which_prog = which("madness") + if config.use_mpiexec: command = create_mpi_invocation(which_prog, config) else: @@ -99,7 +100,7 @@ def get_version(self) -> str: success, output = execute( command, { - "v.moldft": "dft\nxc hf\nend\n\ngeometry\n He 0.00000000 0.00000000 0.00000000 \n end\n" + "v.moldft": "dft\nxc lda\nend\ngeometry\nO 0.0 0.0 0.0\nH 1.4375 0.0 1.15\nH - 1.4375 0.0 1.15\nend\n" }, scratch_directory=config.scratch_directory, ) @@ -137,10 +138,11 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # - madnessrec = {"infiles": {}, "scratch_directory": config.scratch_directory} + madnessrec = {"infiles": {}, "scratch_directory": config.scratch_directory, + "scratch_messy": config.scratch_messy} opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} @@ -170,22 +172,24 @@ def build_input( madnessrec["infiles"]["input"] = optcmd + molcmd ## Determine the command # Determine the command - madnessrec["command"] = [which("moldft")] + madnessrec["command"] = [which("madness")] # print(madnessrec["infiles"]["input"]) return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: success, dexe = execute( inputs["command"], inputs["infiles"], + scratch_exist_ok=True, + scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], ) return success, dexe def parse_output( - self, outfiles: Dict[str, str], input_model: "AtomicInput" + self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) From 4699aa1739ef951ab804d1cdcf3ac666159175c7 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 5 Jan 2022 18:38:05 -0500 Subject: [PATCH 075/102] read new moldft output --- qcengine/programs/madness/harvester.py | 29 ++++++++++---------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 5e2c0e851..dececf34e 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -17,10 +17,10 @@ logger = logging.getLogger(__name__) -def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: +def harvest_moldft_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: """Function to read an entire MADNESS output file. - Reads all of the different "line search" segments of a file and returns + Read all of the different "line search" segments of a file and returns values from the last segment for which a geometry was written. Args: @@ -39,22 +39,15 @@ def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, s pass_grad = [] # Write now we split at Converge counter = 1 - for outpass in re.split(r"Converged!", outtext, re.MULTILINE): - # for outpass in re.split(r"Iteration" + r"\s*" + r"[0 - 256]", outtext, re.MULTILINE): - counter = counter + 1 - psivar, madcoord, madgrad, version, error = harvest_outfile_pass(outpass) - pass_psivar.append(psivar) ## all the variables extracted - pass_coord.append(madcoord) - pass_grad.append(madgrad) + splits = re.split(r"Converged!", outtext, re.MULTILINE)[-2] + final_outpass = re.split(r"Iteration", splits, re.MULTILINE)[-1] + psivar, madcoord, madgrad, version, error = harvest_outfile_moldft_pass(final_outpass) - # Determine which segment contained the last geometry - retindx = -1 # if pass_coord[-1] else -2 + return psivar, madcoord, madgrad, version, error - return pass_psivar[retindx], pass_coord[retindx], pass_grad[retindx], version, error - -def harvest_outfile_pass(outtext): +def harvest_outfile_moldft_pass(outtext): """Function to read Madness output file *outtext* and parse important quantum chemical information from it in @@ -70,7 +63,7 @@ def harvest_outfile_pass(outtext): # Process version mobj = re.search( - r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' +r'\s'+ r'multiresolution suite'+r'\s*$', + r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' + r'\s' + r'multiresolution suite' + r'\s*$', outtext, re.MULTILINE) # fmt: on if mobj: @@ -180,7 +173,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: - """Parses all the pieces of output from NWChem: the stdout in + """Parses all the pieces of output from Madness: the stdout in *nwout* Scratch files are not yet considered at this moment. Args: @@ -197,10 +190,10 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, """ # Parse the Madness output - out_psivar, out_mol, out_grad, version, error = harvest_output(madout) + out_psivar, out_mol, out_grad, version, error = harvest_moldft_output(madout) # If available, read higher-accuracy gradients - # These were output using a Python Task in NWChem to read them out of the database + # These were output using a Python Task in Madness to read them out of the database if outfiles.get("mad.grad") is not None: logger.debug("Reading higher-accuracy gradients") out_grad = json.loads(outfiles.get("mad.grad")) From 1ebe945e3f171a09d713884172d229ff1da8c651 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Sat, 8 Jan 2022 08:28:28 -0500 Subject: [PATCH 076/102] Set up initial molresponse runner. --- qcengine/programs/madness/runner.py | 76 +++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 9d4aa947b..51589023f 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -7,6 +7,7 @@ import pprint from decimal import Decimal from typing import Any, Dict, Optional, Tuple +from pathlib import Path import numpy as np import qcelemental as qcel @@ -122,28 +123,32 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe self.found(raise_error=True) job_inputs = self.build_input(input_model, config) + print(job_inputs) success, dexe = self.execute(job_inputs) - if "There is an error in the input file" in dexe["stdout"]: raise InputError(dexe["stdout"]) if "not compiled" in dexe["stdout"]: # recoverable with a different compilation with optional modules raise InputError(dexe["stdout"]) - if success: dexe["outfiles"]["stdout"] = dexe["stdout"] dexe["outfiles"]["stderr"] = dexe["stderr"] return self.parse_output(dexe["outfiles"], input_model) else: + print(dexe["stdout"]) + raise UnknownError(dexe["stderr"]) def build_input( self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # - madnessrec = {"infiles": {}, "scratch_directory": config.scratch_directory, - "scratch_messy": config.scratch_messy} + madnessrec = { + "infiles": {}, + "scratch_directory": config.scratch_directory, + "scratch_messy": config.scratch_messy} + ## These are the madness keywords opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} @@ -155,7 +160,9 @@ def build_input( opts.update(molData) ## Handle Calc Type (ROBERT) - mdccmd, mdcopts = muster_modelchem(input_model.model.method) + ## now returns respnse options as well + mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver) + opts.update(mdcopts) ## Handle the basis set (ROBERT) the question is what value of k @@ -166,26 +173,58 @@ def build_input( # Handle conversion from schema (flat key/value) keywords into local format optcmd = format_keywords(opts) + madnessrec["commands"] = {} + if mdccmd == "response": + dft_cmds = optcmd.split(mdccmd) + dft_cmds[1] = "response\n" + dft_cmds[1] + + madnessrec["infiles"]["moldft"] = {} + madnessrec["infiles"]["moldft"]["input"] = dft_cmds[0] + molcmd + madnessrec["infiles"]["molresponse"] = {} + madnessrec["infiles"]["molresponse"]["input"] = dft_cmds[1] + madnessrec["commands"]["moldft"] = [which("moldft")] + madnessrec["commands"]["molresponse"] = [which("molresponse")] + else: + dft_cmds = optcmd + madnessrec["infiles"]["moldft"] = {} + madnessrec["infiles"]["moldft"]["input"] = dft_cmds + molcmd + madnessrec["commands"]["moldft"] = [which("moldft")] + print(dft_cmds) # optcmd="dft\n xc hf \nend\n" - - madnessrec["infiles"]["input"] = optcmd + molcmd - ## Determine the command - # Determine the command - madnessrec["command"] = [which("madness")] # print(madnessrec["infiles"]["input"]) return madnessrec def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - success, dexe = execute( - inputs["command"], - inputs["infiles"], - scratch_exist_ok=True, - scratch_name=inputs.get("scratch_name", None), - scratch_directory=inputs["scratch_directory"], - ) + num_commands = len(inputs["commands"]) + print(num_commands) + if num_commands == 2: + success, dexe = execute( + inputs["commands"]["moldft"], + inputs["infiles"]["moldft"], + scratch_name=inputs.get("scratch_name", None), + scratch_directory=inputs["scratch_directory"], + scratch_messy=True + ) + success, dexe_response= execute( + inputs["commands"]["molresponse"], + inputs["infiles"]["molresponse"], + scratch_messy=False, + scratch_name=Path(dexe['scratch_directory']).name, + scratch_exist_ok=True + ) + else: + print(inputs["commands"]["moldft"]) + success, dexe = execute( + inputs["commands"]["moldft"], + inputs["infiles"]["moldft"], + scratch_exist_ok=True, + scratch_name=inputs.get("scratch_name", None), + scratch_directory=inputs["scratch_directory"], + scratch_messy=False, + ) return success, dexe def parse_output( @@ -194,7 +233,6 @@ def parse_output( # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") - # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) @@ -203,7 +241,6 @@ def parse_output( if madhess is not None: qcvars["CURRENT HESSIAN"] = madhess - # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] @@ -218,7 +255,6 @@ def parse_output( # Get the formatted properties qcprops = extract_formatted_properties(qcvars) - # Format them inout an output output_data = { "schema_name": "qcschema_output", From 9085d52d48530501397ba0412dadef14e4fd1232 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Sat, 8 Jan 2022 08:34:14 -0500 Subject: [PATCH 077/102] Run black --- qcengine/programs/madness/runner.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 51589023f..664949b56 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -140,13 +140,14 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { "infiles": {}, "scratch_directory": config.scratch_directory, - "scratch_messy": config.scratch_messy} + "scratch_messy": config.scratch_messy, + } ## These are the madness keywords opts = copy.deepcopy(input_model.keywords) @@ -196,7 +197,7 @@ def build_input( return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) print(num_commands) @@ -204,16 +205,17 @@ def execute( success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], + scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], - scratch_messy=True + scratch_messy=True, ) - success, dexe_response= execute( + success, dexe_response = execute( inputs["commands"]["molresponse"], inputs["infiles"]["molresponse"], scratch_messy=False, - scratch_name=Path(dexe['scratch_directory']).name, - scratch_exist_ok=True + scratch_name=Path(dexe["scratch_directory"]).name, + scratch_exist_ok=True, ) else: print(inputs["commands"]["moldft"]) @@ -228,7 +230,7 @@ def execute( return success, dexe def parse_output( - self, outfiles: Dict[str, str], input_model: "AtomicInput" + self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) From 835920b21f0f85fd19180fead3aa0bfe1365f4fd Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Sat, 8 Jan 2022 08:34:36 -0500 Subject: [PATCH 078/102] Add initial molresponse test --- qcengine/programs/tests/test_madness.py | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 246369085..cb4d665bb 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -41,3 +41,41 @@ def test_mad_hf(program, basis, keywords, h2o): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) + + +@using("madness") +@pytest.mark.parametrize( + "program,basis,keywords", + [ + pytest.param( + "madness", + None, + { + "dft__k": 7, + "dft__aobasis": "sto-3g", + "dft__econv": 1.0000e-05, + "response__firstorder": True, + "response__dipole": True, + "response__maxsub": 10, + "response__maxiter": 10, + "response__omega": 0.0, + }, + ), + ], +) +@using("madness") +def test_mad_hf_response(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "properties", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + # print(res["stdout"]) + + assert res["driver"] == "properties" + assert "provenance" in res + assert res["success"] is True + + # k=7 + scf_tot = -76.06720262 + + atol = 1.0e-5 + assert compare_values(scf_tot, res["return_result"], atol=atol) From e85db3790a3d75b8d985efaa7c627960dc8e7177 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 10 Jan 2022 20:27:53 -0500 Subject: [PATCH 079/102] Add initial molresponse runner and test --- qcengine/programs/madness/germinate.py | 31 ++++--- qcengine/programs/madness/harvester.py | 108 +++++++++++++++++++++++- qcengine/programs/madness/runner.py | 56 ++++++++---- qcengine/programs/tests/test_madness.py | 4 +- 4 files changed, 166 insertions(+), 33 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 1c3b31957..4c666f77a 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -92,6 +92,7 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: def muster_modelchem( method: str, + derint: int, ) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords @@ -106,26 +107,32 @@ def muster_modelchem( # Standardize the method name method = method.lower() + opts = {} # Map the run type to # runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - # runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "property"}[derint] + runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "molresponse"}[derint] # Write out the theory directive + if runtyp == "energy": + if method == "optimization": + opts["dft__gopt"] = True + elif method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + else: + raise InputError(f"Method not recognized: {method}") + mdccmd = f"" + elif runtyp == "molresponse": + if method.split()[0] in _xc_functionals: + opts["dft__xc"] = method + opts["response__xc"] = method + opts["response__archive"]="restartdata" + else: + raise InputError(f"Method not recognized: {method}") + mdccmd = f"response" ## we will split the options with the word response later - mdccmd = f"" ## we don't need this right now - ## in the future when we link other exec this will change ## all we have to do is add options to the dft block in order to change the run type ## default in energy # do nothing - if method == "optimization": - opts["dft__gopt"] = True - elif method == "response": - opts["dft__response"] = True - elif method.split()[0] in _xc_functionals: - opts["dft__xc"] = method - else: - raise InputError(f"Method not recognized: {method}") - return mdccmd, opts diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index dececf34e..8f55b6d67 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -172,7 +172,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert return AtomicResultProperties(**output) -def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: +def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: """Parses all the pieces of output from Madness: the stdout in *nwout* Scratch files are not yet considered at this moment. @@ -190,7 +190,11 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, """ # Parse the Madness output - out_psivar, out_mol, out_grad, version, error = harvest_moldft_output(madout) + out_psivar, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) + print(outfiles) + if "molresponse" in outfiles.keys(): + response_psi_var= harvest_response_file(outfiles["molresponse"]["stdout"]) + out_psivar.update(response_psi_var) # If available, read higher-accuracy gradients # These were output using a Python Task in Madness to read them out of the database @@ -227,3 +231,103 @@ def harvest(in_mol: Molecule, madout: str, **outfiles) -> Tuple[PreservingDict, # out_hess = mill.align_hessian(np.array(out_hess)) return out_psivar, out_hess, out_grad, out_mol, version, error + + +def harvest_response_file(outtext): + psivar = PreservingDict() + psivar_coord = None + psivar_grad = None + version = "" + error = "" # TODO (wardlt): The error string is never used. + pass_psivar = [] + pass_coord = [] + pass_grad = [] + # Write now we split at Converge + counter = 1 + + splits = re.split(r"Converged!", outtext, re.MULTILINE) + print(splits) + splits = splits[-1] + data = re.split(r"Iteration", splits, re.MULTILINE)[-1] + print(data) + + NUMBER = r"(?x:" + regex.NUMBER + ")" # NUMBER + NUMSPACE = NUMBER + r"\s*" # NUMBER + SPACE + + OPTIONS = [ + r"Number of Response States:", + r"Number of Ground States:", + r"k =", + ] + PSIVAR = ["NUM STATES", "NUM ORBITALS", "K"] + optDict = dict(zip(OPTIONS, PSIVAR)) + + for var, VAR in optDict.items(): + mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + r"\s*$", outtext, re.MULTILINE) + # print(mobj) + if mobj: + psivar[VAR] = mobj.group(1) + # Grab the Orbital Energies There are NUM ORBITALS + num_states = int(psivar["NUM STATES"]) + num_orbitals = int(psivar["NUM ORBITALS"]) + + print(num_states) + print(num_orbitals) + # print(NUMSPACE) + NUMSPACEORB = str() + for i in range(num_orbitals): + NUMSPACEORB += NUMSPACE + # print(NUMSPACEORB) + + var = r"Orbital Energies: \[\*\]" + VAR = "ORBITAL ENERGIES" + mobj = re.search( + r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", + outtext, + re.MULTILINE, + ) + # print(mobj) + + if mobj: + oe_list = [] + for i in range(num_orbitals): + oe_list.append(mobj.group(i + 1)) + + psivar[VAR] = np.array(oe_list, dtype=float) + + psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Polarizability Final", "POLARIZABILITY", num_states, num_states, psivar, data) + return psivar + + +def grab_tensor(var, VAR, row, col, psivar, data): + first_line = r"^\s*" + var + r"\s+" + NUMBER = r"(?x:" + regex.NUMBER + ")" # NUMBER + NUMSPACE = NUMBER + r"\s*" # NUMBER + SPACE + # print(first_line) + + CAPTURE_LINE = str() + for j in range(col): + CAPTURE_LINE += NUMSPACE + total = first_line + for i in range(row): + front = r"^\[" + str(i) + r",\*\]\s*" + line = front + CAPTURE_LINE + total += line + # print(line) + + mobj = re.search( + total, + data, + re.MULTILINE, + ) + # print(mobj) + if mobj: + oe_list = [] + for i in range(row): + for j in range(col): + oe_list.append(mobj.group(i + 1)) + tensor = np.array(oe_list) + psivar[VAR] = tensor.reshape((row, col)) + return psivar diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 664949b56..574759539 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -123,24 +123,31 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe self.found(raise_error=True) job_inputs = self.build_input(input_model, config) - print(job_inputs) success, dexe = self.execute(job_inputs) - if "There is an error in the input file" in dexe["stdout"]: - raise InputError(dexe["stdout"]) - if "not compiled" in dexe["stdout"]: + if "There is an error in the input file" in dexe["moldft"]["stdout"]: + raise InputError(dexe["moldft"]["stdout"]) + if "not compiled" in dexe["moldft"]["stdout"]: # recoverable with a different compilation with optional modules - raise InputError(dexe["stdout"]) + raise InputError(dexe["moldft"]["stdout"]) if success: - dexe["outfiles"]["stdout"] = dexe["stdout"] - dexe["outfiles"]["stderr"] = dexe["stderr"] - return self.parse_output(dexe["outfiles"], input_model) + num_commands = len(dexe) + print(num_commands) + if num_commands == 2: + dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] + dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] + dexe["molresponse"]["outfiles"]["stdout"] = dexe["molresponse"]["stdout"] + dexe["molresponse"]["outfiles"]["stderr"] = dexe["molresponse"]["stderr"] + else: + dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] + dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] + return self.parse_output(dexe, input_model) else: print(dexe["stdout"]) raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -182,7 +189,7 @@ def build_input( madnessrec["infiles"]["moldft"] = {} madnessrec["infiles"]["moldft"]["input"] = dft_cmds[0] + molcmd madnessrec["infiles"]["molresponse"] = {} - madnessrec["infiles"]["molresponse"]["input"] = dft_cmds[1] + madnessrec["infiles"]["molresponse"]["rinput"] = dft_cmds[1] madnessrec["commands"]["moldft"] = [which("moldft")] madnessrec["commands"]["molresponse"] = [which("molresponse")] else: @@ -197,10 +204,10 @@ def build_input( return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) - print(num_commands) + oexe = {} if num_commands == 2: success, dexe = execute( inputs["commands"]["moldft"], @@ -210,13 +217,18 @@ def execute( scratch_directory=inputs["scratch_directory"], scratch_messy=True, ) + oexe["moldft"] = dexe success, dexe_response = execute( inputs["commands"]["molresponse"], inputs["infiles"]["molresponse"], - scratch_messy=False, + scratch_messy=True, scratch_name=Path(dexe["scratch_directory"]).name, scratch_exist_ok=True, ) + oexe["molresponse"] = dexe_response + print(dexe) + print(dexe_response) + return success, oexe else: print(inputs["commands"]["moldft"]) success, dexe = execute( @@ -225,18 +237,26 @@ def execute( scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], - scratch_messy=False, + scratch_messy=True, ) - return success, dexe + oexe["moldft"] = dexe + return success, oexe def parse_output( - self, outfiles: Dict[str, str], input_model: "AtomicInput" + self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) - stdout = outfiles.pop("stdout") + stdout = outfiles["moldft"]["stdout"] + if "molresponse" in outfiles.keys(): + stdout += outfiles["molresponse"]["stdout"] + # Read the MADNESj stdout file and, if needed, the hess or grad files - qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) + qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, **outfiles) + ## pop the files because I think I need to + outfiles.pop("moldft") + if "molresponse" in outfiles.keys(): + outfiles.pop("molresponse") if madgrad is not None: qcvars["CURRENT GRADIENT"] = madgrad diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index cb4d665bb..04f2fcb22 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -54,7 +54,9 @@ def test_mad_hf(program, basis, keywords, h2o): "dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05, - "response__firstorder": True, + "dft__kain": True, + "response__first_order": True, + "response__kain": True, "response__dipole": True, "response__maxsub": 10, "response__maxiter": 10, From 7e7482886eb320696904a5ee35c0d37f98341728 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 10 Jan 2022 20:34:01 -0500 Subject: [PATCH 080/102] black formatting --- examples/terachem_pbs.py | 6 +- qcengine/procedures/berny.py | 5 +- qcengine/procedures/torsiondrive.py | 10 +- qcengine/programs/dftd3.py | 4 +- .../empirical_dispersion_resources.py | 5 +- qcengine/programs/madness/germinate.py | 7 +- qcengine/programs/madness/harvester.py | 14 +- qcengine/programs/madness/runner.py | 6 +- qcengine/programs/openmm.py | 6 +- .../tests/standard_suite_contracts.py | 21 +- qcengine/programs/tests/standard_suite_ref.py | 512 ++---------------- .../programs/tests/standard_suite_runner.py | 19 +- qcengine/programs/tests/test_alignment.py | 6 +- .../programs/tests/test_canonical_config.py | 23 +- .../programs/tests/test_canonical_fields.py | 8 +- qcengine/programs/tests/test_dftd3_mp2d.py | 27 +- qcengine/programs/tests/test_dftd4.py | 17 +- qcengine/programs/tests/test_ghost.py | 30 +- qcengine/programs/tests/test_madness.py | 4 +- qcengine/programs/tests/test_mrchem.py | 18 +- qcengine/programs/tests/test_nwchem.py | 6 +- qcengine/programs/tests/test_qcore.py | 6 +- .../programs/tests/test_standard_suite.py | 17 +- qcengine/programs/tests/test_turbomole.py | 15 +- qcengine/programs/tests/test_xtb.py | 50 +- qcengine/tests/test_procedures.py | 7 +- 26 files changed, 103 insertions(+), 746 deletions(-) diff --git a/examples/terachem_pbs.py b/examples/terachem_pbs.py index d0c343f50..0633e49d2 100644 --- a/examples/terachem_pbs.py +++ b/examples/terachem_pbs.py @@ -16,10 +16,6 @@ """ ) -inp = qcel.models.AtomicInput( - molecule=mol, - driver="energy", - model={"method": "pbe0", "basis": "6-31g"}, -) +inp = qcel.models.AtomicInput(molecule=mol, driver="energy", model={"method": "pbe0", "basis": "6-31g"},) ret = prog.compute(inp) print(ret) diff --git a/qcengine/procedures/berny.py b/qcengine/procedures/berny.py index 242f440c4..8122ef721 100644 --- a/qcengine/procedures/berny.py +++ b/qcengine/procedures/berny.py @@ -19,10 +19,7 @@ class BernyProcedure(ProcedureHarness): def found(self, raise_error: bool = False) -> bool: return which_import( - "berny", - return_bool=True, - raise_error=raise_error, - raise_msg="Please install via `pip install pyberny`.", + "berny", return_bool=True, raise_error=raise_error, raise_msg="Please install via `pip install pyberny`.", ) def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> "OptimizationInput": diff --git a/qcengine/procedures/torsiondrive.py b/qcengine/procedures/torsiondrive.py index 2e68906e4..8890d9488 100644 --- a/qcengine/procedures/torsiondrive.py +++ b/qcengine/procedures/torsiondrive.py @@ -190,11 +190,7 @@ def _spawn_optimization( **input_model.optimization_spec.keywords, "constraints": { "set": [ - { - "type": "dihedral", - "indices": dihedral, - "value": int(angle), - } + {"type": "dihedral", "indices": dihedral, "value": int(angle),} for dihedral, angle in zip(dihedrals, angles) ] }, @@ -213,9 +209,7 @@ def _spawn_optimization( ) @staticmethod - def _find_final_results( - optimization_results: List[OptimizationResult], - ) -> Tuple[float, Molecule]: + def _find_final_results(optimization_results: List[OptimizationResult],) -> Tuple[float, Molecule]: """Returns the energy and final molecule of the lowest energy optimization in a set.""" diff --git a/qcengine/programs/dftd3.py b/qcengine/programs/dftd3.py index db733013f..b00cb1d8b 100644 --- a/qcengine/programs/dftd3.py +++ b/qcengine/programs/dftd3.py @@ -284,9 +284,7 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> output_data = { "extras": input_model.extras, "native_files": {k: v for k, v in outfiles.items() if v is not None}, - "properties": { - "return_energy": calcinfo[f"CURRENT ENERGY"], - }, + "properties": {"return_energy": calcinfo[f"CURRENT ENERGY"],}, "provenance": Provenance( creator="DFTD3", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name ), diff --git a/qcengine/programs/empirical_dispersion_resources.py b/qcengine/programs/empirical_dispersion_resources.py index 494e286eb..802e3607a 100644 --- a/qcengine/programs/empirical_dispersion_resources.py +++ b/qcengine/programs/empirical_dispersion_resources.py @@ -868,10 +868,7 @@ def get_params(entry: dict, base: dict, defaults: list) -> dict: # Make Psi4's citation style checker happy if citation is not None: citation = " " + citation + "\n" - definitions[method] = dict( - params=params, - citation=citation, - ) + definitions[method] = dict(params=params, citation=citation,) except KeyError: continue diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 4c666f77a..fd51bc7e1 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -90,10 +90,7 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -def muster_modelchem( - method: str, - derint: int, -) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem(method: str, derint: int,) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords Args: @@ -127,7 +124,7 @@ def muster_modelchem( if method.split()[0] in _xc_functionals: opts["dft__xc"] = method opts["response__xc"] = method - opts["response__archive"]="restartdata" + opts["response__archive"] = "restartdata" else: raise InputError(f"Method not recognized: {method}") mdccmd = f"response" ## we will split the options with the word response later diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 8f55b6d67..33069afbd 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -193,7 +193,7 @@ def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, M out_psivar, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) print(outfiles) if "molresponse" in outfiles.keys(): - response_psi_var= harvest_response_file(outfiles["molresponse"]["stdout"]) + response_psi_var = harvest_response_file(outfiles["molresponse"]["stdout"]) out_psivar.update(response_psi_var) # If available, read higher-accuracy gradients @@ -281,11 +281,7 @@ def harvest_response_file(outtext): var = r"Orbital Energies: \[\*\]" VAR = "ORBITAL ENERGIES" - mobj = re.search( - r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", - outtext, - re.MULTILINE, - ) + mobj = re.search(r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", outtext, re.MULTILINE,) # print(mobj) if mobj: @@ -317,11 +313,7 @@ def grab_tensor(var, VAR, row, col, psivar, data): total += line # print(line) - mobj = re.search( - total, - data, - re.MULTILINE, - ) + mobj = re.search(total, data, re.MULTILINE,) # print(mobj) if mobj: oe_list = [] diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 574759539..699c9e716 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -147,7 +147,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -204,7 +204,7 @@ def build_input( return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) oexe = {} @@ -243,7 +243,7 @@ def execute( return success, oexe def parse_output( - self, outfiles: Dict[str, str], input_model: "AtomicInput" + self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) diff --git a/qcengine/programs/openmm.py b/qcengine/programs/openmm.py index 9606bcb76..a4915392f 100644 --- a/qcengine/programs/openmm.py +++ b/qcengine/programs/openmm.py @@ -100,11 +100,7 @@ def found(raise_error: bool = False) -> bool: ) # for openmm, try new import, then old import if not found - openmm_found = which_import( - "openmm", - return_bool=True, - raise_error=False, - ) + openmm_found = which_import("openmm", return_bool=True, raise_error=False,) if not openmm_found: openmm_found = which_import( ".openmm", diff --git a/qcengine/programs/tests/standard_suite_contracts.py b/qcengine/programs/tests/standard_suite_contracts.py index 1d8143103..264e18358 100644 --- a/qcengine/programs/tests/standard_suite_contracts.py +++ b/qcengine/programs/tests/standard_suite_contracts.py @@ -209,12 +209,7 @@ def contractual_mp2( ) or ( ((qc_module == "psi4-occ" and reference == "rohf" and method in ["olccd"])) - and pv - in [ - "MP2 CORRELATION ENERGY", - "MP2 TOTAL ENERGY", - "MP2 SINGLES ENERGY", - ] + and pv in ["MP2 CORRELATION ENERGY", "MP2 TOTAL ENERGY", "MP2 SINGLES ENERGY",] ) or ( ( @@ -700,21 +695,11 @@ def contractual_ccsd( ) or ( (qc_module == "cfour-vcc" and reference in ["rohf"] and method in ["ccsd", "ccsd(t)"]) - and pv - in [ - "CCSD SAME-SPIN CORRELATION ENERGY", - "CCSD SINGLES ENERGY", - "CCSD DOUBLES ENERGY", - ] + and pv in ["CCSD SAME-SPIN CORRELATION ENERGY", "CCSD SINGLES ENERGY", "CCSD DOUBLES ENERGY",] ) or ( (qc_module == "cfour-ecc" and reference in ["rohf"] and method in ["ccsd", "ccsd(t)"]) - and pv - in [ - "CCSD OPPOSITE-SPIN CORRELATION ENERGY", - "CCSD SINGLES ENERGY", - "CCSD DOUBLES ENERGY", - ] + and pv in ["CCSD OPPOSITE-SPIN CORRELATION ENERGY", "CCSD SINGLES ENERGY", "CCSD DOUBLES ENERGY",] ) or ( ( diff --git a/qcengine/programs/tests/standard_suite_ref.py b/qcengine/programs/tests/standard_suite_ref.py index fd054460f..2e2acb400 100644 --- a/qcengine/programs/tests/standard_suite_ref.py +++ b/qcengine/programs/tests/standard_suite_ref.py @@ -363,16 +363,7 @@ "CCSD CORRELATION ENERGY": -0.208743643, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04857419039, - "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.001989217717, - 0.0, - 0.0, - -0.001989217717, - ] - ).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.001989217717, 0.0, 0.0, -0.001989217717,]).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ [-0.00114793, 0.0, 0.0, 0.00114793, 0.0, 0.0], @@ -537,17 +528,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.05669988343022163, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.009624481085, - 0.0, - 0.005505796371, - -0.004812240542, - 0.0, - -0.005505796371, - -0.004812240542, - ] + [0.0, 0.0, 0.009624481085, 0.0, 0.005505796371, -0.004812240542, 0.0, -0.005505796371, -0.004812240542,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -735,17 +716,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.050177977945205, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.007512595487, - 0.0, - 0.004613769715, - -0.003756297743, - 0.0, - -0.004613769715, - -0.003756297743, - ] + [0.0, 0.0, 0.007512595487, 0.0, 0.004613769715, -0.003756297743, 0.0, -0.004613769715, -0.003756297743,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -1355,17 +1326,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.06530131, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - -0.000531535533, - 0.0, - -0.000960201925, - 0.000265767766, - 0.0, - 0.000960201925, - 0.000265767766, - ] + [0.0, 0.0, -0.000531535533, 0.0, -0.000960201925, 0.000265767766, 0.0, 0.000960201925, 0.000265767766,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -1603,17 +1564,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.058006927914493, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - -0.003374258422, - 0.0, - -0.002334452569, - 0.001687129211, - 0.0, - 0.002334452569, - 0.001687129211, - ] + [0.0, 0.0, -0.003374258422, 0.0, -0.002334452569, 0.001687129211, 0.0, 0.002334452569, 0.001687129211,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3188,17 +3139,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03520162545964887, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.025490652204, - 0.0, - 0.013491755791, - -0.012745326102, - 0.0, - -0.013491755791, - -0.012745326102, - ] + [0.0, 0.0, 0.025490652204, 0.0, 0.013491755791, -0.012745326102, 0.0, -0.013491755791, -0.012745326102,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -3256,17 +3197,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033935818857082, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.029278727285, - 0.0, - 0.015813927533, - -0.014639363642, - 0.0, - -0.015813927533, - -0.014639363642, - ] + [0.0, 0.0, 0.029278727285, 0.0, 0.015813927533, -0.014639363642, 0.0, -0.015813927533, -0.014639363642,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3426,17 +3357,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.04161633, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.013731673196, - 0.0, - 0.005352105826, - -0.006865836598, - 0.0, - -0.005352105826, - -0.006865836598, - ] + [0.0, 0.0, 0.013731673196, 0.0, 0.005352105826, -0.006865836598, 0.0, -0.005352105826, -0.006865836598,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -3494,17 +3415,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.039907245914335, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.016842165003, - 0.0, - 0.007150136873, - -0.008421082502, - 0.0, - -0.007150136873, - -0.008421082502, - ] + [0.0, 0.0, 0.016842165003, 0.0, 0.007150136873, -0.008421082502, 0.0, -0.007150136873, -0.008421082502,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3940,17 +3851,7 @@ "MP2 SINGLES ENERGY": -0.0028296307982793997, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03541709278508698, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.025609525826, - 0.0, - 0.013506941035, - -0.012804762913, - 0.0, - -0.013506941035, - -0.012804762913, - ] + [0.0, 0.0, 0.025609525826, 0.0, 0.013506941035, -0.012804762913, 0.0, -0.013506941035, -0.012804762913,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -3986,17 +3887,7 @@ "CCSD SINGLES ENERGY": -0.00327524740575, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033982707798170, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.029273628227, - 0.0, - 0.015808308241, - -0.014636814114, - 0.0, - -0.015808308241, - -0.014636814114, - ] + [0.0, 0.0, 0.029273628227, 0.0, 0.015808308241, -0.014636814114, 0.0, -0.015808308241, -0.014636814114,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.003901085777, "CCSD(T) TOTAL GRADIENT": np.array( # vcc fd @@ -4055,17 +3946,7 @@ "MP2 SINGLES ENERGY": -0.00298375, "MP2 SAME-SPIN CORRELATION ENERGY": -0.04178535, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.0138883429, - 0.0, - 0.005389090661, - -0.00694417145, - 0.0, - -0.005389090661, - -0.00694417145, - ] + [0.0, 0.0, 0.0138883429, 0.0, 0.005389090661, -0.00694417145, 0.0, -0.005389090661, -0.00694417145,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -4101,17 +3982,7 @@ "CCSD SINGLES ENERGY": -0.00338286103325, "CCSD SAME-SPIN CORRELATION ENERGY": -0.039891470497466, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.016833254665, - 0.0, - 0.007144029475, - -0.008416627332, - 0.0, - -0.007144029475, - -0.008416627332, - ] + [0.0, 0.0, 0.016833254665, 0.0, 0.007144029475, -0.008416627332, 0.0, -0.007144029475, -0.008416627332,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.005233938447, "CCSD(T) TOTAL GRADIENT": np.array( # vcc fd @@ -4214,16 +4085,7 @@ "CCSD CORRELATION ENERGY": -0.2068152041, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.0478712079, - "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.002335204281, - 0.0, - 0.0, - -0.002335204281, - ] - ).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.002335204281, 0.0, 0.0, -0.002335204281,]).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ [-0.00134759, 0.0, 0.0, 0.00134759, 0.0, 0.0], @@ -4332,17 +4194,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.055833980855745646, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.010245839621, - 0.0, - 0.005893268945, - -0.00512291981, - 0.0, - -0.005893268945, - -0.00512291981, - ] + [0.0, 0.0, 0.010245839621, 0.0, 0.005893268945, -0.00512291981, 0.0, -0.005893268945, -0.00512291981,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -4503,17 +4355,7 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # ncc fd [ - [ - -0.009738397928, - 0.0, - 0.0, - 0.004869198964, - 0.0, - 0.0, - 0.004869198964, - 0.0, - 0.0, - ], + [-0.009738397928, 0.0, 0.0, 0.004869198964, 0.0, 0.0, 0.004869198964, 0.0, 0.0,], [ 0.0, 0.711718022073, @@ -4536,17 +4378,7 @@ -0.215200115829, -0.234532728875, ], - [ - 0.004869198964, - 0.0, - 0.0, - -0.004677737322, - 0.0, - 0.0, - -0.000191461642, - 0.0, - 0.0, - ], + [0.004869198964, 0.0, 0.0, -0.004677737322, 0.0, 0.0, -0.000191461642, 0.0, 0.0,], [ 0.0, -0.355859011036, @@ -4569,17 +4401,7 @@ -0.032052678263, 0.011378546847, ], - [ - 0.004869198964, - 0.0, - 0.0, - -0.000191461642, - 0.0, - 0.0, - -0.004677737322, - 0.0, - 0.0, - ], + [0.004869198964, 0.0, 0.0, -0.000191461642, 0.0, 0.0, -0.004677737322, 0.0, 0.0,], [ 0.0, -0.355859011036, @@ -4825,17 +4647,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.06126410, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.00033347691, - 0.0, - -0.00056224437, - -0.000166738455, - 0.0, - 0.00056224437, - -0.000166738455, - ] + [0.0, 0.0, 0.00033347691, 0.0, -0.00056224437, -0.000166738455, 0.0, 0.00056224437, -0.000166738455,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -4970,17 +4782,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.054051928864870, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - -0.002486174824, - 0.0, - -0.001923330621, - 0.001243087412, - 0.0, - 0.001923330621, - 0.001243087412, - ] + [0.0, 0.0, -0.002486174824, 0.0, -0.001923330621, 0.001243087412, 0.0, 0.001923330621, 0.001243087412,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -5096,17 +4898,7 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # ncc fd [ - [ - -0.000589405884, - 0.0, - 0.0, - 0.000294702942, - 0.0, - 0.0, - 0.000294702942, - 0.0, - 0.0, - ], + [-0.000589405884, 0.0, 0.0, 0.000294702942, 0.0, 0.0, 0.000294702942, 0.0, 0.0,], [ 0.0, 0.690892000177, @@ -5129,17 +4921,7 @@ -0.204108329476, -0.230131473162, ], - [ - 0.000294702942, - 0.0, - 0.0, - -0.000024281097, - 0.0, - 0.0, - -0.000270421845, - 0.0, - 0.0, - ], + [0.000294702942, 0.0, 0.0, -0.000024281097, 0.0, 0.0, -0.000270421845, 0.0, 0.0,], [ 0.0, -0.345446000089, @@ -5162,17 +4944,7 @@ -0.031796321125, 0.011304513494, ], - [ - 0.000294702942, - 0.0, - 0.0, - -0.000270421845, - 0.0, - 0.0, - -0.000024281097, - 0.0, - 0.0, - ], + [0.000294702942, 0.0, 0.0, -0.000270421845, 0.0, 0.0, -0.000024281097, 0.0, 0.0,], [ 0.0, -0.345446000089, @@ -6198,17 +5970,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03445360441348938, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.026279427993, - 0.0, - 0.013998590506, - -0.013139713997, - 0.0, - -0.013998590506, - -0.013139713997, - ] + [0.0, 0.0, 0.026279427993, 0.0, 0.013998590506, -0.013139713997, 0.0, -0.013998590506, -0.013139713997,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -6253,17 +6015,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033248190929062, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.030055915902, - 0.0, - 0.016307167756, - -0.015027957951, - 0.0, - -0.016307167756, - -0.015027957951, - ] + [0.0, 0.0, 0.030055915902, 0.0, 0.016307167756, -0.015027957951, 0.0, -0.016307167756, -0.015027957951,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -6289,17 +6041,7 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # vcc fd [ - [ - -0.027539040923, - 0.0, - 0.0, - 0.013769520462, - 0.0, - 0.0, - 0.013769520462, - 0.0, - 0.0, - ], + [-0.027539040923, 0.0, 0.0, 0.013769520462, 0.0, 0.0, 0.013769520462, 0.0, 0.0,], [ 0.0, 0.615442549333, @@ -6322,17 +6064,7 @@ -0.189939555413, -0.196757901375, ], - [ - 0.013769520462, - 0.0, - 0.0, - -0.012609341037, - 0.0, - 0.0, - -0.001160179424, - 0.0, - 0.0, - ], + [0.013769520462, 0.0, 0.0, -0.012609341037, 0.0, 0.0, -0.001160179424, 0.0, 0.0,], [ 0.0, -0.307721274667, @@ -6355,17 +6087,7 @@ -0.028374542796, 0.009458104761, ], - [ - 0.013769520462, - 0.0, - 0.0, - -0.001160179424, - 0.0, - 0.0, - -0.012609341037, - 0.0, - 0.0, - ], + [0.013769520462, 0.0, 0.0, -0.001160179424, 0.0, 0.0, -0.012609341037, 0.0, 0.0,], [ 0.0, -0.307721274667, @@ -6426,17 +6148,7 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03822454, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.014740098324, - 0.0, - 0.005852228009, - -0.007370049162, - 0.0, - -0.005852228009, - -0.007370049162, - ] + [0.0, 0.0, 0.014740098324, 0.0, 0.005852228009, -0.007370049162, 0.0, -0.005852228009, -0.007370049162,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -6481,17 +6193,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.036526852874970, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.017883390799, - 0.0, - 0.00765987541, - -0.0089416954, - 0.0, - -0.00765987541, - -0.0089416954, - ] + [0.0, 0.0, 0.017883390799, 0.0, 0.00765987541, -0.0089416954, 0.0, -0.00765987541, -0.0089416954,] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -6861,17 +6563,7 @@ "MP2 SINGLES ENERGY": -0.0028059971624814647, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03466304269235235, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.026398091851, - 0.0, - 0.014012163884, - -0.013199045925, - 0.0, - -0.014012163884, - -0.013199045925, - ] + [0.0, 0.0, 0.026398091851, 0.0, 0.014012163884, -0.013199045925, 0.0, -0.014012163884, -0.013199045925,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -6902,17 +6594,7 @@ "CCSD SINGLES ENERGY": -0.003256808469230, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033291143258924, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.030051791297, - 0.0, - 0.016301545337, - -0.015025895649, - 0.0, - -0.016301545337, - -0.015025895649, - ] + [0.0, 0.0, 0.030051791297, 0.0, 0.016301545337, -0.015025895649, 0.0, -0.016301545337, -0.015025895649,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.003863167899, # cfour only "CCSDT CORRELATION ENERGY": -0.18030677104047, # vcc (different orbs: -0.18031166502580) @@ -6937,17 +6619,7 @@ "MP2 SINGLES ENERGY": -0.00294339, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03837483, "MP2 TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.014894057335, - 0.0, - 0.005886660707, - -0.007447028667, - 0.0, - -0.005886660707, - -0.007447028667, - ] + [0.0, 0.0, 0.014894057335, 0.0, 0.005886660707, -0.007447028667, 0.0, -0.005886660707, -0.007447028667,] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -6982,17 +6654,7 @@ "CCSD SINGLES ENERGY": -0.003354603508621, "CCSD SAME-SPIN CORRELATION ENERGY": -0.036502859698546, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.017873897449, - 0.0, - 0.007653541045, - -0.008936948724, - 0.0, - -0.007653541045, - -0.008936948724, - ] + [0.0, 0.0, 0.017873897449, 0.0, 0.007653541045, -0.008936948724, 0.0, -0.007653541045, -0.008936948724,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00504351, # cfour only "CCSDT CORRELATION ENERGY": -0.19824510672649, # vcc @@ -7937,17 +7599,7 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.05670210, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 ae cd+cd - [ - 0.0, - 0.0, - 0.009643414073, - 0.0, - 0.005501440694, - -0.004821707036, - 0.0, - -0.005501440694, - -0.004821707036, - ] + [0.0, 0.0, 0.009643414073, 0.0, 0.005501440694, -0.004821707036, 0.0, -0.005501440694, -0.004821707036,] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.22643303, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -7978,17 +7630,7 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.06530655, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 ae cd+cd - [ - 0.0, - 0.0, - -0.000546229785, - 0.0, - -0.000967320028, - 0.000273114892, - 0.0, - 0.000967320028, - 0.000273114892, - ] + [0.0, 0.0, -0.000546229785, 0.0, -0.000967320028, 0.000273114892, 0.0, 0.000967320028, 0.000273114892,] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.27294416, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -8288,17 +7930,7 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.05583617, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 fc cd+cd - [ - 0.0, - 0.0, - 0.010264703011, - 0.0, - 0.00588885358, - -0.005132351506, - 0.0, - -0.00588885358, - -0.005132351506, - ] + [0.0, 0.0, 0.010264703011, 0.0, 0.00588885358, -0.005132351506, 0.0, -0.00588885358, -0.005132351506,] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.22415794, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -8329,17 +7961,7 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.06126931, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 fc cd+cd - [ - 0.0, - 0.0, - 0.000318778691, - 0.0, - -0.000569356625, - -0.000159389346, - 0.0, - 0.000569356625, - -0.000159389346, - ] + [0.0, 0.0, 0.000318778691, 0.0, -0.000569356625, -0.000159389346, 0.0, 0.000569356625, -0.000159389346,] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.24747710, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -9082,16 +8704,7 @@ "CCSD CORRELATION ENERGY": -0.20886884012911314, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04845491, - "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.001970675302, - 0.0, - 0.0, - -0.001970675302, - ] - ).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.001970675302, 0.0, 0.0, -0.001970675302,]).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.0019380186429220421, # "XXX TOTAL GRADIENT": np.zeros(6).reshape((-1, 3)), # "XXX TOTAL HESSIAN": np.zeros(36).reshape((6, 6)), @@ -9139,17 +8752,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.05009877, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.007518759967, - 0.0, - 0.004613106602, - -0.003759379983, - 0.0, - -0.004613106602, - -0.003759379983, - ] + [0.0, 0.0, 0.007518759967, 0.0, 0.004613106602, -0.003759379983, 0.0, -0.004613106602, -0.003759379983,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00524345, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), @@ -9504,16 +9107,7 @@ "CCSD CORRELATION ENERGY": -0.20694032546082639, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04775171, - "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.002316563628, - 0.0, - 0.0, - -0.002316563628, - ] - ).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.002316563628, 0.0, 0.0, -0.002316563628,]).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.001922093564526723, # "XXX TOTAL GRADIENT": np.zeros(6).reshape((-1, 3)), # "XXX TOTAL HESSIAN": np.zeros(36).reshape((6, 6)), @@ -9561,17 +9155,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04931891, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - 0.008124347934, - 0.0, - 0.004987676555, - -0.004062173967, - 0.0, - -0.004987676555, - -0.004062173967, - ] + [0.0, 0.0, 0.008124347934, 0.0, 0.004987676555, -0.004062173967, 0.0, -0.004987676555, -0.004062173967,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00521721, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), @@ -9620,17 +9204,7 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.05404876, "CCSD TOTAL GRADIENT": np.array( - [ - 0.0, - 0.0, - -0.002520920562, - 0.0, - -0.001932133533, - 0.001260460281, - 0.0, - 0.001932133533, - 0.001260460281, - ] + [0.0, 0.0, -0.002520920562, 0.0, -0.001932133533, 0.001260460281, 0.0, 0.001932133533, 0.001260460281,] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00709505, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), diff --git a/qcengine/programs/tests/standard_suite_runner.py b/qcengine/programs/tests/standard_suite_runner.py index d84b3fbe5..ada719592 100644 --- a/qcengine/programs/tests/standard_suite_runner.py +++ b/qcengine/programs/tests/standard_suite_runner.py @@ -126,12 +126,7 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): if is_dft: atol_g = 6.0e-6 chash = answer_hash( - system=subject.name, - basis=basis, - fcae=fcae, - scf_type=scf_type, - reference=reference, - corl_type=corl_type, + system=subject.name, basis=basis, fcae=fcae, scf_type=scf_type, reference=reference, corl_type=corl_type, ) ref_block = std_suite[chash] @@ -139,12 +134,7 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): atol_conv = 1.0e-4 rtol_conv = 1.0e-3 chash_conv = answer_hash( - system=subject.name, - basis=basis, - fcae=fcae, - reference=reference, - corl_type="conv", - scf_type="pk", + system=subject.name, basis=basis, fcae=fcae, reference=reference, corl_type="conv", scf_type="pk", ) ref_block_conv = std_suite[chash_conv] @@ -154,10 +144,7 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): **{ "molecule": subject, "driver": driver, - "model": { - "method": method, - "basis": inp.get("basis", "(auto)"), - }, + "model": {"method": method, "basis": inp.get("basis", "(auto)"),}, "keywords": inp["keywords"], } ) diff --git a/qcengine/programs/tests/test_alignment.py b/qcengine/programs/tests/test_alignment.py index 8870d5241..019f0c5c1 100644 --- a/qcengine/programs/tests/test_alignment.py +++ b/qcengine/programs/tests/test_alignment.py @@ -62,11 +62,7 @@ def clsd_open_pmols(): ) @pytest.mark.parametrize( "driver", - [ - pytest.param("energy", id="ene0"), - pytest.param("gradient", id="grd1"), - pytest.param("hessian", id="hes2"), - ], + [pytest.param("energy", id="ene0"), pytest.param("gradient", id="grd1"), pytest.param("hessian", id="hes2"),], ) @pytest.mark.parametrize( "basis, subjects", diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index c0108d1ad..19cea1163 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -90,14 +90,7 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req # << Config - config = qcng.config.get_config( - hostname="something", - local_options={ - "ncores": 1, - "nnodes": 1, - "memory": 1.555, - }, - ) + config = qcng.config.get_config(hostname="something", local_options={"ncores": 1, "nnodes": 1, "memory": 1.555,},) # << Run @@ -156,11 +149,7 @@ def test_local_options_scratch(program, model, keywords): scratch_directory = tempfile.mkdtemp(suffix="_" + program) config = qcng.config.get_config( - hostname="something", - local_options={ - "scratch_directory": scratch_directory, - "scratch_messy": True, - }, + hostname="something", local_options={"scratch_directory": scratch_directory, "scratch_messy": True,}, ) # << Run @@ -232,13 +221,7 @@ def test_local_options_ncores(program, model, keywords, ncores): # << Config - config = qcng.config.get_config( - hostname="something", - local_options={ - "ncores": ncores, - "nnodes": 1, - }, - ) + config = qcng.config.get_config(hostname="something", local_options={"ncores": ncores, "nnodes": 1,},) # << Run diff --git a/qcengine/programs/tests/test_canonical_fields.py b/qcengine/programs/tests/test_canonical_fields.py index eeaed01c2..a76026333 100644 --- a/qcengine/programs/tests/test_canonical_fields.py +++ b/qcengine/programs/tests/test_canonical_fields.py @@ -36,13 +36,7 @@ def test_protocol_native(program, model, keywords, native): protocols = { "native_files": native, } - config = qcng.config.get_config( - hostname="something", - local_options={ - "ncores": 1, - "nnodes": 1, - }, - ) + config = qcng.config.get_config(hostname="something", local_options={"ncores": 1, "nnodes": 1,},) # << Run diff --git a/qcengine/programs/tests/test_dftd3_mp2d.py b/qcengine/programs/tests/test_dftd3_mp2d.py index ba5c1321c..6f604463c 100644 --- a/qcengine/programs/tests/test_dftd3_mp2d.py +++ b/qcengine/programs/tests/test_dftd3_mp2d.py @@ -1728,11 +1728,7 @@ def test_dftd3__run_dftd3__2body(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( - molecule=mol, - driver="gradient", - **inp["qcsk"], - ) + atin = AtomicInput(molecule=mol, driver="gradient", **inp["qcsk"],) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() pprint.pprint(jrec) @@ -1793,11 +1789,7 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( - molecule=mol, - driver="gradient", - **inp["qcsk"], - ) + atin = AtomicInput(molecule=mol, driver="gradient", **inp["qcsk"],) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() @@ -1896,14 +1888,7 @@ def test_sapt_pairwise(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( - molecule=mol, - driver="energy", - model={"method": inp["lbl"]}, - keywords={ - "pair_resolved": True, - }, - ) + atin = AtomicInput(molecule=mol, driver="energy", model={"method": inp["lbl"]}, keywords={"pair_resolved": True,},) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() @@ -1917,11 +1902,7 @@ def test_sapt_pairwise(inp, subjects, request): @pytest.mark.parametrize( - "program", - [ - pytest.param("gcp", marks=using("gcp")), - pytest.param("mctc-gcp", marks=using("mctc-gcp")), - ], + "program", [pytest.param("gcp", marks=using("gcp")), pytest.param("mctc-gcp", marks=using("mctc-gcp")),], ) @pytest.mark.parametrize( "subjects", diff --git a/qcengine/programs/tests/test_dftd4.py b/qcengine/programs/tests/test_dftd4.py index fdd88d061..4eda4e5ab 100644 --- a/qcengine/programs/tests/test_dftd4.py +++ b/qcengine/programs/tests/test_dftd4.py @@ -21,9 +21,7 @@ def test_dftd4_task_b97m_m01(): return_result = -0.025024986301735823 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), - model={"method": "b97m"}, - driver="energy", + molecule=qcng.get_molecule("mindless-01"), model={"method": "b97m"}, driver="energy", ) atomic_result = qcng.compute(atomic_input, "dftd4") @@ -62,13 +60,7 @@ def test_dftd4_task_tpss_m02(): atomic_input = qcel.models.AtomicInput( molecule=qcng.get_molecule("mindless-02"), model={"method": ""}, - keywords={ - "params_tweaks": { - "s8": 1.76596355, - "a1": 0.42822303, - "a2": 4.54257102, - }, - }, + keywords={"params_tweaks": {"s8": 1.76596355, "a1": 0.42822303, "a2": 4.54257102,},}, driver="gradient", ) @@ -156,10 +148,7 @@ def test_dftd4_task_cold_fusion(): model={"method": "pbe"}, driver="energy", ) - error = qcel.models.ComputeError( - error_type="input error", - error_message="Too close interatomic distances found", - ) + error = qcel.models.ComputeError(error_type="input error", error_message="Too close interatomic distances found",) atomic_result = qcng.compute(atomic_input, "dftd4") diff --git a/qcengine/programs/tests/test_ghost.py b/qcengine/programs/tests/test_ghost.py index 710679da3..14095b7e3 100644 --- a/qcengine/programs/tests/test_ghost.py +++ b/qcengine/programs/tests/test_ghost.py @@ -81,16 +81,7 @@ def test_simple_ghost(program, basis, keywords, hene): marks=using("gamess"), ), pytest.param("nwchem", "6-31g*", {"scf__thresh": 1.0e-8}, id="nwchem", marks=using("nwchem")), - pytest.param( - "psi4", - "6-31g*", - { - "scf_type": "pk", - "mp2_type": "conv", - }, - id="psi4", - marks=using("psi4"), - ), + pytest.param("psi4", "6-31g*", {"scf_type": "pk", "mp2_type": "conv",}, id="psi4", marks=using("psi4"),), ], ) def test_tricky_ghost(qcprog, subject, basis, keywords): @@ -151,26 +142,11 @@ def test_tricky_ghost(qcprog, subject, basis, keywords): "qcprog, basis, keywords", [ pytest.param("cfour", "aug-pvdz", {"scf_conv": 12}, id="cfour", marks=using("cfour")), - pytest.param( - "gamess", - "accd", - {"mp2__nacore": 0, "contrl__ispher": 1}, - id="gamess", - marks=using("gamess"), - ), + pytest.param("gamess", "accd", {"mp2__nacore": 0, "contrl__ispher": 1}, id="gamess", marks=using("gamess"),), pytest.param( "nwchem", "aug-cc-pvdz", {"scf__nr": 1.0, "scf__thresh": 1.0e-8}, id="nwchem", marks=using("nwchem") ), - pytest.param( - "psi4", - "aug-cc-pvdz", - { - "scf_type": "pk", - "mp2_type": "conv", - }, - id="psi4", - marks=using("psi4"), - ), + pytest.param("psi4", "aug-cc-pvdz", {"scf_type": "pk", "mp2_type": "conv",}, id="psi4", marks=using("psi4"),), ], ) def test_atom_labels(qcprog, basis, keywords): diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 04f2fcb22..38d7ea831 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -21,9 +21,7 @@ def h2o(): @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [ - pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}), - ], + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) @using("madness") def test_mad_hf(program, basis, keywords, h2o): diff --git a/qcengine/programs/tests/test_mrchem.py b/qcengine/programs/tests/test_mrchem.py index 2ad79e32c..fee8eab80 100644 --- a/qcengine/programs/tests/test_mrchem.py +++ b/qcengine/programs/tests/test_mrchem.py @@ -25,14 +25,7 @@ def test_energy(h2o): "world_unit": "bohr", } - inp = qcel.models.AtomicInput( - molecule=h2o, - driver="energy", - model={ - "method": "BLYP", - }, - keywords=mr_kws, - ) + inp = qcel.models.AtomicInput(molecule=h2o, driver="energy", model={"method": "BLYP",}, keywords=mr_kws,) res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) @@ -59,14 +52,7 @@ def test_dipole(h2o): "world_unit": "bohr", } - inp = qcel.models.AtomicInput( - molecule=h2o, - driver="properties", - model={ - "method": "BLYP", - }, - keywords=mr_kws, - ) + inp = qcel.models.AtomicInput(molecule=h2o, driver="properties", model={"method": "BLYP",}, keywords=mr_kws,) res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 9e1feb857..9f9021090 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -322,11 +322,7 @@ def test_conv_threshold(h20v2, method, keyword, init_iters, use_tce): "molecule": h20v2, "model": {"method": method, "basis": "sto-3g"}, "driver": "energy", - "keywords": { - keyword: init_iters, - "qc_module": use_tce, - "scf__uhf": True, - }, # UHF needed for SCF test + "keywords": {keyword: init_iters, "qc_module": use_tce, "scf__uhf": True,}, # UHF needed for SCF test }, "nwchem", raise_error=True, diff --git a/qcengine/programs/tests/test_qcore.py b/qcengine/programs/tests/test_qcore.py index c4ff4e9fa..10d66a508 100644 --- a/qcengine/programs/tests/test_qcore.py +++ b/qcengine/programs/tests/test_qcore.py @@ -18,11 +18,7 @@ ) def test_qcore_methods(method, energy, gradient_norm): - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), - model=method, - driver="gradient", - ) + atomic_input = qcel.models.AtomicInput(molecule=qcng.get_molecule("water"), model=method, driver="gradient",) atomic_result = qcng.compute(atomic_input, "qcore") diff --git a/qcengine/programs/tests/test_standard_suite.py b/qcengine/programs/tests/test_standard_suite.py index b08af9cdb..a5b41cb35 100644 --- a/qcengine/programs/tests/test_standard_suite.py +++ b/qcengine/programs/tests/test_standard_suite.py @@ -123,10 +123,7 @@ def _trans_key(qc, bas, key): @pytest.mark.parametrize( - "dertype", - [ - pytest.param(0, id="ene0"), - ], + "dertype", [pytest.param(0, id="ene0"),], ) @pytest.mark.parametrize( "basis, subjects", @@ -276,11 +273,7 @@ def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, reque @pytest.mark.parametrize( - "dertype", - [ - 0, - ], - ids=["ene0"], + "dertype", [0,], ids=["ene0"], ) @pytest.mark.parametrize( "basis, subjects", @@ -353,11 +346,7 @@ def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reque @pytest.mark.parametrize( - "dertype", - [ - 0, - ], - ids=["ene0"], + "dertype", [0,], ids=["ene0"], ) @pytest.mark.parametrize( "basis, subjects", diff --git a/qcengine/programs/tests/test_turbomole.py b/qcengine/programs/tests/test_turbomole.py index 16a97c3c3..456024e9b 100644 --- a/qcengine/programs/tests/test_turbomole.py +++ b/qcengine/programs/tests/test_turbomole.py @@ -126,10 +126,7 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): resi = { "molecule": h2o, "driver": "hessian", - "model": { - "method": method, - "basis": "def2-SVP", - }, + "model": {"method": method, "basis": "def2-SVP",}, "keywords": keywords, } @@ -145,19 +142,13 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): @using("turbomole") @pytest.mark.parametrize( - "method, keywords, ref_eigvals", - [ - ("ricc2", {}, (1.65405531e-01, 9.63690706e-01, 1.24676634e00)), - ], + "method, keywords, ref_eigvals", [("ricc2", {}, (1.65405531e-01, 9.63690706e-01, 1.24676634e00)),], ) def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp): resi = { "molecule": h2o_ricc2_def2svp, "driver": "hessian", - "model": { - "method": method, - "basis": "def2-SVP", - }, + "model": {"method": method, "basis": "def2-SVP",}, "keywords": keywords, } diff --git a/qcengine/programs/tests/test_xtb.py b/qcengine/programs/tests/test_xtb.py index 86148ce06..ce1e5d859 100644 --- a/qcengine/programs/tests/test_xtb.py +++ b/qcengine/programs/tests/test_xtb.py @@ -41,9 +41,7 @@ def test_xtb_task_gfn1xtb_m01(): ) atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), - model={"method": "GFN1-xTB"}, - driver="gradient", + molecule=qcng.get_molecule("mindless-01"), model={"method": "GFN1-xTB"}, driver="gradient", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -82,10 +80,7 @@ def test_xtb_task_gfn1xtb_m02(): molecule=qcng.get_molecule("mindless-02"), model={"method": "GFN1-xTB"}, driver="gradient", - keywords={ - "accuracy": 0.1, - "electronic_temperature": 500.0, - }, + keywords={"accuracy": 0.1, "electronic_temperature": 500.0,}, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -124,9 +119,7 @@ def test_xtb_task_gfn1xtb_m03(): molecule=qcng.get_molecule("mindless-03"), model={"method": "GFN1-xTB"}, driver="gradient", - keywords={ - "solvent": "chcl3", - }, + keywords={"solvent": "chcl3",}, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -165,9 +158,7 @@ def test_xtb_task_gfn1xtb_m04(): } atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), - model={"method": "GFN1-xTB"}, - driver="properties", + molecule=qcng.get_molecule("mindless-04"), model={"method": "GFN1-xTB"}, driver="properties", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -186,9 +177,7 @@ def test_xtb_task_gfn1xtb_m05(): return_result = -29.038403257613453 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), - model={"method": "GFN1-xTB"}, - driver="energy", + molecule=qcng.get_molecule("mindless-05"), model={"method": "GFN1-xTB"}, driver="energy", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -225,9 +214,7 @@ def test_xtb_task_gfn2xtb_m01(): ) atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), - model={"method": "GFN2-xTB"}, - driver="gradient", + molecule=qcng.get_molecule("mindless-01"), model={"method": "GFN2-xTB"}, driver="gradient", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -266,10 +253,7 @@ def test_xtb_task_gfn2xtb_m02(): molecule=qcng.get_molecule("mindless-02"), model={"method": "GFN2-xTB"}, driver="gradient", - keywords={ - "accuracy": 0.1, - "electronic_temperature": 500.0, - }, + keywords={"accuracy": 0.1, "electronic_temperature": 500.0,}, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -308,9 +292,7 @@ def test_xtb_task_gfn2xtb_m03(): molecule=qcng.get_molecule("mindless-03"), model={"method": "GFN2-xTB"}, driver="gradient", - keywords={ - "solvent": "chcl3", - }, + keywords={"solvent": "chcl3",}, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -349,9 +331,7 @@ def test_xtb_task_gfn2xtb_m04(): } atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), - model={"method": "GFN2-xTB"}, - driver="properties", + molecule=qcng.get_molecule("mindless-04"), model={"method": "GFN2-xTB"}, driver="properties", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -370,9 +350,7 @@ def test_xtb_task_gfn2xtb_m05(): return_result = -27.73598761779656 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), - model={"method": "GFN2-xTB"}, - driver="energy", + molecule=qcng.get_molecule("mindless-05"), model={"method": "GFN2-xTB"}, driver="energy", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -386,9 +364,7 @@ def test_xtb_task_gfn2xtb_m05(): def test_xtb_task_unknown_method(): atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), - model={"method": "GFN-xTB"}, - driver="energy", + molecule=qcng.get_molecule("water"), model={"method": "GFN-xTB"}, driver="energy", ) error = qcel.models.ComputeError(error_type="input_error", error_message="Invalid method GFN-xTB provided in model") @@ -402,9 +378,7 @@ def test_xtb_task_unknown_method(): def test_xtb_task_unsupported_driver(): atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), - model={"method": "GFN2-xTB"}, - driver="hessian", + molecule=qcng.get_molecule("water"), model={"method": "GFN2-xTB"}, driver="hessian", ) error = qcel.models.ComputeError( error_type="input_error", error_message="Calculation succeeded but invalid driver request provided" diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index 99dce2717..a0345acba 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -297,12 +297,7 @@ def test_torsiondrive_generic(): input_specification=QCInputSpecification(driver=DriverEnum.gradient, model=Model(method="UFF", basis=None)), initial_molecule=[qcng.get_molecule("ethane")] * 2, optimization_spec=OptimizationSpecification( - procedure="geomeTRIC", - keywords={ - "coordsys": "dlc", - "maxiter": 300, - "program": "rdkit", - }, + procedure="geomeTRIC", keywords={"coordsys": "dlc", "maxiter": 300, "program": "rdkit",}, ), ) From 240f48fb08223dbe2ea9555ea9a2a5add265727d Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 18 Jan 2022 13:27:14 -0500 Subject: [PATCH 081/102] harvest using the new scf_info and calcinfo json files --- qcengine/programs/madness/harvester.py | 104 ++++++++++++++++++++++-- qcengine/programs/madness/runner.py | 16 ++-- qcengine/programs/tests/test_madness.py | 34 ++++++++ 3 files changed, 141 insertions(+), 13 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 33069afbd..ca719300a 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -133,6 +133,9 @@ def harvest_hessian(hess: str) -> np.ndarray: # return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines + +# gets calc info from psivars preservingDict +# before def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: """Get named properties out of the general variables extracted out of the result file @@ -145,6 +148,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Initialize the output output = dict() + print("extract_formatted_properties",output) # Extract the Calc Info output.update( @@ -158,12 +162,12 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert ) # Get the "canonical" properties - output["return_energy"] = psivars["CURRENT ENERGY"] - output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + #output["return_energy"] = psivars["CURRENT ENERGY"] + #output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] # Get the SCF properties - output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) + #output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + #output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) # TODO AdrianH right madness to output these variables # output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) # output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) @@ -172,7 +176,7 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert return AtomicResultProperties(**output) -def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: +def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Molecule, str, str]: """Parses all the pieces of output from Madness: the stdout in *nwout* Scratch files are not yet considered at this moment. @@ -188,10 +192,33 @@ def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, M - (str): Version string - (str): Error message, if any """ - + print("harvest ",outfiles) # Parse the Madness output - out_psivar, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) - print(outfiles) + # This is a weird unpacking but i'm sure i'll find a more elegant way to do this later + moldft_info=outfiles.get("moldft") + moldft_outfiles=moldft_info.get("outfiles") + # At this point scf prints a list of json outputs where each list refers to the scf at givin protocol + ## Here I load the scf_info and calc_info as json + scf_info=json.loads(moldft_outfiles.get("scf_info.json")) + calc_info=json.loads(moldft_outfiles.get("calc_info.json")) + + + # Write harvest scf_info and harvest calc_info + out_calc_vars=harvest_calc_info(calc_info) + print("harvesting calc info",out_calc_vars) + out_scf_vars=harvest_scf_info(scf_info) + + + out_psivar=PreservingDict() + out_psivar.update(out_calc_vars) + out_psivar.update(out_scf_vars) + + + print("harvest scf_info json",scf_info) + print("harvest calc_info json",calc_info) + # no need for this when we now have json files with all of the scf and calc info + ## currently only use this for verson + Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) if "molresponse" in outfiles.keys(): response_psi_var = harvest_response_file(outfiles["molresponse"]["stdout"]) out_psivar.update(response_psi_var) @@ -203,6 +230,8 @@ def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, M out_grad = json.loads(outfiles.get("mad.grad")) # If available, read the hessian + # TODO read in the geometry outputs from a geometry optimization + #out_mol = None out_hess = None if outfiles.get("mad.hess") is not None: out_hess = harvest_hessian(outfiles.get("mad.hess")) @@ -229,9 +258,67 @@ def harvest(in_mol: Molecule, **outfiles) -> Tuple[PreservingDict, None, None, M # out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) # if out_hess is not None: # out_hess = mill.align_hessian(np.array(out_hess)) + # TODO create a madness json that outputs basic info like the version and github hash? + + return out_psivar, out_hess, out_grad, out_mol, version, error +#collect the scf_info json +# first iterate through single numbers +# Then iteration throught tensor values +# this format should work for response values in the future +def harvest_scf_info(scf_info): + psivar = PreservingDict() + + # We only need to last set in the list + scf_info=scf_info[-1][0] + print("harvest scf info",scf_info) + + scf_number_vars=['scf_one_electron_energy', + 'scf_two_electron_energy', + 'nuclear_repulsion_energy', + 'scf_vv10_energy', + 'scf_xc_energy', + 'scf_dispersion_correction_energy', + 'scf_total_energy', + 'scf_iterations'] + + for var in scf_number_vars: + if scf_info.get(var) is not None: + psivar[var.upper()]=scf_info.get(var) + + scf_tensor_vars=[ + 'scf_dipole_moment' + ] + + for var in scf_tensor_vars: + if scf_info.get(var) is not None: + psivar[var.upper()]=tensor_to_numpy(scf_info.get(var)) + + + return psivar + +def harvest_calc_info(calc_info): + psivar = PreservingDict() + qcvars=['calcinfo_nbasis', + 'calcinfo_nmo', + 'calcinfo_nalpha', + 'calcinfo_nbeta', + 'calcinfo_natom', + 'return_energy'] + + for var in qcvars: + if calc_info.get(var) is not None: + psivar[var.upper()]=calc_info.get(var) + + return psivar + +def tensor_to_numpy(j): + array = np.empty(j["size"]) + array[:] = j["vals"] + print(tuple(j['dims'])) + return np.reshape(array,tuple(j["dims"])) def harvest_response_file(outtext): psivar = PreservingDict() @@ -296,6 +383,7 @@ def harvest_response_file(outtext): psivar = grab_tensor(r"Polarizability Final", "POLARIZABILITY", num_states, num_states, psivar, data) return psivar +# Translate a madness tensor defined within json output to a numpy array def grab_tensor(var, VAR, row, col, psivar, data): first_line = r"^\s*" + var + r"\s+" diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 699c9e716..e12c222c6 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -62,7 +62,7 @@ def found(raise_error: bool = False) -> bool: Returns ------- bool - If both nwchem and its harness dependency networkx are found, returns True. + If both m-a-d-n-e-s-s and its harness dependency networkx are found, returns True. If raise_error is False and nwchem or networkx are missing, returns False. If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. @@ -140,6 +140,9 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe else: dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] + print(dexe["moldft"]["outfiles"]["scf_info.json"]) + print(dexe["moldft"]["outfiles"]["calc_info.json"]) + print("Write before self.parse",dexe) return self.parse_output(dexe, input_model) else: print(dexe["stdout"]) @@ -212,6 +215,7 @@ def execute( success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], + ["calc_info.json","scf_info.json"], scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], @@ -234,6 +238,7 @@ def execute( success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], + ["calc_info.json","scf_info.json"], scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], @@ -243,16 +248,18 @@ def execute( return success, oexe def parse_output( - self, outfiles: Dict[str, str], input_model: "AtomicInput" + self, outfiles , input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles["moldft"]["stdout"] if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] + print("within parse_output scf_info.json",outfiles["moldft"]["outfiles"]["scf_info.json"]) + print("within parse output calc_info",outfiles["moldft"]["outfiles"]["calc_info.json"]) # Read the MADNESj stdout file and, if needed, the hess or grad files - qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, **outfiles) + qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) ## pop the files because I think I need to outfiles.pop("moldft") if "molresponse" in outfiles.keys(): @@ -267,8 +274,7 @@ def parse_output( if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: - print(qcvars) - retres = qcvars[f"CURRENT {input_model.driver.upper()}"] + retres = qcvars["RETURN_ENERGY"] if isinstance(retres, Decimal): retres = float(retres) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 38d7ea831..893d97a71 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -5,6 +5,13 @@ import qcengine as qcng from qcengine.testing import using +@pytest.fixture +def Be(): + smol = """ + Be 0.000000000000 0.000000000000 0.000000000000 + units au +""" + return qcel.models.Molecule.from_data(smol) @pytest.fixture def h2o(): @@ -23,6 +30,7 @@ def h2o(): "program,basis,keywords", [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], ) + @using("madness") def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} @@ -79,3 +87,29 @@ def test_mad_hf_response(program, basis, keywords, h2o): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) + +@using("madness") +@pytest.mark.parametrize( + "program,basis,keywords", + [pytest.param("madness", None,{} ),], +) + + + +@using("madness") +def test_mad_hf_be(program, basis, keywords, Be): + resi = {"molecule": Be, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + # print(res["stdout"]) + + assert res["driver"] == "energy" + assert "provenance" in res + assert res["success"] is True + + # k=7 + scf_tot = -14.57293657 + + atol = 1.0e-5 + assert compare_values(scf_tot, res["return_result"], atol=atol) + From 3a35cc9bb04eddbe742fc5eb114a88b9d006e95f Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 18 Jan 2022 15:19:20 -0500 Subject: [PATCH 082/102] build input can now deal with geomtry related keywords like "eprec" --- qcengine/programs/madness/runner.py | 41 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index e12c222c6..6544b4d8f 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -131,7 +131,6 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise InputError(dexe["moldft"]["stdout"]) if success: num_commands = len(dexe) - print(num_commands) if num_commands == 2: dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] @@ -140,9 +139,6 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe else: dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] - print(dexe["moldft"]["outfiles"]["scf_info.json"]) - print(dexe["moldft"]["outfiles"]["calc_info.json"]) - print("Write before self.parse",dexe) return self.parse_output(dexe, input_model) else: print(dexe["stdout"]) @@ -150,7 +146,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -162,9 +158,12 @@ def build_input( ## These are the madness keywords opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} + print(opts) # Handle Molecule molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) + print(moldata) + print(molcmd) molData = {} for k, v in moldata["keywords"].items(): molData["dft__" + k] = v @@ -173,9 +172,7 @@ def build_input( ## Handle Calc Type (ROBERT) ## now returns respnse options as well mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver) - opts.update(mdcopts) - ## Handle the basis set (ROBERT) the question is what value of k # Log the job settings (LORI) Not sure if i need this @@ -184,6 +181,20 @@ def build_input( # Handle conversion from schema (flat key/value) keywords into local format optcmd = format_keywords(opts) + # I need to split to geometry keywords and add it to the end of the geometry command in molcommand + # if the geometry keyword exits + if optcmd.find("geometry") != -1: + geo_index = optcmd.find("geometry") # find first occurrence of geometry + end_index = optcmd[geo_index:].find("end") # find first occurrence of end after geometry + geometry_input = optcmd[geo_index + 8:end_index + geo_index] # grab everything in between geometry and end + + optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4:] # optcmd becomes everything else + molcmd=molcmd.replace("end", geometry_input.strip() + "\nend") # replace end with the added geometry input + print("optcmd\n",optcmd) + print("molcmd\n",molcmd) + + print(optcmd.find("geometry")) + print(optcmd) madnessrec["commands"] = {} if mdccmd == "response": dft_cmds = optcmd.split(mdccmd) @@ -201,13 +212,13 @@ def build_input( madnessrec["infiles"]["moldft"]["input"] = dft_cmds + molcmd madnessrec["commands"]["moldft"] = [which("moldft")] - print(dft_cmds) + print(madnessrec) # optcmd="dft\n xc hf \nend\n" # print(madnessrec["infiles"]["input"]) return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) oexe = {} @@ -215,7 +226,7 @@ def execute( success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], - ["calc_info.json","scf_info.json"], + ["calc_info.json", "scf_info.json"], scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], @@ -238,7 +249,7 @@ def execute( success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], - ["calc_info.json","scf_info.json"], + ["calc_info.json", "scf_info.json"], scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], @@ -248,15 +259,15 @@ def execute( return success, oexe def parse_output( - self, outfiles , input_model: "AtomicInput" + self, outfiles, input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles["moldft"]["stdout"] if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] - print("within parse_output scf_info.json",outfiles["moldft"]["outfiles"]["scf_info.json"]) - print("within parse output calc_info",outfiles["moldft"]["outfiles"]["calc_info.json"]) + print("within parse_output scf_info.json", outfiles["moldft"]["outfiles"]["scf_info.json"]) + print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) @@ -272,7 +283,7 @@ def parse_output( qcvars["CURRENT HESSIAN"] = madhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": - retres = qcvars[f"CURRENT ENERGY"] + retres = qcvars[f"RETURN_ENERGY"] else: retres = qcvars["RETURN_ENERGY"] From 924ffb1e6a88eb4550959bf5a39f061969e2463e Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Fri, 11 Feb 2022 12:37:14 -0500 Subject: [PATCH 083/102] Update germinate.py change name to madness --- qcengine/programs/madness/germinate.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index fd51bc7e1..2a00141bf 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -92,14 +92,16 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: def muster_modelchem(method: str, derint: int,) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords + Options include energy calculation with moldft + Geometry optimiazation with moldft + propreties calculation with molresponse...runs moldft then molresponse Args: method (str): Name of the QC method to use derint (str): Index of the run type - use_tce (bool): Whether to use the Tensor Contraction Engine Returns: - (str): Task command for NWChem - (dict): Any options for NWChem + (str): Task command for MADNESS + (dict): Any options for MADNESS """ # Standardize the method name From 16b018224958e91560156c64905d03d43d0c2ead Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Fri, 11 Feb 2022 12:38:11 -0500 Subject: [PATCH 084/102] We now use json files to read output --- qcengine/programs/madness/harvester.py | 232 ++++++++++++++++-------- qcengine/programs/madness/runner.py | 1 + qcengine/programs/tests/test_madness.py | 46 ++++- 3 files changed, 192 insertions(+), 87 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index ca719300a..0feca5759 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -42,7 +42,8 @@ def harvest_moldft_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, splits = re.split(r"Converged!", outtext, re.MULTILINE)[-2] final_outpass = re.split(r"Iteration", splits, re.MULTILINE)[-1] - psivar, madcoord, madgrad, version, error = harvest_outfile_moldft_pass(final_outpass) + psivar, madcoord, madgrad, version, error = harvest_outfile_moldft_pass( + final_outpass) return psivar, madcoord, madgrad, version, error @@ -63,7 +64,8 @@ def harvest_outfile_moldft_pass(outtext): # Process version mobj = re.search( - r'^\s+' + r'MADNESS' + r'\s+' + r'(\d+.\d\d+.\d)' + r'\s' + r'multiresolution suite' + r'\s*$', + r'^\s+' + r'MADNESS' + r'\s+' + + r'(\d+.\d\d+.\d)' + r'\s' + r'multiresolution suite' + r'\s*$', outtext, re.MULTILINE) # fmt: on if mobj: @@ -73,7 +75,8 @@ def harvest_outfile_moldft_pass(outtext): # Process SCF # 1)Fail to converge (TODO Robert ask for failed convergence) # fmt: off - mobj = re.search(r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) + mobj = re.search( + r'^\s+' + r'(?:Calculation failed to converge)' + r'\s*$', outtext, re.MULTILINE) # fmt: on if mobj: logger.debug("failed to converge") @@ -81,15 +84,17 @@ def harvest_outfile_moldft_pass(outtext): # 2)Calculation converged else: OPTIONS = [r"exchange-correlation", r"nuclear-repulsion", r"total"] - PSIVAR = ["EXCHANGE-CORRELATION", "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] + PSIVAR = ["EXCHANGE-CORRELATION", + "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] # OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] # PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] optDict = dict(zip(OPTIONS, PSIVAR)) for var, VAR in optDict.items(): - mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + r"s*$", outtext, re.MULTILINE) + mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + + r"s*$", outtext, re.MULTILINE) if mobj: - logger.debug("matched SCF") ## not sure what this means + logger.debug("matched SCF") # not sure what this means psivar[VAR] = mobj.group(1) # Other options @@ -133,9 +138,8 @@ def harvest_hessian(hess: str) -> np.ndarray: # return hess_arr.T # So that the array is listed in C-order, needed by some alignment routines - # gets calc info from psivars preservingDict -# before +# before def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultProperties: """Get named properties out of the general variables extracted out of the result file @@ -148,26 +152,29 @@ def extract_formatted_properties(psivars: PreservingDict) -> AtomicResultPropert # Initialize the output output = dict() - print("extract_formatted_properties",output) + print("extract_formatted_properties", output) # Extract the Calc Info output.update( { - "calcinfo_nbasis": psivars.get("N BASIS", None), ## Not a thing in madness - "calcinfo_nmo": psivars.get("N MO", None), ## Number of Mo orbitals - "calcinfo_natom": psivars.get("N ATOMS", None), ## Get madness to print this out - "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), ## TODO (figure out how to read) + # Not a thing in madness + "calcinfo_nbasis": psivars.get("N BASIS", None), + "calcinfo_nmo": psivars.get("N MO", None), # Number of Mo orbitals + # Get madness to print this out + "calcinfo_natom": psivars.get("N ATOMS", None), + # TODO (figure out how to read) + "calcinfo_nalpha": psivars.get("N ALPHA ELECTRONS", None), "calcinfo_nbeta": psivars.get("N BETA ELECTRONS", None), } ) # Get the "canonical" properties - #output["return_energy"] = psivars["CURRENT ENERGY"] - #output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] + # output["return_energy"] = psivars["CURRENT ENERGY"] + # output["nuclear_repulsion_energy"] = psivars["NUCLEAR REPULSION ENERGY"] # Get the SCF properties - #output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) - #output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) + # output["scf_total_energy"] = psivars.get("TOTAL SCF ENERGY", None) + # output["scf_xc_energy"] = psivars.get("EXCHANGE-CORRELATION", None) # TODO AdrianH right madness to output these variables # output["scf_one_electron_energy"] = psivars.get("ONE-ELECTRON ENERGY", None) # output["scf_two_electron_energy"] = psivars.get("TWO-ELECTRON ENERGY", None) @@ -192,46 +199,44 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol - (str): Version string - (str): Error message, if any """ - print("harvest ",outfiles) + out_psivar = PreservingDict() # Parse the Madness output # This is a weird unpacking but i'm sure i'll find a more elegant way to do this later - moldft_info=outfiles.get("moldft") - moldft_outfiles=moldft_info.get("outfiles") + moldft_info = outfiles.get("moldft") + moldft_outfiles = moldft_info.get("outfiles") # At this point scf prints a list of json outputs where each list refers to the scf at givin protocol - ## Here I load the scf_info and calc_info as json - scf_info=json.loads(moldft_outfiles.get("scf_info.json")) - calc_info=json.loads(moldft_outfiles.get("calc_info.json")) - - - # Write harvest scf_info and harvest calc_info - out_calc_vars=harvest_calc_info(calc_info) - print("harvesting calc info",out_calc_vars) - out_scf_vars=harvest_scf_info(scf_info) - - - out_psivar=PreservingDict() + # Here I load the scf_info and calc_info as json + scf_info = json.loads(moldft_outfiles.get("scf_info.json")) + calc_info = json.loads(moldft_outfiles.get("calc_info.json")) + # Write harvest scf_info and harvest calc_info + out_calc_vars = harvest_calc_info(calc_info) + out_scf_vars = harvest_scf_info(scf_info) out_psivar.update(out_calc_vars) out_psivar.update(out_scf_vars) - - print("harvest scf_info json",scf_info) - print("harvest calc_info json",calc_info) - # no need for this when we now have json files with all of the scf and calc info - ## currently only use this for verson - Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) if "molresponse" in outfiles.keys(): - response_psi_var = harvest_response_file(outfiles["molresponse"]["stdout"]) + molresponse_info = outfiles.get("moldft") + molresponse_outfiles = molresponse_info.get("outfiles") + # At this point scf prints a list of json outputs where each list refers to the scf at given protocol + # Here I load the scf_info and calc_info as json + response_info = json.loads(molresponse_outfiles.get("respones.json")) + response_params, response_data_dict = read_molrespone_json( + response_info) + + Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output( + outfiles["moldft"]["stdout"]) + if "molresponse" in outfiles.keys(): + response_psi_var = harvest_response_file( + outfiles["molresponse"]["stdout"]) out_psivar.update(response_psi_var) - # If available, read higher-accuracy gradients # These were output using a Python Task in Madness to read them out of the database if outfiles.get("mad.grad") is not None: logger.debug("Reading higher-accuracy gradients") out_grad = json.loads(outfiles.get("mad.grad")) - # If available, read the hessian # TODO read in the geometry outputs from a geometry optimization - #out_mol = None + # out_mol = None out_hess = None if outfiles.get("mad.hess") is not None: out_hess = harvest_hessian(outfiles.get("mad.hess")) @@ -260,11 +265,10 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol # out_hess = mill.align_hessian(np.array(out_hess)) # TODO create a madness json that outputs basic info like the version and github hash? - - return out_psivar, out_hess, out_grad, out_mol, version, error -#collect the scf_info json + +# collect the scf_info json # first iterate through single numbers # Then iteration throught tensor values # this format should work for response values in the future @@ -272,53 +276,115 @@ def harvest_scf_info(scf_info): psivar = PreservingDict() # We only need to last set in the list - scf_info=scf_info[-1][0] - print("harvest scf info",scf_info) - - scf_number_vars=['scf_one_electron_energy', - 'scf_two_electron_energy', - 'nuclear_repulsion_energy', - 'scf_vv10_energy', - 'scf_xc_energy', - 'scf_dispersion_correction_energy', - 'scf_total_energy', - 'scf_iterations'] - - for var in scf_number_vars: + scf_info = scf_info[-1][0] + print("harvest scf info", scf_info) + + scf_number_vars = ['scf_one_electron_energy', + 'scf_two_electron_energy', + 'nuclear_repulsion_energy', + 'scf_vv10_energy', + 'scf_xc_energy', + 'scf_dispersion_correction_energy', + 'scf_total_energy', + 'scf_iterations'] + + for var in scf_number_vars: if scf_info.get(var) is not None: - psivar[var.upper()]=scf_info.get(var) + psivar[var.upper()] = scf_info.get(var) - scf_tensor_vars=[ - 'scf_dipole_moment' + scf_tensor_vars = [ + 'scf_dipole_moment' ] - for var in scf_tensor_vars: + for var in scf_tensor_vars: if scf_info.get(var) is not None: - psivar[var.upper()]=tensor_to_numpy(scf_info.get(var)) - + psivar[var.upper()] = tensor_to_numpy(scf_info.get(var)) return psivar + def harvest_calc_info(calc_info): psivar = PreservingDict() - qcvars=['calcinfo_nbasis', - 'calcinfo_nmo', - 'calcinfo_nalpha', - 'calcinfo_nbeta', - 'calcinfo_natom', - 'return_energy'] - - for var in qcvars: + qcvars = ['calcinfo_nbasis', + 'calcinfo_nmo', + 'calcinfo_nalpha', + 'calcinfo_nbeta', + 'calcinfo_natom', + 'return_energy'] + + for var in qcvars: if calc_info.get(var) is not None: - psivar[var.upper()]=calc_info.get(var) + psivar[var.upper()] = calc_info.get(var) return psivar + def tensor_to_numpy(j): array = np.empty(j["size"]) array[:] = j["vals"] print(tuple(j['dims'])) - return np.reshape(array,tuple(j["dims"])) + return np.reshape(array, tuple(j["dims"])) + + +def read_frequency_proto_iter_data(my_iter_data, num_states, num_orbitals): + num_iters = len(my_iter_data) + dres = np.empty((num_iters, num_states)) + res_X = np.empty((num_iters, num_states)) + res_Y = np.empty((num_iters, num_states)) + polar = np.empty((num_iters, 3, 3)) + for i in range(num_iters): + dres[i, :] = tensor_to_numpy(my_iter_data[i]["density_residuals"]) + res_X[i, :] = tensor_to_numpy(my_iter_data[i]["res_X"]) + res_Y[i, :] = tensor_to_numpy(my_iter_data[i]["res_Y"]) + polar[i, :, :] = tensor_to_numpy(my_iter_data[i]["polar"]) + data = {} + names = ["density_residuals", "res_X", "res_Y", "polar"] + vals = [dres, res_X, res_Y, polar] + for name, val in zip(names, vals): + data[name] = val + return data + + +def read_excited_proto_iter_data(my_iter_data, num_states, num_orbitals): + num_iters = len(my_iter_data) + dres = np.empty((num_iters, num_states)) + res_X = np.empty((num_iters, num_states)) + res_Y = np.empty((num_iters, num_states)) + omega = np.empty((num_iters, num_states)) + for i in range(num_iters): + dres[i, :] = tensor_to_numpy(my_iter_data[i]["density_residuals"]) + res_X[i, :] = tensor_to_numpy(my_iter_data[i]["res_X"]) + res_Y[i, :] = tensor_to_numpy(my_iter_data[i]["res_Y"]) + omega[i, :] = tensor_to_numpy(my_iter_data[i]["omega"]) + data = {} + names = ["density_residuals", "res_X", "res_Y", "omega"] + vals = [dres, res_X, res_Y, omega] + for name, val in zip(names, vals): + data[name] = val + return data + + +# input response_info json and returns a dict of response paramters +# and a list of dicts of numpy arrays holding response data +def read_molrespone_json(response_info): + protocol_data = response_info['protocol_data'] + response_parameters = response_info['response_parameters'] + n_states = response_parameters["states"] + n_orbitals = response_parameters["num_orbitals"] + num_protos = len(protocol_data) + protos = [] + proto_data = [] + for p in range(num_protos): + protos.append(protocol_data[p]["proto"]) + iter_data = protocol_data[p]["iter_data"] + if response_parameters["excited_state"]: + proto_data.append(read_excited_proto_iter_data( + iter_data, n_states, n_orbitals)) + else: + proto_data.append(read_frequency_proto_iter_data( + iter_data, n_states, n_orbitals)) + return response_parameters, proto_data + def harvest_response_file(outtext): psivar = PreservingDict() @@ -350,7 +416,8 @@ def harvest_response_file(outtext): optDict = dict(zip(OPTIONS, PSIVAR)) for var, VAR in optDict.items(): - mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + r"\s*$", outtext, re.MULTILINE) + mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + + r"\s*$", outtext, re.MULTILINE) # print(mobj) if mobj: psivar[VAR] = mobj.group(1) @@ -368,7 +435,8 @@ def harvest_response_file(outtext): var = r"Orbital Energies: \[\*\]" VAR = "ORBITAL ENERGIES" - mobj = re.search(r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", outtext, re.MULTILINE,) + mobj = re.search(r"^\s*" + var + r"\s*" + NUMSPACEORB + + r"$", outtext, re.MULTILINE, ) # print(mobj) if mobj: @@ -378,11 +446,15 @@ def harvest_response_file(outtext): psivar[VAR] = np.array(oe_list, dtype=float) - psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Polarizability Final", "POLARIZABILITY", num_states, num_states, psivar, data) + psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", + num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", + num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Polarizability Final", + "POLARIZABILITY", num_states, num_states, psivar, data) return psivar + # Translate a madness tensor defined within json output to a numpy array def grab_tensor(var, VAR, row, col, psivar, data): @@ -401,7 +473,7 @@ def grab_tensor(var, VAR, row, col, psivar, data): total += line # print(line) - mobj = re.search(total, data, re.MULTILINE,) + mobj = re.search(total, data, re.MULTILINE, ) # print(mobj) if mobj: oe_list = [] diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 6544b4d8f..073fc2bb1 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -236,6 +236,7 @@ def execute( success, dexe_response = execute( inputs["commands"]["molresponse"], inputs["infiles"]["molresponse"], + ["response.json"], scratch_messy=True, scratch_name=Path(dexe["scratch_directory"]).name, scratch_exist_ok=True, diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 893d97a71..d6e53264a 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -5,6 +5,7 @@ import qcengine as qcng from qcengine.testing import using + @pytest.fixture def Be(): smol = """ @@ -13,6 +14,7 @@ def Be(): """ return qcel.models.Molecule.from_data(smol) + @pytest.fixture def h2o(): smol = """ @@ -28,9 +30,8 @@ def h2o(): @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}),], + [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}), ], ) - @using("madness") def test_mad_hf(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} @@ -88,14 +89,12 @@ def test_mad_hf_response(program, basis, keywords, h2o): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) + @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [pytest.param("madness", None,{} ),], + [pytest.param("madness", None, {}), ], ) - - - @using("madness") def test_mad_hf_be(program, basis, keywords, Be): resi = {"molecule": Be, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} @@ -103,7 +102,7 @@ def test_mad_hf_be(program, basis, keywords, Be): res = qcng.compute(resi, program, raise_error=True, return_dict=True) # print(res["stdout"]) - assert res["driver"] == "energy" + assert res["driver"] == "optimizatino" assert "provenance" in res assert res["success"] is True @@ -113,3 +112,36 @@ def test_mad_hf_be(program, basis, keywords, Be): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) +@using("madness") +@pytest.mark.parametrize( + "program,basis,keywords", + [ + pytest.param( + "madness", + None, + { + "dft__k": 7, + "dft__econv": 1.0000e-05, + "dft__kain": True, + }, + ), + ], +) + +@using("madness") +def test_mad_geometry_optimization(program, basis, keywords, h2o): + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + + res = qcng.compute(resi, program, raise_error=True, return_dict=True) + # print(res["stdout"]) + + assert res["driver"] == "properties" + assert "provenance" in res + assert res["success"] is True + + # k=7 + scf_tot = -76.06720262 + + atol = 1.0e-5 + assert compare_values(scf_tot, res["return_result"], atol=atol) + From a980001ca8516473bc0db8da5ff5e35348b9dfec Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Fri, 11 Feb 2022 12:47:50 -0500 Subject: [PATCH 085/102] formatting --- examples/terachem_pbs.py | 6 +- qcengine/procedures/berny.py | 5 +- qcengine/procedures/torsiondrive.py | 10 +- qcengine/programs/dftd3.py | 4 +- .../empirical_dispersion_resources.py | 5 +- qcengine/programs/madness/germinate.py | 5 +- qcengine/programs/madness/harvester.py | 85 ++- qcengine/programs/madness/runner.py | 22 +- qcengine/programs/openmm.py | 6 +- .../tests/standard_suite_contracts.py | 21 +- qcengine/programs/tests/standard_suite_ref.py | 512 ++++++++++++++++-- .../programs/tests/standard_suite_runner.py | 19 +- qcengine/programs/tests/test_alignment.py | 6 +- .../programs/tests/test_canonical_config.py | 23 +- .../programs/tests/test_canonical_fields.py | 8 +- qcengine/programs/tests/test_dftd3_mp2d.py | 27 +- qcengine/programs/tests/test_dftd4.py | 17 +- qcengine/programs/tests/test_ghost.py | 30 +- qcengine/programs/tests/test_madness.py | 11 +- qcengine/programs/tests/test_mrchem.py | 18 +- qcengine/programs/tests/test_nwchem.py | 6 +- qcengine/programs/tests/test_qcore.py | 6 +- .../programs/tests/test_standard_suite.py | 17 +- qcengine/programs/tests/test_turbomole.py | 15 +- qcengine/programs/tests/test_xtb.py | 50 +- qcengine/tests/test_procedures.py | 7 +- 26 files changed, 785 insertions(+), 156 deletions(-) diff --git a/examples/terachem_pbs.py b/examples/terachem_pbs.py index 0633e49d2..d0c343f50 100644 --- a/examples/terachem_pbs.py +++ b/examples/terachem_pbs.py @@ -16,6 +16,10 @@ """ ) -inp = qcel.models.AtomicInput(molecule=mol, driver="energy", model={"method": "pbe0", "basis": "6-31g"},) +inp = qcel.models.AtomicInput( + molecule=mol, + driver="energy", + model={"method": "pbe0", "basis": "6-31g"}, +) ret = prog.compute(inp) print(ret) diff --git a/qcengine/procedures/berny.py b/qcengine/procedures/berny.py index 8122ef721..242f440c4 100644 --- a/qcengine/procedures/berny.py +++ b/qcengine/procedures/berny.py @@ -19,7 +19,10 @@ class BernyProcedure(ProcedureHarness): def found(self, raise_error: bool = False) -> bool: return which_import( - "berny", return_bool=True, raise_error=raise_error, raise_msg="Please install via `pip install pyberny`.", + "berny", + return_bool=True, + raise_error=raise_error, + raise_msg="Please install via `pip install pyberny`.", ) def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> "OptimizationInput": diff --git a/qcengine/procedures/torsiondrive.py b/qcengine/procedures/torsiondrive.py index 8890d9488..2e68906e4 100644 --- a/qcengine/procedures/torsiondrive.py +++ b/qcengine/procedures/torsiondrive.py @@ -190,7 +190,11 @@ def _spawn_optimization( **input_model.optimization_spec.keywords, "constraints": { "set": [ - {"type": "dihedral", "indices": dihedral, "value": int(angle),} + { + "type": "dihedral", + "indices": dihedral, + "value": int(angle), + } for dihedral, angle in zip(dihedrals, angles) ] }, @@ -209,7 +213,9 @@ def _spawn_optimization( ) @staticmethod - def _find_final_results(optimization_results: List[OptimizationResult],) -> Tuple[float, Molecule]: + def _find_final_results( + optimization_results: List[OptimizationResult], + ) -> Tuple[float, Molecule]: """Returns the energy and final molecule of the lowest energy optimization in a set.""" diff --git a/qcengine/programs/dftd3.py b/qcengine/programs/dftd3.py index b00cb1d8b..db733013f 100644 --- a/qcengine/programs/dftd3.py +++ b/qcengine/programs/dftd3.py @@ -284,7 +284,9 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> output_data = { "extras": input_model.extras, "native_files": {k: v for k, v in outfiles.items() if v is not None}, - "properties": {"return_energy": calcinfo[f"CURRENT ENERGY"],}, + "properties": { + "return_energy": calcinfo[f"CURRENT ENERGY"], + }, "provenance": Provenance( creator="DFTD3", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name ), diff --git a/qcengine/programs/empirical_dispersion_resources.py b/qcengine/programs/empirical_dispersion_resources.py index 802e3607a..494e286eb 100644 --- a/qcengine/programs/empirical_dispersion_resources.py +++ b/qcengine/programs/empirical_dispersion_resources.py @@ -868,7 +868,10 @@ def get_params(entry: dict, base: dict, defaults: list) -> dict: # Make Psi4's citation style checker happy if citation is not None: citation = " " + citation + "\n" - definitions[method] = dict(params=params, citation=citation,) + definitions[method] = dict( + params=params, + citation=citation, + ) except KeyError: continue diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 1e9d17044..248a8f7d8 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -91,7 +91,10 @@ # def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: -def muster_modelchem(method: str, derint: int,) -> Tuple[str, Dict[str, Any]]: +def muster_modelchem( + method: str, + derint: int, +) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords Options include energy calculation with moldft Geometry optimiazation with moldft diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 0feca5759..41146f421 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -42,8 +42,7 @@ def harvest_moldft_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, splits = re.split(r"Converged!", outtext, re.MULTILINE)[-2] final_outpass = re.split(r"Iteration", splits, re.MULTILINE)[-1] - psivar, madcoord, madgrad, version, error = harvest_outfile_moldft_pass( - final_outpass) + psivar, madcoord, madgrad, version, error = harvest_outfile_moldft_pass(final_outpass) return psivar, madcoord, madgrad, version, error @@ -84,15 +83,13 @@ def harvest_outfile_moldft_pass(outtext): # 2)Calculation converged else: OPTIONS = [r"exchange-correlation", r"nuclear-repulsion", r"total"] - PSIVAR = ["EXCHANGE-CORRELATION", - "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] + PSIVAR = ["EXCHANGE-CORRELATION", "NUCLEAR REPULSION ENERGY", "TOTAL SCF ENERGY"] # OPTIONS=[r'kinetic',r'nonlocal psp',r'nuclear attraction',r'coulomb',r'PCM',r'exchange-correlation',r'nuclear-repulsion',r'total'] # PSIVAR=['KINETIC ENERGY','NONLOCAL PSP','NUCLEAR ATTRACTION ENERGY','COULOMB','PCM','EXCHANGE-CORRELATION','NUCLEAR REPULSION ENERGY','TOTAL SCF ENERGY'] optDict = dict(zip(OPTIONS, PSIVAR)) for var, VAR in optDict.items(): - mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + - r"s*$", outtext, re.MULTILINE) + mobj = re.search(r"^\s+" + var + r"\s*" + NUMBER + r"s*$", outtext, re.MULTILINE) if mobj: logger.debug("matched SCF") # not sure what this means psivar[VAR] = mobj.group(1) @@ -220,14 +217,11 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol # At this point scf prints a list of json outputs where each list refers to the scf at given protocol # Here I load the scf_info and calc_info as json response_info = json.loads(molresponse_outfiles.get("respones.json")) - response_params, response_data_dict = read_molrespone_json( - response_info) + response_params, response_data_dict = read_molrespone_json(response_info) - Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output( - outfiles["moldft"]["stdout"]) + Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) if "molresponse" in outfiles.keys(): - response_psi_var = harvest_response_file( - outfiles["molresponse"]["stdout"]) + response_psi_var = harvest_response_file(outfiles["molresponse"]["stdout"]) out_psivar.update(response_psi_var) # If available, read higher-accuracy gradients # These were output using a Python Task in Madness to read them out of the database @@ -279,22 +273,22 @@ def harvest_scf_info(scf_info): scf_info = scf_info[-1][0] print("harvest scf info", scf_info) - scf_number_vars = ['scf_one_electron_energy', - 'scf_two_electron_energy', - 'nuclear_repulsion_energy', - 'scf_vv10_energy', - 'scf_xc_energy', - 'scf_dispersion_correction_energy', - 'scf_total_energy', - 'scf_iterations'] + scf_number_vars = [ + "scf_one_electron_energy", + "scf_two_electron_energy", + "nuclear_repulsion_energy", + "scf_vv10_energy", + "scf_xc_energy", + "scf_dispersion_correction_energy", + "scf_total_energy", + "scf_iterations", + ] for var in scf_number_vars: if scf_info.get(var) is not None: psivar[var.upper()] = scf_info.get(var) - scf_tensor_vars = [ - 'scf_dipole_moment' - ] + scf_tensor_vars = ["scf_dipole_moment"] for var in scf_tensor_vars: if scf_info.get(var) is not None: @@ -305,12 +299,7 @@ def harvest_scf_info(scf_info): def harvest_calc_info(calc_info): psivar = PreservingDict() - qcvars = ['calcinfo_nbasis', - 'calcinfo_nmo', - 'calcinfo_nalpha', - 'calcinfo_nbeta', - 'calcinfo_natom', - 'return_energy'] + qcvars = ["calcinfo_nbasis", "calcinfo_nmo", "calcinfo_nalpha", "calcinfo_nbeta", "calcinfo_natom", "return_energy"] for var in qcvars: if calc_info.get(var) is not None: @@ -322,7 +311,7 @@ def harvest_calc_info(calc_info): def tensor_to_numpy(j): array = np.empty(j["size"]) array[:] = j["vals"] - print(tuple(j['dims'])) + print(tuple(j["dims"])) return np.reshape(array, tuple(j["dims"])) @@ -367,8 +356,8 @@ def read_excited_proto_iter_data(my_iter_data, num_states, num_orbitals): # input response_info json and returns a dict of response paramters # and a list of dicts of numpy arrays holding response data def read_molrespone_json(response_info): - protocol_data = response_info['protocol_data'] - response_parameters = response_info['response_parameters'] + protocol_data = response_info["protocol_data"] + response_parameters = response_info["response_parameters"] n_states = response_parameters["states"] n_orbitals = response_parameters["num_orbitals"] num_protos = len(protocol_data) @@ -378,11 +367,9 @@ def read_molrespone_json(response_info): protos.append(protocol_data[p]["proto"]) iter_data = protocol_data[p]["iter_data"] if response_parameters["excited_state"]: - proto_data.append(read_excited_proto_iter_data( - iter_data, n_states, n_orbitals)) + proto_data.append(read_excited_proto_iter_data(iter_data, n_states, n_orbitals)) else: - proto_data.append(read_frequency_proto_iter_data( - iter_data, n_states, n_orbitals)) + proto_data.append(read_frequency_proto_iter_data(iter_data, n_states, n_orbitals)) return response_parameters, proto_data @@ -416,8 +403,7 @@ def harvest_response_file(outtext): optDict = dict(zip(OPTIONS, PSIVAR)) for var, VAR in optDict.items(): - mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + - r"\s*$", outtext, re.MULTILINE) + mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + r"\s*$", outtext, re.MULTILINE) # print(mobj) if mobj: psivar[VAR] = mobj.group(1) @@ -435,8 +421,11 @@ def harvest_response_file(outtext): var = r"Orbital Energies: \[\*\]" VAR = "ORBITAL ENERGIES" - mobj = re.search(r"^\s*" + var + r"\s*" + NUMSPACEORB + - r"$", outtext, re.MULTILINE, ) + mobj = re.search( + r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", + outtext, + re.MULTILINE, + ) # print(mobj) if mobj: @@ -446,17 +435,15 @@ def harvest_response_file(outtext): psivar[VAR] = np.array(oe_list, dtype=float) - psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", - num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", - num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Polarizability Final", - "POLARIZABILITY", num_states, num_states, psivar, data) + psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", num_orbitals, num_orbitals, psivar, outtext) + psivar = grab_tensor(r"Polarizability Final", "POLARIZABILITY", num_states, num_states, psivar, data) return psivar # Translate a madness tensor defined within json output to a numpy array + def grab_tensor(var, VAR, row, col, psivar, data): first_line = r"^\s*" + var + r"\s+" NUMBER = r"(?x:" + regex.NUMBER + ")" # NUMBER @@ -473,7 +460,11 @@ def grab_tensor(var, VAR, row, col, psivar, data): total += line # print(line) - mobj = re.search(total, data, re.MULTILINE, ) + mobj = re.search( + total, + data, + re.MULTILINE, + ) # print(mobj) if mobj: oe_list = [] diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 073fc2bb1..4e3c47193 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -146,7 +146,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -186,12 +186,16 @@ def build_input( if optcmd.find("geometry") != -1: geo_index = optcmd.find("geometry") # find first occurrence of geometry end_index = optcmd[geo_index:].find("end") # find first occurrence of end after geometry - geometry_input = optcmd[geo_index + 8:end_index + geo_index] # grab everything in between geometry and end + geometry_input = optcmd[ + geo_index + 8 : end_index + geo_index + ] # grab everything in between geometry and end - optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4:] # optcmd becomes everything else - molcmd=molcmd.replace("end", geometry_input.strip() + "\nend") # replace end with the added geometry input - print("optcmd\n",optcmd) - print("molcmd\n",molcmd) + optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4 :] # optcmd becomes everything else + molcmd = molcmd.replace( + "end", geometry_input.strip() + "\nend" + ) # replace end with the added geometry input + print("optcmd\n", optcmd) + print("molcmd\n", molcmd) print(optcmd.find("geometry")) print(optcmd) @@ -218,7 +222,7 @@ def build_input( return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) oexe = {} @@ -259,9 +263,7 @@ def execute( oexe["moldft"] = dexe return success, oexe - def parse_output( - self, outfiles, input_model: "AtomicInput" - ) -> "AtomicResult": # lgtm: [py/similar-function] + def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles["moldft"]["stdout"] diff --git a/qcengine/programs/openmm.py b/qcengine/programs/openmm.py index a4915392f..9606bcb76 100644 --- a/qcengine/programs/openmm.py +++ b/qcengine/programs/openmm.py @@ -100,7 +100,11 @@ def found(raise_error: bool = False) -> bool: ) # for openmm, try new import, then old import if not found - openmm_found = which_import("openmm", return_bool=True, raise_error=False,) + openmm_found = which_import( + "openmm", + return_bool=True, + raise_error=False, + ) if not openmm_found: openmm_found = which_import( ".openmm", diff --git a/qcengine/programs/tests/standard_suite_contracts.py b/qcengine/programs/tests/standard_suite_contracts.py index 264e18358..1d8143103 100644 --- a/qcengine/programs/tests/standard_suite_contracts.py +++ b/qcengine/programs/tests/standard_suite_contracts.py @@ -209,7 +209,12 @@ def contractual_mp2( ) or ( ((qc_module == "psi4-occ" and reference == "rohf" and method in ["olccd"])) - and pv in ["MP2 CORRELATION ENERGY", "MP2 TOTAL ENERGY", "MP2 SINGLES ENERGY",] + and pv + in [ + "MP2 CORRELATION ENERGY", + "MP2 TOTAL ENERGY", + "MP2 SINGLES ENERGY", + ] ) or ( ( @@ -695,11 +700,21 @@ def contractual_ccsd( ) or ( (qc_module == "cfour-vcc" and reference in ["rohf"] and method in ["ccsd", "ccsd(t)"]) - and pv in ["CCSD SAME-SPIN CORRELATION ENERGY", "CCSD SINGLES ENERGY", "CCSD DOUBLES ENERGY",] + and pv + in [ + "CCSD SAME-SPIN CORRELATION ENERGY", + "CCSD SINGLES ENERGY", + "CCSD DOUBLES ENERGY", + ] ) or ( (qc_module == "cfour-ecc" and reference in ["rohf"] and method in ["ccsd", "ccsd(t)"]) - and pv in ["CCSD OPPOSITE-SPIN CORRELATION ENERGY", "CCSD SINGLES ENERGY", "CCSD DOUBLES ENERGY",] + and pv + in [ + "CCSD OPPOSITE-SPIN CORRELATION ENERGY", + "CCSD SINGLES ENERGY", + "CCSD DOUBLES ENERGY", + ] ) or ( ( diff --git a/qcengine/programs/tests/standard_suite_ref.py b/qcengine/programs/tests/standard_suite_ref.py index 2e2acb400..fd054460f 100644 --- a/qcengine/programs/tests/standard_suite_ref.py +++ b/qcengine/programs/tests/standard_suite_ref.py @@ -363,7 +363,16 @@ "CCSD CORRELATION ENERGY": -0.208743643, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04857419039, - "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.001989217717, 0.0, 0.0, -0.001989217717,]).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array( + [ + 0.0, + 0.0, + 0.001989217717, + 0.0, + 0.0, + -0.001989217717, + ] + ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ [-0.00114793, 0.0, 0.0, 0.00114793, 0.0, 0.0], @@ -528,7 +537,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.05669988343022163, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.009624481085, 0.0, 0.005505796371, -0.004812240542, 0.0, -0.005505796371, -0.004812240542,] + [ + 0.0, + 0.0, + 0.009624481085, + 0.0, + 0.005505796371, + -0.004812240542, + 0.0, + -0.005505796371, + -0.004812240542, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -716,7 +735,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.050177977945205, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.007512595487, 0.0, 0.004613769715, -0.003756297743, 0.0, -0.004613769715, -0.003756297743,] + [ + 0.0, + 0.0, + 0.007512595487, + 0.0, + 0.004613769715, + -0.003756297743, + 0.0, + -0.004613769715, + -0.003756297743, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -1326,7 +1355,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.06530131, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, -0.000531535533, 0.0, -0.000960201925, 0.000265767766, 0.0, 0.000960201925, 0.000265767766,] + [ + 0.0, + 0.0, + -0.000531535533, + 0.0, + -0.000960201925, + 0.000265767766, + 0.0, + 0.000960201925, + 0.000265767766, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -1564,7 +1603,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.058006927914493, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, -0.003374258422, 0.0, -0.002334452569, 0.001687129211, 0.0, 0.002334452569, 0.001687129211,] + [ + 0.0, + 0.0, + -0.003374258422, + 0.0, + -0.002334452569, + 0.001687129211, + 0.0, + 0.002334452569, + 0.001687129211, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3139,7 +3188,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03520162545964887, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.025490652204, 0.0, 0.013491755791, -0.012745326102, 0.0, -0.013491755791, -0.012745326102,] + [ + 0.0, + 0.0, + 0.025490652204, + 0.0, + 0.013491755791, + -0.012745326102, + 0.0, + -0.013491755791, + -0.012745326102, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -3197,7 +3256,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033935818857082, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.029278727285, 0.0, 0.015813927533, -0.014639363642, 0.0, -0.015813927533, -0.014639363642,] + [ + 0.0, + 0.0, + 0.029278727285, + 0.0, + 0.015813927533, + -0.014639363642, + 0.0, + -0.015813927533, + -0.014639363642, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3357,7 +3426,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.04161633, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.013731673196, 0.0, 0.005352105826, -0.006865836598, 0.0, -0.005352105826, -0.006865836598,] + [ + 0.0, + 0.0, + 0.013731673196, + 0.0, + 0.005352105826, + -0.006865836598, + 0.0, + -0.005352105826, + -0.006865836598, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -3415,7 +3494,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.039907245914335, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.016842165003, 0.0, 0.007150136873, -0.008421082502, 0.0, -0.007150136873, -0.008421082502,] + [ + 0.0, + 0.0, + 0.016842165003, + 0.0, + 0.007150136873, + -0.008421082502, + 0.0, + -0.007150136873, + -0.008421082502, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -3851,7 +3940,17 @@ "MP2 SINGLES ENERGY": -0.0028296307982793997, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03541709278508698, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.025609525826, 0.0, 0.013506941035, -0.012804762913, 0.0, -0.013506941035, -0.012804762913,] + [ + 0.0, + 0.0, + 0.025609525826, + 0.0, + 0.013506941035, + -0.012804762913, + 0.0, + -0.013506941035, + -0.012804762913, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -3887,7 +3986,17 @@ "CCSD SINGLES ENERGY": -0.00327524740575, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033982707798170, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.029273628227, 0.0, 0.015808308241, -0.014636814114, 0.0, -0.015808308241, -0.014636814114,] + [ + 0.0, + 0.0, + 0.029273628227, + 0.0, + 0.015808308241, + -0.014636814114, + 0.0, + -0.015808308241, + -0.014636814114, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.003901085777, "CCSD(T) TOTAL GRADIENT": np.array( # vcc fd @@ -3946,7 +4055,17 @@ "MP2 SINGLES ENERGY": -0.00298375, "MP2 SAME-SPIN CORRELATION ENERGY": -0.04178535, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.0138883429, 0.0, 0.005389090661, -0.00694417145, 0.0, -0.005389090661, -0.00694417145,] + [ + 0.0, + 0.0, + 0.0138883429, + 0.0, + 0.005389090661, + -0.00694417145, + 0.0, + -0.005389090661, + -0.00694417145, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -3982,7 +4101,17 @@ "CCSD SINGLES ENERGY": -0.00338286103325, "CCSD SAME-SPIN CORRELATION ENERGY": -0.039891470497466, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.016833254665, 0.0, 0.007144029475, -0.008416627332, 0.0, -0.007144029475, -0.008416627332,] + [ + 0.0, + 0.0, + 0.016833254665, + 0.0, + 0.007144029475, + -0.008416627332, + 0.0, + -0.007144029475, + -0.008416627332, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.005233938447, "CCSD(T) TOTAL GRADIENT": np.array( # vcc fd @@ -4085,7 +4214,16 @@ "CCSD CORRELATION ENERGY": -0.2068152041, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.0478712079, - "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.002335204281, 0.0, 0.0, -0.002335204281,]).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array( + [ + 0.0, + 0.0, + 0.002335204281, + 0.0, + 0.0, + -0.002335204281, + ] + ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ [-0.00134759, 0.0, 0.0, 0.00134759, 0.0, 0.0], @@ -4194,7 +4332,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.055833980855745646, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.010245839621, 0.0, 0.005893268945, -0.00512291981, 0.0, -0.005893268945, -0.00512291981,] + [ + 0.0, + 0.0, + 0.010245839621, + 0.0, + 0.005893268945, + -0.00512291981, + 0.0, + -0.005893268945, + -0.00512291981, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -4355,7 +4503,17 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # ncc fd [ - [-0.009738397928, 0.0, 0.0, 0.004869198964, 0.0, 0.0, 0.004869198964, 0.0, 0.0,], + [ + -0.009738397928, + 0.0, + 0.0, + 0.004869198964, + 0.0, + 0.0, + 0.004869198964, + 0.0, + 0.0, + ], [ 0.0, 0.711718022073, @@ -4378,7 +4536,17 @@ -0.215200115829, -0.234532728875, ], - [0.004869198964, 0.0, 0.0, -0.004677737322, 0.0, 0.0, -0.000191461642, 0.0, 0.0,], + [ + 0.004869198964, + 0.0, + 0.0, + -0.004677737322, + 0.0, + 0.0, + -0.000191461642, + 0.0, + 0.0, + ], [ 0.0, -0.355859011036, @@ -4401,7 +4569,17 @@ -0.032052678263, 0.011378546847, ], - [0.004869198964, 0.0, 0.0, -0.000191461642, 0.0, 0.0, -0.004677737322, 0.0, 0.0,], + [ + 0.004869198964, + 0.0, + 0.0, + -0.000191461642, + 0.0, + 0.0, + -0.004677737322, + 0.0, + 0.0, + ], [ 0.0, -0.355859011036, @@ -4647,7 +4825,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.06126410, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.00033347691, 0.0, -0.00056224437, -0.000166738455, 0.0, 0.00056224437, -0.000166738455,] + [ + 0.0, + 0.0, + 0.00033347691, + 0.0, + -0.00056224437, + -0.000166738455, + 0.0, + 0.00056224437, + -0.000166738455, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -4782,7 +4970,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.054051928864870, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, -0.002486174824, 0.0, -0.001923330621, 0.001243087412, 0.0, 0.001923330621, 0.001243087412,] + [ + 0.0, + 0.0, + -0.002486174824, + 0.0, + -0.001923330621, + 0.001243087412, + 0.0, + 0.001923330621, + 0.001243087412, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -4898,7 +5096,17 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # ncc fd [ - [-0.000589405884, 0.0, 0.0, 0.000294702942, 0.0, 0.0, 0.000294702942, 0.0, 0.0,], + [ + -0.000589405884, + 0.0, + 0.0, + 0.000294702942, + 0.0, + 0.0, + 0.000294702942, + 0.0, + 0.0, + ], [ 0.0, 0.690892000177, @@ -4921,7 +5129,17 @@ -0.204108329476, -0.230131473162, ], - [0.000294702942, 0.0, 0.0, -0.000024281097, 0.0, 0.0, -0.000270421845, 0.0, 0.0,], + [ + 0.000294702942, + 0.0, + 0.0, + -0.000024281097, + 0.0, + 0.0, + -0.000270421845, + 0.0, + 0.0, + ], [ 0.0, -0.345446000089, @@ -4944,7 +5162,17 @@ -0.031796321125, 0.011304513494, ], - [0.000294702942, 0.0, 0.0, -0.000270421845, 0.0, 0.0, -0.000024281097, 0.0, 0.0,], + [ + 0.000294702942, + 0.0, + 0.0, + -0.000270421845, + 0.0, + 0.0, + -0.000024281097, + 0.0, + 0.0, + ], [ 0.0, -0.345446000089, @@ -5970,7 +6198,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03445360441348938, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.026279427993, 0.0, 0.013998590506, -0.013139713997, 0.0, -0.013998590506, -0.013139713997,] + [ + 0.0, + 0.0, + 0.026279427993, + 0.0, + 0.013998590506, + -0.013139713997, + 0.0, + -0.013998590506, + -0.013139713997, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -6015,7 +6253,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033248190929062, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.030055915902, 0.0, 0.016307167756, -0.015027957951, 0.0, -0.016307167756, -0.015027957951,] + [ + 0.0, + 0.0, + 0.030055915902, + 0.0, + 0.016307167756, + -0.015027957951, + 0.0, + -0.016307167756, + -0.015027957951, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -6041,7 +6289,17 @@ ), "CCSD(T) TOTAL HESSIAN": np.array( # vcc fd [ - [-0.027539040923, 0.0, 0.0, 0.013769520462, 0.0, 0.0, 0.013769520462, 0.0, 0.0,], + [ + -0.027539040923, + 0.0, + 0.0, + 0.013769520462, + 0.0, + 0.0, + 0.013769520462, + 0.0, + 0.0, + ], [ 0.0, 0.615442549333, @@ -6064,7 +6322,17 @@ -0.189939555413, -0.196757901375, ], - [0.013769520462, 0.0, 0.0, -0.012609341037, 0.0, 0.0, -0.001160179424, 0.0, 0.0,], + [ + 0.013769520462, + 0.0, + 0.0, + -0.012609341037, + 0.0, + 0.0, + -0.001160179424, + 0.0, + 0.0, + ], [ 0.0, -0.307721274667, @@ -6087,7 +6355,17 @@ -0.028374542796, 0.009458104761, ], - [0.013769520462, 0.0, 0.0, -0.001160179424, 0.0, 0.0, -0.012609341037, 0.0, 0.0,], + [ + 0.013769520462, + 0.0, + 0.0, + -0.001160179424, + 0.0, + 0.0, + -0.012609341037, + 0.0, + 0.0, + ], [ 0.0, -0.307721274667, @@ -6148,7 +6426,17 @@ "MP2 SINGLES ENERGY": 0.0, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03822454, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.014740098324, 0.0, 0.005852228009, -0.007370049162, 0.0, -0.005852228009, -0.007370049162,] + [ + 0.0, + 0.0, + 0.014740098324, + 0.0, + 0.005852228009, + -0.007370049162, + 0.0, + -0.005852228009, + -0.007370049162, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # cfour [ @@ -6193,7 +6481,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.036526852874970, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.017883390799, 0.0, 0.00765987541, -0.0089416954, 0.0, -0.00765987541, -0.0089416954,] + [ + 0.0, + 0.0, + 0.017883390799, + 0.0, + 0.00765987541, + -0.0089416954, + 0.0, + -0.00765987541, + -0.0089416954, + ] ).reshape((-1, 3)), "CCSD TOTAL HESSIAN": np.array( # vcc [ @@ -6563,7 +6861,17 @@ "MP2 SINGLES ENERGY": -0.0028059971624814647, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03466304269235235, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.026398091851, 0.0, 0.014012163884, -0.013199045925, 0.0, -0.014012163884, -0.013199045925,] + [ + 0.0, + 0.0, + 0.026398091851, + 0.0, + 0.014012163884, + -0.013199045925, + 0.0, + -0.014012163884, + -0.013199045925, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -6594,7 +6902,17 @@ "CCSD SINGLES ENERGY": -0.003256808469230, "CCSD SAME-SPIN CORRELATION ENERGY": -0.033291143258924, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.030051791297, 0.0, 0.016301545337, -0.015025895649, 0.0, -0.016301545337, -0.015025895649,] + [ + 0.0, + 0.0, + 0.030051791297, + 0.0, + 0.016301545337, + -0.015025895649, + 0.0, + -0.016301545337, + -0.015025895649, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.003863167899, # cfour only "CCSDT CORRELATION ENERGY": -0.18030677104047, # vcc (different orbs: -0.18031166502580) @@ -6619,7 +6937,17 @@ "MP2 SINGLES ENERGY": -0.00294339, "MP2 SAME-SPIN CORRELATION ENERGY": -0.03837483, "MP2 TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.014894057335, 0.0, 0.005886660707, -0.007447028667, 0.0, -0.005886660707, -0.007447028667,] + [ + 0.0, + 0.0, + 0.014894057335, + 0.0, + 0.005886660707, + -0.007447028667, + 0.0, + -0.005886660707, + -0.007447028667, + ] ).reshape((-1, 3)), "MP2 TOTAL HESSIAN": np.array( # vcc by psi fd [ @@ -6654,7 +6982,17 @@ "CCSD SINGLES ENERGY": -0.003354603508621, "CCSD SAME-SPIN CORRELATION ENERGY": -0.036502859698546, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.017873897449, 0.0, 0.007653541045, -0.008936948724, 0.0, -0.007653541045, -0.008936948724,] + [ + 0.0, + 0.0, + 0.017873897449, + 0.0, + 0.007653541045, + -0.008936948724, + 0.0, + -0.007653541045, + -0.008936948724, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00504351, # cfour only "CCSDT CORRELATION ENERGY": -0.19824510672649, # vcc @@ -7599,7 +7937,17 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.05670210, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 ae cd+cd - [0.0, 0.0, 0.009643414073, 0.0, 0.005501440694, -0.004821707036, 0.0, -0.005501440694, -0.004821707036,] + [ + 0.0, + 0.0, + 0.009643414073, + 0.0, + 0.005501440694, + -0.004821707036, + 0.0, + -0.005501440694, + -0.004821707036, + ] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.22643303, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -7630,7 +7978,17 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.06530655, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 ae cd+cd - [0.0, 0.0, -0.000546229785, 0.0, -0.000967320028, 0.000273114892, 0.0, 0.000967320028, 0.000273114892,] + [ + 0.0, + 0.0, + -0.000546229785, + 0.0, + -0.000967320028, + 0.000273114892, + 0.0, + 0.000967320028, + 0.000273114892, + ] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.27294416, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -7930,7 +8288,17 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.05583617, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 fc cd+cd - [0.0, 0.0, 0.010264703011, 0.0, 0.00588885358, -0.005132351506, 0.0, -0.00588885358, -0.005132351506,] + [ + 0.0, + 0.0, + 0.010264703011, + 0.0, + 0.00588885358, + -0.005132351506, + 0.0, + -0.00588885358, + -0.005132351506, + ] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.22415794, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -7961,7 +8329,17 @@ "MP2 SAME-SPIN CORRELATION ENERGY": -0.06126931, "MP2 TOTAL GRADIENT": np.array( # dfocc findif-5 fc cd+cd - [0.0, 0.0, 0.000318778691, 0.0, -0.000569356625, -0.000159389346, 0.0, 0.000569356625, -0.000159389346,] + [ + 0.0, + 0.0, + 0.000318778691, + 0.0, + -0.000569356625, + -0.000159389346, + 0.0, + 0.000569356625, + -0.000159389346, + ] ).reshape((-1, 3)), "MP3 CORRELATION ENERGY": -0.24747710, # dfocc "MP3 SINGLES ENERGY": 0.0, @@ -8704,7 +9082,16 @@ "CCSD CORRELATION ENERGY": -0.20886884012911314, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04845491, - "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.001970675302, 0.0, 0.0, -0.001970675302,]).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array( + [ + 0.0, + 0.0, + 0.001970675302, + 0.0, + 0.0, + -0.001970675302, + ] + ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.0019380186429220421, # "XXX TOTAL GRADIENT": np.zeros(6).reshape((-1, 3)), # "XXX TOTAL HESSIAN": np.zeros(36).reshape((6, 6)), @@ -8752,7 +9139,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.05009877, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.007518759967, 0.0, 0.004613106602, -0.003759379983, 0.0, -0.004613106602, -0.003759379983,] + [ + 0.0, + 0.0, + 0.007518759967, + 0.0, + 0.004613106602, + -0.003759379983, + 0.0, + -0.004613106602, + -0.003759379983, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00524345, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), @@ -9107,7 +9504,16 @@ "CCSD CORRELATION ENERGY": -0.20694032546082639, "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04775171, - "CCSD TOTAL GRADIENT": np.array([0.0, 0.0, 0.002316563628, 0.0, 0.0, -0.002316563628,]).reshape((-1, 3)), + "CCSD TOTAL GRADIENT": np.array( + [ + 0.0, + 0.0, + 0.002316563628, + 0.0, + 0.0, + -0.002316563628, + ] + ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.001922093564526723, # "XXX TOTAL GRADIENT": np.zeros(6).reshape((-1, 3)), # "XXX TOTAL HESSIAN": np.zeros(36).reshape((6, 6)), @@ -9155,7 +9561,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.04931891, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, 0.008124347934, 0.0, 0.004987676555, -0.004062173967, 0.0, -0.004987676555, -0.004062173967,] + [ + 0.0, + 0.0, + 0.008124347934, + 0.0, + 0.004987676555, + -0.004062173967, + 0.0, + -0.004987676555, + -0.004062173967, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00521721, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), @@ -9204,7 +9620,17 @@ "CCSD SINGLES ENERGY": 0.0, "CCSD SAME-SPIN CORRELATION ENERGY": -0.05404876, "CCSD TOTAL GRADIENT": np.array( - [0.0, 0.0, -0.002520920562, 0.0, -0.001932133533, 0.001260460281, 0.0, 0.001932133533, 0.001260460281,] + [ + 0.0, + 0.0, + -0.002520920562, + 0.0, + -0.001932133533, + 0.001260460281, + 0.0, + 0.001932133533, + 0.001260460281, + ] ).reshape((-1, 3)), "(T) CORRECTION ENERGY": -0.00709505, # "XXX TOTAL GRADIENT": np.zeros(9).reshape((-1, 3)), diff --git a/qcengine/programs/tests/standard_suite_runner.py b/qcengine/programs/tests/standard_suite_runner.py index ada719592..d84b3fbe5 100644 --- a/qcengine/programs/tests/standard_suite_runner.py +++ b/qcengine/programs/tests/standard_suite_runner.py @@ -126,7 +126,12 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): if is_dft: atol_g = 6.0e-6 chash = answer_hash( - system=subject.name, basis=basis, fcae=fcae, scf_type=scf_type, reference=reference, corl_type=corl_type, + system=subject.name, + basis=basis, + fcae=fcae, + scf_type=scf_type, + reference=reference, + corl_type=corl_type, ) ref_block = std_suite[chash] @@ -134,7 +139,12 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): atol_conv = 1.0e-4 rtol_conv = 1.0e-3 chash_conv = answer_hash( - system=subject.name, basis=basis, fcae=fcae, reference=reference, corl_type="conv", scf_type="pk", + system=subject.name, + basis=basis, + fcae=fcae, + reference=reference, + corl_type="conv", + scf_type="pk", ) ref_block_conv = std_suite[chash_conv] @@ -144,7 +154,10 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): **{ "molecule": subject, "driver": driver, - "model": {"method": method, "basis": inp.get("basis", "(auto)"),}, + "model": { + "method": method, + "basis": inp.get("basis", "(auto)"), + }, "keywords": inp["keywords"], } ) diff --git a/qcengine/programs/tests/test_alignment.py b/qcengine/programs/tests/test_alignment.py index 019f0c5c1..8870d5241 100644 --- a/qcengine/programs/tests/test_alignment.py +++ b/qcengine/programs/tests/test_alignment.py @@ -62,7 +62,11 @@ def clsd_open_pmols(): ) @pytest.mark.parametrize( "driver", - [pytest.param("energy", id="ene0"), pytest.param("gradient", id="grd1"), pytest.param("hessian", id="hes2"),], + [ + pytest.param("energy", id="ene0"), + pytest.param("gradient", id="grd1"), + pytest.param("hessian", id="hes2"), + ], ) @pytest.mark.parametrize( "basis, subjects", diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index 19cea1163..c0108d1ad 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -90,7 +90,14 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req # << Config - config = qcng.config.get_config(hostname="something", local_options={"ncores": 1, "nnodes": 1, "memory": 1.555,},) + config = qcng.config.get_config( + hostname="something", + local_options={ + "ncores": 1, + "nnodes": 1, + "memory": 1.555, + }, + ) # << Run @@ -149,7 +156,11 @@ def test_local_options_scratch(program, model, keywords): scratch_directory = tempfile.mkdtemp(suffix="_" + program) config = qcng.config.get_config( - hostname="something", local_options={"scratch_directory": scratch_directory, "scratch_messy": True,}, + hostname="something", + local_options={ + "scratch_directory": scratch_directory, + "scratch_messy": True, + }, ) # << Run @@ -221,7 +232,13 @@ def test_local_options_ncores(program, model, keywords, ncores): # << Config - config = qcng.config.get_config(hostname="something", local_options={"ncores": ncores, "nnodes": 1,},) + config = qcng.config.get_config( + hostname="something", + local_options={ + "ncores": ncores, + "nnodes": 1, + }, + ) # << Run diff --git a/qcengine/programs/tests/test_canonical_fields.py b/qcengine/programs/tests/test_canonical_fields.py index a76026333..eeaed01c2 100644 --- a/qcengine/programs/tests/test_canonical_fields.py +++ b/qcengine/programs/tests/test_canonical_fields.py @@ -36,7 +36,13 @@ def test_protocol_native(program, model, keywords, native): protocols = { "native_files": native, } - config = qcng.config.get_config(hostname="something", local_options={"ncores": 1, "nnodes": 1,},) + config = qcng.config.get_config( + hostname="something", + local_options={ + "ncores": 1, + "nnodes": 1, + }, + ) # << Run diff --git a/qcengine/programs/tests/test_dftd3_mp2d.py b/qcengine/programs/tests/test_dftd3_mp2d.py index 6f604463c..ba5c1321c 100644 --- a/qcengine/programs/tests/test_dftd3_mp2d.py +++ b/qcengine/programs/tests/test_dftd3_mp2d.py @@ -1728,7 +1728,11 @@ def test_dftd3__run_dftd3__2body(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput(molecule=mol, driver="gradient", **inp["qcsk"],) + atin = AtomicInput( + molecule=mol, + driver="gradient", + **inp["qcsk"], + ) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() pprint.pprint(jrec) @@ -1789,7 +1793,11 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput(molecule=mol, driver="gradient", **inp["qcsk"],) + atin = AtomicInput( + molecule=mol, + driver="gradient", + **inp["qcsk"], + ) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() @@ -1888,7 +1896,14 @@ def test_sapt_pairwise(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput(molecule=mol, driver="energy", model={"method": inp["lbl"]}, keywords={"pair_resolved": True,},) + atin = AtomicInput( + molecule=mol, + driver="energy", + model={"method": inp["lbl"]}, + keywords={ + "pair_resolved": True, + }, + ) jrec = qcng.compute(atin, program, raise_error=True) jrec = jrec.dict() @@ -1902,7 +1917,11 @@ def test_sapt_pairwise(inp, subjects, request): @pytest.mark.parametrize( - "program", [pytest.param("gcp", marks=using("gcp")), pytest.param("mctc-gcp", marks=using("mctc-gcp")),], + "program", + [ + pytest.param("gcp", marks=using("gcp")), + pytest.param("mctc-gcp", marks=using("mctc-gcp")), + ], ) @pytest.mark.parametrize( "subjects", diff --git a/qcengine/programs/tests/test_dftd4.py b/qcengine/programs/tests/test_dftd4.py index 4eda4e5ab..fdd88d061 100644 --- a/qcengine/programs/tests/test_dftd4.py +++ b/qcengine/programs/tests/test_dftd4.py @@ -21,7 +21,9 @@ def test_dftd4_task_b97m_m01(): return_result = -0.025024986301735823 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), model={"method": "b97m"}, driver="energy", + molecule=qcng.get_molecule("mindless-01"), + model={"method": "b97m"}, + driver="energy", ) atomic_result = qcng.compute(atomic_input, "dftd4") @@ -60,7 +62,13 @@ def test_dftd4_task_tpss_m02(): atomic_input = qcel.models.AtomicInput( molecule=qcng.get_molecule("mindless-02"), model={"method": ""}, - keywords={"params_tweaks": {"s8": 1.76596355, "a1": 0.42822303, "a2": 4.54257102,},}, + keywords={ + "params_tweaks": { + "s8": 1.76596355, + "a1": 0.42822303, + "a2": 4.54257102, + }, + }, driver="gradient", ) @@ -148,7 +156,10 @@ def test_dftd4_task_cold_fusion(): model={"method": "pbe"}, driver="energy", ) - error = qcel.models.ComputeError(error_type="input error", error_message="Too close interatomic distances found",) + error = qcel.models.ComputeError( + error_type="input error", + error_message="Too close interatomic distances found", + ) atomic_result = qcng.compute(atomic_input, "dftd4") diff --git a/qcengine/programs/tests/test_ghost.py b/qcengine/programs/tests/test_ghost.py index 14095b7e3..710679da3 100644 --- a/qcengine/programs/tests/test_ghost.py +++ b/qcengine/programs/tests/test_ghost.py @@ -81,7 +81,16 @@ def test_simple_ghost(program, basis, keywords, hene): marks=using("gamess"), ), pytest.param("nwchem", "6-31g*", {"scf__thresh": 1.0e-8}, id="nwchem", marks=using("nwchem")), - pytest.param("psi4", "6-31g*", {"scf_type": "pk", "mp2_type": "conv",}, id="psi4", marks=using("psi4"),), + pytest.param( + "psi4", + "6-31g*", + { + "scf_type": "pk", + "mp2_type": "conv", + }, + id="psi4", + marks=using("psi4"), + ), ], ) def test_tricky_ghost(qcprog, subject, basis, keywords): @@ -142,11 +151,26 @@ def test_tricky_ghost(qcprog, subject, basis, keywords): "qcprog, basis, keywords", [ pytest.param("cfour", "aug-pvdz", {"scf_conv": 12}, id="cfour", marks=using("cfour")), - pytest.param("gamess", "accd", {"mp2__nacore": 0, "contrl__ispher": 1}, id="gamess", marks=using("gamess"),), + pytest.param( + "gamess", + "accd", + {"mp2__nacore": 0, "contrl__ispher": 1}, + id="gamess", + marks=using("gamess"), + ), pytest.param( "nwchem", "aug-cc-pvdz", {"scf__nr": 1.0, "scf__thresh": 1.0e-8}, id="nwchem", marks=using("nwchem") ), - pytest.param("psi4", "aug-cc-pvdz", {"scf_type": "pk", "mp2_type": "conv",}, id="psi4", marks=using("psi4"),), + pytest.param( + "psi4", + "aug-cc-pvdz", + { + "scf_type": "pk", + "mp2_type": "conv", + }, + id="psi4", + marks=using("psi4"), + ), ], ) def test_atom_labels(qcprog, basis, keywords): diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index d6e53264a..09220d237 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -30,7 +30,9 @@ def h2o(): @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}), ], + [ + pytest.param("madness", None, {"dft__k": 7, "dft__aobasis": "sto-3g", "dft__econv": 1.0000e-05}), + ], ) @using("madness") def test_mad_hf(program, basis, keywords, h2o): @@ -93,7 +95,9 @@ def test_mad_hf_response(program, basis, keywords, h2o): @using("madness") @pytest.mark.parametrize( "program,basis,keywords", - [pytest.param("madness", None, {}), ], + [ + pytest.param("madness", None, {}), + ], ) @using("madness") def test_mad_hf_be(program, basis, keywords, Be): @@ -112,6 +116,7 @@ def test_mad_hf_be(program, basis, keywords, Be): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) + @using("madness") @pytest.mark.parametrize( "program,basis,keywords", @@ -127,7 +132,6 @@ def test_mad_hf_be(program, basis, keywords, Be): ), ], ) - @using("madness") def test_mad_geometry_optimization(program, basis, keywords, h2o): resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} @@ -144,4 +148,3 @@ def test_mad_geometry_optimization(program, basis, keywords, h2o): atol = 1.0e-5 assert compare_values(scf_tot, res["return_result"], atol=atol) - diff --git a/qcengine/programs/tests/test_mrchem.py b/qcengine/programs/tests/test_mrchem.py index fee8eab80..2ad79e32c 100644 --- a/qcengine/programs/tests/test_mrchem.py +++ b/qcengine/programs/tests/test_mrchem.py @@ -25,7 +25,14 @@ def test_energy(h2o): "world_unit": "bohr", } - inp = qcel.models.AtomicInput(molecule=h2o, driver="energy", model={"method": "BLYP",}, keywords=mr_kws,) + inp = qcel.models.AtomicInput( + molecule=h2o, + driver="energy", + model={ + "method": "BLYP", + }, + keywords=mr_kws, + ) res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) @@ -52,7 +59,14 @@ def test_dipole(h2o): "world_unit": "bohr", } - inp = qcel.models.AtomicInput(molecule=h2o, driver="properties", model={"method": "BLYP",}, keywords=mr_kws,) + inp = qcel.models.AtomicInput( + molecule=h2o, + driver="properties", + model={ + "method": "BLYP", + }, + keywords=mr_kws, + ) res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 9f9021090..9e1feb857 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -322,7 +322,11 @@ def test_conv_threshold(h20v2, method, keyword, init_iters, use_tce): "molecule": h20v2, "model": {"method": method, "basis": "sto-3g"}, "driver": "energy", - "keywords": {keyword: init_iters, "qc_module": use_tce, "scf__uhf": True,}, # UHF needed for SCF test + "keywords": { + keyword: init_iters, + "qc_module": use_tce, + "scf__uhf": True, + }, # UHF needed for SCF test }, "nwchem", raise_error=True, diff --git a/qcengine/programs/tests/test_qcore.py b/qcengine/programs/tests/test_qcore.py index 10d66a508..c4ff4e9fa 100644 --- a/qcengine/programs/tests/test_qcore.py +++ b/qcengine/programs/tests/test_qcore.py @@ -18,7 +18,11 @@ ) def test_qcore_methods(method, energy, gradient_norm): - atomic_input = qcel.models.AtomicInput(molecule=qcng.get_molecule("water"), model=method, driver="gradient",) + atomic_input = qcel.models.AtomicInput( + molecule=qcng.get_molecule("water"), + model=method, + driver="gradient", + ) atomic_result = qcng.compute(atomic_input, "qcore") diff --git a/qcengine/programs/tests/test_standard_suite.py b/qcengine/programs/tests/test_standard_suite.py index a5b41cb35..b08af9cdb 100644 --- a/qcengine/programs/tests/test_standard_suite.py +++ b/qcengine/programs/tests/test_standard_suite.py @@ -123,7 +123,10 @@ def _trans_key(qc, bas, key): @pytest.mark.parametrize( - "dertype", [pytest.param(0, id="ene0"),], + "dertype", + [ + pytest.param(0, id="ene0"), + ], ) @pytest.mark.parametrize( "basis, subjects", @@ -273,7 +276,11 @@ def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, reque @pytest.mark.parametrize( - "dertype", [0,], ids=["ene0"], + "dertype", + [ + 0, + ], + ids=["ene0"], ) @pytest.mark.parametrize( "basis, subjects", @@ -346,7 +353,11 @@ def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reque @pytest.mark.parametrize( - "dertype", [0,], ids=["ene0"], + "dertype", + [ + 0, + ], + ids=["ene0"], ) @pytest.mark.parametrize( "basis, subjects", diff --git a/qcengine/programs/tests/test_turbomole.py b/qcengine/programs/tests/test_turbomole.py index 456024e9b..16a97c3c3 100644 --- a/qcengine/programs/tests/test_turbomole.py +++ b/qcengine/programs/tests/test_turbomole.py @@ -126,7 +126,10 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): resi = { "molecule": h2o, "driver": "hessian", - "model": {"method": method, "basis": "def2-SVP",}, + "model": { + "method": method, + "basis": "def2-SVP", + }, "keywords": keywords, } @@ -142,13 +145,19 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): @using("turbomole") @pytest.mark.parametrize( - "method, keywords, ref_eigvals", [("ricc2", {}, (1.65405531e-01, 9.63690706e-01, 1.24676634e00)),], + "method, keywords, ref_eigvals", + [ + ("ricc2", {}, (1.65405531e-01, 9.63690706e-01, 1.24676634e00)), + ], ) def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp): resi = { "molecule": h2o_ricc2_def2svp, "driver": "hessian", - "model": {"method": method, "basis": "def2-SVP",}, + "model": { + "method": method, + "basis": "def2-SVP", + }, "keywords": keywords, } diff --git a/qcengine/programs/tests/test_xtb.py b/qcengine/programs/tests/test_xtb.py index ce1e5d859..86148ce06 100644 --- a/qcengine/programs/tests/test_xtb.py +++ b/qcengine/programs/tests/test_xtb.py @@ -41,7 +41,9 @@ def test_xtb_task_gfn1xtb_m01(): ) atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), model={"method": "GFN1-xTB"}, driver="gradient", + molecule=qcng.get_molecule("mindless-01"), + model={"method": "GFN1-xTB"}, + driver="gradient", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -80,7 +82,10 @@ def test_xtb_task_gfn1xtb_m02(): molecule=qcng.get_molecule("mindless-02"), model={"method": "GFN1-xTB"}, driver="gradient", - keywords={"accuracy": 0.1, "electronic_temperature": 500.0,}, + keywords={ + "accuracy": 0.1, + "electronic_temperature": 500.0, + }, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -119,7 +124,9 @@ def test_xtb_task_gfn1xtb_m03(): molecule=qcng.get_molecule("mindless-03"), model={"method": "GFN1-xTB"}, driver="gradient", - keywords={"solvent": "chcl3",}, + keywords={ + "solvent": "chcl3", + }, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -158,7 +165,9 @@ def test_xtb_task_gfn1xtb_m04(): } atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), model={"method": "GFN1-xTB"}, driver="properties", + molecule=qcng.get_molecule("mindless-04"), + model={"method": "GFN1-xTB"}, + driver="properties", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -177,7 +186,9 @@ def test_xtb_task_gfn1xtb_m05(): return_result = -29.038403257613453 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), model={"method": "GFN1-xTB"}, driver="energy", + molecule=qcng.get_molecule("mindless-05"), + model={"method": "GFN1-xTB"}, + driver="energy", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -214,7 +225,9 @@ def test_xtb_task_gfn2xtb_m01(): ) atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), model={"method": "GFN2-xTB"}, driver="gradient", + molecule=qcng.get_molecule("mindless-01"), + model={"method": "GFN2-xTB"}, + driver="gradient", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -253,7 +266,10 @@ def test_xtb_task_gfn2xtb_m02(): molecule=qcng.get_molecule("mindless-02"), model={"method": "GFN2-xTB"}, driver="gradient", - keywords={"accuracy": 0.1, "electronic_temperature": 500.0,}, + keywords={ + "accuracy": 0.1, + "electronic_temperature": 500.0, + }, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -292,7 +308,9 @@ def test_xtb_task_gfn2xtb_m03(): molecule=qcng.get_molecule("mindless-03"), model={"method": "GFN2-xTB"}, driver="gradient", - keywords={"solvent": "chcl3",}, + keywords={ + "solvent": "chcl3", + }, ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -331,7 +349,9 @@ def test_xtb_task_gfn2xtb_m04(): } atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), model={"method": "GFN2-xTB"}, driver="properties", + molecule=qcng.get_molecule("mindless-04"), + model={"method": "GFN2-xTB"}, + driver="properties", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -350,7 +370,9 @@ def test_xtb_task_gfn2xtb_m05(): return_result = -27.73598761779656 atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), model={"method": "GFN2-xTB"}, driver="energy", + molecule=qcng.get_molecule("mindless-05"), + model={"method": "GFN2-xTB"}, + driver="energy", ) atomic_result = qcng.compute(atomic_input, "xtb") @@ -364,7 +386,9 @@ def test_xtb_task_gfn2xtb_m05(): def test_xtb_task_unknown_method(): atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), model={"method": "GFN-xTB"}, driver="energy", + molecule=qcng.get_molecule("water"), + model={"method": "GFN-xTB"}, + driver="energy", ) error = qcel.models.ComputeError(error_type="input_error", error_message="Invalid method GFN-xTB provided in model") @@ -378,7 +402,9 @@ def test_xtb_task_unknown_method(): def test_xtb_task_unsupported_driver(): atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), model={"method": "GFN2-xTB"}, driver="hessian", + molecule=qcng.get_molecule("water"), + model={"method": "GFN2-xTB"}, + driver="hessian", ) error = qcel.models.ComputeError( error_type="input_error", error_message="Calculation succeeded but invalid driver request provided" diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index a0345acba..99dce2717 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -297,7 +297,12 @@ def test_torsiondrive_generic(): input_specification=QCInputSpecification(driver=DriverEnum.gradient, model=Model(method="UFF", basis=None)), initial_molecule=[qcng.get_molecule("ethane")] * 2, optimization_spec=OptimizationSpecification( - procedure="geomeTRIC", keywords={"coordsys": "dlc", "maxiter": 300, "program": "rdkit",}, + procedure="geomeTRIC", + keywords={ + "coordsys": "dlc", + "maxiter": 300, + "program": "rdkit", + }, ), ) From f89847c41d60cf2f884f6e0210fda1a18139e504 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 15 Jun 2022 12:47:04 -0400 Subject: [PATCH 086/102] some additions --- qcengine/programs/madness/runner.py | 6 +++--- qcengine/programs/tests/test_canonical_config.py | 1 + qcengine/tests/test_harness_canonical.py | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 4e3c47193..80b8049d1 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -68,7 +68,7 @@ def found(raise_error: bool = False) -> bool: """ qc = which( - "madness", + "moldft", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", @@ -89,7 +89,7 @@ def get_version(self) -> str: config = get_config() # Run MADNESS - which_prog = which("madness") + which_prog = which("moldft") if config.use_mpiexec: command = create_mpi_invocation(which_prog, config) @@ -101,7 +101,7 @@ def get_version(self) -> str: success, output = execute( command, { - "v.moldft": "dft\nxc lda\nend\ngeometry\nO 0.0 0.0 0.0\nH 1.4375 0.0 1.15\nH - 1.4375 0.0 1.15\nend\n" + "v.moldft": "dft\nxc lda\nend\ngeometry\nBe 0.0 0.0 0.0\n\nend\n" }, scratch_directory=config.scratch_directory, ) diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index c0108d1ad..da439aae9 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -24,6 +24,7 @@ ("mp2d", {"method": "MP2-DMP2"}, {}), # needs attn ("mrchem", {"method": "blyp"}, {"world_prec": 1.0e-3}), ("nwchem", {"method": "hf", "basis": "6-31G"}, {}), + ("madness", {"method": "hf"}, {}), # needs attn ("openmm", {"method": "openff-1.0.0", "basis": "smirnoff"}, {}), ("psi4", {"method": "hf", "basis": "6-31G"}, {"gradient_write": True}), # needs attn ("qchem", {"method": "hf", "basis": "6-31G"}, {}), diff --git a/qcengine/tests/test_harness_canonical.py b/qcengine/tests/test_harness_canonical.py index 0b10193e3..458dc1903 100644 --- a/qcengine/tests/test_harness_canonical.py +++ b/qcengine/tests/test_harness_canonical.py @@ -29,6 +29,7 @@ ("adcc", {"method": "adc2", "basis": "6-31G"}, {"n_triplets": 3}), ("gcp", {"method": "hf3c"}, {}), ("mrchem", {"method": "blyp"}, {"world_prec": 1.0e-3}), + ("madness", {"method": "hf"}, {}), ("cfour", {"method": "hf", "basis": "6-31G"}, {}), ("gamess", {"method": "hf", "basis": "n31"}, {"basis__NGAUSS": 6}), ("mctc-gcp", {"method": "dft/sv"}, {}), @@ -131,6 +132,7 @@ def test_compute_energy_qcsk_basis(program, model, keywords): ("adcc", {"method": "bad"}), ("gcp", {"method": "bad"}), ("mrchem", {"method": "bad"}), + ("madness", {"method": "bad"}), ("mctc-gcp", {"method": "bad"}), # add as programs available # ("molpro", {"method": "bad"}), From ea814e5b90e2b6e4c092240d922e3800c08394c6 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Fri, 24 Jun 2022 14:26:25 -0400 Subject: [PATCH 087/102] formatting --- docs/source/conf.py | 30 +++++++++++++-------------- qcengine/config.py | 2 +- qcengine/programs/cfour/runner.py | 2 +- qcengine/programs/gamess/runner.py | 2 +- qcengine/programs/madness/runner.py | 4 +--- qcengine/programs/molpro.py | 2 +- qcengine/programs/nwchem/runner.py | 2 +- qcengine/programs/turbomole/runner.py | 2 +- 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 72725bb98..9e44cce2b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -46,26 +46,26 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.extlinks', - 'sphinx.ext.graphviz', - 'sphinx.ext.autosummary', - 'sphinx.ext.napoleon', - 'sphinx_automodapi.automodapi', - 'sphinx_automodapi.automodsumm', - 'sphinx_automodapi.smart_resolver', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.extlinks", + "sphinx.ext.graphviz", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx_automodapi.automodapi", + "sphinx_automodapi.automodsumm", + "sphinx_automodapi.smart_resolver", "sphinx_autodoc_typehints", "sphinxcontrib.autodoc_pydantic", ] autosummary_generate = True -automodapi_toctreedirnm = 'api' +automodapi_toctreedirnm = "api" autodoc_typehints = "description" napoleon_use_param = True napoleon_use_rtype = True diff --git a/qcengine/config.py b/qcengine/config.py index fee53c89a..9909f2a7a 100644 --- a/qcengine/config.py +++ b/qcengine/config.py @@ -34,7 +34,7 @@ def get_global(key: Optional[str] = None) -> Union[str, Dict[str, Any]]: if _global_values is None: _global_values = {} _global_values["hostname"] = socket.gethostname() - _global_values["memory"] = round(psutil.virtual_memory().available / (1024**3), 3) + _global_values["memory"] = round(psutil.virtual_memory().available / (1024 ** 3), 3) _global_values["username"] = getpass.getuser() # Work through VMs and logical cores. diff --git a/qcengine/programs/cfour/runner.py b/qcengine/programs/cfour/runner.py index a19f55c84..b0ba21b42 100644 --- a/qcengine/programs/cfour/runner.py +++ b/qcengine/programs/cfour/runner.py @@ -90,7 +90,7 @@ def build_input( # Handle memory # for cfour, [GiB] --> [QW] - opts["memory_size"] = int(config.memory * (1024**3) / 8) + opts["memory_size"] = int(config.memory * (1024 ** 3) / 8) opts["mem_unit"] = "integerwords" # Handle molecule diff --git a/qcengine/programs/gamess/runner.py b/qcengine/programs/gamess/runner.py index b456dbc4d..5b463333c 100644 --- a/qcengine/programs/gamess/runner.py +++ b/qcengine/programs/gamess/runner.py @@ -119,7 +119,7 @@ def build_input( # * docs on mwords: "This is given in units of 1,000,000 words (as opposed to 1024*1024 words)" # * docs: "the memory required on each processor core for a run using p cores is therefore MEMDDI/p + MWORDS." # * int() rounds down - mwords_total = int(config.memory * (1024**3) / 8e6) + mwords_total = int(config.memory * (1024 ** 3) / 8e6) for mem_frac_replicated in (1, 0.5, 0.1, 0.75): mwords, memddi = self._partition(mwords_total, mem_frac_replicated, config.ncores) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 80b8049d1..1438b7c88 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -100,9 +100,7 @@ def get_version(self) -> str: if which_prog not in self.version_cache: success, output = execute( command, - { - "v.moldft": "dft\nxc lda\nend\ngeometry\nBe 0.0 0.0 0.0\n\nend\n" - }, + {"v.moldft": "dft\nxc lda\nend\ngeometry\nBe 0.0 0.0 0.0\n\nend\n"}, scratch_directory=config.scratch_directory, ) diff --git a/qcengine/programs/molpro.py b/qcengine/programs/molpro.py index 0025ae0f4..0a9f0ed6a 100644 --- a/qcengine/programs/molpro.py +++ b/qcengine/programs/molpro.py @@ -184,7 +184,7 @@ def build_input( unrestricted = True # Memory is in megawords per core for Molpro - memory_mw_core = int(config.memory * (1024**3) / 8e6 / config.ncores) + memory_mw_core = int(config.memory * (1024 ** 3) / 8e6 / config.ncores) input_file.append("memory,{},M".format(memory_mw_core)) input_file.append("") diff --git a/qcengine/programs/nwchem/runner.py b/qcengine/programs/nwchem/runner.py index d528a9d77..441ca096a 100644 --- a/qcengine/programs/nwchem/runner.py +++ b/qcengine/programs/nwchem/runner.py @@ -168,7 +168,7 @@ def build_input( # * int() rounds down # * was [GiB] --> [B] c. v6.6 but fails in v7.0 probably b/c https://github.com/nwchemgit/nwchem/commit/fca382eab477c3e85548457bfceb1fc9be31b47c#diff-7baaf4807cc9b853af14d9127f63db47d706e12f697a98560bc98bb647ef8326 # * memory_size = int(config.memory * (1024 ** 3)) - memory_size = int(config.memory * (1024**3) / 8) + memory_size = int(config.memory * (1024 ** 3) / 8) if config.use_mpiexec: # It is the memory per MPI rank memory_size //= config.nnodes * config.ncores // config.cores_per_rank opts["memory"] = memory_size diff --git a/qcengine/programs/turbomole/runner.py b/qcengine/programs/turbomole/runner.py index 2fc51a7fa..644a4860a 100644 --- a/qcengine/programs/turbomole/runner.py +++ b/qcengine/programs/turbomole/runner.py @@ -154,7 +154,7 @@ def build_input( control = turbomolerec["infiles"]["control"] # Calculate total available memory in MB - mem_mb = config.memory * (1024**3) / 1e6 + mem_mb = config.memory * (1024 ** 3) / 1e6 ri_fraction = 0.25 # Total amount of memory allocated to ricore ricore = 0 From 68a919326f305f6baa6f6cfee4c11ac41dc2186a Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 27 Jun 2022 18:21:45 -0400 Subject: [PATCH 088/102] fix the geometry input for get_version function --- qcengine/programs/madness/runner.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 1438b7c88..39bc5ab69 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -99,8 +99,10 @@ def get_version(self) -> str: if which_prog not in self.version_cache: success, output = execute( - command, - {"v.moldft": "dft\nxc lda\nend\ngeometry\nBe 0.0 0.0 0.0\n\nend\n"}, + command, + { + "v.moldft": "dft\nxc lda\nend\ngeometry\nH 0.0 0.0 0.0\nH 0.7 0.0 0.0\nend\n" + }, scratch_directory=config.scratch_directory, ) @@ -305,7 +307,6 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": "return_result": retres, "stdout": stdout, } - # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { From 4c74677940c25baa4c82e6d5bffffa6b8ecabf09 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 28 Jun 2022 13:35:58 -0400 Subject: [PATCH 089/102] remove reading scf_info.json --- qcengine/programs/madness/harvester.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 41146f421..15ae124b1 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -203,13 +203,13 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol moldft_outfiles = moldft_info.get("outfiles") # At this point scf prints a list of json outputs where each list refers to the scf at givin protocol # Here I load the scf_info and calc_info as json - scf_info = json.loads(moldft_outfiles.get("scf_info.json")) + #scf_info = json.loads(moldft_outfiles.get("scf_info.json")) calc_info = json.loads(moldft_outfiles.get("calc_info.json")) # Write harvest scf_info and harvest calc_info out_calc_vars = harvest_calc_info(calc_info) - out_scf_vars = harvest_scf_info(scf_info) + #out_scf_vars = harvest_scf_info(scf_info) out_psivar.update(out_calc_vars) - out_psivar.update(out_scf_vars) + #out_psivar.update(out_scf_vars) if "molresponse" in outfiles.keys(): molresponse_info = outfiles.get("moldft") From 3feaf7ed8192b1ec4997223e8b552f1347fc6b91 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 28 Jun 2022 15:32:40 -0400 Subject: [PATCH 090/102] first attempt at saving calc_info.json into "native_files" --- qcengine/programs/madness/harvester.py | 2 +- qcengine/programs/madness/runner.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 15ae124b1..9a8a97f33 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -216,7 +216,7 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol molresponse_outfiles = molresponse_info.get("outfiles") # At this point scf prints a list of json outputs where each list refers to the scf at given protocol # Here I load the scf_info and calc_info as json - response_info = json.loads(molresponse_outfiles.get("respones.json")) + response_info = json.loads(molresponse_outfiles.get("response_base.json")) response_params, response_data_dict = read_molrespone_json(response_info) Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 39bc5ab69..87a2fe6d9 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -99,10 +99,10 @@ def get_version(self) -> str: if which_prog not in self.version_cache: success, output = execute( - command, + command, { "v.moldft": "dft\nxc lda\nend\ngeometry\nH 0.0 0.0 0.0\nH 0.7 0.0 0.0\nend\n" - }, + }, scratch_directory=config.scratch_directory, ) @@ -146,7 +146,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(dexe["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -187,10 +187,10 @@ def build_input( geo_index = optcmd.find("geometry") # find first occurrence of geometry end_index = optcmd[geo_index:].find("end") # find first occurrence of end after geometry geometry_input = optcmd[ - geo_index + 8 : end_index + geo_index - ] # grab everything in between geometry and end + geo_index + 8: end_index + geo_index + ] # grab everything in between geometry and end - optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4 :] # optcmd becomes everything else + optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4:] # optcmd becomes everything else molcmd = molcmd.replace( "end", geometry_input.strip() + "\nend" ) # replace end with the added geometry input @@ -222,7 +222,7 @@ def build_input( return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) oexe = {} @@ -269,8 +269,9 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": stdout = outfiles["moldft"]["stdout"] if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] - print("within parse_output scf_info.json", outfiles["moldft"]["outfiles"]["scf_info.json"]) print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) + native_dict = {} + native_dict["calc_info"] = outfiles["moldft"]["outfiles"]["calc_info.json"] # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) @@ -302,11 +303,13 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": "schema_name": "qcschema_output", "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, + "native_files": {k: v for k, v in native_dict.items() if v is not None}, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, "stdout": stdout, } + print(output_data) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { From 2a70856d1732175946504fefb7e91127e6689b8b Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 28 Jun 2022 16:30:17 -0400 Subject: [PATCH 091/102] save calc_info.json in results.extras[outfiles][calcinfo.json] --- qcengine/programs/madness/runner.py | 39 +++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 87a2fe6d9..73e1427e2 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -123,27 +123,27 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe self.found(raise_error=True) job_inputs = self.build_input(input_model, config) - success, dexe = self.execute(job_inputs) - if "There is an error in the input file" in dexe["moldft"]["stdout"]: - raise InputError(dexe["moldft"]["stdout"]) - if "not compiled" in dexe["moldft"]["stdout"]: + success, output = self.execute(job_inputs, extra_outfiles=['calc_info.json']) + if "There is an error in the input file" in output["moldft"]["stdout"]: + raise InputError(output["moldft"]["stdout"]) + if "not compiled" in output["moldft"]["stdout"]: # recoverable with a different compilation with optional modules - raise InputError(dexe["moldft"]["stdout"]) + raise InputError(output["moldft"]["stdout"]) if success: - num_commands = len(dexe) + num_commands = len(output) if num_commands == 2: - dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] - dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] - dexe["molresponse"]["outfiles"]["stdout"] = dexe["molresponse"]["stdout"] - dexe["molresponse"]["outfiles"]["stderr"] = dexe["molresponse"]["stderr"] + output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] + output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] + output["molresponse"]["outfiles"]["stdout"] = output["molresponse"]["stdout"] + output["molresponse"]["outfiles"]["stderr"] = output["molresponse"]["stderr"] else: - dexe["moldft"]["outfiles"]["stdout"] = dexe["moldft"]["stdout"] - dexe["moldft"]["outfiles"]["stderr"] = dexe["moldft"]["stderr"] - return self.parse_output(dexe, input_model) + output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] + output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] + return self.parse_output(output, input_model) else: - print(dexe["stdout"]) + print(output["stdout"]) - raise UnknownError(dexe["stderr"]) + raise UnknownError(output["stderr"]) def build_input( self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None @@ -270,8 +270,6 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) - native_dict = {} - native_dict["calc_info"] = outfiles["moldft"]["outfiles"]["calc_info.json"] # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) @@ -303,18 +301,21 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": "schema_name": "qcschema_output", "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, - "native_files": {k: v for k, v in native_dict.items() if v is not None}, + "native_files": {"input": native_dict["input"]}, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, "stdout": stdout, } - print(output_data) + print(output_data['native_files']['input']) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } + calc_info_json = outfiles["moldft"]["outfiles"]["calc_info.json"] + output_data["extras"]["outfiles"] = {"calc_info.json": calc_info_json} + output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data}) From 839fb0353d4c7392ec48fd4e8ca2323e59b82377 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Tue, 28 Jun 2022 16:31:31 -0400 Subject: [PATCH 092/102] remove native_files line --- qcengine/programs/madness/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 73e1427e2..1ca9bb1f2 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -301,7 +301,7 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": "schema_name": "qcschema_output", "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, - "native_files": {"input": native_dict["input"]}, + #TODO (figure out what this does and how to use it) "native_files": {"input": native_dict["input"]}, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, From 54b09b4d10d486e722c8c48b9606ec7a21365a4f Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 13 Jul 2022 16:54:16 -0400 Subject: [PATCH 093/102] save calc_info json as dictionary into extras --- qcengine/programs/madness/runner.py | 46 +++++++++++++---------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 1ca9bb1f2..0ad032e9d 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -3,6 +3,7 @@ """ # import re import copy +import json import logging import pprint from decimal import Decimal @@ -100,9 +101,7 @@ def get_version(self) -> str: if which_prog not in self.version_cache: success, output = execute( command, - { - "v.moldft": "dft\nxc lda\nend\ngeometry\nH 0.0 0.0 0.0\nH 0.7 0.0 0.0\nend\n" - }, + {"v.moldft": "dft\nxc lda\nend\ngeometry\nH 0.0 0.0 0.0\nH 0.7 0.0 0.0\nend\n"}, scratch_directory=config.scratch_directory, ) @@ -110,7 +109,7 @@ def get_version(self) -> str: for line in output["stdout"].splitlines(): if "multiresolution suite" in line: version = line.strip().split()[1] - self.version_cache[which_prog] = safe_version(version) + self.version_cache[which_prog] = safe_version(version) else: raise UnknownError(output["stderr"]) @@ -123,7 +122,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe self.found(raise_error=True) job_inputs = self.build_input(input_model, config) - success, output = self.execute(job_inputs, extra_outfiles=['calc_info.json']) + success, output = self.execute(job_inputs, extra_outfiles=["calc_info.json"]) if "There is an error in the input file" in output["moldft"]["stdout"]: raise InputError(output["moldft"]["stdout"]) if "not compiled" in output["moldft"]["stdout"]: @@ -146,7 +145,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe raise UnknownError(output["stderr"]) def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: # madnessrec = { @@ -187,18 +186,18 @@ def build_input( geo_index = optcmd.find("geometry") # find first occurrence of geometry end_index = optcmd[geo_index:].find("end") # find first occurrence of end after geometry geometry_input = optcmd[ - geo_index + 8: end_index + geo_index - ] # grab everything in between geometry and end + geo_index + 8 : end_index + geo_index + ] # grab everything in between geometry and end - optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4:] # optcmd becomes everything else + optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4 :] # optcmd becomes everything else molcmd = molcmd.replace( "end", geometry_input.strip() + "\nend" ) # replace end with the added geometry input - print("optcmd\n", optcmd) - print("molcmd\n", molcmd) + # print("optcmd\n", optcmd) + # print("molcmd\n", molcmd) - print(optcmd.find("geometry")) - print(optcmd) + # print(optcmd.find("geometry")) + # print(optcmd) madnessrec["commands"] = {} if mdccmd == "response": dft_cmds = optcmd.split(mdccmd) @@ -216,13 +215,13 @@ def build_input( madnessrec["infiles"]["moldft"]["input"] = dft_cmds + molcmd madnessrec["commands"]["moldft"] = [which("moldft")] - print(madnessrec) + # print(madnessrec) # optcmd="dft\n xc hf \nend\n" # print(madnessrec["infiles"]["input"]) return madnessrec def execute( - self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None + self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: num_commands = len(inputs["commands"]) oexe = {} @@ -246,11 +245,11 @@ def execute( scratch_exist_ok=True, ) oexe["molresponse"] = dexe_response - print(dexe) - print(dexe_response) + # print(dexe) + # print(dexe_response) return success, oexe else: - print(inputs["commands"]["moldft"]) + # print(inputs["commands"]["moldft"]) success, dexe = execute( inputs["commands"]["moldft"], inputs["infiles"]["moldft"], @@ -269,11 +268,11 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": stdout = outfiles["moldft"]["stdout"] if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] - print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) + # print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) - ## pop the files because I think I need to + calc_info_json = outfiles["moldft"]["outfiles"]["calc_info.json"] outfiles.pop("moldft") if "molresponse" in outfiles.keys(): outfiles.pop("molresponse") @@ -301,21 +300,18 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": "schema_name": "qcschema_output", "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, - #TODO (figure out what this does and how to use it) "native_files": {"input": native_dict["input"]}, + # TODO (figure out what this does and how to use it) "native_files": {"input": native_dict["input"]}, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, "stdout": stdout, } - print(output_data['native_files']['input']) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } - - calc_info_json = outfiles["moldft"]["outfiles"]["calc_info.json"] - output_data["extras"]["outfiles"] = {"calc_info.json": calc_info_json} + output_data["extras"]["outfiles"] = {"calc_info.json": json.loads(calc_info_json)} output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data}) From 3db2391e1da852732177648148311f23292236d7 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 13 Jul 2022 16:56:04 -0400 Subject: [PATCH 094/102] black --- qcengine/programs/madness/harvester.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 9a8a97f33..42ed85ffd 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -203,13 +203,13 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol moldft_outfiles = moldft_info.get("outfiles") # At this point scf prints a list of json outputs where each list refers to the scf at givin protocol # Here I load the scf_info and calc_info as json - #scf_info = json.loads(moldft_outfiles.get("scf_info.json")) + # scf_info = json.loads(moldft_outfiles.get("scf_info.json")) calc_info = json.loads(moldft_outfiles.get("calc_info.json")) # Write harvest scf_info and harvest calc_info out_calc_vars = harvest_calc_info(calc_info) - #out_scf_vars = harvest_scf_info(scf_info) + # out_scf_vars = harvest_scf_info(scf_info) out_psivar.update(out_calc_vars) - #out_psivar.update(out_scf_vars) + # out_psivar.update(out_scf_vars) if "molresponse" in outfiles.keys(): molresponse_info = outfiles.get("moldft") From c04a005b79bbc3dba1a5d450d58c282951b90124 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Thu, 14 Jul 2022 12:49:21 -0400 Subject: [PATCH 095/102] save calc json and input in native files --- qcengine/programs/madness/runner.py | 47 +++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 0ad032e9d..b3bc14819 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -131,13 +131,17 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe if success: num_commands = len(output) if num_commands == 2: + stdin = job_inputs["infiles"]["moldft"]["input"] output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] output["molresponse"]["outfiles"]["stdout"] = output["molresponse"]["stdout"] output["molresponse"]["outfiles"]["stderr"] = output["molresponse"]["stderr"] + else: + stdin = job_inputs["infiles"]["moldft"]["input"] output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] + output["moldft"]["outfiles"]["input"] = stdin return self.parse_output(output, input_model) else: print(output["stdout"]) @@ -239,7 +243,7 @@ def execute( success, dexe_response = execute( inputs["commands"]["molresponse"], inputs["infiles"]["molresponse"], - ["response.json"], + ["response_base.json"], scratch_messy=True, scratch_name=Path(dexe["scratch_directory"]).name, scratch_exist_ok=True, @@ -264,18 +268,27 @@ def execute( def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] - # Get the stdout from the calculation (required) - stdout = outfiles["moldft"]["stdout"] - if "molresponse" in outfiles.keys(): - stdout += outfiles["molresponse"]["stdout"] - # print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) - - # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) - calc_info_json = outfiles["moldft"]["outfiles"]["calc_info.json"] - outfiles.pop("moldft") + + moldft_out = outfiles.pop("moldft") + + print(moldft_out["outfiles"].keys()) + + m_stdout = moldft_out.pop("stdout") + m_stderr = moldft_out.pop("stderr") + print("after pop", moldft_out["outfiles"].keys()) + + response_out = None + r_stdout = None + r_stderr = None if "molresponse" in outfiles.keys(): - outfiles.pop("molresponse") + response_out = outfiles.pop("molresponse") + r_stdout = response_out.pop("stdout") + r_stderr = response_out.pop("stderr") + + stdout = m_stdout + if r_stdout is not None: + stdout.update(r_stdout) if madgrad is not None: qcvars["CURRENT GRADIENT"] = madgrad @@ -296,11 +309,16 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # Get the formatted properties qcprops = extract_formatted_properties(qcvars) # Format them inout an output + m_native_files = {k: v for k, v in moldft_out["outfiles"].items() if v is not None} + native_files = { + "input": m_native_files["input"], + "calc_info": m_native_files["calc_info.json"], + } output_data = { "schema_name": "qcschema_output", "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, - # TODO (figure out what this does and how to use it) "native_files": {"input": native_dict["input"]}, + "native_files": native_files, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, @@ -311,7 +329,10 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } - output_data["extras"]["outfiles"] = {"calc_info.json": json.loads(calc_info_json)} + output_data["extras"]["outfiles"] = { + "input": native_files["input"], + "calc_info": json.loads(native_files["calc_info"]), + } output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data}) From cc86262e9fc88545135f81c18a926fdf335e7283 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 6 Mar 2024 15:59:17 -0500 Subject: [PATCH 096/102] typo --- qcengine/programs/tests/test_madness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/tests/test_madness.py b/qcengine/programs/tests/test_madness.py index 09220d237..c2293118a 100644 --- a/qcengine/programs/tests/test_madness.py +++ b/qcengine/programs/tests/test_madness.py @@ -106,7 +106,7 @@ def test_mad_hf_be(program, basis, keywords, Be): res = qcng.compute(resi, program, raise_error=True, return_dict=True) # print(res["stdout"]) - assert res["driver"] == "optimizatino" + assert res["driver"] == "optimization" assert "provenance" in res assert res["success"] is True From 48bffb7d5471d1cea77b18c4e94240ab31266030 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 6 Mar 2024 16:10:35 -0500 Subject: [PATCH 097/102] MADNESS runs --- qcengine/programs/madness/germinate.py | 83 +--------- qcengine/programs/madness/harvester.py | 136 +---------------- qcengine/programs/madness/runner.py | 204 +++++++++---------------- 3 files changed, 76 insertions(+), 347 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index 248a8f7d8..e0bbd35ba 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -6,98 +6,17 @@ _xc_functionals = [ "hf", "lda", - "acm", "b3lyp", - "beckehandh", - "pbe0", - "becke97", - "becke97-1", - "becke97-2", - "becke97-3", - "becke97-d", - "becke98", - "hcth", - "hcth120", - "hcth147", - "hcth407", - "becke97gga1", - "hcth407p", - "mpw91", - "mpw1k", - "xft97", - "cft97", - "ft97", - "op", - "bop", - "pbeop", - "xpkzb99", - "cpkzb99", - "xtpss03", - "ctpss03", - "xctpssh", - "b1b95", - "bb1k", - "mpw1b95", - "mpwb1k", - "pw6b95", - "pwb6k", - "m05", - "m05-2x", - "vs98", - "m06", - "m06-hf", - "m06-L", - "m06-2x", - "HFexch", - "becke88", - "xperdew91", - "xpbe96", - "gill96", - "lyp", - "perdew81", - "perdew86", - "perdew91", - "cpbe96", - "pw91lda", - "slater", - "vwn_1", - "vwn_2", - "vwn_3", - "vwn_4", - "vwn_5", - "vwn_1_rpa", - "xtpss03", - "ctpss03", - "bc95", - "xpw6b95", - "xpwb6k", - "xm05", - "xm05-2x", - "cpw6b95", - "cpwb6k", - "cm05", - "cm05-2x", - "xvs98", - "cvs98", - "xm06-L", - "xm06-hf", - "xm06", - "xm06-2x", - "cm06-L", - "cm06-hf", - "cm06", - "cm06-2x", ] -# def muster_modelchem(method: str, derint: int, use_tce: bool) -> Tuple[str, Dict[str, Any]]: def muster_modelchem( method: str, derint: int, ) -> Tuple[str, Dict[str, Any]]: """Converts the QC method into MADNESS keywords Options include energy calculation with moldft - Geometry optimiazation with moldft + Geometry optimization with moldft propreties calculation with molresponse...runs moldft then molresponse Args: diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 42ed85ffd..2423fdfe2 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -199,30 +199,18 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol out_psivar = PreservingDict() # Parse the Madness output # This is a weird unpacking but i'm sure i'll find a more elegant way to do this later - moldft_info = outfiles.get("moldft") - moldft_outfiles = moldft_info.get("outfiles") + moldft_outfiles = outfiles.get("outfiles") # At this point scf prints a list of json outputs where each list refers to the scf at givin protocol # Here I load the scf_info and calc_info as json # scf_info = json.loads(moldft_outfiles.get("scf_info.json")) - calc_info = json.loads(moldft_outfiles.get("calc_info.json")) + calc_info = json.loads(moldft_outfiles.get("mad.calc_info.json")) # Write harvest scf_info and harvest calc_info out_calc_vars = harvest_calc_info(calc_info) # out_scf_vars = harvest_scf_info(scf_info) out_psivar.update(out_calc_vars) # out_psivar.update(out_scf_vars) - if "molresponse" in outfiles.keys(): - molresponse_info = outfiles.get("moldft") - molresponse_outfiles = molresponse_info.get("outfiles") - # At this point scf prints a list of json outputs where each list refers to the scf at given protocol - # Here I load the scf_info and calc_info as json - response_info = json.loads(molresponse_outfiles.get("response_base.json")) - response_params, response_data_dict = read_molrespone_json(response_info) - - Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["moldft"]["stdout"]) - if "molresponse" in outfiles.keys(): - response_psi_var = harvest_response_file(outfiles["molresponse"]["stdout"]) - out_psivar.update(response_psi_var) + Idontneed_vars, out_mol, out_grad, version, error = harvest_moldft_output(outfiles["stdout"]) # If available, read higher-accuracy gradients # These were output using a Python Task in Madness to read them out of the database if outfiles.get("mad.grad") is not None: @@ -243,21 +231,6 @@ def harvest(in_mol: Molecule, outfiles) -> Tuple[PreservingDict, None, None, Mol """Madness outfile (NRE: %f) inconsistent with MADNESS input (NRE: %f).""" % (out_mol.nuclear_repulsion_energy(), in_mol.nuclear_repulsion_energy()) ) - # else: - # raise ValueError("""No coordinate information extracted from Madness output.""") - - # If present, align the gradients and hessian with the original molecular coordinates - # Madness rotates the coordinates of the input molecule. `out_mol` contains the coordinates for the - # rotated molecule, which we can use to determine how to rotate the gradients/hessian - # amol, data = out_mol.align(in_mol, atoms_map=False, mols_align=True, verbose=0) - - # mill = data["mill"] # Retrieve tool with alignment routines - - # if out_grad is not None: - # out_grad = mill.align_gradient(np.array(out_grad).reshape(-1, 3)) - # if out_hess is not None: - # out_hess = mill.align_hessian(np.array(out_hess)) - # TODO create a madness json that outputs basic info like the version and github hash? return out_psivar, out_hess, out_grad, out_mol, version, error @@ -371,106 +344,3 @@ def read_molrespone_json(response_info): else: proto_data.append(read_frequency_proto_iter_data(iter_data, n_states, n_orbitals)) return response_parameters, proto_data - - -def harvest_response_file(outtext): - psivar = PreservingDict() - psivar_coord = None - psivar_grad = None - version = "" - error = "" # TODO (wardlt): The error string is never used. - pass_psivar = [] - pass_coord = [] - pass_grad = [] - # Write now we split at Converge - counter = 1 - - splits = re.split(r"Converged!", outtext, re.MULTILINE) - print(splits) - splits = splits[-1] - data = re.split(r"Iteration", splits, re.MULTILINE)[-1] - print(data) - - NUMBER = r"(?x:" + regex.NUMBER + ")" # NUMBER - NUMSPACE = NUMBER + r"\s*" # NUMBER + SPACE - - OPTIONS = [ - r"Number of Response States:", - r"Number of Ground States:", - r"k =", - ] - PSIVAR = ["NUM STATES", "NUM ORBITALS", "K"] - optDict = dict(zip(OPTIONS, PSIVAR)) - - for var, VAR in optDict.items(): - mobj = re.search(r"^\s*" + var + r"\s*" + NUMBER + r"\s*$", outtext, re.MULTILINE) - # print(mobj) - if mobj: - psivar[VAR] = mobj.group(1) - # Grab the Orbital Energies There are NUM ORBITALS - num_states = int(psivar["NUM STATES"]) - num_orbitals = int(psivar["NUM ORBITALS"]) - - print(num_states) - print(num_orbitals) - # print(NUMSPACE) - NUMSPACEORB = str() - for i in range(num_orbitals): - NUMSPACEORB += NUMSPACE - # print(NUMSPACEORB) - - var = r"Orbital Energies: \[\*\]" - VAR = "ORBITAL ENERGIES" - mobj = re.search( - r"^\s*" + var + r"\s*" + NUMSPACEORB + r"$", - outtext, - re.MULTILINE, - ) - # print(mobj) - - if mobj: - oe_list = [] - for i in range(num_orbitals): - oe_list.append(mobj.group(i + 1)) - - psivar[VAR] = np.array(oe_list, dtype=float) - - psivar = grab_tensor(r"Ground state overlap:", "OVERLAP", num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Ground state hamiltonian:", "HAMILTONIAN", num_orbitals, num_orbitals, psivar, outtext) - psivar = grab_tensor(r"Polarizability Final", "POLARIZABILITY", num_states, num_states, psivar, data) - return psivar - - -# Translate a madness tensor defined within json output to a numpy array - - -def grab_tensor(var, VAR, row, col, psivar, data): - first_line = r"^\s*" + var + r"\s+" - NUMBER = r"(?x:" + regex.NUMBER + ")" # NUMBER - NUMSPACE = NUMBER + r"\s*" # NUMBER + SPACE - # print(first_line) - - CAPTURE_LINE = str() - for j in range(col): - CAPTURE_LINE += NUMSPACE - total = first_line - for i in range(row): - front = r"^\[" + str(i) + r",\*\]\s*" - line = front + CAPTURE_LINE - total += line - # print(line) - - mobj = re.search( - total, - data, - re.MULTILINE, - ) - # print(mobj) - if mobj: - oe_list = [] - for i in range(row): - for j in range(col): - oe_list.append(mobj.group(i + 1)) - tensor = np.array(oe_list) - psivar[VAR] = tensor.reshape((row, col)) - return psivar diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index b3bc14819..21a3120fc 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -1,6 +1,7 @@ """ Calls the Madness moldft executable. """ + # import re import copy import json @@ -8,23 +9,20 @@ import pprint from decimal import Decimal from typing import Any, Dict, Optional, Tuple -from pathlib import Path import numpy as np + import qcelemental as qcel from qcelemental.models import AtomicResult, Provenance, AtomicInput from qcelemental.util import safe_version, which - from qcengine.config import TaskConfig, get_config from qcengine.exceptions import UnknownError - -from ...exceptions import InputError -from ...util import execute, create_mpi_invocation -from ..model import ProgramHarness - from .germinate import muster_modelchem from .harvester import extract_formatted_properties, harvest from .keywords import format_keywords +from ..model import ProgramHarness +from ...exceptions import InputError +from ...util import execute, create_mpi_invocation, popen pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -34,13 +32,12 @@ class MadnessHarness(ProgramHarness): """ Notes ----- - * To use the TCE, specify ``AtomicInput.model.method`` as usual, then also include ``qc_module = True`` in ``AtomicInput.keywords``. """ _defaults = { "name": "madness", "scratch": True, - "thread_safe": True, + "thread_safe": False, # TODO: Check that MADNESS is thread safe? "thread_parallel": True, "node_parallel": True, "managed_memory": True, @@ -64,25 +61,20 @@ def found(raise_error: bool = False) -> bool: ------- bool If both m-a-d-n-e-s-s and its harness dependency networkx are found, returns True. - If raise_error is False and nwchem or networkx are missing, returns False. - If raise_error is True and nwchem or networkx are missing, the error message for the first missing one is raised. + If raise_error is False and MADNESS, returns False. """ + + # We only look for moldft but madness provides other QC functionality through more applications + qc = which( "moldft", - return_bool=True, + return_bool=False, raise_error=raise_error, raise_msg="Please install via https://github.com/m-a-d-n-e-s-s/madness", ) - # dep = which_import( - # "networkx", - # return_bool=True, - # raise_error=raise_error, - # raise_msg="For NWChem harness, please install via `conda install networkx -c conda-forge`.", - # ) - return qc # and dep - - ## gotta figure out which input file and from where + return qc + def get_version(self) -> str: self.found(raise_error=True) @@ -90,29 +82,21 @@ def get_version(self) -> str: config = get_config() # Run MADNESS - which_prog = which("moldft") + which_prog = which("moldft", return_bool=False) - if config.use_mpiexec: - command = create_mpi_invocation(which_prog, config) - else: - command = [which_prog] - command.append("v.moldft") + command = str(which_prog) if which_prog not in self.version_cache: - success, output = execute( - command, - {"v.moldft": "dft\nxc lda\nend\ngeometry\nH 0.0 0.0 0.0\nH 0.7 0.0 0.0\nend\n"}, - scratch_directory=config.scratch_directory, - ) - - if success: - for line in output["stdout"].splitlines(): + with popen([which_prog, "--help"]) as exc: + exc["proc"].wait(timeout=30) + + if exc["proc"].returncode != 0: + raise UnknownError(exc["stderr"]) + else: + for line in exc["stdout"].splitlines(): if "multiresolution suite" in line: version = line.strip().split()[1] self.version_cache[which_prog] = safe_version(version) - else: - raise UnknownError(output["stderr"]) - return self.version_cache[which_prog] def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": @@ -122,29 +106,23 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe self.found(raise_error=True) job_inputs = self.build_input(input_model, config) - success, output = self.execute(job_inputs, extra_outfiles=["calc_info.json"]) - if "There is an error in the input file" in output["moldft"]["stdout"]: - raise InputError(output["moldft"]["stdout"]) - if "not compiled" in output["moldft"]["stdout"]: + print("job_inputs", job_inputs) + success, output = self.execute(job_inputs, extra_outfiles=["mad.calc_info.json"]) + + print("output", output["stdout"]) + if "There is an error in the input file" in output["stdout"]: + raise InputError(output["stdout"]) + if "not compiled" in output["stdout"]: # recoverable with a different compilation with optional modules - raise InputError(output["moldft"]["stdout"]) + raise InputError(output["stdout"]) if success: num_commands = len(output) - if num_commands == 2: - stdin = job_inputs["infiles"]["moldft"]["input"] - output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] - output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] - output["molresponse"]["outfiles"]["stdout"] = output["molresponse"]["stdout"] - output["molresponse"]["outfiles"]["stderr"] = output["molresponse"]["stderr"] - - else: - stdin = job_inputs["infiles"]["moldft"]["input"] - output["moldft"]["outfiles"]["stdout"] = output["moldft"]["stdout"] - output["moldft"]["outfiles"]["stderr"] = output["moldft"]["stderr"] - output["moldft"]["outfiles"]["input"] = stdin + stdin = job_inputs["infiles"]["input"] + output["outfiles"]["stdout"] = output["stdout"] + output["outfiles"]["stderr"] = output["stderr"] + output["outfiles"]["input"] = stdin return self.parse_output(output, input_model) else: - print(output["stdout"]) raise UnknownError(output["stderr"]) @@ -158,27 +136,30 @@ def build_input( "scratch_messy": config.scratch_messy, } - ## These are the madness keywords + # Prepare to write out the options opts = copy.deepcopy(input_model.keywords) opts = {k.lower(): v for k, v in opts.items()} - print(opts) + print("opts: ", opts) + + # Determine the command to use to launch the code + if config.use_mpiexec: + madnessrec["command"] = create_mpi_invocation(which("moldft"), config) + logger.info(f"Launching with mpiexec: {' '.join(madnessrec['command'])}") + else: + madnessrec["command"] = [which("nwchem")] # Handle Molecule - molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="bohr", return_data=True) - print(moldata) - print(molcmd) + molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="Bohr", return_data=True) + print("moldata: ", moldata) + print("molcmd: ", molcmd) molData = {} for k, v in moldata["keywords"].items(): molData["dft__" + k] = v opts.update(molData) - - ## Handle Calc Type (ROBERT) - ## now returns respnse options as well + print("Method", input_model.model.method) mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver) opts.update(mdcopts) - ## Handle the basis set (ROBERT) the question is what value of k - # Log the job settings (LORI) Not sure if i need this logger.debug("JOB_OPTS") logger.debug(pp.pformat(opts)) @@ -197,82 +178,39 @@ def build_input( molcmd = molcmd.replace( "end", geometry_input.strip() + "\nend" ) # replace end with the added geometry input - # print("optcmd\n", optcmd) - # print("molcmd\n", molcmd) - - # print(optcmd.find("geometry")) - # print(optcmd) - madnessrec["commands"] = {} - if mdccmd == "response": - dft_cmds = optcmd.split(mdccmd) - dft_cmds[1] = "response\n" + dft_cmds[1] - - madnessrec["infiles"]["moldft"] = {} - madnessrec["infiles"]["moldft"]["input"] = dft_cmds[0] + molcmd - madnessrec["infiles"]["molresponse"] = {} - madnessrec["infiles"]["molresponse"]["rinput"] = dft_cmds[1] - madnessrec["commands"]["moldft"] = [which("moldft")] - madnessrec["commands"]["molresponse"] = [which("molresponse")] - else: - dft_cmds = optcmd - madnessrec["infiles"]["moldft"] = {} - madnessrec["infiles"]["moldft"]["input"] = dft_cmds + molcmd - madnessrec["commands"]["moldft"] = [which("moldft")] - - # print(madnessrec) - # optcmd="dft\n xc hf \nend\n" - # print(madnessrec["infiles"]["input"]) + + madnessrec["command"] = {} + dft_cmds = optcmd + madnessrec["infiles"] = {} + madnessrec["infiles"]["input"] = dft_cmds + molcmd + madnessrec["command"] = [which("moldft")] + + print("madness_rec", madnessrec) return madnessrec def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - num_commands = len(inputs["commands"]) oexe = {} - if num_commands == 2: - success, dexe = execute( - inputs["commands"]["moldft"], - inputs["infiles"]["moldft"], - ["calc_info.json", "scf_info.json"], - scratch_exist_ok=True, - scratch_name=inputs.get("scratch_name", None), - scratch_directory=inputs["scratch_directory"], - scratch_messy=True, - ) - oexe["moldft"] = dexe - success, dexe_response = execute( - inputs["commands"]["molresponse"], - inputs["infiles"]["molresponse"], - ["response_base.json"], - scratch_messy=True, - scratch_name=Path(dexe["scratch_directory"]).name, - scratch_exist_ok=True, - ) - oexe["molresponse"] = dexe_response - # print(dexe) - # print(dexe_response) - return success, oexe - else: - # print(inputs["commands"]["moldft"]) - success, dexe = execute( - inputs["commands"]["moldft"], - inputs["infiles"]["moldft"], - ["calc_info.json", "scf_info.json"], - scratch_exist_ok=True, - scratch_name=inputs.get("scratch_name", None), - scratch_directory=inputs["scratch_directory"], - scratch_messy=True, - ) - oexe["moldft"] = dexe - return success, oexe + success, dexe = execute( + command=inputs["command"], + infiles=inputs["infiles"], + outfiles=["mad.calc_info.json", "mad.scf_info.json"], + scratch_exist_ok=True, + scratch_name=inputs.get("scratch_name", None), + scratch_directory=inputs["scratch_directory"], + scratch_messy=True, + ) + print("success", success) + print("dexe", dexe) + + return success, dexe def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) - moldft_out = outfiles.pop("moldft") - - print(moldft_out["outfiles"].keys()) + moldft_out = outfiles m_stdout = moldft_out.pop("stdout") m_stderr = moldft_out.pop("stderr") @@ -312,7 +250,8 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": m_native_files = {k: v for k, v in moldft_out["outfiles"].items() if v is not None} native_files = { "input": m_native_files["input"], - "calc_info": m_native_files["calc_info.json"], + "calc_info": m_native_files["mad.calc_info.json"], + "scf_info": m_native_files["mad.scf_info.json"], } output_data = { "schema_name": "qcschema_output", @@ -332,6 +271,7 @@ def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": output_data["extras"]["outfiles"] = { "input": native_files["input"], "calc_info": json.loads(native_files["calc_info"]), + "scf_info": json.loads(native_files["scf_info"]), } output_data["success"] = True From d14841b0c11b8c188cf7c182d64adb9675c2c971 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 6 Mar 2024 16:10:50 -0500 Subject: [PATCH 098/102] Ran black....was this a mistake? --- docs/source/conf.py | 96 +++++++++---------- qcengine/compute.py | 1 + qcengine/config.py | 2 +- qcengine/mdi_server.py | 1 + qcengine/programs/adcc.py | 1 + .../empirical_dispersion_resources.py | 6 +- qcengine/programs/gamess/runner.py | 2 +- qcengine/programs/molpro.py | 4 +- qcengine/programs/mopac.py | 1 + qcengine/programs/mrchem.py | 1 + qcengine/programs/nwchem/errors.py | 1 + qcengine/programs/nwchem/harvester.py | 6 +- qcengine/programs/nwchem/runner.py | 3 +- qcengine/programs/openmm.py | 1 + qcengine/programs/psi4.py | 1 + qcengine/programs/terachem_frontend.py | 1 + qcengine/programs/terachem_pbs.py | 1 + qcengine/programs/tests/test_adcc.py | 1 + .../programs/tests/test_canonical_config.py | 1 + qcengine/programs/tests/test_mrchem.py | 1 + qcengine/programs/tests/test_nwchem.py | 5 +- qcengine/programs/tests/test_programs.py | 1 - qcengine/programs/turbomole/runner.py | 3 +- qcengine/tests/test_harness_canonical.py | 1 + qcengine/tests/test_mdi.py | 1 - 25 files changed, 80 insertions(+), 63 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1d210f1ff..08913c1b1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,7 +10,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath("../..")) import qcengine # -- Path setup -------------------------------------------------------------- @@ -26,9 +26,9 @@ # -- Project information ----------------------------------------------------- -project = 'QCEngine' -copyright = f'2018-{datetime.datetime.today().year}, The Molecular Sciences Software Institute' -author = 'The QCArchive Development Team' +project = "QCEngine" +copyright = f"2018-{datetime.datetime.today().year}, The Molecular Sciences Software Institute" +author = "The QCArchive Development Team" # The short X.Y version version = qcengine.__version__ @@ -46,26 +46,26 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.doctest', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.extlinks', - 'sphinx.ext.graphviz', - 'sphinx.ext.autosummary', - 'sphinx.ext.napoleon', - 'sphinx_automodapi.automodapi', - 'sphinx_automodapi.automodsumm', - 'sphinx_automodapi.smart_resolver', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.doctest", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.extlinks", + "sphinx.ext.graphviz", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx_automodapi.automodapi", + "sphinx_automodapi.automodsumm", + "sphinx_automodapi.smart_resolver", "sphinx_autodoc_typehints", "sphinxcontrib.autodoc_pydantic", ] autosummary_generate = True -automodapi_toctreedirnm = 'api' +automodapi_toctreedirnm = "api" autodoc_typehints = "description" napoleon_use_param = True napoleon_use_rtype = True @@ -74,16 +74,16 @@ autodoc_pydantic_field_swap_name_and_alias = True # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -98,7 +98,7 @@ exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'default' +pygments_style = "default" # -- Options for HTML output ------------------------------------------------- @@ -106,7 +106,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -117,7 +117,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -133,7 +133,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'QCEnginedoc' +htmlhelp_basename = "QCEnginedoc" # -- Options for LaTeX output ------------------------------------------------ @@ -142,15 +142,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -160,8 +157,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'QCEngine.tex', 'QCEngine Documentation', - 'The QCArchive Development Team', 'manual'), + (master_doc, "QCEngine.tex", "QCEngine Documentation", "The QCArchive Development Team", "manual"), ] @@ -169,10 +165,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'qcengine', 'QCEngine Documentation', - [author], 1) -] +man_pages = [(master_doc, "qcengine", "QCEngine Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -181,30 +174,37 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'QCEngine', 'QCEngine Documentation', - author, 'QCEngine', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "QCEngine", + "QCEngine Documentation", + author, + "QCEngine", + "One line description of project.", + "Miscellaneous", + ), ] # -- Extension configuration ------------------------------------------------- extlinks = { - 'issue': ('https://github.com/MolSSI/QCEngine/issues/%s', 'GH#%s'), - 'pr': ('https://github.com/MolSSI/QCEngine/pull/%s', 'GH#%s') + "issue": ("https://github.com/MolSSI/QCEngine/issues/%s", "GH#%s"), + "pr": ("https://github.com/MolSSI/QCEngine/pull/%s", "GH#%s"), } # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/3.10', None), - "numpy": ("https://numpy.org/doc/stable/", None), - 'scipy': ('https://docs.scipy.org/doc/scipy/', None), - 'matplotlib': ('https://matplotlib.org/stable/', None), - "qcelemental": ("http://docs.qcarchive.molssi.org/projects/QCElemental/en/latest/", None), - "qcportal": ("http://docs.qcarchive.molssi.org/projects/QCPortal/en/latest/", None), - "qcfractal": ("http://docs.qcarchive.molssi.org/projects/QCFractal/en/latest/", None), - } +intersphinx_mapping = { + "python": ("https://docs.python.org/3.10", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "qcelemental": ("http://docs.qcarchive.molssi.org/projects/QCElemental/en/latest/", None), + "qcportal": ("http://docs.qcarchive.molssi.org/projects/QCPortal/en/latest/", None), + "qcfractal": ("http://docs.qcarchive.molssi.org/projects/QCFractal/en/latest/", None), +} # -- Options for todo extension ---------------------------------------------- diff --git a/qcengine/compute.py b/qcengine/compute.py index 00e94a329..6b664e8a3 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -1,6 +1,7 @@ """ Integrates the computes together """ + import warnings from typing import TYPE_CHECKING, Any, Dict, Optional, Union diff --git a/qcengine/config.py b/qcengine/config.py index 41efa2ab5..a836ddbe9 100644 --- a/qcengine/config.py +++ b/qcengine/config.py @@ -37,7 +37,7 @@ def get_global(key: Optional[str] = None) -> Union[str, Dict[str, Any]]: if _global_values is None: _global_values = {} _global_values["hostname"] = socket.gethostname() - _global_values["memory"] = round(psutil.virtual_memory().available / (1024 ** 3), 3) + _global_values["memory"] = round(psutil.virtual_memory().available / (1024**3), 3) _global_values["username"] = getpass.getuser() # Work through VMs and logical cores. diff --git a/qcengine/mdi_server.py b/qcengine/mdi_server.py index 90fff9e32..07b23141f 100644 --- a/qcengine/mdi_server.py +++ b/qcengine/mdi_server.py @@ -2,6 +2,7 @@ For details regarding MDI, see https://molssi.github.io/MDI_Library/html/index.html. """ + from typing import Any, Dict, List, Optional import numpy as np diff --git a/qcengine/programs/adcc.py b/qcengine/programs/adcc.py index ff641c5ef..f1cdf440a 100644 --- a/qcengine/programs/adcc.py +++ b/qcengine/programs/adcc.py @@ -1,6 +1,7 @@ """ Calls adcc """ + from typing import TYPE_CHECKING, Dict from qcelemental.models import AtomicResult, BasisSet, Provenance diff --git a/qcengine/programs/empirical_dispersion_resources.py b/qcengine/programs/empirical_dispersion_resources.py index c6f60ecc7..8a35813d1 100644 --- a/qcengine/programs/empirical_dispersion_resources.py +++ b/qcengine/programs/empirical_dispersion_resources.py @@ -1159,9 +1159,9 @@ def from_arrays( "d3mbj2b", ] for d in ["d3zero", "d3bj", "d3mzero", "d3mbj"]: - psi4.procrouting.empirical_dispersion._capable_engines_for_disp[ - d + "2b" - ] = psi4.procrouting.empirical_dispersion._capable_engines_for_disp.pop(d) + psi4.procrouting.empirical_dispersion._capable_engines_for_disp[d + "2b"] = ( + psi4.procrouting.empirical_dispersion._capable_engines_for_disp.pop(d) + ) except ImportError: pass diff --git a/qcengine/programs/gamess/runner.py b/qcengine/programs/gamess/runner.py index 5b463333c..b456dbc4d 100644 --- a/qcengine/programs/gamess/runner.py +++ b/qcengine/programs/gamess/runner.py @@ -119,7 +119,7 @@ def build_input( # * docs on mwords: "This is given in units of 1,000,000 words (as opposed to 1024*1024 words)" # * docs: "the memory required on each processor core for a run using p cores is therefore MEMDDI/p + MWORDS." # * int() rounds down - mwords_total = int(config.memory * (1024 ** 3) / 8e6) + mwords_total = int(config.memory * (1024**3) / 8e6) for mem_frac_replicated in (1, 0.5, 0.1, 0.75): mwords, memddi = self._partition(mwords_total, mem_frac_replicated, config.ncores) diff --git a/qcengine/programs/molpro.py b/qcengine/programs/molpro.py index 0a9f0ed6a..b9ff1830b 100644 --- a/qcengine/programs/molpro.py +++ b/qcengine/programs/molpro.py @@ -184,7 +184,7 @@ def build_input( unrestricted = True # Memory is in megawords per core for Molpro - memory_mw_core = int(config.memory * (1024 ** 3) / 8e6 / config.ncores) + memory_mw_core = int(config.memory * (1024**3) / 8e6 / config.ncores) input_file.append("memory,{},M".format(memory_mw_core)) input_file.append("") @@ -338,7 +338,7 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> molpro_variable_map = { "_ENUC": "nuclear_repulsion_energy", "_DFTFUN": "scf_xc_energy", - "_NELEC": ["calcinfo_nalpha", "calcinfo_nbeta"] + "_NELEC": ["calcinfo_nalpha", "calcinfo_nbeta"], # "_EMP2_SCS": "scs_mp2_total_energy" } diff --git a/qcengine/programs/mopac.py b/qcengine/programs/mopac.py index e97a319c8..458f6fed9 100644 --- a/qcengine/programs/mopac.py +++ b/qcengine/programs/mopac.py @@ -1,6 +1,7 @@ """ Calls the Psi4 executable. """ + import os from typing import Any, Dict, List, Optional, Tuple diff --git a/qcengine/programs/mrchem.py b/qcengine/programs/mrchem.py index e52c889ee..7531fc1ac 100644 --- a/qcengine/programs/mrchem.py +++ b/qcengine/programs/mrchem.py @@ -1,6 +1,7 @@ """ Calls the MRChem executable. """ + import copy import json import logging diff --git a/qcengine/programs/nwchem/errors.py b/qcengine/programs/nwchem/errors.py index e1c214d14..e9e2d00aa 100644 --- a/qcengine/programs/nwchem/errors.py +++ b/qcengine/programs/nwchem/errors.py @@ -1,4 +1,5 @@ """Known errors for NWChem""" + import logging from typing import Any, Dict diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index 10c0065ec..c58a67ed7 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -583,9 +583,9 @@ def harvest_outfile_pass(outtext): if ext_energy_list[nroot] in e_val: symm = k # in hartree - psivar[ - f"EOM-{cc_name} ROOT 0 -> ROOT {nroot + 1} EXCITATION ENERGY - {symm} SYMMETRY" - ] = ext_energy_list[nroot] + psivar[f"EOM-{cc_name} ROOT 0 -> ROOT {nroot + 1} EXCITATION ENERGY - {symm} SYMMETRY"] = ( + ext_energy_list[nroot] + ) psivar[f"EOM-{cc_name} ROOT 0 -> ROOT {nroot + 1} TOTAL ENERGY - {symm} SYMMETRY"] = psivar[ f"{cc_name} TOTAL ENERGY" ] + Decimal(ext_energy_list[nroot]) diff --git a/qcengine/programs/nwchem/runner.py b/qcengine/programs/nwchem/runner.py index 833e94447..64f0c5727 100644 --- a/qcengine/programs/nwchem/runner.py +++ b/qcengine/programs/nwchem/runner.py @@ -1,6 +1,7 @@ """ Calls the NWChem executable. """ + import copy import hashlib import logging @@ -168,7 +169,7 @@ def build_input( # * int() rounds down # * was [GiB] --> [B] c. v6.6 but fails in v7.0 probably b/c https://github.com/nwchemgit/nwchem/commit/fca382eab477c3e85548457bfceb1fc9be31b47c#diff-7baaf4807cc9b853af14d9127f63db47d706e12f697a98560bc98bb647ef8326 # * memory_size = int(config.memory * (1024 ** 3)) - memory_size = int(config.memory * (1024 ** 3) / 8) + memory_size = int(config.memory * (1024**3) / 8) if config.use_mpiexec: # It is the memory per MPI rank memory_size //= config.nnodes * config.ncores // config.cores_per_rank opts["memory"] = memory_size diff --git a/qcengine/programs/openmm.py b/qcengine/programs/openmm.py index bb54567b3..e81061905 100644 --- a/qcengine/programs/openmm.py +++ b/qcengine/programs/openmm.py @@ -3,6 +3,7 @@ Requires RDKit """ + import datetime import hashlib import os diff --git a/qcengine/programs/psi4.py b/qcengine/programs/psi4.py index 1ba3d7fac..32f630082 100644 --- a/qcengine/programs/psi4.py +++ b/qcengine/programs/psi4.py @@ -1,6 +1,7 @@ """ Calls the Psi4 executable. """ + import json import os import sys diff --git a/qcengine/programs/terachem_frontend.py b/qcengine/programs/terachem_frontend.py index 09b8503a3..b4aba6350 100644 --- a/qcengine/programs/terachem_frontend.py +++ b/qcengine/programs/terachem_frontend.py @@ -1,4 +1,5 @@ """Harness for TeraChem Frontend""" + import logging from os import getenv from typing import Any, Dict diff --git a/qcengine/programs/terachem_pbs.py b/qcengine/programs/terachem_pbs.py index 4cee0a31e..f7542d734 100644 --- a/qcengine/programs/terachem_pbs.py +++ b/qcengine/programs/terachem_pbs.py @@ -1,6 +1,7 @@ """ Calls TeraChem in its "server mode" via a protobuf interface. """ + import logging from importlib import import_module from os import getenv diff --git a/qcengine/programs/tests/test_adcc.py b/qcengine/programs/tests/test_adcc.py index a9625bfa1..76344fd33 100644 --- a/qcengine/programs/tests/test_adcc.py +++ b/qcengine/programs/tests/test_adcc.py @@ -1,4 +1,5 @@ """Tests for adcc functionality""" + import numpy as np import pytest import qcelemental as qcel diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index 2ded4b3e3..bd1c040c3 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -1,6 +1,7 @@ """ Tests the DQM compute dispatch module """ + import pprint import re import sys diff --git a/qcengine/programs/tests/test_mrchem.py b/qcengine/programs/tests/test_mrchem.py index 8158b4af3..e83ceac88 100644 --- a/qcengine/programs/tests/test_mrchem.py +++ b/qcengine/programs/tests/test_mrchem.py @@ -1,4 +1,5 @@ """Tests for MRChem functionality""" + import numpy as np import pytest import qcelemental as qcel diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index fceb21d68..6bd198b1e 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -1,4 +1,5 @@ """Tests for NWChem functionality""" + import numpy as np import pytest import qcelemental as qcel @@ -8,7 +9,9 @@ from qcengine.testing import using # Molecule where autoz fails -_auto_z_problem = xyz = """C 15.204188380000 -3.519180270000 -10.798726560000 +_auto_z_problem = ( + xyz +) = """C 15.204188380000 -3.519180270000 -10.798726560000 C 15.097645630000 -2.650246400000 -8.505033680000 C 14.976892130000 -1.867030510000 -6.378827230000 C 14.827093540000 -1.105281910000 -4.239222620000 diff --git a/qcengine/programs/tests/test_programs.py b/qcengine/programs/tests/test_programs.py index e5249302b..72b9e2f38 100644 --- a/qcengine/programs/tests/test_programs.py +++ b/qcengine/programs/tests/test_programs.py @@ -2,7 +2,6 @@ Tests the DQM compute dispatch module """ - import numpy as np import pytest from qcelemental.models import AtomicInput, Molecule diff --git a/qcengine/programs/turbomole/runner.py b/qcengine/programs/turbomole/runner.py index 2711778c3..2375471b8 100644 --- a/qcengine/programs/turbomole/runner.py +++ b/qcengine/programs/turbomole/runner.py @@ -1,6 +1,7 @@ """ Calls the Turbomole executable. """ + import os import re from decimal import Decimal @@ -155,7 +156,7 @@ def build_input( control = turbomolerec["infiles"]["control"] # Calculate total available memory in MB - mem_mb = config.memory * (1024 ** 3) / 1e6 + mem_mb = config.memory * (1024**3) / 1e6 ri_fraction = 0.25 # Total amount of memory allocated to ricore ricore = 0 diff --git a/qcengine/tests/test_harness_canonical.py b/qcengine/tests/test_harness_canonical.py index 81e457449..03c8116b7 100644 --- a/qcengine/tests/test_harness_canonical.py +++ b/qcengine/tests/test_harness_canonical.py @@ -1,6 +1,7 @@ """ Tests the DQM compute dispatch module """ + import msgpack import numpy as np import pytest diff --git a/qcengine/tests/test_mdi.py b/qcengine/tests/test_mdi.py index 285ecaff2..6ced2ec54 100644 --- a/qcengine/tests/test_mdi.py +++ b/qcengine/tests/test_mdi.py @@ -2,7 +2,6 @@ Tests the MDI interface """ - from qcelemental.testing import compare_values import qcengine as qcng From a1c1b96c728f3f707110d815521d9b99a63d3749 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Wed, 27 Mar 2024 10:46:54 -0400 Subject: [PATCH 099/102] Starting new implementation for multiple executables --- qcengine/programs/madness/germinate.py | 33 ++- qcengine/programs/madness/runner.py | 358 +++++++++++++++---------- 2 files changed, 230 insertions(+), 161 deletions(-) diff --git a/qcengine/programs/madness/germinate.py b/qcengine/programs/madness/germinate.py index e0bbd35ba..6dadba229 100644 --- a/qcengine/programs/madness/germinate.py +++ b/qcengine/programs/madness/germinate.py @@ -13,7 +13,7 @@ def muster_modelchem( method: str, derint: int, -) -> Tuple[str, Dict[str, Any]]: +) -> Dict[str, Any]: """Converts the QC method into MADNESS keywords Options include energy calculation with moldft Geometry optimization with moldft @@ -23,38 +23,37 @@ def muster_modelchem( method (str): Name of the QC method to use derint (str): Index of the run type Returns: - (str): Task command for MADNESS (dict): Any options for MADNESS """ # Standardize the method name method = method.lower() + # Initialize the options opts = {} + moldft_opts = opts["moldft"] = {} + molresponse_opts = opts["molresponse"] = {} - # Map the run type to - # runtyp = {"energy": "energy", "gradient": "gradient", "hessian": "hessian", "properties": "property"}[derint] - runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "molresponse"}[derint] + runtyp = {"energy": "energy", "optimization": "gopt", "hessian": "hessian", "properties": "response"}[derint] # Write out the theory directive if runtyp == "energy": if method == "optimization": - opts["dft__gopt"] = True + moldft_opts["dft__gopt"] = True + elif method.split()[0] in _xc_functionals: - opts["dft__xc"] = method + moldft_opts["dft__xc"] = method + else: raise InputError(f"Method not recognized: {method}") - mdccmd = f"" - elif runtyp == "molresponse": + elif runtyp == "response": if method.split()[0] in _xc_functionals: - opts["dft__xc"] = method - opts["response__xc"] = method - opts["response__archive"] = "restartdata" + + moldft_opts["dft__xc"] = method + molresponse_opts["response__xc"] = method + molresponse_opts["response__archive"] = "restartdata" + else: raise InputError(f"Method not recognized: {method}") - mdccmd = f"response" ## we will split the options with the word response later - ## all we have to do is add options to the dft block in order to change the run type - ## default in energy - # do nothing - return mdccmd, opts + return opts diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 21a3120fc..68333f33c 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -17,12 +17,13 @@ from qcelemental.util import safe_version, which from qcengine.config import TaskConfig, get_config from qcengine.exceptions import UnknownError -from .germinate import muster_modelchem -from .harvester import extract_formatted_properties, harvest +from .harvester import extract_formatted_properties, tensor_to_numpy from .keywords import format_keywords from ..model import ProgramHarness from ...exceptions import InputError from ...util import execute, create_mpi_invocation, popen +from ..util import error_stamp + pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) @@ -82,9 +83,11 @@ def get_version(self) -> str: config = get_config() # Run MADNESS - which_prog = which("moldft", return_bool=False) - - command = str(which_prog) + which_prog = which("moldft") + if config.use_mpiexec: + which_prog = create_mpi_invocation(which_prog, config) + else: + command = [which_prog] if which_prog not in self.version_cache: with popen([which_prog, "--help"]) as exc: @@ -99,103 +102,158 @@ def get_version(self) -> str: self.version_cache[which_prog] = safe_version(version) return self.version_cache[which_prog] - def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": + def compute(self, input_model: AtomicInput, config: TaskConfig) -> "AtomicResult": """ Runs madness in executable mode """ self.found(raise_error=True) job_inputs = self.build_input(input_model, config) - print("job_inputs", job_inputs) - success, output = self.execute(job_inputs, extra_outfiles=["mad.calc_info.json"]) - - print("output", output["stdout"]) - if "There is an error in the input file" in output["stdout"]: - raise InputError(output["stdout"]) - if "not compiled" in output["stdout"]: - # recoverable with a different compilation with optional modules - raise InputError(output["stdout"]) - if success: - num_commands = len(output) - stdin = job_inputs["infiles"]["input"] - output["outfiles"]["stdout"] = output["stdout"] - output["outfiles"]["stderr"] = output["stderr"] - output["outfiles"]["input"] = stdin - return self.parse_output(output, input_model) - else: - raise UnknownError(output["stderr"]) + # Location resolution order config.scratch_dir, /tmp + parent = config.scratch_directory + scratch_messy = config.scratch_messy + + error_message = None + compute_success = False + # Now here we may have mulitple applications to run defined by the keys in the madnessrec dictionary + + extra_outfiles = {"moldft": ["mad.calc_info.json", "mad.scf_info.json"], "molresponse": ["response_base.json"]} + + all_output = {} + app_succeed = [] + + for app, job_inputs in job_inputs.items(): + output = {} + + print("app:", app) + print("job_inputs:", job_inputs) + print("____________________________________") + + outfiles = extra_outfiles[app] + command = job_inputs["command"] + infiles = job_inputs["infiles"] + + # print evertything + print("command", command) + print("infiles", infiles) + print("outfiles", outfiles) + print("parent", parent) + print("scratch_name", app) + print("scratch_suffix", "_madness") + + print("____________________________________") + + if app == "moldft": + success, dexe = execute( + command=command, + infiles=infiles, + outfiles=outfiles, + scratch_directory=parent, + scratch_name=app, + scratch_messy=True, + scratch_exist_ok=True, + ) + else: + tempdir = dexe["scratch_directory"] + success, dexe = execute( + command=command, + infiles=infiles, + outfiles=outfiles, + scratch_directory=tempdir, + scratch_name=app, + scratch_messy=True, + scratch_exist_ok=True, + ) + print("____________________________________") + + print("success", success) + print("dexe", dexe) + + stdin = job_inputs["infiles"]["input"] + if "There is an error in the input file" in output["stdout"]: + raise InputError(error_stamp(stdin, output["stdout"], output["stderr"])) + if "not compiled" in output["stdout"]: + # recoverable with a different compilation with optional modules + raise InputError(error_stamp(stdin, output["stdout"], output["stderr"])) + + if success: + output["outfiles"]["stdout"] = output["stdout"] + output["outfiles"]["stderr"] = output["stderr"] + output["outfiles"]["input"] = stdin + app_succeed.append(app) + all_output[app] = output + else: + raise UnknownError(error_stamp(stdin, output["stdout"], output["stderr"])) + return self.parse_output(all_output, input_model) def build_input( self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: + + method = input_model.model.method.lower() + run_type = input_model.driver # - madnessrec = { - "infiles": {}, - "scratch_directory": config.scratch_directory, - "scratch_messy": config.scratch_messy, - } - - # Prepare to write out the options - opts = copy.deepcopy(input_model.keywords) - opts = {k.lower(): v for k, v in opts.items()} - print("opts: ", opts) - - # Determine the command to use to launch the code - if config.use_mpiexec: - madnessrec["command"] = create_mpi_invocation(which("moldft"), config) - logger.info(f"Launching with mpiexec: {' '.join(madnessrec['command'])}") - else: - madnessrec["command"] = [which("nwchem")] - - # Handle Molecule - molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="Bohr", return_data=True) - print("moldata: ", moldata) - print("molcmd: ", molcmd) - molData = {} - for k, v in moldata["keywords"].items(): - molData["dft__" + k] = v - opts.update(molData) - print("Method", input_model.model.method) - mdccmd, mdcopts = muster_modelchem(input_model.model.method, input_model.driver) - opts.update(mdcopts) - - logger.debug("JOB_OPTS") - logger.debug(pp.pformat(opts)) - - # Handle conversion from schema (flat key/value) keywords into local format - optcmd = format_keywords(opts) - # I need to split to geometry keywords and add it to the end of the geometry command in molcommand - # if the geometry keyword exits - if optcmd.find("geometry") != -1: - geo_index = optcmd.find("geometry") # find first occurrence of geometry - end_index = optcmd[geo_index:].find("end") # find first occurrence of end after geometry - geometry_input = optcmd[ - geo_index + 8 : end_index + geo_index - ] # grab everything in between geometry and end - - optcmd = optcmd[0:geo_index] + optcmd[geo_index + end_index + 4 :] # optcmd becomes everything else - molcmd = molcmd.replace( - "end", geometry_input.strip() + "\nend" - ) # replace end with the added geometry input - - madnessrec["command"] = {} - dft_cmds = optcmd - madnessrec["infiles"] = {} - madnessrec["infiles"]["input"] = dft_cmds + molcmd - madnessrec["command"] = [which("moldft")] - - print("madness_rec", madnessrec) + madnessrec = {} + + for exec, exec_keywords in input_model.keywords.items(): + + exec_rec = {} + + # Prepare to write out the options + opts = copy.deepcopy(exec_keywords) + opts = {k.lower(): v for k, v in opts.items()} + + # Determine the command to use to launch the code + if config.use_mpiexec: + exec_rec["command"] = create_mpi_invocation(which(exec), config) + logger.info(f"Launching with mpiexec: {' '.join(exec_rec['command'])}") + else: + exec_rec["command"] = [which(exec)] + + if exec == "moldft": + # Handle Molecule + molcmd, moldata = input_model.molecule.to_string(dtype="madness", units="Bohr", return_data=True) + molData = {} + for k, v in moldata["keywords"].items(): + molData["dft__" + k] = v + opts.update(molData) + if run_type == "gradient": + opts["dft__derivatives"] = True + elif run_type == "hessian": + Pass + # opts["dft__hessian"] = True + elif run_type == "optimization": + opts["dft__gopt"] = True + elif exec == "molresponse": + opts["dft__save"] = True + opts["response__archive"] = "../moldft/restartdata" + opts["response__xc"] = method + + print(opts) + + logger.debug("JOB_OPTS") + logger.debug(pp.pformat(opts)) + exec_commands = format_keywords(opts) + exec_rec["infiles"] = {} + if exec == "moldft": + exec_rec["infiles"]["input"] = exec_commands + molcmd + else: + exec_rec["infiles"]["molresponse.in"] = exec_commands + print(exec_rec["infiles"]) + + madnessrec[exec] = exec_rec + print(madnessrec) + return madnessrec def execute( self, inputs: Dict[str, Any], *, extra_outfiles=None, extra_commands=None, scratch_name=None, timeout=None ) -> Tuple[bool, Dict]: - oexe = {} success, dexe = execute( command=inputs["command"], infiles=inputs["infiles"], - outfiles=["mad.calc_info.json", "mad.scf_info.json"], + outfiles=["mad.calc_info.json", "mad.scf_info.json", "response_base.json"], scratch_exist_ok=True, scratch_name=inputs.get("scratch_name", None), scratch_directory=inputs["scratch_directory"], @@ -206,73 +264,85 @@ def execute( return success, dexe - def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] + def extract_properties(moldft_calcinfo: Dict[str, Any], scfinfo) -> Dict[str, Any]: + """Translate MRChem output to QCSChema properties. - qcvars, madhess, madgrad, madmol, version, errorTMP = harvest(input_model.molecule, outfiles) + Parameters + ---------- - moldft_out = outfiles + Returns + ------- + """ + properties = {} - m_stdout = moldft_out.pop("stdout") - m_stderr = moldft_out.pop("stderr") - print("after pop", moldft_out["outfiles"].keys()) + properties["calcinfo_nmo"] = moldft_calcinfo["calcinfo_nmo"] + properties["calcinfo_nalpha"] = moldft_calcinfo["calcinfo_nalpha"] + properties["calcinfo_nbeta"] = moldft_calcinfo["calcinfo_nbeta"] + properties["calcinfo_natom"] = moldft_calcinfo["calcinfo_natom"] + properties["return_energy"] = moldft_calcinfo["return_energy"] - response_out = None - r_stdout = None - r_stderr = None - if "molresponse" in outfiles.keys(): - response_out = outfiles.pop("molresponse") - r_stdout = response_out.pop("stdout") - r_stderr = response_out.pop("stderr") + properties["scf_dipole_moment"] = tensor_to_numpy(scfinfo["scf_dipole_moment"]) - stdout = m_stdout - if r_stdout is not None: - stdout.update(r_stdout) + properties["nuclear_repulsion_energy"] = moldft_calcinfo["e_nrep"][-1] + properties["scf_xc_energy"] = moldft_calcinfo["e_xc"][-1] + properties["scf_total_energy"] = moldft_calcinfo["e_tot"][-1] + properties["scf_iterations"] = moldft_calcinfo["iterations"] + properties["scf_dipole_moment"] = scfinfo = ["scf_dipole_moment"] - if madgrad is not None: - qcvars["CURRENT GRADIENT"] = madgrad + properties["scf_gradient"] = tensor_to_numpy(scfinfo["gradient"]) - if madhess is not None: - qcvars["CURRENT HESSIAN"] = madhess - # Normalize the output as a float or list of floats - if input_model.driver.upper() == "PROPERTIES": - retres = qcvars[f"RETURN_ENERGY"] - else: - retres = qcvars["RETURN_ENERGY"] - - if isinstance(retres, Decimal): - retres = float(retres) - elif isinstance(retres, np.ndarray): - retres = retres.tolist() - - # Get the formatted properties - qcprops = extract_formatted_properties(qcvars) - # Format them inout an output - m_native_files = {k: v for k, v in moldft_out["outfiles"].items() if v is not None} - native_files = { - "input": m_native_files["input"], - "calc_info": m_native_files["mad.calc_info.json"], - "scf_info": m_native_files["mad.scf_info.json"], - } - output_data = { - "schema_name": "qcschema_output", - "schema_version": 1, - "extras": {"outfiles": outfiles, **input_model.extras}, - "native_files": native_files, - "properties": qcprops, - "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), - "return_result": retres, - "stdout": stdout, - } - # got to even out who needs plump/flat/Decimal/float/ndarray/list - # Decimal --> str preserves precision - output_data["extras"]["qcvars"] = { - k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() - } - output_data["extras"]["outfiles"] = { - "input": native_files["input"], - "calc_info": json.loads(native_files["calc_info"]), - "scf_info": json.loads(native_files["scf_info"]), - } - - output_data["success"] = True + return properties + + def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] + + output_data = {} + for app, outfiles in outfiles.items(): + if app == "moldft": + + # Get the qcvars + moldft_out = {} + moldft_out["stdout"] = outfiles["stdout"] + moldft_out["stderr"] = outfiles["stderr"] + + calcinfo = json.loads(outfiles["outfiles"]["calc_info"]) + scfinfo = json.loads(outfiles["outfiles"]["scf_info"]) + + output_data["properties"] = extract_properties(calcinfo, scfinfo) + + return_result = scfinfo["scf_energy"] + if isinstance(return_result, Decimal): + retres = float(return_result) + + # Get the formatted properties + qcprops = extract_formatted_properties(qcvars) + # Format them inout an output + m_native_files = {k: v for k, v in moldft_out["outfiles"].items() if v is not None} + native_files = { + "input": m_native_files["input"], + "calc_info": m_native_files["mad.calc_info.json"], + "scf_info": m_native_files["mad.scf_info.json"], + } + output_data = { + "schema_name": "qcschema_output", + "schema_version": 1, + "extras": {"outfiles": outfiles, **input_model.extras}, + "native_files": native_files, + "properties": qcprops, + "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), + "return_result": retres, + "stdout": stdout, + } + # got to even out who needs plump/flat/Decimal/float/ndarray/list + # Decimal --> str preserves precision + output_data["extras"]["qcvars"] = { + k.upper(): str(v) if isinstance(v, Decimal) else v + for k, v in qcel.util.unnp(qcvars, flat=True).items() + } + output_data["extras"]["outfiles"] = { + "input": native_files["input"], + "calc_info": json.loads(native_files["calc_info"]), + "scf_info": json.loads(native_files["scf_info"]), + } + + output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data}) From fd9fb82450608f5ead549a78722ee9556c66248a Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 8 Apr 2024 17:01:39 +0200 Subject: [PATCH 100/102] A running implementation that can run both moldft and molresponse --- qcengine/programs/madness/harvester.py | 1 - qcengine/programs/madness/runner.py | 193 ++++++++++++++----------- 2 files changed, 110 insertions(+), 84 deletions(-) diff --git a/qcengine/programs/madness/harvester.py b/qcengine/programs/madness/harvester.py index 2423fdfe2..9c2485de3 100644 --- a/qcengine/programs/madness/harvester.py +++ b/qcengine/programs/madness/harvester.py @@ -284,7 +284,6 @@ def harvest_calc_info(calc_info): def tensor_to_numpy(j): array = np.empty(j["size"]) array[:] = j["vals"] - print(tuple(j["dims"])) return np.reshape(array, tuple(j["dims"])) diff --git a/qcengine/programs/madness/runner.py b/qcengine/programs/madness/runner.py index 68333f33c..c3a159a41 100644 --- a/qcengine/programs/madness/runner.py +++ b/qcengine/programs/madness/runner.py @@ -17,6 +17,7 @@ from qcelemental.util import safe_version, which from qcengine.config import TaskConfig, get_config from qcengine.exceptions import UnknownError +from qcengine.programs.mrchem import extract_properties from .harvester import extract_formatted_properties, tensor_to_numpy from .keywords import format_keywords from ..model import ProgramHarness @@ -28,6 +29,11 @@ pp = pprint.PrettyPrinter(width=120, compact=True, indent=1) logger = logging.getLogger(__name__) +extra_outfiles = {"moldft": ["mad.calc_info.json", "mad.scf_info.json"], "molresponse": ["response_base.json"]} +calc_info_json = "mad.calc_info.json" +scfinfo_json = "mad.scf_info.json" +response_json = "response_base.json" + class MadnessHarness(ProgramHarness): """ @@ -126,24 +132,10 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> "AtomicResult for app, job_inputs in job_inputs.items(): output = {} - print("app:", app) - print("job_inputs:", job_inputs) - print("____________________________________") - outfiles = extra_outfiles[app] command = job_inputs["command"] infiles = job_inputs["infiles"] - # print evertything - print("command", command) - print("infiles", infiles) - print("outfiles", outfiles) - print("parent", parent) - print("scratch_name", app) - print("scratch_suffix", "_madness") - - print("____________________________________") - if app == "moldft": success, dexe = execute( command=command, @@ -165,12 +157,9 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> "AtomicResult scratch_messy=True, scratch_exist_ok=True, ) - print("____________________________________") - - print("success", success) - print("dexe", dexe) + output = {"stdout": dexe["stdout"], "stderr": dexe["stderr"], "outfiles": dexe["outfiles"]} - stdin = job_inputs["infiles"]["input"] + stdin = job_inputs["infiles"] if "There is an error in the input file" in output["stdout"]: raise InputError(error_stamp(stdin, output["stdout"], output["stderr"])) if "not compiled" in output["stdout"]: @@ -181,6 +170,7 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> "AtomicResult output["outfiles"]["stdout"] = output["stdout"] output["outfiles"]["stderr"] = output["stderr"] output["outfiles"]["input"] = stdin + output["success"] = success app_succeed.append(app) all_output[app] = output else: @@ -225,13 +215,15 @@ def build_input( # opts["dft__hessian"] = True elif run_type == "optimization": opts["dft__gopt"] = True + if len(input_model.keywords["moldft"]) == 0: + opts["dft__save"] = True + # if the number of execs is greater than 1, we need to save moldft archive + if len(input_model.keywords) > 1: + opts["dft__save"] = True elif exec == "molresponse": - opts["dft__save"] = True - opts["response__archive"] = "../moldft/restartdata" + opts["response__archive"] = "../mad.restartdata" opts["response__xc"] = method - print(opts) - logger.debug("JOB_OPTS") logger.debug(pp.pformat(opts)) exec_commands = format_keywords(opts) @@ -239,11 +231,9 @@ def build_input( if exec == "moldft": exec_rec["infiles"]["input"] = exec_commands + molcmd else: - exec_rec["infiles"]["molresponse.in"] = exec_commands - print(exec_rec["infiles"]) + exec_rec["infiles"]["response.in"] = exec_commands madnessrec[exec] = exec_rec - print(madnessrec) return madnessrec @@ -259,12 +249,10 @@ def execute( scratch_directory=inputs["scratch_directory"], scratch_messy=True, ) - print("success", success) - print("dexe", dexe) return success, dexe - def extract_properties(moldft_calcinfo: Dict[str, Any], scfinfo) -> Dict[str, Any]: + def extract_moldft_properties(self, moldft_calcinfo: Dict[str, Any], scfinfo: Dict[str, Any]) -> Dict[str, Any]: """Translate MRChem output to QCSChema properties. Parameters @@ -283,66 +271,105 @@ def extract_properties(moldft_calcinfo: Dict[str, Any], scfinfo) -> Dict[str, An properties["scf_dipole_moment"] = tensor_to_numpy(scfinfo["scf_dipole_moment"]) - properties["nuclear_repulsion_energy"] = moldft_calcinfo["e_nrep"][-1] - properties["scf_xc_energy"] = moldft_calcinfo["e_xc"][-1] - properties["scf_total_energy"] = moldft_calcinfo["e_tot"][-1] - properties["scf_iterations"] = moldft_calcinfo["iterations"] - properties["scf_dipole_moment"] = scfinfo = ["scf_dipole_moment"] + scf_data = moldft_calcinfo["scf_e_data"] + + properties["nuclear_repulsion_energy"] = scf_data["e_nrep"][-1] + properties["scf_xc_energy"] = scf_data["e_xc"][-1] + properties["scf_total_energy"] = scf_data["e_tot"][-1] + properties["scf_iterations"] = scf_data["iterations"] + + extra_properties = {} + extra_properties["cpu_time"] = moldft_calcinfo["time_tag"]["cpu_time"] + extra_properties["wall_time"] = moldft_calcinfo["time_tag"]["wall_time"] + extra_properties["iterations"] = scf_data["iterations"] + + # properties["scf_gradient"] = tensor_to_numpy(scfinfo["gradient"]) + + return properties, extra_properties - properties["scf_gradient"] = tensor_to_numpy(scfinfo["gradient"]) + def extract_response_properties(self, response_json): - return properties + extra_properties = {} + polarizability = tensor_to_numpy(response_json["response_data"]["data"]["alpha"][0]) + extra_properties["polarizability"] = polarizability[-1, :].reshape(3, 3) + + extra_properties["omega"] = response_json["parameters"]["omega"] + extra_properties["cpu_time"] = response_json["time_data"]["cpu_time"] + extra_properties["wall_time"] = response_json["time_data"]["wall_time"] + extra_properties["iterations"] = response_json["response_data"]["iterations"] + + properties = {} + properties["scf_iterations"] = response_json["response_data"]["iterations"] + + return properties, extra_properties + # prepare a list of computed response properties + + # fill up return_result def parse_output(self, outfiles, input_model: "AtomicInput") -> "AtomicResult": # lgtm: [py/similar-function] output_data = {} - for app, outfiles in outfiles.items(): + + driver = input_model.driver + + # things to collect + # 1. If the application succeded + # 2. Standard output and error for each application + # 4. The native files for each application + # 3. The properties for each application from the native files + + # Then based on the driver we will also collect either moldft energy or all properties + all_success = {} + all_std_out = {} + all_std_err = {} + all_native_files = {} + all_properties = {} + all_extras = {} + total_iterations = [] + + for app, output in outfiles.items(): + all_success[app] = output["success"] + all_std_out[app] = output["stdout"] + all_std_err[app] = output["stderr"] + all_native_files[app] = output["outfiles"] + + # collect native files from outfiles if app == "moldft": + moldft_calcinfo = json.loads(output["outfiles"][calc_info_json]) + scfinfo = json.loads(output["outfiles"][scfinfo_json]) + all_properties[app], all_extras[app] = self.extract_moldft_properties(moldft_calcinfo, scfinfo) + elif app == "molresponse": + response_base_file = json.loads(output["outfiles"][response_json]) + ( + all_properties[app], + extra_properties, + ) = self.extract_response_properties(response_base_file) + all_extras[app] = extra_properties + all_success[app] = response_base_file["converged"] + + total_iterations.append(all_properties[app]["scf_iterations"]) + del all_properties[app]["scf_iterations"] + output_data["success"] = True if np.array([v for v in all_success.values()]).all() else False + output_data["stdout"] = " ".join(["\n"] + [v for v in all_std_out.values()]) + output_data["stderr"] = " ".join(["\n"] + [v for v in all_std_err.values()]) + output_data["native_files"] = all_native_files + output_data["extras"] = all_extras + + # + all_prop = {} + for app, prop in all_properties.items(): + all_prop.update(prop) + # first sum up total interations for each application + total_scf_iter = sum(total_iterations) + all_prop["scf_iterations"] = total_scf_iter + output_data["properties"] = all_prop + + if input_model.driver == "energy": + output_data["return_result"] = output_data["properties"]["return_energy"] + elif input_model.driver == "properties": + output_data["return_result"] = output_data["properties"] + else: + raise InputError(f"Driver {input_model.driver} not implemented for MRChem.") + # Get the qcvars - # Get the qcvars - moldft_out = {} - moldft_out["stdout"] = outfiles["stdout"] - moldft_out["stderr"] = outfiles["stderr"] - - calcinfo = json.loads(outfiles["outfiles"]["calc_info"]) - scfinfo = json.loads(outfiles["outfiles"]["scf_info"]) - - output_data["properties"] = extract_properties(calcinfo, scfinfo) - - return_result = scfinfo["scf_energy"] - if isinstance(return_result, Decimal): - retres = float(return_result) - - # Get the formatted properties - qcprops = extract_formatted_properties(qcvars) - # Format them inout an output - m_native_files = {k: v for k, v in moldft_out["outfiles"].items() if v is not None} - native_files = { - "input": m_native_files["input"], - "calc_info": m_native_files["mad.calc_info.json"], - "scf_info": m_native_files["mad.scf_info.json"], - } - output_data = { - "schema_name": "qcschema_output", - "schema_version": 1, - "extras": {"outfiles": outfiles, **input_model.extras}, - "native_files": native_files, - "properties": qcprops, - "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), - "return_result": retres, - "stdout": stdout, - } - # got to even out who needs plump/flat/Decimal/float/ndarray/list - # Decimal --> str preserves precision - output_data["extras"]["qcvars"] = { - k.upper(): str(v) if isinstance(v, Decimal) else v - for k, v in qcel.util.unnp(qcvars, flat=True).items() - } - output_data["extras"]["outfiles"] = { - "input": native_files["input"], - "calc_info": json.loads(native_files["calc_info"]), - "scf_info": json.loads(native_files["scf_info"]), - } - - output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data}) From 3de261b3e5ed85f3e7cbd4d9e5ebdd08b667b4e3 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 8 Apr 2024 17:05:14 +0200 Subject: [PATCH 101/102] format --- qcengine/programs/cfour/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qcengine/programs/cfour/runner.py b/qcengine/programs/cfour/runner.py index a47d69e4f..6fe67e2b6 100644 --- a/qcengine/programs/cfour/runner.py +++ b/qcengine/programs/cfour/runner.py @@ -90,7 +90,7 @@ def build_input( # Handle memory # for cfour, [GiB] --> [QW] - opts["memory_size"] = int(config.memory * (1024 ** 3) / 8) + opts["memory_size"] = int(config.memory * (1024**3) / 8) opts["mem_unit"] = "integerwords" # Handle molecule From cbe29d988a9f8514ba215bf3df94bfdc0ae31408 Mon Sep 17 00:00:00 2001 From: Adrian Hurtado Date: Mon, 8 Apr 2024 17:29:52 +0200 Subject: [PATCH 102/102] Add some test lines --- qcengine/programs/tests/test_canonical_config.py | 2 +- qcengine/tests/test_harness_canonical.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index bd1c040c3..6573809e9 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -26,7 +26,7 @@ ("mp2d", {"method": "MP2-DMP2"}, {}), # needs attn ("mrchem", {"method": "blyp"}, {"world_prec": 1.0e-3}), ("nwchem", {"method": "hf", "basis": "6-31G"}, {}), - ("madness", {"method": "hf"}, {}), + ("madness", {"method": "hf"}, {"moldft": {"eprec": 1.0e-3}}), # needs attn ("openmm", {"method": "openff-1.0.0", "basis": "smirnoff"}, {}), ("psi4", {"method": "hf", "basis": "6-31G"}, {"gradient_write": True}), # needs attn ("qchem", {"method": "hf", "basis": "6-31G"}, {}), diff --git a/qcengine/tests/test_harness_canonical.py b/qcengine/tests/test_harness_canonical.py index 03c8116b7..6b9b66056 100644 --- a/qcengine/tests/test_harness_canonical.py +++ b/qcengine/tests/test_harness_canonical.py @@ -31,7 +31,7 @@ ("adcc", {"method": "adc2", "basis": "6-31G"}, {"n_triplets": 3}), ("gcp", {"method": "hf3c"}, {}), ("mrchem", {"method": "blyp"}, {"world_prec": 1.0e-3}), - ("madness", {"method": "hf"}, {}), + ("madness", {"method": "hf"}, {"eprec": 1.0e-3}), ("cfour", {"method": "hf", "basis": "6-31G"}, {}), ("gamess", {"method": "hf", "basis": "n31"}, {"basis__NGAUSS": 6}), ("mctc-gcp", {"method": "dft/sv"}, {}), @@ -134,7 +134,6 @@ def test_compute_energy_qcsk_basis(program, model, keywords): ("adcc", {"method": "bad"}), ("gcp", {"method": "bad"}), ("mrchem", {"method": "bad"}), - ("madness", {"method": "bad"}), ("mctc-gcp", {"method": "bad"}), # add as programs available # ("molpro", {"method": "bad"}),