def acquire_complete(self) -> Dict[str, Any]:
        ret = {}

        # Pull out completed results that match our queue ids
        cursor = self.client.launches.find(
            {
                "fw_id": {
                    "$in": list(self.queue.keys())
                },
                "state": {
                    "$in": ["COMPLETED", "FIZZLED"]
                },
            }, {
                "action.stored_data.fw_results": True,
                "action.stored_data._task.args": True,
                "action.stored_data._exception": True,
                "_id": False,
                "fw_id": True,
                "state": True
            })

        for tmp_data in cursor:
            key = self.queue.pop(tmp_data["fw_id"])
            if tmp_data["state"] == "COMPLETED":
                key_data = tmp_data["action"]["stored_data"]["fw_results"]
                if key_data['success']:
                    # Cast dict to the Result or Optimization based on the schema
                    ret[key] = schema_mapper[key_data['schema_name']](
                        **key_data)
                else:
                    ret[key] = FailedOperation(**key_data)
            else:
                blob = tmp_data["action"]["stored_data"]["_task"]["args"][0]
                msg = tmp_data["action"]["stored_data"]["_exception"][
                    "_stacktrace"]
                blob["error_message"] = msg
                blob["success"] = False
                key_data = blob
                ret[key] = FailedOperation(**key_data)

        return ret
Example #2
0
    def compute(self, input_data: 'ResultInput', config: 'JobConfig') -> 'Result':
        self.found(raise_error=True)

        # Set up the job
        input_data = input_data.copy().dict()
        input_data["success"] = False

        output_data = run_json(input_data)

        if output_data["success"]:
            return Result(**output_data)
        return FailedOperation(
            success=output_data.pop("success", False), error=output_data.pop("error"), input_data=output_data)
Example #3
0
def _get_future(future):
    try:
        return future.result()
    except Exception as e:
        msg = "Caught Executor Error:\n" + traceback.format_exc()
        ret = FailedOperation(
            **{
                "success": False,
                "error": {
                    "error_type": e.__class__.__name__,
                    "error_message": msg
                }
            })
        return ret
Example #4
0
def _get_future(future):
    # if future.exception() is None: # This always seems to return None
    try:
        return future.result()
    except Exception as e:
        msg = "Caught Parsl Error:\n" + traceback.format_exc()
        ret = FailedOperation(
            **{
                "success": False,
                "error": {
                    "error_type": e.__class__.__name__,
                    "error_message": msg
                }
            })
        return ret
Example #5
0
    def compute(self, input_data: 'ResultInput',
                config: 'JobConfig') -> 'Result':

        if not which('dftd3', return_bool=True):
            raise ImportError("Could not find dftd3 in the envvar path.")

        # Setup the job
        input_data = input_data.copy().dict()
        input_data["success"] = False

        output_data = run_json(input_data)

        if output_data["success"]:
            return Result(**output_data)
        return FailedOperation(success=output_data.pop("success", False),
                               error=output_data.pop("error"),
                               input_data=output_data)
Example #6
0
def model_wrapper(input_data, model):
    """
    Wrap input data in the given model, or return a controlled error
    """
    try:
        if isinstance(input_data, dict):
            input_data = model(**input_data)
    except Exception:
        failure = FailedOperation(
            input_data=input_data,
            success=False,
            error=ComputeError(
                error_type="input_error",
                error_message=(
                    "Input data could not be processed correctly:\n" +
                    traceback.format_exc())))
        return failure
    return input_data
Example #7
0
    def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
        self.found(raise_error=True)

        job_inputs = self.build_input(input_model, config)

        success, dexe = self.execute(job_inputs)

        if success:
            dexe["outfiles"]["stdout"] = dexe["stdout"]
            dexe["outfiles"]["stderr"] = dexe["stderr"]
            output_model = self.parse_output(dexe["outfiles"], input_model)

        else:
            output_model = FailedOperation(
                success=False,
                error={"error_type": "execution_error", "error_message": dexe["stderr"]},
                input_data=input_model.dict(),
            )

        return output_model
Example #8
0
    def compute(self, input_data: 'ResultInput',
                config: 'JobConfig') -> 'Result':
        """
        Run Molpro
        """
        # Check if Molpro executable is found
        self.found(raise_error=True)

        # Check Molpro version
        if parse_version(self.get_version()) < parse_version("2018.1"):
            raise TypeError("Molpro version '{}' not supported".format(
                self.get_version()))

        # Setup the job
        job_inputs = self.build_input(input_data, config)

        # Run Molpro
        exe_success, proc = execute(
            job_inputs["commands"],
            infiles=job_inputs["infiles"],
            outfiles=["dispatch.out", "dispatch.xml", "dispatch.wfu"],
            scratch_location=job_inputs["scratch_location"],
            timeout=None)

        # Determine whether the calculation succeeded
        output_data = {}
        if not exe_success:
            output_data["success"] = False
            output_data["error"] = {
                "error_type": "internal_error",
                "error_message": proc["stderr"]
            }
            return FailedOperation(success=output_data.pop("success", False),
                                   error=output_data.pop("error"),
                                   input_data=output_data)

        # If execution succeeded, collect results
        result = self.parse_output(proc["outfiles"], input_data)
        return result
Example #9
0
    def compute(self, input_model: AtomicInput,
                config: "TaskConfig") -> "AtomicResult":
        self.found(raise_error=True)

        verbose = 1

        print_jobrec(f"[1] {self.name} RESULTINPUT PRE-PLANT",
                     input_model.dict(), verbose >= 3)

        job_inputs = self.qcdb_build_input(input_model, config)

        print_jobrec(f"[2] {self.name}REC PRE-ENGINE", job_inputs,
                     verbose >= 4)

        # 'NWCHEM_OMP_NUM_CORES': os.environ.get('NWCHEM_OMP_NUM_CORES'),

        success, dexe = self.execute(job_inputs)

        stdin = job_inputs["infiles"]["nwchem.nw"]

        print_jobrec(f"[3] {self.name}REC POST-ENGINE", dexe, verbose >= 4)

        if "There is an error in the input file" in dexe["stdout"]:
            raise InputError(error_stamp(stdin, dexe["stdout"],
                                         dexe["stderr"]))
        if "not compiled" in dexe["stdout"]:
            # recoverable with a different compilation with optional modules
            raise InputError(error_stamp(stdin, dexe["stdout"],
                                         dexe["stderr"]))

        if success:
            dexe["outfiles"]["stdout"] = dexe["stdout"]
            dexe["outfiles"]["stderr"] = dexe["stderr"]
            dexe["outfiles"]["input"] = stdin
            output_model = self.parse_output(dexe["outfiles"], input_model)

            print_jobrec(f"[4a] {self.name} RESULT POST-HARVEST",
                         output_model.dict(), verbose >= 5)

            output_model = self.qcdb_post_parse_output(input_model,
                                                       output_model)

            print_jobrec(f"[4] {self.name} RESULT POST-POST-HARVEST",
                         output_model.dict(), verbose >= 2)

        else:
            ## Check if any of the errors are known
            #for error in all_errors:
            #    error.detect_error(dexe)
            output_model = FailedOperation(
                success=False,
                error={
                    "error_type":
                    "execution_error",
                    "error_message":
                    error_stamp(stdin, dexe["stdout"], dexe["stderr"]),
                },
                input_data=input_model.dict(),
            )

        return output_model
Example #10
0
    def compute(
        self, input_data: "OptimizationInput", config: "TaskConfig"
    ) -> Union["OptimizationResult", "FailedOperation"]:
        try:
            import berny
        except ModuleNotFoundError:
            raise ModuleNotFoundError("Could not find Berny in the Python path.")

        # Get berny version from the installed package, use setuptools'
        # pkg_resources for python < 3.8
        if sys.version_info >= (3, 8):
            from importlib.metadata import distribution
        else:
            from pkg_resources import get_distribution as distribution
        berny_version = distribution("pyberny").version

        # Berny uses the stdlib logging module and by default uses per-module
        # loggers. For QCEngine, we create one logger per BernyProcedure
        # instance, by using the instance's id(), and send all logging messages
        # to a string stream
        log_stream = StringIO()
        log = logging.getLogger(f"{__name__}.{id(self)}")
        log.addHandler(logging.StreamHandler(log_stream))
        log.setLevel("INFO")

        input_data = input_data.dict()
        geom_qcng = input_data["initial_molecule"]
        comput = {**input_data["input_specification"], "molecule": geom_qcng}
        program = input_data["keywords"].pop("program")
        trajectory = []
        output_data = input_data.copy()
        try:
            # Pyberny uses angstroms for the Cartesian geometry, but atomic
            # units for everything else, including the gradients (hartree/bohr).
            geom_berny = berny.Geometry(geom_qcng["symbols"], geom_qcng["geometry"] / berny.angstrom)
            opt = berny.Berny(geom_berny, logger=log, **input_data["keywords"])
            for geom_berny in opt:
                geom_qcng["geometry"] = np.stack(geom_berny.coords * berny.angstrom)
                ret = qcengine.compute(comput, program)
                if ret.success:
                    trajectory.append(ret.dict())
                    opt.send((ret.properties.return_energy, ret.return_result))
                else:
                    # qcengine.compute returned FailedOperation
                    raise UnknownError("Gradient computation failed")

        except UnknownError:
            error = ret.error.dict()  # ComputeError
        except Exception:
            error = {"error_type": "unknown", "error_message": f"Berny error:\n{traceback.format_exc()}"}
        else:
            output_data["success"] = True
            output_data.update(
                {
                    "schema_name": "qcschema_optimization_output",
                    "final_molecule": trajectory[-1]["molecule"],
                    "energies": [r["properties"]["return_energy"] for r in trajectory],
                    "trajectory": trajectory,
                    "provenance": {"creator": "Berny", "routine": "berny.Berny", "version": berny_version},
                    "stdout": log_stream.getvalue(),  # collect logged messages
                }
            )
            return OptimizationResult(**output_data)
        return FailedOperation(input_data=input_data, error=error)
Example #11
0
    def compute(self, input_model: 'ResultInput',
                config: 'JobConfig') -> 'Result':
        """
        Runs Psi4 in API mode
        """
        self.found(raise_error=True)

        # Setup the job
        input_data = input_model.json_dict()
        input_data["nthreads"] = config.ncores
        input_data["memory"] = int(config.memory * 1024 * 1024 * 1024 *
                                   0.95)  # Memory in bytes
        input_data["success"] = False
        input_data["return_output"] = True

        if input_data["schema_name"] == "qcschema_input":
            input_data["schema_name"] = "qc_schema_input"

        if config.scratch_directory:
            input_data["scratch_location"] = config.scratch_directory

        if parse_version(self.get_version()) > parse_version("1.2"):

            caseless_keywords = {
                k.lower(): v
                for k, v in input_model.keywords.items()
            }
            if (input_model.molecule.molecular_multiplicity !=
                    1) and ("reference" not in caseless_keywords):
                input_data["keywords"]["reference"] = "uhf"

            # Execute the program
            success, output = execute([which("psi4"), "--json", "data.json"],
                                      {"data.json": json.dumps(input_data)},
                                      ["data.json"])

            if success:
                output_data = json.loads(output["outfiles"]["data.json"])
                if "extras" not in output_data:
                    output_data["extras"] = {}

                output_data["extras"]["local_qcvars"] = output_data.pop(
                    "psi4:qcvars", None)
                if output_data["success"] is False:
                    if "error_message" not in output_data["error"]:
                        # older c. 1.3 message-only run_json
                        output_data["error"] = {
                            "error_type": "internal_error",
                            "error_message": output_data["error"]
                        }
            else:
                output_data = input_data
                output_data["error"] = {
                    "error_type": "execution_error",
                    "error_message": output["stderr"]
                }

        else:
            raise TypeError("Psi4 version '{}' not understood.".format(
                self.get_version()))

        # Reset the schema if required
        output_data["schema_name"] = "qcschema_output"

        # Dispatch errors, PSIO Errors are not recoverable for future runs
        if output_data["success"] is False:

            if "PSIO Error" in output_data["error"]:
                raise ValueError(output_data["error"])

        # Move several pieces up a level
        if output_data["success"]:
            output_data["provenance"]["memory"] = round(
                output_data.pop("memory") / (1024**3), 3)  # Move back to GB
            output_data["provenance"]["nthreads"] = output_data.pop("nthreads")
            output_data["stdout"] = output_data.pop("raw_output", None)

            # Delete keys
            output_data.pop("return_output", None)
            output_data.pop("scratch_location", None)

            return Result(**output_data)
        else:
            return FailedOperation(success=output_data.pop("success", False),
                                   error=output_data.pop("error"),
                                   input_data=output_data)
Example #12
0
def compute_procedure(input_data: Union[Dict[str, Any], 'BaseModel'],
                      procedure: str,
                      raise_error: bool = False,
                      local_options: Optional[Dict[str, str]] = None,
                      return_dict: bool = False) -> 'BaseModel':
    """Runs a procedure (a collection of the quantum chemistry executions)

    Parameters
    ----------
    input_data : dict or qcelemental.models.OptimizationInput
        A JSON input specific to the procedure executed in dictionary or model from QCElemental.models
    procedure : {"geometric"}
        The name of the procedure to run
    raise_error : bool, option
        Determines if compute should raise an error or not.
    local_options : dict, optional
        A dictionary of local configuration options
    return_dict : bool, optional, default True
        Returns a dict instead of qcelemental.models.ResultInput

    Returns
    ------
    dict, Optimization, FailedOperation
        A QC Schema representation of the requested output, type depends on return_dict key.
    """

    procedure = procedure.lower()
    if procedure not in list_all_procedures():
        input_data = FailedOperation(
            input_data=input_data,
            error=ComputeError(
                error_type="not_registered",
                error_message="QCEngine Call Error:\n"
                "Procedure {} is not registered with QCEngine".format(
                    procedure)))
    elif procedure not in list_available_procedures():
        input_data = FailedOperation(
            input_data=input_data,
            error=ComputeError(
                error_type="not_available",
                error_message="QCEngine Call Error:\n"
                "Procedure {} is registered with QCEngine, but cannot be found"
                .format(procedure)))
    error = _process_failure_and_return(input_data, return_dict, raise_error)
    if error:
        return error

    # Grab the executor and build the input model
    executor = get_procedure(procedure)

    config = get_config(local_options=local_options)
    input_data = executor.build_input_model(input_data)
    error = _process_failure_and_return(input_data, return_dict, raise_error)
    if error:
        return error

    # Run the procedure
    with compute_wrapper(capture_output=False) as metadata:

        # Create a base output data in case of errors
        output_data = input_data.copy()  # lgtm [py/multiple-definition]
        output_data = executor.compute(input_data, config)

    return handle_output_metadata(output_data,
                                  metadata,
                                  raise_error=raise_error,
                                  return_dict=return_dict)
Example #13
0
def handle_output_metadata(
        output_data: Union[Dict[str, Any], 'BaseModel'],
        metadata: Dict[str, Any],
        raise_error: bool = False,
        return_dict: bool = True) -> Union[Dict[str, Any], 'BaseModel']:
    """
    Fuses general metadata and output together.

    Returns
    -------
    result : dict or pydantic.models.Result
        Output type depends on return_dict or a dict if an error was generated in model construction
    """

    if isinstance(output_data, dict):
        output_fusion = output_data  # Error handling
    else:
        output_fusion = output_data.dict()

    # Do not override if computer generates
    output_fusion["stdout"] = output_fusion.get("stdout",
                                                None) or metadata["stdout"]
    output_fusion["stderr"] = output_fusion.get("stderr",
                                                None) or metadata["stderr"]

    if metadata["success"] is not True:
        output_fusion["success"] = False
        output_fusion["error"] = {
            "error_type": metadata["error_type"],
            "error_message": metadata["error_message"]
        }

    # Raise an error if one exists and a user requested a raise
    if raise_error and (output_fusion["success"] is not True):
        msg = "stdout:\n{}".format(output_fusion["stdout"])
        msg += "\nstderr:\n{}".format(output_fusion["stderr"])
        LOGGER.info(msg)
        raise ValueError(output_fusion["error"]["error_message"])

    # Fill out provenance datadata
    provenance_augments = get_provenance_augments()
    provenance_augments["wall_time"] = metadata["wall_time"]
    if "provenance" in output_fusion:
        output_fusion["provenance"].update(provenance_augments)
    else:
        # Add onto the augments with some missing info
        provenance_augments["creator"] = "QCEngine"
        provenance_augments["version"] = provenance_augments[
            "qcengine_version"]
        output_fusion["provenance"] = provenance_augments

    if metadata["retries"] != 0:
        output_fusion["provenance"]["retries"] = metadata["retries"]

    # Make sure pydantic sparsity is upheld
    for val in ["stdout", "stderr"]:
        if output_fusion[val] is None:
            output_fusion.pop(val)

    # We need to return the correct objects; e.g. Results, Procedures
    if output_fusion["success"]:
        # This will only execute if everything went well
        ret = output_data.__class__(**output_fusion)
    else:
        # Should only be reachable on failures
        ret = FailedOperation(success=output_fusion.pop("success", False),
                              error=output_fusion.pop("error"),
                              input_data=output_fusion)

    if return_dict:
        return json.loads(
            ret.json()
        )  # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals
    else:
        return ret
Example #14
0
def psi4(input_model, config):
    """
    Runs Psi4 in API mode
    """

    from pkg_resources import parse_version

    def _parse_psi_version(version):
        if "undef" in version:
            raise TypeError(
                "Using custom build Psi4 without tags. Please `git pull origin master --tags` and recompile Psi4."
            )

        return parse_version(version)

    try:
        import psi4
    except ImportError:
        raise ImportError("Could not find Psi4 in the Python path.")

    # Setup the job
    input_data = input_model.copy().dict()
    input_data["nthreads"] = config.ncores
    input_data["memory"] = int(config.memory * 1024 * 1024 * 1024 *
                               0.95)  # Memory in bytes
    input_data["success"] = False
    reset_schema = False
    if input_data["schema_name"] == "qcschema_input":
        input_data["schema_name"] = "qc_schema_input"
        reset_schema = True

    scratch = config.scratch_directory
    if scratch is not None:
        input_data["scratch_location"] = scratch

    psi_version = _parse_psi_version(psi4.__version__)

    if psi_version > parse_version("1.2"):

        mol = psi4.core.Molecule.from_schema(input_data)
        if mol.multiplicity() != 1:
            input_data["keywords"]["reference"] = "uks"

        output_data = psi4.json_wrapper.run_json(input_data)

    else:
        raise TypeError(
            "Psi4 version '{}' not understood.".format(psi_version))

    if reset_schema:
        output_data["schema_name"] = "qcschema_input"
    # Dispatch errors, PSIO Errors are not recoverable for future runs
    if output_data["success"] is False:

        if "PSIO Error" in output_data["error"]:
            raise ValueError(output_data["error"])

    # Move several pieces up a level
    if output_data["success"]:
        output_data["provenance"]["memory"] = round(
            input_data["memory"] / (1024**3), 3)
        output_data["provenance"]["nthreads"] = input_data["nthreads"]
        del output_data["memory"], input_data["nthreads"]
        return Result(**output_data)
    return FailedOperation(success=output_data.pop("success", False),
                           error=output_data.pop("error"),
                           input_data=output_data)
Example #15
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})
Example #16
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})
Example #17
0
def test_repr_failed_op():
    fail_op = FailedOperation(error=ComputeError(error_type="random_error", error_message="this is bad"))

    assert "random_error" in str(fail_op)
    assert "random_error" in repr(fail_op)
Example #18
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})
Example #19
0
def compute(input_data,
            program,
            raise_error=False,
            capture_output=True,
            local_options=None,
            return_dict=True):
    """Executes a single quantum chemistry program given a QC Schema input.

    The full specification can be found at:
        http://molssi-qc-schema.readthedocs.io/en/latest/index.html#

    Parameters
    ----------
    input_data : dict or qcelemental.models.ResultInput
        A QC Schema input specification in dictionary or model from QCElemental.models
    program : {"psi4", "rdkit"}
        The program to run the input under
    raise_error : bool, optional
        Determines if compute should raise an error or not.
    capture_output : bool, optional
        Determines if stdout/stderr should be captured.
    local_options : dict, optional
        A dictionary of local configuration options
    return_dict : bool, optional, default True
        Returns a dict instead of qcelemental.models.ResultInput

    Returns
    -------
    ret : dict, Result, FailedOperation
        A QC Schema output, type depends on return_dict key
        A FailedOperation returns

    """
    input_data = model_wrapper(input_data, ResultInput)
    if isinstance(input_data, FailedOperation):
        if return_dict:
            return input_data.dict()
        return input_data

    if local_options is None:
        local_options = {}

    try:
        input_engine_options = input_data._qcengine_local_config
        input_data = input_data.copy(exclude={'_qcengine_local_config'})
    except AttributeError:
        input_engine_options = {}

    local_options = {**local_options, **input_engine_options}
    config = get_config(local_options=local_options)

    # Run the program
    with compute_wrapper(capture_output=capture_output) as metadata:
        output_data = input_data.copy()  # Initial in case of error handling
        try:
            output_data = get_program(program)(input_data, config)
        except KeyError as e:
            output_data = FailedOperation(
                input_data=output_data.dict(),
                success=False,
                error=ComputeError(
                    error_type='program_error',
                    error_message=
                    "QCEngine Call Error:\nProgram {} not understood."
                    "\nError Message: {}".format(program, str(e))))

    return handle_output_metadata(output_data,
                                  metadata,
                                  raise_error=raise_error,
                                  return_dict=return_dict)
Example #20
0
def compute_procedure(input_data,
                      procedure,
                      raise_error=False,
                      capture_output=True,
                      local_options=None,
                      return_dict=True):
    """Runs a procedure (a collection of the quantum chemistry executions)

    Parameters
    ----------
    input_data : dict or qcelemental.models.OptimizationInput
        A JSON input specific to the procedure executed in dictionary or model from QCElemental.models
    procedure : {"geometric"}
        The name of the procedure to run
    raise_error : bool, option
        Determines if compute should raise an error or not.
    capture_output : bool, optional
        Determines if stdout/stderr should be captured.
    local_options : dict, optional
        A dictionary of local configuration options
    return_dict : bool, optional, default True
        Returns a dict instead of qcelemental.models.ResultInput

    Returns
    ------
    dict, Optimization, FailedOperation
        A QC Schema representation of the requested output, type depends on return_dict key.
    """

    input_data = model_wrapper(input_data, OptimizationInput)
    if isinstance(input_data, FailedOperation):
        if return_dict:
            return input_data.dict()
        return input_data

    config = get_config(local_options=local_options)

    # Run the procedure
    with compute_wrapper(capture_output=capture_output) as metadata:
        # Create a base output data in case of errors
        output_data = input_data.copy()  # lgtm [py/multiple-definition]
        if procedure == "geometric":
            # Augment the input
            geometric_input = input_data.dict()
            geometric_input["input_specification"][
                "_qcengine_local_config"] = config.dict()

            # Run the program
            output_data = get_module_function(
                procedure, "run_json.geometric_run_json")(geometric_input)

            output_data["schema_name"] = "qcschema_optimization_output"
            output_data["input_specification"].pop("_qcengine_local_config",
                                                   None)
            output_data = Optimization(**output_data)
        else:
            output_data = FailedOperation(
                input_data=input_data.dict(),
                success=False,
                error=ComputeError(
                    error_type="program_error",
                    error_message="QCEngine Call Error:"
                    "\nProcedure {} not understood".format(procedure)))

    return handle_output_metadata(output_data,
                                  metadata,
                                  raise_error=raise_error,
                                  return_dict=return_dict)
Example #21
0
    def compute(self, input_model: 'ResultInput', config: 'JobConfig') -> 'Result':
        """
        Runs Psi4 in API mode
        """

        try:
            import psi4
        except ModuleNotFoundError:
            raise ModuleNotFoundError("Could not find Psi4 in the Python path.")

        # Setup the job
        input_model = input_model.copy().dict()
        input_model["nthreads"] = config.ncores
        input_model["memory"] = int(config.memory * 1024 * 1024 * 1024 * 0.95)  # Memory in bytes
        input_model["success"] = False
        input_model["return_output"] = True

        if input_model["schema_name"] == "qcschema_input":
            input_model["schema_name"] = "qc_schema_input"

        scratch = config.scratch_directory
        if scratch is not None:
            input_model["scratch_location"] = scratch

        psi_version = self.parse_version(psi4.__version__)

        if psi_version > self.parse_version("1.2"):

            mol = psi4.core.Molecule.from_schema(input_model)
            if (mol.multiplicity() != 1) and ("reference" not in input_model["keywords"]):
                input_model["keywords"]["reference"] = "uhf"

            output_data = psi4.json_wrapper.run_json(input_model)
            if "extras" not in output_data:
                output_data["extras"] = {}

            output_data["extras"]["local_qcvars"] = output_data.pop("psi4:qcvars", None)
            if output_data["success"] is False:
                output_data["error"] = {"error_type": "internal_error", "error_message": output_data["error"]}

        else:
            raise TypeError("Psi4 version '{}' not understood.".format(psi_version))

        # Reset the schema if required
        output_data["schema_name"] = "qcschema_output"

        # Dispatch errors, PSIO Errors are not recoverable for future runs
        if output_data["success"] is False:

            if "PSIO Error" in output_data["error"]:
                raise ValueError(output_data["error"])

        # Move several pieces up a level
        if output_data["success"]:
            output_data["provenance"]["memory"] = round(output_data.pop("memory") / (1024**3), 3)  # Move back to GB
            output_data["provenance"]["nthreads"] = output_data.pop("nthreads")
            output_data["stdout"] = output_data.pop("raw_output", None)

            # Delete keys
            output_data.pop("return_ouput", None)

            return Result(**output_data)
        else:
            return FailedOperation(
                success=output_data.pop("success", False), error=output_data.pop("error"), input_data=output_data)
Example #22
0
def compute(input_data: Union[Dict[str, Any], 'ResultInput'],
            program: str,
            raise_error: bool = False,
            local_options: Optional[Dict[str, str]] = None,
            return_dict: bool = False) -> 'Result':
    """Executes a single quantum chemistry program given a QC Schema input.

    The full specification can be found at:
        http://molssi-qc-schema.readthedocs.io/en/latest/index.html#

    Parameters
    ----------
    input_data:
        A QC Schema input specification in dictionary or model from QCElemental.models
    program:
        The program to execute the input with
    raise_error:
        Determines if compute should raise an error or not.
    capture_output:
        Determines if stdout/stderr should be captured.
    local_options:
        A dictionary of local configuration options
    return_dict:
        Returns a dict instead of qcelemental.models.ResultInput

    Returns
    -------
    : Result
        A QC Schema output or type depending on return_dict key

    """

    program = program.lower()
    if program not in list_all_programs():
        input_data = FailedOperation(
            input_data=input_data,
            error=ComputeError(
                error_type="not_registered",
                error_message="QCEngine Call Error:\n"
                "Program {} is not registered with QCEngine".format(program)))
    elif program not in list_available_programs():
        input_data = FailedOperation(
            input_data=input_data,
            error=ComputeError(
                error_type="not_available",
                error_message="QCEngine Call Error:\n"
                "Program {} is registered with QCEngine, but cannot be found".
                format(program)))
    error = _process_failure_and_return(input_data, return_dict, raise_error)
    if error:
        return error

    # Build the model and validate
    input_data = model_wrapper(input_data, ResultInput)
    error = _process_failure_and_return(input_data, return_dict, raise_error)
    if error:
        return error

    # Grab the executor and build the input model
    executor = get_program(program)

    # Build out local options
    if local_options is None:
        local_options = {}

    input_engine_options = input_data.extras.pop("_qcengine_local_config", {})

    local_options = {**local_options, **input_engine_options}
    config = get_config(local_options=local_options)

    # Run the program
    with compute_wrapper(capture_output=False) as metadata:

        output_data = input_data.copy()  # lgtm [py/multiple-definition]
        output_data = executor.compute(input_data, config)

    return handle_output_metadata(output_data,
                                  metadata,
                                  raise_error=raise_error,
                                  return_dict=return_dict)
Example #23
0
def handle_output_metadata(output_data,
                           metadata,
                           raise_error=False,
                           return_dict=True):
    """
    Fuses general metadata and output together.

    Returns
    -------
    result : dict or pydantic.models.Result
        Output type depends on return_dict or a dict if an error was generated in model construction
    """

    if isinstance(output_data, dict):
        output_fusion = output_data  # Error handling
    else:
        output_fusion = output_data.dict()

    output_fusion["stdout"] = metadata["stdout"]
    output_fusion["stderr"] = metadata["stderr"]
    if metadata["success"] is not True:
        output_fusion["success"] = False
        output_fusion["error"] = {
            "error_type": "meta_error",
            "error_message": metadata["error_message"]
        }

    # Raise an error if one exists and a user requested a raise
    if raise_error and (output_fusion["success"] is not True):
        msg = "stdout:\n" + output_fusion["stdout"]
        msg += "\nstderr:\n" + output_fusion["stderr"]
        print(msg)
        raise ValueError(output_fusion["error"]["error_message"])

    # Fill out provenance datadata
    wall_time = metadata["wall_time"]
    provenance_augments = config.get_provenance_augments()
    provenance_augments["wall_time"] = wall_time
    if "provenance" in output_fusion:
        output_fusion["provenance"].update(provenance_augments)
    else:
        # Add onto the augments with some missing info
        provenance_augments["creator"] = "QCEngine"
        provenance_augments["version"] = provenance_augments[
            "qcengine_version"]
        output_fusion["provenance"] = provenance_augments

    # We need to return the correct objects; e.g. Results, Procedures
    if output_fusion["success"]:
        # This will only execute if everything went well
        ret = output_data.__class__(**output_fusion)
    else:
        # Should only be reachable on failures
        ret = FailedOperation(success=output_fusion.pop("success", False),
                              error=output_fusion.pop("error"),
                              input_data=output_fusion)
    if return_dict:
        return json.loads(
            ret.json()
        )  # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals
    else:
        return ret