Exemple #1
0
    def get_model(self, name: str) -> "torchani.models.BuiltinModels":
        name = name.lower()

        if name in self._CACHE:
            return self._CACHE[name]

        import torch
        import torchani

        # graft a custom forward pass to Ensemble class
        def ensemble_forward(self, species_input):
            outputs = torch.cat([x(species_input)[1] for x in self])
            species, _ = species_input
            return species, outputs

        torchani.nn.Ensemble.forward = ensemble_forward

        ani_models = {
            "ani1x": torchani.models.ANI1x(),
            "ani1ccx": torchani.models.ANI1ccx(),
        }

        if parse_version(self.get_version()) >= parse_version("2.0"):
            ani_models["ani2x"] = torchani.models.ANI2x()

        try:
            self._CACHE[name] = ani_models[name]
        except KeyError:
            raise InputError(
                f"TorchANI only accepts methods: {ani_models.keys()}")

        return self._CACHE[name]
Exemple #2
0
    def compute(self, input_data: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        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 = self.execute(job_inputs)

        # Determine whether the calculation succeeded
        if exe_success:
            # If execution succeeded, collect results
            result = self.parse_output(proc["outfiles"], input_data)
            return result
        else:
            # Return UnknownError for error propagation
            return UnknownError(proc["stderr"])
Exemple #3
0
    def compute(self, input_data: 'ResultInput',
                config: 'JobConfig') -> 'Result':
        """
        Run entos
        """
        # Check if entos executable is found
        self.found(raise_error=True)

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

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

        # Run entos
        exe_success, proc = self.execute(job_inputs)

        # Determine whether the calculation succeeded
        if exe_success:
            # If execution succeeded, collect results
            result = self.parse_output(proc["outfiles"], input_data)
            return result
        else:
            # Return UnknownError for error propagation
            return UnknownError(proc["stderr"])
Exemple #4
0
def test_compute_wavefunction(fractal_compute_server):

    psiver = qcng.get_program("psi4").get_version()
    if parse_version(psiver) < parse_version("1.4a2.dev160"):
        pytest.skip("Must be used a modern version of Psi4 to execute")

    # Build a client
    client = ptl.FractalClient(fractal_compute_server)

    # Add a hydrogen and helium molecule
    hydrogen = ptl.Molecule.from_data([[1, 0, 0, -0.5], [1, 0, 0, 0.5]], dtype="numpy", units="bohr")

    # Ask the server to compute a new computation
    r = client.add_compute(
        program="psi4",
        driver="energy",
        method="HF",
        basis="sto-3g",
        molecule=hydrogen,
        protocols={"wavefunction": "orbitals_and_eigenvalues"},
    )

    fractal_compute_server.await_results()
    assert len(fractal_compute_server.list_current_tasks()) == 0

    result = client.query_results(id=r.ids)[0]
    assert result.wavefunction

    r = result.get_wavefunction("orbitals_a")
    assert isinstance(r, np.ndarray)
    assert r.shape == (2, 2)

    r = result.get_wavefunction(["orbitals_a", "basis"])
    assert r.keys() == {"orbitals_a", "basis"}
Exemple #5
0
    def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
        """
        Run qchem
        """
        # Check if qchem executable is found
        self.found(raise_error=True)

        # Check qchem version
        qceng_ver = "5.2"
        if parse_version(self.get_version()) < parse_version(qceng_ver):
            raise TypeError(f"Q-Chem version <{qceng_ver} not supported (found version {self.get_version()})")

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

        # Run qchem
        exe_success, proc = self.execute(job_inputs)

        # Determine whether the calculation succeeded
        if exe_success:
            # If execution succeeded, collect results
            result = self.parse_output(proc["outfiles"], input_model)
            return result
        else:
            outfile = proc["outfiles"]["dispatch.out"]
            if "fatal error occurred in module qparser" in outfile:
                raise InputError(proc["outfiles"]["dispatch.out"])
            else:
                # Return UnknownError for error propagation
                raise UnknownError(proc["outfiles"]["dispatch.out"])
Exemple #6
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
        proc = self.execute(job_inputs)

        # Return proc if it is type UnknownError for error propagation otherwise process the output
        if isinstance(proc, UnknownError):
            return proc
        else:
            # If execution succeeded, collect results
            result = self.parse_output(proc["outfiles"], input_data)
            return result
Exemple #7
0
    def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
        """
        Run TeraChem
        """
        self.found(raise_error=True)

        # Check TeraChem version
        if parse_version(self.get_version()) < parse_version("1.5"):
            raise TypeError("TeraChem version '{}' not understood".format(self.get_version()))

        # Setup the job
        job_inputs = self.build_input(input_data, config)
        # Run terachem
        exe_outputs = self.execute(job_inputs, extra_outfiles=input_data.extras)
        exe_success, proc = exe_outputs
        # Determine whether the calculation succeeded
        output_data = {}
        if not exe_success:
            output_data["success"] = False
            output_data["error"] = {"error_type": "unknown_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
Exemple #8
0
    def compute(self, input_data: 'ResultInput',
                config: 'JobConfig') -> 'Result':
        """
        Run entos
        """
        # Check if entos executable is found
        self.found(raise_error=True)

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

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

        # Run entos
        proc = self.execute(job_inputs)

        if isinstance(proc, FailedOperation):
            return proc
        else:
            # If execution succeeded, collect results
            result = self.parse_output(proc["outfiles"], input_data)
            return result
Exemple #9
0
def is_dftd3_new_enough(version_feature_introduced):
    if not which('dftd3', return_bool=True):
        return False
    # Note: anything below v3.2.1 will return the help menu here. but that's fine as version compare evals to False.
    command = [which('dftd3'), '-version']
    proc = subprocess.run(command, stdout=subprocess.PIPE)
    candidate_version = proc.stdout.decode('utf-8').strip()

    return parse_version(candidate_version) >= parse_version(version_feature_introduced)
Exemple #10
0
    def compute(self, input_model: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Run qcore
        """
        # Check if qcore executable is found
        self.found(raise_error=True)

        # Check qcore version
        if parse_version(self.get_version()) < parse_version("0.8.9"):
            raise TypeError(
                f"qcore version {self.get_version()} not supported")

        import qcore

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

        method = input_model.model.method.upper()
        if method in self._dft_functionals:
            method = {
                "kind": "dft",
                "xc": method,
                "ao": input_model.model.basis
            }
        elif method == "HF":
            method = {"kind": "hf", "ao": input_model.model.basis}
        elif method in self._xtb_models:
            method = {"kind": "xtb", "model": method}
        else:
            raise InputError(f"Method is not valid: {method}")

        method["details"] = input_model.keywords

        qcore_input = {
            # "schema_name": "single_input",
            "molecule": {
                "geometry": input_model.molecule.geometry,
                "atomic_numbers": input_model.molecule.atomic_numbers,
                "charge": input_model.molecule.molecular_charge,
                "multiplicity": input_model.molecule.molecular_multiplicity,
            },
            "method": method,
            "result_contract": {
                "wavefunction": "all"
            },
            "result_type": input_model.driver,
        }
        try:
            result = qcore.run(qcore_input, ncores=config.ncores)
        except Exception as exc:
            return UnknownError(str(exc))

        return self.parse_output(result.dict(), input_model)
Exemple #11
0
def is_dftd3_new_enough(version_feature_introduced):
    if not which('dftd3', return_bool=True):
        return False
    # Note: anything below v3.2.1 will return the help menu here. but that's fine as version compare evals to False.
    command = [which('dftd3'), '-version']
    proc = subprocess.run(command, stdout=subprocess.PIPE)
    candidate_version = proc.stdout.decode('utf-8').strip()

    return parse_version(candidate_version) >= parse_version(
        version_feature_introduced)
Exemple #12
0
 def is_available(cls) -> bool:
     """
     The MBIS option is only available via new psi4 so make sure it is installed.
     """
     # check installed
     psi4 = which_import(
         "psi4",
         return_bool=True,
         raise_error=True,
         raise_msg="Please install via `conda install psi4 -c psi4`.",
     )
     # now check the version meets the minimum requirement
     which_psi4 = which("psi4")
     with popen([which_psi4, "--version"]) as exc:
         exc["proc"].wait(timeout=30)
     version = parse_version(safe_version(exc["stdout"].split()[-1]))
     if version <= parse_version("1.4a1"):
         raise SpecificationError(
             f"The version of psi4 installed is {version} and needs to be 1.4 or newer please update it to continue."
         )
     return psi4
Exemple #13
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
Exemple #14
0
def is_psi4_new_enough(version_feature_introduced):
    if not which_import('psi4', return_bool=True):
        return False
    import psi4
    return parse_version(
        psi4.__version__) >= parse_version(version_feature_introduced)
Exemple #15
0
def is_psi4_new_enough(version_feature_introduced):
    if not which_import('psi4', return_bool=True):
        return False
    import psi4
    return parse_version(psi4.__version__) >= parse_version(version_feature_introduced)
Exemple #16
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"] = {}

                # Check QCVars
                local_qcvars = output_data.pop("psi4:qcvars", None)
                if local_qcvars:
                    # Edge case where we might already have qcvars, should not happen
                    if "qcvars" in output_data["extras"]:
                        output_data["extras"]["local_qcvars"] = local_qcvars
                    else:
                        output_data["extras"]["qcvars"] = local_qcvars

                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 ResourceError("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:
            error_message = output_data["error"]["error_message"]

            if "PSIO Error" in error_message:
                if "scratch directory" in error_message:
                    # Psi4 cannot access the folder or file
                    raise ResourceError(error_message)
                else:
                    # Likely a random error, worth retrying
                    raise RandomError(error_message)
            elif "SIGSEV" in error_message:
                raise RandomError(error_message)
            elif "TypeError: set_global_option" in error_message:
                raise InputError(error_message)
            elif "RHF reference is only for singlets" in error_message:
                raise InputError(error_message)
            else:
                raise UnknownError(error_message)

        # Move several pieces up a level
        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)
Exemple #17
0
def is_numpy_new_enough(version_feature_introduced):
    if not which_import('numpy', return_bool=True):
        return False
    import numpy
    return parse_version(
        numpy.version.version) >= parse_version(version_feature_introduced)
Exemple #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})
Exemple #19
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})
Exemple #20
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)
Exemple #21
0
    def compute(self, input_model: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs Psi4 in API mode
        """
        self.found(raise_error=True)
        pversion = parse_version(self.get_version())

        if pversion < parse_version("1.2"):
            raise ResourceError("Psi4 version '{}' not understood.".format(
                self.get_version()))

        # Location resolution order config.scratch_dir, $PSI_SCRATCH, /tmp
        parent = config.scratch_directory
        if parent is None:
            parent = os.environ.get("PSI_SCRATCH", None)

        error_type = None
        error_message = None
        compute_success = False

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

        # Basis must not be None for HF3c
        old_basis = input_model.model.basis
        input_model.model.__dict__["basis"] = old_basis or ""

        with temporary_directory(parent=parent,
                                 suffix="_psi_scratch") as tmpdir:

            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_model.keywords["reference"] = "uhf"

            # Old-style JSON-based command line
            if pversion < parse_version("1.4a2.dev160"):

                # Setup the job
                input_data = input_model.dict(encoding="json")
                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"

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

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

                    # Check QCVars
                    local_qcvars = output_data.pop("psi4:qcvars", None)
                    if local_qcvars:
                        # Edge case where we might already have qcvars, should not happen
                        if "qcvars" in output_data["extras"]:
                            output_data["extras"][
                                "local_qcvars"] = local_qcvars
                        else:
                            output_data["extras"]["qcvars"] = local_qcvars

                    if output_data.get("success", False) is False:
                        error_message, error_type = self._handle_errors(
                            output_data)
                    else:
                        compute_success = True

                else:
                    error_message = output.get("stderr", "No STDERR output")
                    error_type = "execution_error"

                # Reset the schema if required
                output_data["schema_name"] = "qcschema_output"
                output_data.pop("memory", None)
                output_data.pop("nthreads", None)
                output_data["stdout"] = output_data.pop("raw_output", None)

            else:

                if input_model.extras.get("psiapi", False):
                    import psi4

                    orig_scr = psi4.core.IOManager.shared_object(
                    ).get_default_path()
                    psi4.core.set_num_threads(config.ncores, quiet=True)
                    psi4.set_memory(f"{config.memory}GB", quiet=True)
                    # psi4.core.IOManager.shared_object().set_default_path(str(tmpdir))
                    if pversion < parse_version(
                            "1.4"):  # adjust to where DDD merged
                        # slightly dangerous in that if `qcng.compute({..., psiapi=True}, "psi4")` called *from psi4
                        #   session*, session could unexpectedly get its own files cleaned away.
                        output_data = psi4.schema_wrapper.run_qcschema(
                            input_model).dict()
                    else:
                        output_data = psi4.schema_wrapper.run_qcschema(
                            input_model, postclean=False).dict()
                    # success here means execution returned. output_data may yet be qcel.models.AtomicResult or qcel.models.FailedOperation
                    success = True
                    if output_data.get("success", False):
                        output_data["extras"]["psiapi_evaluated"] = True
                    psi4.core.IOManager.shared_object().set_default_path(
                        orig_scr)
                else:
                    run_cmd = [
                        which("psi4"),
                        "--scratch",
                        str(tmpdir),
                        "--nthread",
                        str(config.ncores),
                        "--memory",
                        f"{config.memory}GB",
                        "--qcschema",
                        "data.msgpack",
                    ]
                    input_files = {
                        "data.msgpack": input_model.serialize("msgpack-ext")
                    }
                    success, output = execute(run_cmd,
                                              input_files, ["data.msgpack"],
                                              as_binary=["data.msgpack"],
                                              scratch_directory=tmpdir)
                    if success:
                        output_data = deserialize(
                            output["outfiles"]["data.msgpack"], "msgpack-ext")
                    else:
                        output_data = input_model.dict()

                if success:
                    if output_data.get("success", False) is False:
                        error_message, error_type = self._handle_errors(
                            output_data)
                    else:
                        compute_success = True
                else:
                    error_message = output.get("stderr", "No STDERR output")
                    error_type = "execution_error"

        # Dispatch errors, PSIO Errors are not recoverable for future runs
        if compute_success is False:

            if "PSIO Error" in error_message:
                if "scratch directory" in error_message:
                    # Psi4 cannot access the folder or file
                    raise ResourceError(error_message)
                else:
                    # Likely a random error, worth retrying
                    raise RandomError(error_message)
            elif ("SIGSEV" in error_message) or (
                    "SIGSEGV" in error_message) or ("segmentation fault"
                                                    in error_message):
                raise RandomError(error_message)
            elif ("TypeError: set_global_option"
                  in error_message) or (error_type == "ValidationError"):
                raise InputError(error_message)
            elif "RHF reference is only for singlets" in error_message:
                raise InputError(error_message)
            else:
                raise UnknownError(error_message)

        # Reset basis
        output_data["model"]["basis"] = old_basis

        # Move several pieces up a level
        output_data["provenance"]["memory"] = round(config.memory, 3)
        output_data["provenance"]["nthreads"] = config.ncores

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

        return AtomicResult(**output_data)
Exemple #22
0
def is_numpy_new_enough(version_feature_introduced):
    if not which_import('numpy', return_bool=True):
        return False
    import numpy
    return parse_version(numpy.version.version) >= parse_version(version_feature_introduced)