Ejemplo n.º 1
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: OptimizationInput) -> OptimizationResult:

        # Get the stdout from the calculation (required)
        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        # Parse out the atomic results from the file
        atomic_results = harvest_as_atomic_result(input_model, stdout)

        # Isolate the converged result
        final_step = atomic_results[-1]

        return OptimizationResult(
            initial_molecule=input_model.initial_molecule,
            input_specification=input_model.input_specification,
            final_molecule=final_step.molecule,
            trajectory=atomic_results,
            energies=[
                float(r.extras["qcvars"]["CURRENT ENERGY"])
                for r in atomic_results
            ],
            stdout=stdout,
            stderr=stderr,
            success=True,
            provenance=Provenance(creator="NWChemRelax",
                                  version=self.get_version(),
                                  routine="nwchem_opt"),
        )
Ejemplo n.º 2
0
def test_repr_provenance(request):

    prov = Provenance(creator="qcel", version="v0.3.2")
    drop_qcsk(prov, request.node.name)

    assert "qcel" in str(prov)
    assert "qcel" in repr(prov)
Ejemplo n.º 3
0
    def parse_output(
        self, outfiles: Dict[str, str], input_model: "AtomicInput"
    ) -> "AtomicResult":  # lgtm: [py/similar-function]

        stdout = outfiles.pop("stdout")

        qcvars, gradient, hessian = harvest(input_model.molecule, stdout, **outfiles)

        if gradient is not None:
            qcvars["CURRENT GRADIENT"] = gradient

        if hessian is not None:
            qcvars["CURRENT HESSIAN"] = hessian

        retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        if isinstance(retres, Decimal):
            retres = float(retres)

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        output_data = input_model.dict()
        output_data["extras"]["outfiles"] = outfiles
        output_data["properties"] = atprop
        output_data["provenance"] = Provenance(creator="Turbomole", version=self.get_version(), routine="turbomole")
        output_data["return_result"] = retres
        output_data["stdout"] = stdout
        output_data["success"] = True

        return AtomicResult(**output_data)
Ejemplo n.º 4
0
def harvest_as_atomic_result(input_model: OptimizationInput,
                             nwout: str) -> List[AtomicResult]:
    """Parse each step in the geometry relaxation as a separate AtomicResult

    Args:
        input_model: Input specification for the relaxation
        nwout: Standard out from the NWChem simulation
    Returns:
        A list of the results at each step
    """
    # Parse the files
    out_psivars, out_mols, out_grads, version, error = harvest_output(nwout)

    # Make atomic results
    results = []
    for qcvars, nwgrad, out_mol in zip(out_psivars, out_grads, out_mols):
        if nwgrad is not None:
            qcvars[
                f"{input_model.input_specification.model.method.upper()[4:]} TOTAL GRADIENT"] = nwgrad
            qcvars["CURRENT GRADIENT"] = nwgrad

        # Get the formatted properties
        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        # Format them inout an output
        output_data = {
            "schema_version":
            1,
            "molecule":
            out_mol,
            "driver":
            "gradient",
            "extras":
            input_model.extras.copy(),
            "model":
            input_model.input_specification.model,
            "keywords":
            input_model.input_specification.keywords,
            "properties":
            atprop,
            "provenance":
            Provenance(creator="NWChem", version=version,
                       routine="nwchem_opt"),
            "return_result":
            nwgrad,
            "success":
            True,
        }

        # 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 unnp(qcvars, flat=True).items()
        }

        results.append(AtomicResult(**output_data))
    return results
Ejemplo n.º 5
0
    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
        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})
Ejemplo n.º 6
0
    def compute(self, input_data: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs RDKit in FF typing
        """

        self.found(raise_error=True)
        import rdkit
        from rdkit.Chem import AllChem

        # Failure flag
        ret_data = {"success": False}

        # Build the Molecule
        jmol = input_data.molecule
        mol = self._process_molecule_rdkit(jmol)

        if input_data.model.method.lower() == "uff":
            ff = AllChem.UFFGetMoleculeForceField(mol)
            all_params = AllChem.UFFHasAllMoleculeParams(mol)
        else:
            raise InputError("RDKit only supports the UFF method currently.")

        if all_params is False:
            raise InputError(
                "RDKit parameters not found for all atom types in molecule.")

        ff.Initialize()

        ret_data["properties"] = {
            "return_energy":
            ff.CalcEnergy() * ureg.conversion_factor("kJ / mol", "hartree")
        }

        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]
        elif input_data.driver == "gradient":
            coef = ureg.conversion_factor("kJ / mol",
                                          "hartree") * ureg.conversion_factor(
                                              "angstrom", "bohr")
            ret_data["return_result"] = [x * coef for x in ff.CalcGrad()]
        else:
            raise InputError(
                f"RDKit can only compute energy and gradient driver methods. Found {input_data.driver}."
            )

        ret_data["provenance"] = Provenance(
            creator="rdkit",
            version=rdkit.__version__,
            routine="rdkit.Chem.AllChem.UFFGetMoleculeForceField")

        ret_data["schema_name"] = "qcschema_output"
        ret_data["success"] = True

        # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other
        return AtomicResult(**{**input_data.dict(), **ret_data})
Ejemplo n.º 7
0
    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")
        stderr = outfiles.pop("stderr")

        # Read the NWChem stdout file and, if needed, the hess or grad files
        try:
            qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles)
        except Exception as e:
            raise UnknownError(stdout)

        if nwgrad is not None:
            qcvars[f"{input_model.model.method.upper()[4:]} TOTAL GRADIENT"] = nwgrad
            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
        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        # Format them inout an output
        output_data = {
            "schema_version": 1,
            "extras": {"outfiles": outfiles, **input_model.extras},
            "properties": atprop,
            "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"),
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 8
0
    def parse_output(
        self, outfiles: Dict[str, str], input_model: AtomicInput
    ) -> AtomicResult:  # lgtm: [py/similar-function]

        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        # c4mol, if it exists, is dinky, just a clue to geometry of cfour results
        try:
            qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles)
        except Exception as e:
            raise UnknownError(stdout)

        if c4grad is not None:
            qcvars["CURRENT GRADIENT"] = c4grad
            qcvars[f"{input_model.model.method.upper()[3:]} TOTAL GRADIENT"] = c4grad

        if c4hess is not None:
            qcvars[f"{input_model.model.method.upper()[3:]} TOTAL HESSIAN"] = c4hess
            qcvars["CURRENT HESSIAN"] = c4hess

        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.ravel().tolist()

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        output_data = {
            "schema_version": 1,
            "extras": {"outfiles": outfiles, **input_model.extras},
            "properties": atprop,
            "provenance": Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour"),
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 9
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: 'ResultInput') -> 'Result':

        stdout = outfiles.pop("stdout")

        # nwmol, if it exists, is dinky, just a clue to geometry of nwchem results
        #        qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles)
        #ORIGpsivar, nwhess, nwgrad, nwmol, version, errorTMP = harvester.harvest(qmol, nwchemrec['stdout'], **nwfiles)
        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

        retres = qcvars[f'CURRENT {input_model.driver.upper()}']
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            'schema_name':
            'qcschema_output',
            'schema_version':
            1,
            'extras': {
                'outfiles': outfiles,
            },
            'properties': {},
            '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 Result(**{**input_model.dict(), **output_data})
Ejemplo n.º 10
0
    def parse_output(
        self, outfiles: Dict[str, str], input_model: "AtomicInput"
    ) -> "AtomicResult":  # lgtm: [py/similar-function]

        stdout = outfiles.pop("stdout")

        # c4mol, if it exists, is dinky, just a clue to geometry of cfour results
        qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest(
            input_model.molecule, stdout, **outfiles)

        if c4grad is not None:
            qcvars["CURRENT GRADIENT"] = c4grad

        if c4hess is not None:
            qcvars["CURRENT HESSIAN"] = c4hess

        retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            "schema_name":
            "qcschema_output",
            "schema_version":
            1,
            "extras": {
                "outfiles": outfiles
            },
            "properties": {},
            "provenance":
            Provenance(creator="CFOUR",
                       version=self.get_version(),
                       routine="xcfour"),
            "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})
Ejemplo n.º 11
0
    def parse_output(self, outfiles: Dict[str, str], input_model: AtomicInput) -> AtomicResult:

        # Get the stdout from the calculation (required)
        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results
        qcvars, gamessgrad, gamessmol = harvest(input_model.molecule, stdout, **outfiles)

        if gamessgrad is not None:
            qcvars["CURRENT GRADIENT"] = gamessgrad

        if input_model.driver.upper() == "PROPERTIES":
            retres = qcvars[f"CURRENT ENERGY"]
        else:
            retres = qcvars[f"CURRENT {input_model.driver.upper()}"]

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        output_data = {
            "schema_version": 1,
            "molecule": gamessmol,
            "extras": {"outfiles": outfiles, **input_model.extras},
            "properties": atprop,
            "provenance": Provenance(creator="GAMESS", version=self.get_version(), routine="rungms"),
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in unnp(qcvars, flat=True).items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 12
0
    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")
        stderr = outfiles.pop("stderr")

        method = input_model.model.method.lower()
        method = method[4:] if method.startswith("nwc-") else method

        # Read the NWChem stdout file and, if needed, the hess or grad files
        # July 2021: nwmol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always atin orientation
        try:
            qcvars, nwhess, nwgrad, nwmol, version, module, errorTMP = harvest(
                input_model.molecule, method, stdout, **outfiles)
        except Exception:
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        try:
            if nwgrad is not None:
                qcvars[f"{method.upper()} TOTAL GRADIENT"] = nwgrad
                qcvars["CURRENT GRADIENT"] = nwgrad

            if nwhess is not None:
                qcvars[f"{method.upper()} TOTAL 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"]
            else:
                retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        except KeyError:
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.tolist()

        # Get the formatted properties
        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        provenance = Provenance(creator="NWChem",
                                version=self.get_version(),
                                routine="nwchem").dict()
        if module is not None:
            provenance["module"] = module

        # Format them inout an output
        output_data = {
            "schema_version": 1,
            "molecule":
            nwmol,  # overwrites with outfile Cartesians in case fix_*=F
            "extras": {
                **input_model.extras
            },
            "native_files":
            {k: v
             for k, v in outfiles.items() if v is not None},
            "properties": atprop,
            "provenance": provenance,
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 13
0
    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")
        stderr = outfiles.pop("stderr")

        method = input_model.model.method.lower()
        method = method[4:] if method.startswith("nwc-") else method

        # Read the NWChem stdout file and, if needed, the hess or grad files
        # LW 7Jul21: I allow exceptions to be raised so that we can detect errors
        #   in the parsing of output files
        qcvars, nwhess, nwgrad, nwmol, version, module, errorTMP = harvest(
            input_model.molecule, method, stdout, **outfiles)

        try:
            if nwgrad is not None:
                qcvars[f"{method.upper()} TOTAL GRADIENT"] = nwgrad
                qcvars["CURRENT GRADIENT"] = nwgrad

            if nwhess is not None:
                qcvars[f"{method.upper()} TOTAL 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"]
            else:
                retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        except KeyError as e:
            raise UnknownError(
                "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr +
                "\nTRACEBACK:\n" +
                "".join(traceback.format_exception(*sys.exc_info())))

        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.tolist()

        # Get the formatted properties
        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        provenance = Provenance(creator="NWChem",
                                version=self.get_version(),
                                routine="nwchem").dict()
        if module is not None:
            provenance["module"] = module

        # Format them inout an output
        output_data = {
            "schema_version": 1,
            "extras": {
                "outfiles": outfiles,
                **input_model.extras
            },
            "properties": atprop,
            "provenance": provenance,
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 14
0
    def _parse_logfile_common(self, outtext: str, input_dict: Dict[str, Any]):
        """
        Parses fields from log file that are not parsed from QCSCRATCH in parse_output
        """

        properties = {}
        provenance = Provenance(creator="QChem",
                                version=self.get_version(),
                                routine="qchem").dict()
        mobj = re.search(r"This is a multi-thread run using ([0-9]+) threads",
                         outtext)
        if mobj:
            provenance["nthreads"] = int(mobj.group(1))

        mobj = re.search(r"Total job time:\s*" + NUMBER + r"s\(wall\)",
                         outtext)
        if mobj:
            provenance["wall_time"] = float(mobj.group(1))

        mobj = re.search(
            r"Archival summary:\s*\n[0-9]+\\[0-9+]\\([\w\.\-]+)\\", outtext)
        if mobj:
            provenance["hostname"] = mobj.group(1)

        mobj = re.search(
            r"\n\s*There are\s+(\d+) alpha and\s+(\d+) beta electrons\s*\n",
            outtext)
        if mobj:
            properties["calcinfo_nalpha"] = int(mobj.group(1))
            properties["calcinfo_nbeta"] = int(mobj.group(2))

        mobj = re.search(
            r"\n\s*There are\s+\d+ shells and\s+(\d+) basis functions\s*\n",
            outtext)
        if mobj:
            properties["calcinfo_nbasis"] = int(mobj.group(1))

        mobj = re.search(
            r"\n\s*RI-MP2 CORRELATION ENERGY\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        if mobj:
            properties["mp2_correlation_energy"] = float(mobj.group(1))

        mobj = re.search(
            r"\n\s*RI-MP2 SINGLES ENERGY\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        if mobj:
            properties["mp2_singles_energy"] = float(mobj.group(1))

        mobj_aaaa = re.search(
            r"\n\s*RI-MP2 ENERGY \(aa\|aa\)\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        mobj_bbbb = re.search(
            r"\n\s*RI-MP2 ENERGY \(bb\|bb\)\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        if mobj_aaaa and mobj_bbbb:
            properties["mp2_same_spin_correlation_energy"] = float(
                mobj_aaaa.group(1)) + float(mobj_bbbb.group(1))

        mobj_aabb = re.search(
            r"\n\s*RI-MP2 ENERGY \(aa\|bb\)\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        mobj_bbaa = re.search(
            r"\n\s*RI-MP2 ENERGY \(bb\|aa\)\s+=\s+" + NUMBER + r"\s+au\s*\n",
            outtext)
        if mobj_aaaa and mobj_bbbb:
            properties["mp2_opposite_spin_correlation_energy"] = float(
                mobj_aabb.group(1)) + float(mobj_bbaa.group(1))

        properties["calcinfo_natom"] = len(input_dict["molecule"]["symbols"])

        mobj = re.search(
            r"\n\s*(\d+)\s+" + NUMBER + "\s+" + NUMBER +
            r"\s+Convergence criterion met\s*\n", outtext)
        if mobj:
            properties["scf_iterations"] = int(mobj.group(1))

        mobj = re.search(
            r"\n\s+Dipole Moment \(Debye\)\s*\n\s+X\s+" + NUMBER + r"\s+Y\s+" +
            NUMBER + r"\s+Z\s+" + NUMBER + r"\s*\n",
            outtext,
        )
        if mobj:
            cf = constants.conversion_factor("debye", "e * bohr")
            properties["scf_dipole_moment"] = [
                float(mobj.group(i)) * cf for i in range(1, 4)
            ]

        return properties, provenance
Ejemplo n.º 15
0
    def compute(self, input_model: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs adcc
        """
        self.found(raise_error=True)

        import adcc
        import psi4

        mol = input_model.molecule
        model = input_model.model
        conv_tol = input_model.keywords.get("conv_tol", 1e-6)

        if input_model.driver not in ["energy", "properties"]:
            raise InputError(
                f"Driver {input_model.driver} not implemented for ADCC.")

        if isinstance(input_model.model.basis, BasisSet):
            raise InputError(
                "QCSchema BasisSet for model.basis not implemented. Use string basis name."
            )
        if not input_model.model.basis:
            raise InputError("Model must contain a basis set.")

        psi4_molecule = psi4.core.Molecule.from_schema(
            dict(mol.dict(), fix_symmetry="c1"))
        psi4.core.clean()
        psi4.core.be_quiet()
        psi4.set_options({
            "basis":
            model.basis,
            "scf_type":
            "pk",
            "e_convergence":
            conv_tol / 100,
            "d_convergence":
            conv_tol / 10,
            # 'maxiter': max_iter,
            "reference":
            "RHF" if mol.molecular_multiplicity == 1 else "UHF",
        })
        _, wfn = psi4.energy("HF", return_wfn=True, molecule=psi4_molecule)
        adcc.set_n_threads(config.ncores)
        compute_success = False
        try:
            adcc_state = adcc.run_adc(wfn,
                                      method=model.method,
                                      **input_model.keywords)
            compute_success = adcc_state.converged
        except adcc.InputError as e:
            raise InputError(str(e))
        except Exception as e:
            raise UnknownError(str(e))

        input_data = input_model.dict(encoding="json")
        output_data = input_data.copy()
        output_data["success"] = compute_success

        if compute_success:
            output_data["return_result"] = adcc_state.excitation_energy[0]

            extract_props = input_model.driver == "properties"
            qcvars = adcc_state.to_qcvars(recurse=True,
                                          properties=extract_props)
            atprop = build_atomicproperties(qcvars)
            output_data["extras"]["qcvars"] = qcvars
            output_data["properties"] = atprop

        provenance = Provenance(creator="adcc",
                                version=self.get_version(),
                                routine="adcc").dict()
        provenance["nthreads"] = adcc.get_n_threads()
        output_data["provenance"] = provenance

        return AtomicResult(**output_data)
Ejemplo n.º 16
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: AtomicInput) -> AtomicResult:

        # Get the stdout from the calculation (required)
        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        method = input_model.model.method.lower()
        method = method[4:] if method.startswith("gms-") else method

        # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results
        try:
            qcvars, gamesshess, gamessgrad, gamessmol, module = harvest(
                input_model.molecule, method, stdout, **outfiles)

        except Exception as e:
            raise UnknownError(
                "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr +
                "\nTRACEBACK:\n" +
                "".join(traceback.format_exception(*sys.exc_info())))

        try:
            if gamessgrad is not None:
                qcvars[f"{method.upper()} TOTAL GRADIENT"] = gamessgrad
                qcvars["CURRENT GRADIENT"] = gamessgrad

            if gamesshess is not None:
                qcvars[f"{method.upper()} TOTAL HESSIAN"] = gamesshess
                qcvars["CURRENT HESSIAN"] = gamesshess

            if input_model.driver.upper() == "PROPERTIES":
                retres = qcvars[f"CURRENT ENERGY"]
            else:
                retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        except KeyError as e:
            raise UnknownError(
                "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr +
                "\nTRACEBACK:\n" +
                "".join(traceback.format_exception(*sys.exc_info())))

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        provenance = Provenance(creator="GAMESS",
                                version=self.get_version(),
                                routine="rungms").dict()
        if module is not None:
            provenance["module"] = module

        output_data = {
            "schema_version": 1,
            "molecule": gamessmol,
            "extras": {
                "outfiles": outfiles,
                **input_model.extras
            },
            "properties": atprop,
            "provenance": provenance,
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 17
0
    def compute(self, input_data: 'ResultInput', config: 'JobConfig') -> 'Result':
        """
        Runs TorchANI in FF typing
        """

        # Check if existings and version
        self.found(raise_error=True)
        if parse_version(self.get_version()) < parse_version("0.5"):
            ret_data["error"] = ComputeError(
                error_type="version_error", error_message="QCEngine's TorchANI wrapper requires version 0.5 or greater.")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        import torch
        import numpy as np

        device = torch.device('cpu')

        # Failure flag
        ret_data = {"success": False}

        # Build model
        model = self.get_model(input_data.model.method)
        if model is False:
            ret_data["error"] = ComputeError(
                error_type="input_error", error_message="run_torchani only accepts the ANI1x or ANI1ccx method.")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        # Build species
        species = "".join(input_data.molecule.symbols)
        unknown_sym = set(species) - {"H", "C", "N", "O"}
        if unknown_sym:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message="The '{}' model does not support symbols: {}.".format(
                    input_data.model.method, unknown_sym))
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        species = model.species_to_tensor(species).to(device).unsqueeze(0)

        # Build coord array
        geom_array = input_data.molecule.geometry.reshape(1, -1, 3) * ureg.conversion_factor("bohr", "angstrom")
        coordinates = torch.tensor(geom_array.tolist(), requires_grad=True, device=device)

        _, energy = model((species, coordinates))
        ret_data["properties"] = {"return_energy": energy.item()}

        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]
        elif input_data.driver == "gradient":
            derivative = torch.autograd.grad(energy.sum(), coordinates)[0].squeeze()
            ret_data["return_result"] = np.asarray(
                derivative * ureg.conversion_factor("angstrom", "bohr")).ravel().tolist()
        else:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message="run_torchani did not understand driver method '{}'.".format(input_data.driver))
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        ret_data["provenance"] = Provenance(
            creator="torchani", version="unknown", routine='torchani.builtin.aev_computer')

        ret_data["schema_name"] = "qcschema_output"
        ret_data["success"] = True

        # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other
        return Result(**{**input_data.dict(), **ret_data})
Ejemplo n.º 18
0
    def compute(self, input_data: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs TorchANI in FF typing
        """

        # Check if existings and version
        self.found(raise_error=True)
        if parse_version(self.get_version()) < parse_version("0.9"):
            raise ResourceError(
                "QCEngine's TorchANI wrapper requires version 0.9 or greater.")

        import torch
        import torchani
        import numpy as np

        device = torch.device("cpu")

        # Failure flag
        ret_data = {"success": False}

        # Build model
        method = input_data.model.method
        model = self.get_model(method)

        # Build species
        species = input_data.molecule.symbols

        known_sym = {"H", "C", "N", "O"}
        if method.lower() == "ani2x":
            known_sym.update({"S", "F", "Cl"})

        unknown_sym = set(species) - known_sym
        if unknown_sym:
            raise InputError(
                f"TorchANI model '{method}' does not support symbols: {unknown_sym}."
            )

        num_atoms = len(species)
        species = model.species_to_tensor(species).to(device).unsqueeze(0)

        # Build coord array
        geom_array = input_data.molecule.geometry.reshape(
            1, -1, 3) * ureg.conversion_factor("bohr", "angstrom")
        coordinates = torch.tensor(geom_array.tolist(),
                                   requires_grad=True,
                                   device=device)

        _, energy_array = model((species, coordinates))
        energy = energy_array.mean()
        ensemble_std = energy_array.std()
        ensemble_scaled_std = ensemble_std / np.sqrt(num_atoms)

        ret_data["properties"] = {"return_energy": energy.item()}

        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]
        elif input_data.driver == "gradient":
            derivative = torch.autograd.grad(energy.sum(),
                                             coordinates)[0].squeeze()
            ret_data["return_result"] = (np.asarray(
                derivative *
                ureg.conversion_factor("angstrom", "bohr")).ravel().tolist())
        elif input_data.driver == "hessian":
            hessian = torchani.utils.hessian(coordinates, energies=energy)
            ret_data["return_result"] = np.asarray(hessian)
        else:
            raise InputError(
                f"TorchANI can only compute energy, gradient, and hessian driver methods. Found {input_data.driver}."
            )

        #######################################################################
        # Description of the quantities stored in `extras`
        #
        # ensemble_energies:
        #   An energy array of all members (models) in an ensemble of models
        #
        # ensemble_energy_avg:
        #   The average value of energy array which is also recorded with as
        #   `energy` in QCEngine
        #
        # ensemble_energy_std:
        #   The standard deviation of energy array
        #
        # ensemble_per_root_atom_disagreement:
        #   The standard deviation scaled by the square root of N, with N being
        #   the number of atoms in the molecule. This is the quantity used in
        #   the query-by-committee (QBC) process in active learning to infer
        #   the reliability of the models in an ensemble, and produce more data
        #   points in the regions where this quantity is below a certain
        #   threshold (inclusion criteria)
        ret_data["extras"] = input_data.extras.copy()
        ret_data["extras"].update({
            "ensemble_energies":
            energy_array.detach().numpy(),
            "ensemble_energy_avg":
            energy.item(),
            "ensemble_energy_std":
            ensemble_std.item(),
            "ensemble_per_root_atom_disagreement":
            ensemble_scaled_std.item(),
        })

        ret_data["provenance"] = Provenance(
            creator="torchani",
            version="unknown",
            routine="torchani.builtin.aev_computer")

        ret_data["schema_name"] = "qcschema_output"
        ret_data["success"] = True

        # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other
        return AtomicResult(**{**input_data.dict(), **ret_data})
Ejemplo n.º 19
0
    def compute(self, input_data: 'ResultInput',
                config: 'JobConfig') -> 'Result':
        """
        Runs RDKit in FF typing
        """

        try:
            import rdkit
            from rdkit import Chem
            from rdkit.Chem import AllChem
        except ModuleNotFoundError:
            raise ModuleNotFoundError(
                "Could not find RDKit in the Python path.")

        # Failure flag
        ret_data = {"success": False}

        # Build the Molecule
        jmol = input_data.molecule

        # Handle errors
        if abs(jmol.molecular_charge) > 1.e-6:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message=
                "run_rdkit does not currently support charged molecules")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        if not jmol.connectivity:  # Check for empty list
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message=
                "run_rdkit molecule must have a connectivity graph")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        # Build out the base molecule
        base_mol = Chem.Mol()
        rw_mol = Chem.RWMol(base_mol)
        for sym in jmol.symbols:
            rw_mol.AddAtom(Chem.Atom(sym.title()))

        # Add in connectivity
        bond_types = {
            1: Chem.BondType.SINGLE,
            2: Chem.BondType.DOUBLE,
            3: Chem.BondType.TRIPLE
        }
        for atom1, atom2, bo in jmol.connectivity:
            rw_mol.AddBond(atom1, atom2, bond_types[bo])

        mol = rw_mol.GetMol()

        # Write out the conformer
        natom = len(jmol.symbols)
        conf = Chem.Conformer(natom)
        bohr2ang = ureg.conversion_factor("bohr", "angstrom")
        for line in range(natom):
            conf.SetAtomPosition(line, (bohr2ang * jmol.geometry[line, 0],
                                        bohr2ang * jmol.geometry[line, 1],
                                        bohr2ang * jmol.geometry[line, 2]))  # yapf: disable

        mol.AddConformer(conf)
        Chem.rdmolops.SanitizeMol(mol)

        if input_data.model.method.lower() == "uff":
            ff = AllChem.UFFGetMoleculeForceField(mol)
            all_params = AllChem.UFFHasAllMoleculeParams(mol)
        else:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message="run_rdkit can only accepts UFF methods")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        if all_params is False:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message=
                "run_rdkit did not match all parameters to molecule")
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        ff.Initialize()

        ret_data["properties"] = {
            "return_energy":
            ff.CalcEnergy() * ureg.conversion_factor("kJ / mol", "hartree")
        }

        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]
        elif input_data.driver == "gradient":
            coef = ureg.conversion_factor("kJ / mol",
                                          "hartree") * ureg.conversion_factor(
                                              "angstrom", "bohr")
            ret_data["return_result"] = [x * coef for x in ff.CalcGrad()]
        else:
            ret_data["error"] = ComputeError(
                error_type="input_error",
                error_message="run_rdkit did not understand driver method "
                "'{}'.".format(ret_data["driver"]))
            return FailedOperation(input_data=input_data.dict(), **ret_data)

        ret_data["provenance"] = Provenance(
            creator="rdkit",
            version=rdkit.__version__,
            routine="rdkit.Chem.AllChem.UFFGetMoleculeForceField")

        ret_data["schema_name"] = "qcschema_output"
        ret_data["success"] = True

        # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other
        return Result(**{**input_data.dict(), **ret_data})
Ejemplo n.º 20
0
    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 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)
        ## 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

        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
        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})
Ejemplo n.º 21
0
def torchani(input_data, config):
    """
    Runs TorchANI in FF typing
    """

    import numpy as np
    try:
        import torch
    except ImportError:
        raise ImportError("Could not find PyTorch in the Python path.")
    try:
        import torchani
    except ImportError:
        raise ImportError("Could not find TorchANI in the Python path.")

    device = torch.device('cpu')
    builtin = torchani.neurochem.Builtins()

    # Failure flag
    ret_data = {"success": False}

    # Build model
    model = get_model(input_data.model.method)
    if model is False:
        ret_data["error"] = ComputeError(
            error_type="input_error",
            error_message="run_torchani only accepts the ANI1 method.")
        return FailedOperation(input_data=input_data.dict(), **ret_data)

    # Build species
    species = "".join(input_data.molecule.symbols)
    unknown_sym = set(species) - {"H", "C", "N", "O"}
    if unknown_sym:
        ret_data["error"] = ComputeError(
            error_type="input_error",
            error_message="The '{}' model does not support symbols: {}.".
            format(input_data.model.method, unknown_sym))
        return FailedOperation(input_data=input_data.dict(), **ret_data)

    species = builtin.consts.species_to_tensor(species).to(device).unsqueeze(0)

    # Build coord array
    geom_array = input_data.molecule.geometry.reshape(
        1, -1, 3) * ureg.conversion_factor("bohr", "angstrom")
    coordinates = torch.tensor(geom_array.tolist(),
                               requires_grad=True,
                               device=device)

    _, energy = model((species, coordinates))
    ret_data["properties"] = {"return_energy": energy.item()}

    if input_data.driver == "energy":
        ret_data["return_result"] = ret_data["properties"]["return_energy"]
    elif input_data.driver == "gradient":
        derivative = torch.autograd.grad(energy.sum(),
                                         coordinates)[0].squeeze()
        ret_data["return_result"] = np.asarray(
            derivative *
            ureg.conversion_factor("angstrom", "bohr")).ravel().tolist()
    else:
        ret_data["error"] = ComputeError(
            error_type="input_error",
            error_message="run_torchani did not understand driver method '{}'."
            .format(input_data.driver))
        return FailedOperation(input_data=input_data.dict(), **ret_data)

    ret_data["provenance"] = Provenance(
        creator="torchani",
        version="unknown",
        routine='torchani.builtin.aev_computer')

    ret_data["schema_name"] = "qcschema_output"
    ret_data["success"] = True

    # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other
    return Result(**{**input_data.dict(), **ret_data})
Ejemplo n.º 22
0
def test_repr_provenance():

    prov = Provenance(creator="qcel", version="v0.3.2")

    assert "qcel" in str(prov)
    assert "qcel" in repr(prov)
Ejemplo n.º 23
0
    def compute(self, input_data: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs OpenMM on given structure, inputs, in vacuum.
        """
        self.found(raise_error=True)

        from simtk import openmm
        from simtk import unit

        with capture_stdout():
            import openforcefield.topology as offtop

        # Failure flag
        ret_data = {"success": False}

        # generate basis, not given
        if not input_data.model.basis:
            raise InputError("Method must contain a basis set.")

        # Make sure we are using smirnoff or antechamber
        basis = input_data.model.basis.lower()
        if basis in ["smirnoff", "antechamber"]:

            with capture_stdout():
                # try and make the molecule from the cmiles
                cmiles = None
                if input_data.molecule.extras:
                    cmiles = input_data.molecule.extras.get(
                        "canonical_isomeric_explicit_hydrogen_mapped_smiles",
                        None)
                    if cmiles is None:
                        cmiles = input_data.molecule.extras.get(
                            "cmiles", {}
                        ).get(
                            "canonical_isomeric_explicit_hydrogen_mapped_smiles",
                            None)

                if cmiles is not None:
                    off_mol = offtop.Molecule.from_mapped_smiles(
                        mapped_smiles=cmiles)
                    # add the conformer
                    conformer = unit.Quantity(value=np.array(
                        input_data.molecule.geometry),
                                              unit=unit.bohr)
                    off_mol.add_conformer(conformer)
                else:
                    # Process molecule with RDKit
                    rdkit_mol = RDKitHarness._process_molecule_rdkit(
                        input_data.molecule)

                    # Create an Open Force Field `Molecule` from the RDKit Molecule
                    off_mol = offtop.Molecule(rdkit_mol)

            # now we need to create the system
            openmm_system = self._generate_openmm_system(
                molecule=off_mol,
                method=input_data.model.method,
                keywords=input_data.keywords)
        else:
            raise InputError(
                "Accepted bases are: {'smirnoff', 'antechamber', }")

        # Need an integrator for simulation even if we don't end up using it really
        integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds)

        # Set platform to CPU explicitly
        platform = openmm.Platform.getPlatformByName("CPU")

        # Set number of threads to use
        # if `nthreads` is `None`, OpenMM default of all logical cores on
        # processor will be used
        nthreads = config.ncores
        if nthreads is None:
            nthreads = os.environ.get("OPENMM_CPU_THREADS")

        if nthreads:
            properties = {"Threads": str(nthreads)}
        else:
            properties = {}

        # Initialize context
        context = openmm.Context(openmm_system, integrator, platform,
                                 properties)

        # Set positions from our Open Force Field `Molecule`
        context.setPositions(off_mol.conformers[0])

        # Compute the energy of the configuration
        state = context.getState(getEnergy=True)

        # Get the potential as a simtk.unit.Quantity, put into units of hartree
        q = state.getPotentialEnergy(
        ) / unit.hartree / unit.AVOGADRO_CONSTANT_NA

        ret_data["properties"] = {"return_energy": q}

        # Execute driver
        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]

        elif input_data.driver == "gradient":
            # Compute the forces
            state = context.getState(getForces=True)

            # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3)
            gradient = state.getForces(asNumpy=True)

            # Convert to hartree/bohr and reformat as 1D array
            q = (gradient / (unit.hartree / unit.bohr)
                 ).reshape(-1) / unit.AVOGADRO_CONSTANT_NA

            # Force to gradient
            ret_data["return_result"] = -1 * q
        else:
            raise InputError(
                f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}."
            )

        ret_data["success"] = True
        ret_data["extras"] = input_data.extras

        # Move several pieces up a level
        ret_data["provenance"] = Provenance(creator="openmm",
                                            version=openmm.__version__,
                                            nthreads=nthreads)

        return AtomicResult(**{**input_data.dict(), **ret_data})
Ejemplo n.º 24
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: 'ResultInput') -> 'Result':
        stdout = outfiles.pop("stdout")

        for fl, contents in outfiles.items():
            if contents is not None:
                # LOG text += f'\n  DFTD3 scratch file {fl} has been read.\n'
                pass

        # parse energy output (could go further and break into E6, E8, E10 and Cn coeff)
        real = np.array(input_model.molecule.real)
        full_nat = real.shape[0]
        real_nat = np.sum(real)

        for ln in stdout.splitlines():
            if re.match(' Edisp /kcal,au', ln):
                ene = Decimal(ln.split()[3])
            elif re.match(r" E6\(ABC\) \"   :", ln):  # c. v3.2.0
                raise ResourceError(
                    "Cannot process ATM results from DFTD3 prior to v3.2.1.")
            elif re.match(r""" E6\(ABC\) /kcal,au:""", ln):
                atm = Decimal(ln.split()[-1])
            elif re.match(' normal termination of dftd3', ln):
                break
        else:
            if not ((real_nat == 1) and (input_model.driver == 'gradient')):
                raise UnknownError(
                    'Unsuccessful run. Possibly -D variant not available in dftd3 version.'
                )

        # parse gradient output
        # * DFTD3 crashes on one-atom gradients. Avoid the error (above) and just force the correct result (below).
        if outfiles['dftd3_gradient'] is not None:
            srealgrad = outfiles['dftd3_gradient'].replace('D', 'E')
            realgrad = np.fromstring(srealgrad, count=3 * real_nat,
                                     sep=' ').reshape((-1, 3))
        elif real_nat == 1:
            realgrad = np.zeros((1, 3))

        if outfiles['dftd3_abc_gradient'] is not None:
            srealgrad = outfiles['dftd3_abc_gradient'].replace('D', 'E')
            realgradabc = np.fromstring(srealgrad, count=3 * real_nat,
                                        sep=' ').reshape((-1, 3))
        elif real_nat == 1:
            realgradabc = np.zeros((1, 3))

        if input_model.driver == 'gradient':
            ireal = np.argwhere(real).reshape((-1))
            fullgrad = np.zeros((full_nat, 3))
            rg = realgradabc if (input_model.extras['info']['dashlevel']
                                 == 'atmgr') else realgrad
            try:
                fullgrad[ireal, :] = rg
            except NameError as exc:
                raise UnknownError(
                    'Unsuccessful gradient collection.') from exc

        qcvkey = input_model.extras['info']['fctldash'].upper()

        calcinfo = []
        if input_model.extras['info']['dashlevel'] == 'atmgr':
            calcinfo.append(qcel.Datum('CURRENT ENERGY', 'Eh', atm))
            calcinfo.append(
                qcel.Datum('DISPERSION CORRECTION ENERGY', 'Eh', atm))
            calcinfo.append(
                qcel.Datum('3-BODY DISPERSION CORRECTION ENERGY', 'Eh', atm))
            calcinfo.append(
                qcel.Datum(
                    'AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION ENERGY',
                    'Eh', atm))

            if input_model.driver == 'gradient':
                calcinfo.append(
                    qcel.Datum('CURRENT GRADIENT', 'Eh/a0', fullgrad))
                calcinfo.append(
                    qcel.Datum('DISPERSION CORRECTION GRADIENT', 'Eh/a0',
                               fullgrad))
                calcinfo.append(
                    qcel.Datum('3-BODY DISPERSION CORRECTION GRADIENT',
                               'Eh/a0', fullgrad))
                calcinfo.append(
                    qcel.Datum(
                        'AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION GRADIENT',
                        'Eh/a0', fullgrad))

        else:
            calcinfo.append(qcel.Datum('CURRENT ENERGY', 'Eh', ene))
            calcinfo.append(
                qcel.Datum('DISPERSION CORRECTION ENERGY', 'Eh', ene))
            calcinfo.append(
                qcel.Datum('2-BODY DISPERSION CORRECTION ENERGY', 'Eh', ene))
            if qcvkey:
                calcinfo.append(
                    qcel.Datum(f'{qcvkey} DISPERSION CORRECTION ENERGY', 'Eh',
                               ene))

            if input_model.driver == 'gradient':
                calcinfo.append(
                    qcel.Datum('CURRENT GRADIENT', 'Eh/a0', fullgrad))
                calcinfo.append(
                    qcel.Datum('DISPERSION CORRECTION GRADIENT', 'Eh/a0',
                               fullgrad))
                calcinfo.append(
                    qcel.Datum('2-BODY DISPERSION CORRECTION GRADIENT',
                               'Eh/a0', fullgrad))
                if qcvkey:
                    calcinfo.append(
                        qcel.Datum(f'{qcvkey} DISPERSION CORRECTION GRADIENT',
                                   'Eh/a0', fullgrad))

        #LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo})
        calcinfo = {info.label: info.data for info in calcinfo}
        #calcinfo = qcel.util.unnp(calcinfo, flat=True)

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        calcinfo = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in qcel.util.unnp(calcinfo, flat=True).items()
        }

        # jobrec['properties'] = {"return_energy": ene}
        # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"])

        retres = calcinfo[f'CURRENT {input_model.driver.upper()}']
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            'extras': input_model.extras,
            'properties': {},
            'provenance': Provenance(creator="DFTD3",
                                     version=self.get_version(),
                                     routine=__name__ + '.' + sys._getframe().f_code.co_name),
            'return_result': retres,
            'stdout': stdout,
        }  # yapf: disable
        output_data["extras"]['local_keywords'] = input_model.extras['info']
        output_data["extras"]['qcvars'] = calcinfo

        output_data['success'] = True
        return Result(**{**input_model.dict(), **output_data})
Ejemplo n.º 25
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: "AtomicInput") -> "AtomicResult":
        stdout = outfiles.pop("stdout")

        for fl, contents in outfiles.items():
            if contents is not None:
                # LOG text += f'\n  MP2D scratch file {fl} has been read.\n'
                pass

        # parse energy output (could go further and break into UCHF, CKS)
        real = np.array(input_model.molecule.real)
        full_nat = real.shape[0]
        real_nat = np.sum(real)

        for ln in stdout.splitlines():
            if re.match("   MP2D dispersion correction Eh", ln):
                ene = Decimal(ln.split()[4])
            elif re.match("Atomic Coordinates in Angstroms", ln):
                break
        else:
            if not ((real_nat == 1) and (input_model.driver == "gradient")):
                raise UnknownError("Unknown issue occured.")

        # parse gradient output
        if outfiles["mp2d_gradient"] is not None:
            srealgrad = outfiles["mp2d_gradient"]
            realgrad = np.fromstring(srealgrad, count=3 * real_nat,
                                     sep=" ").reshape((-1, 3))

        if input_model.driver == "gradient":
            ireal = np.argwhere(real).reshape((-1))
            fullgrad = np.zeros((full_nat, 3))
            try:
                fullgrad[ireal, :] = realgrad
            except NameError as exc:
                raise UnknownError(
                    "Unsuccessful gradient collection.") from exc

        qcvkey = input_model.extras["info"]["fctldash"].upper()

        calcinfo = []
        calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene))
        calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", ene))
        calcinfo.append(
            qcel.Datum("2-BODY DISPERSION CORRECTION ENERGY", "Eh", ene))
        if qcvkey:
            calcinfo.append(
                qcel.Datum(f"{qcvkey} DISPERSION CORRECTION ENERGY", "Eh",
                           ene))

        if input_model.driver == "gradient":
            calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad))
            calcinfo.append(
                qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0",
                           fullgrad))
            calcinfo.append(
                qcel.Datum("2-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0",
                           fullgrad))
            if qcvkey:
                calcinfo.append(
                    qcel.Datum(f"{qcvkey} DISPERSION CORRECTION GRADIENT",
                               "Eh/a0", fullgrad))

        # LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo})
        calcinfo = {info.label: info.data for info in calcinfo}
        # calcinfo = qcel.util.unnp(calcinfo, flat=True)

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        calcinfo = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in calcinfo.items()
        }

        # jobrec['properties'] = {"return_energy": ene}
        # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"])

        retres = calcinfo[f"CURRENT {input_model.driver.upper()}"]
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            "extras":
            input_model.extras,
            "properties": {},
            "provenance":
            Provenance(creator="MP2D",
                       version=self.get_version(),
                       routine=__name__ + "." +
                       sys._getframe().f_code.co_name),
            "return_result":
            retres,
            "stdout":
            stdout,
        }
        output_data["extras"]["local_keywords"] = input_model.extras["info"]
        output_data["extras"]["qcvars"] = calcinfo

        output_data["success"] = True
        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 26
0
    def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> "AtomicResult":
        Grimme_h2kcal = 627.509541
        stdout = outfiles.pop("stdout")

        for fl, contents in outfiles.items():
            if contents is not None:
                # LOG text += f'\n  DFTD3 scratch file {fl} has been read.\n'
                pass

        # parse energy output (could go further and break into E6, E8, E10 and Cn coeff)
        real = np.array(input_model.molecule.real)
        full_nat = real.shape[0]
        real_nat = np.sum(real)
        for ln in stdout.splitlines():
            if re.match(" Edisp /kcal,au", ln):
                ene = Decimal(ln.split()[3])
            elif re.match(r" E6\(ABC\) \"   :", ln):  # c. v3.2.0
                raise ResourceError("Cannot process ATM results from DFTD3 prior to v3.2.1.")
            elif re.match(r""" E6\(ABC\) /kcal,au:""", ln):
                atm = Decimal(ln.split()[-1])
            elif re.match(" analysis of pair-wise terms", ln):
                D3pairs = np.zeros((full_nat, full_nat))
                # Iterate over block
                start = stdout.splitlines().index(ln) + 2
                for l in stdout.splitlines()[start:]:
                    data = l.replace("-", " -").split()
                    # print(data)
                    if len(data) == 0:
                        break
                    atom1 = int(data[0]) - 1
                    atom2 = int(data[1]) - 1
                    Edisp = Decimal(data[-1])
                    D3pairs[atom1, atom2] = Edisp / Decimal(Grimme_h2kcal)
                    D3pairs[atom2, atom1] = D3pairs[atom1, atom2]

            elif re.match(" normal termination of dftd3", ln):
                break
        else:
            if not ((real_nat == 1) and (input_model.driver == "gradient")):
                raise UnknownError(
                    f"Unsuccessful run. Check input, particularly geometry in [a0]. Model: {input_model.model}"
                )

        # parse gradient output
        # * DFTD3 crashes on one-atom gradients. Avoid the error (above) and just force the correct result (below).
        if outfiles["dftd3_gradient"] is not None:
            srealgrad = outfiles["dftd3_gradient"].replace("D", "E")
            realgrad = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3))
        elif real_nat == 1:
            realgrad = np.zeros((1, 3))

        if outfiles["dftd3_abc_gradient"] is not None:
            srealgrad = outfiles["dftd3_abc_gradient"].replace("D", "E")
            realgradabc = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3))
        elif real_nat == 1:
            realgradabc = np.zeros((1, 3))

        if input_model.driver == "gradient":
            ireal = np.argwhere(real).reshape((-1))
            fullgrad = np.zeros((full_nat, 3))
            rg = realgradabc if (input_model.extras["info"]["dashlevel"] == "atmgr") else realgrad
            try:
                fullgrad[ireal, :] = rg
            except NameError as exc:
                raise UnknownError("Unsuccessful gradient collection.") from exc

        qcvkey = input_model.extras["info"]["fctldash"].upper()

        calcinfo = []
        if input_model.extras["info"]["dashlevel"] == "atmgr":
            calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", atm))
            calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", atm))
            calcinfo.append(qcel.Datum("3-BODY DISPERSION CORRECTION ENERGY", "Eh", atm))
            calcinfo.append(qcel.Datum("AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION ENERGY", "Eh", atm))

            if input_model.driver == "gradient":
                calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad))
                calcinfo.append(qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad))
                calcinfo.append(qcel.Datum("3-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad))
                calcinfo.append(
                    qcel.Datum("AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)
                )

        else:
            calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene))
            calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", ene))
            calcinfo.append(qcel.Datum("2-BODY DISPERSION CORRECTION ENERGY", "Eh", ene))
            if qcvkey:
                calcinfo.append(qcel.Datum(f"{qcvkey} DISPERSION CORRECTION ENERGY", "Eh", ene))

            if input_model.driver == "gradient":
                calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad))
                calcinfo.append(qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad))
                calcinfo.append(qcel.Datum("2-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad))
                if qcvkey:
                    calcinfo.append(qcel.Datum(f"{qcvkey} DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad))

        # LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo})
        calcinfo = {info.label: info.data for info in calcinfo}
        # calcinfo = qcel.util.unnp(calcinfo, flat=True)

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        calcinfo = {
            k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(calcinfo, flat=True).items()
        }

        # jobrec['properties'] = {"return_energy": ene}
        # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"])

        retres = calcinfo[f"CURRENT {input_model.driver.upper()}"]
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            "extras": input_model.extras,
            "properties": {},
            "provenance": Provenance(
                creator="DFTD3", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name
            ),
            "return_result": retres,
            "stdout": stdout,
        }
        output_data["extras"]["local_keywords"] = input_model.extras["info"]
        output_data["extras"]["qcvars"] = calcinfo
        if input_model.keywords.get("save_pairwise_dispersion") is True:
            output_data["extras"]["qcvars"]["PAIRWISE DISPERSION CORRECTION ANALYSIS"] = D3pairs
        output_data["success"] = True

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 27
0
    def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
        """
        Runs OpenMM on given structure, inputs, in vacuum.
        """
        self.found(raise_error=True)

        from simtk import openmm
        from simtk import unit

        import openforcefield.topology as offtop

        # Failure flag
        ret_data = {"success": False}

        # generate basis, not given
        if not input_data.model.basis:
            basis = self._generate_basis(input_data)
            ret_data["basis"] = basis

        # get number of threads to use from `TaskConfig.ncores`; otherwise, try environment variable
        nthreads = config.ncores
        if nthreads is None:
            nthreads = os.environ.get("OPENMM_CPU_THREADS")

        # Set workdir to scratch
        # Location resolution order config.scratch_dir, /tmp
        parent = config.scratch_directory
        with temporary_directory(parent=parent, suffix="_openmm_scratch") as tmpdir:

            # Grab molecule, forcefield
            jmol = input_data.molecule

            # TODO: If urls are supported by
            # `openforcefield.typing.engines.smirnoff.ForceField` already, we
            # can eliminate the `offxml` and `url` distinction
            # URL processing can happen there instead
            if getattr(input_data.model, "offxml", None):
                # we were given a file path or relative path
                offxml = input_data.model.offxml

                # Load an Open Force Field `ForceField`
                off_forcefield = self._get_off_forcefield(offxml, offxml)
            elif getattr(input_data.model, "url", None):
                # we were given a url
                with urllib.request.urlopen(input_data.model.url) as req:
                    xml = req.read()

                # Load an Open Force Field `ForceField`
                off_forcefield = self._get_off_forcefield(xml.decode(), xml)
            else:
                raise InputError("OpenMM requires either `model.offxml` or `model.url` to be set")

            # Process molecule with RDKit
            rdkit_mol = RDKitHarness._process_molecule_rdkit(jmol)

            # Create an Open Force Field `Molecule` from the RDKit Molecule
            off_mol = offtop.Molecule(rdkit_mol)

            # Create OpenMM system in vacuum from forcefield, molecule
            off_top = off_mol.to_topology()
            openmm_system = self._get_openmm_system(off_forcefield, off_top)

            # Need an integrator for simulation even if we don't end up using it really
            integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds)

            # Set platform to CPU explicitly
            platform = openmm.Platform.getPlatformByName("CPU")

            # Set number of threads to use
            # if `nthreads` is `None`, OpenMM default of all logical cores on
            # processor will be used
            if nthreads:
                properties = {"Threads": str(nthreads)}
            else:
                properties = {}

            # Initialize context
            context = openmm.Context(openmm_system, integrator, platform, properties)

            # Set positions from our Open Force Field `Molecule`
            context.setPositions(off_mol.conformers[0])

            # Compute the energy of the configuration
            state = context.getState(getEnergy=True)

            # Get the potential as a simtk.unit.Quantity, put into units of hartree
            q = state.getPotentialEnergy() / unit.hartree

            ret_data["properties"] = {"return_energy": q.value_in_unit(q.unit)}

            # Execute driver
            if input_data.driver == "energy":
                ret_data["return_result"] = ret_data["properties"]["return_energy"]

            elif input_data.driver == "gradient":
                # Get number of atoms
                n_atoms = len(jmol.symbols)

                # Compute the forces
                state = context.getState(getForces=True)

                # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3)
                gradient = state.getForces(asNumpy=True)

                # Convert to hartree/bohr and reformat as 1D array
                q = (gradient / (unit.hartree / unit.bohr)).reshape([n_atoms * 3])
                ret_data["return_result"] = q.value_in_unit(q.unit)
            else:
                raise InputError(
                    f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}."
                )

        ret_data["success"] = True

        # Move several pieces up a level
        ret_data["provenance"] = Provenance(creator="openmm", version=openmm.__version__, nthreads=nthreads)

        return AtomicResult(**{**input_data.dict(), **ret_data})
Ejemplo n.º 28
0
    def parse_output(
        self, outfiles: Dict[str, str], input_model: AtomicInput
    ) -> AtomicResult:  # lgtm: [py/similar-function]

        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        method = input_model.model.method.lower()
        method = method[3:] if method.startswith("c4-") else method

        # c4mol, if it exists, is dinky, just a clue to geometry of cfour results
        try:
            # July 2021: c4mol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always atin orientation
            qcvars, c4hess, c4grad, c4mol, version, module, errorTMP = harvest(
                input_model.molecule, method, stdout, **outfiles
            )
        except Exception:
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        if errorTMP != "":
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        try:
            if c4grad is not None:
                qcvars["CURRENT GRADIENT"] = c4grad
                qcvars[f"{method.upper()} TOTAL GRADIENT"] = c4grad

            if c4hess is not None:
                qcvars[f"{method.upper()} TOTAL HESSIAN"] = c4hess
                qcvars["CURRENT HESSIAN"] = c4hess

            if input_model.driver.upper() == "PROPERTIES":
                retres = qcvars[f"CURRENT ENERGY"]
            else:
                retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        except KeyError:
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        # TODO: "xalloc(): memory allocation failed!"

        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        provenance = Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour").dict()
        if module is not None:
            provenance["module"] = module

        output_data = {
            "schema_version": 1,
            "molecule": c4mol,  # overwrites with outfile Cartesians in case fix_*=F
            "extras": {**input_model.extras},
            "native_files": {k: v for k, v in outfiles.items() if v is not None},
            "properties": atprop,
            "provenance": provenance,
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # Decimal --> str preserves precision
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 29
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: "AtomicInput") -> "AtomicResult":
        stdout = outfiles.pop("stdout")

        # parse energy output (could go further and break into E6, E8, E10 and Cn coeff)
        real = np.array(input_model.molecule.real)
        full_nat = real.shape[0]
        real_nat = np.sum(real)

        for ln in stdout.splitlines():
            if re.match("  Egcp:", ln):
                ene = Decimal(ln.split()[1])
            elif re.match("     normal termination of gCP", ln):
                break
        else:
            if self._defaults["name"] == "GCP" and not (
                (real_nat == 1) and (input_model.driver == "gradient")):
                raise UnknownError(
                    f"Unsuccessful run. Check input, particularly geometry in [a0]. Model: {input_model.model}"
                )

        # parse gradient output
        if outfiles["gcp_gradient"] is not None:
            srealgrad = outfiles["gcp_gradient"].replace("D", "E")
            realgrad = np.fromstring(srealgrad, count=3 * real_nat,
                                     sep=" ").reshape((-1, 3))
        elif real_nat == 1:
            realgrad = np.zeros((1, 3))

        if input_model.driver == "gradient":
            ireal = np.argwhere(real).reshape((-1))
            fullgrad = np.zeros((full_nat, 3))
            try:
                fullgrad[ireal, :] = realgrad
            except NameError as exc:
                raise UnknownError(
                    "Unsuccessful gradient collection.") from exc

        qcvkey = input_model.model.method.upper()

        calcinfo = []

        calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene))
        calcinfo.append(qcel.Datum("GCP CORRECTION ENERGY", "Eh", ene))
        if qcvkey:
            calcinfo.append(
                qcel.Datum(f"{qcvkey} GCP CORRECTION ENERGY", "Eh", ene))

        if input_model.driver == "gradient":
            calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad))
            calcinfo.append(
                qcel.Datum("GCP CORRECTION GRADIENT", "Eh/a0", fullgrad))
            if qcvkey:
                calcinfo.append(
                    qcel.Datum(f"{qcvkey} GCP CORRECTION GRADIENT", "Eh/a0",
                               fullgrad))

        calcinfo = {info.label: info.data for info in calcinfo}

        # Decimal --> str preserves precision
        calcinfo = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in calcinfo.items()
        }

        retres = calcinfo[f"CURRENT {input_model.driver.upper()}"]
        if isinstance(retres, Decimal):
            retres = float(retres)
        elif isinstance(retres, np.ndarray):
            retres = retres.ravel().tolist()

        output_data = {
            "extras":
            input_model.extras,
            "properties": {},
            "provenance":
            Provenance(creator="GCP",
                       version=self.get_version(),
                       routine=__name__ + "." +
                       sys._getframe().f_code.co_name),
            "return_result":
            retres,
            "stdout":
            stdout,
        }

        output_data["extras"]["qcvars"] = calcinfo

        output_data["success"] = True
        return AtomicResult(**{**input_model.dict(), **output_data})
Ejemplo n.º 30
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: AtomicInput) -> AtomicResult:

        # Get the stdout from the calculation (required)
        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        method = input_model.model.method.lower()
        method = method[4:] if method.startswith("gms-") else method

        # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results
        try:
            # July 2021: gamessmol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always outfile orientation
            qcvars, gamesshess, gamessgrad, gamessmol, module = harvest(
                input_model.molecule, method, stdout, **outfiles)
            # TODO:  "EXECUTION OF GAMESS TERMINATED -ABNORMALLY-" in dexe["stdout"]:

        except Exception:
            raise UnknownError(error_stamp(outfiles["input"], stdout, stderr))

        try:
            if gamessgrad is not None:
                qcvars[f"{method.upper()} TOTAL GRADIENT"] = gamessgrad
                qcvars["CURRENT GRADIENT"] = gamessgrad

            if gamesshess is not None:
                qcvars[f"{method.upper()} TOTAL HESSIAN"] = gamesshess
                qcvars["CURRENT HESSIAN"] = gamesshess

            if input_model.driver.upper() == "PROPERTIES":
                retres = qcvars[f"CURRENT ENERGY"]
            else:
                retres = qcvars[f"CURRENT {input_model.driver.upper()}"]
        except KeyError:
            if "EXETYP=CHECK" in stdout and "EXECUTION OF GAMESS TERMINATED NORMALLY" in stdout:
                # check run that completed normally
                # * on one hand, it's still an error return_result-wise
                # * but on the other hand, often the reason for the job is to get gamessmol, so let it return success=T below
                retres = 0.0
            else:
                raise UnknownError(
                    error_stamp(outfiles["input"], stdout, stderr))

        build_out(qcvars)
        atprop = build_atomicproperties(qcvars)

        provenance = Provenance(creator="GAMESS",
                                version=self.get_version(),
                                routine="rungms").dict()
        if module is not None:
            provenance["module"] = module

        output_data = {
            "schema_version": 1,
            "molecule":
            gamessmol,  # overwrites with outfile Cartesians in case fix_*=F
            "extras": {
                **input_model.extras
            },
            "native_files":
            {k: v
             for k, v in outfiles.items() if v is not None},
            "properties": atprop,
            "provenance": provenance,
            "return_result": retres,
            "stderr": stderr,
            "stdout": stdout,
            "success": True,
        }

        # got to even out who needs plump/flat/Decimal/float/ndarray/list
        # * formerly unnp(qcvars, flat=True).items()
        output_data["extras"]["qcvars"] = {
            k.upper(): str(v) if isinstance(v, Decimal) else v
            for k, v in qcvars.items()
        }

        return AtomicResult(**{**input_model.dict(), **output_data})