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]
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"])
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"])
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"}
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"])
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
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
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
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)
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)
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)
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
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
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)
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)
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)
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)
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})
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})
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)
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)
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)