def parse_output(self, outfiles: Dict[str, str], input_model: OptimizationInput) -> OptimizationResult: # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") # Parse out the atomic results from the file atomic_results = harvest_as_atomic_result(input_model, stdout) # Isolate the converged result final_step = atomic_results[-1] return OptimizationResult( initial_molecule=input_model.initial_molecule, input_specification=input_model.input_specification, final_molecule=final_step.molecule, trajectory=atomic_results, energies=[ float(r.extras["qcvars"]["CURRENT ENERGY"]) for r in atomic_results ], stdout=stdout, stderr=stderr, success=True, provenance=Provenance(creator="NWChemRelax", version=self.get_version(), routine="nwchem_opt"), )
def test_repr_provenance(request): prov = Provenance(creator="qcel", version="v0.3.2") drop_qcsk(prov, request.node.name) assert "qcel" in str(prov) assert "qcel" in repr(prov)
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] stdout = outfiles.pop("stdout") qcvars, gradient, hessian = harvest(input_model.molecule, stdout, **outfiles) if gradient is not None: qcvars["CURRENT GRADIENT"] = gradient if hessian is not None: qcvars["CURRENT HESSIAN"] = hessian retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) build_out(qcvars) atprop = build_atomicproperties(qcvars) output_data = input_model.dict() output_data["extras"]["outfiles"] = outfiles output_data["properties"] = atprop output_data["provenance"] = Provenance(creator="Turbomole", version=self.get_version(), routine="turbomole") output_data["return_result"] = retres output_data["stdout"] = stdout output_data["success"] = True return AtomicResult(**output_data)
def harvest_as_atomic_result(input_model: OptimizationInput, nwout: str) -> List[AtomicResult]: """Parse each step in the geometry relaxation as a separate AtomicResult Args: input_model: Input specification for the relaxation nwout: Standard out from the NWChem simulation Returns: A list of the results at each step """ # Parse the files out_psivars, out_mols, out_grads, version, error = harvest_output(nwout) # Make atomic results results = [] for qcvars, nwgrad, out_mol in zip(out_psivars, out_grads, out_mols): if nwgrad is not None: qcvars[ f"{input_model.input_specification.model.method.upper()[4:]} TOTAL GRADIENT"] = nwgrad qcvars["CURRENT GRADIENT"] = nwgrad # Get the formatted properties build_out(qcvars) atprop = build_atomicproperties(qcvars) # Format them inout an output output_data = { "schema_version": 1, "molecule": out_mol, "driver": "gradient", "extras": input_model.extras.copy(), "model": input_model.input_specification.model, "keywords": input_model.input_specification.keywords, "properties": atprop, "provenance": Provenance(creator="NWChem", version=version, routine="nwchem_opt"), "return_result": nwgrad, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in unnp(qcvars, flat=True).items() } results.append(AtomicResult(**output_data)) return results
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") # Read the NWChem stdout file and, if needed, the hess or grad files qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest( input_model.molecule, stdout, **outfiles) if nwgrad is not None: qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.tolist() # Get the formatted properties qcprops = extract_formatted_properties(qcvars) # Format them inout an output output_data = { "schema_name": "qcschema_output", "schema_version": 1, "extras": { "outfiles": outfiles, **input_model.extras }, "properties": qcprops, "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), "return_result": retres, "stdout": stdout, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs RDKit in FF typing """ self.found(raise_error=True) import rdkit from rdkit.Chem import AllChem # Failure flag ret_data = {"success": False} # Build the Molecule jmol = input_data.molecule mol = self._process_molecule_rdkit(jmol) if input_data.model.method.lower() == "uff": ff = AllChem.UFFGetMoleculeForceField(mol) all_params = AllChem.UFFHasAllMoleculeParams(mol) else: raise InputError("RDKit only supports the UFF method currently.") if all_params is False: raise InputError( "RDKit parameters not found for all atom types in molecule.") ff.Initialize() ret_data["properties"] = { "return_energy": ff.CalcEnergy() * ureg.conversion_factor("kJ / mol", "hartree") } if input_data.driver == "energy": ret_data["return_result"] = ret_data["properties"]["return_energy"] elif input_data.driver == "gradient": coef = ureg.conversion_factor("kJ / mol", "hartree") * ureg.conversion_factor( "angstrom", "bohr") ret_data["return_result"] = [x * coef for x in ff.CalcGrad()] else: raise InputError( f"RDKit can only compute energy and gradient driver methods. Found {input_data.driver}." ) ret_data["provenance"] = Provenance( creator="rdkit", version=rdkit.__version__, routine="rdkit.Chem.AllChem.UFFGetMoleculeForceField") ret_data["schema_name"] = "qcschema_output" ret_data["success"] = True # Form up a dict first, then sent to BaseModel to avoid repeat kwargs which don't override each other return AtomicResult(**{**input_data.dict(), **ret_data})
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> AtomicResult: # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") # Read the NWChem stdout file and, if needed, the hess or grad files try: qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) except Exception as e: raise UnknownError(stdout) if nwgrad is not None: qcvars[f"{input_model.model.method.upper()[4:]} TOTAL GRADIENT"] = nwgrad qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.tolist() # Get the formatted properties build_out(qcvars) atprop = build_atomicproperties(qcvars) # Format them inout an output output_data = { "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, "properties": atprop, "provenance": Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output( self, outfiles: Dict[str, str], input_model: AtomicInput ) -> AtomicResult: # lgtm: [py/similar-function] stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") # c4mol, if it exists, is dinky, just a clue to geometry of cfour results try: qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) except Exception as e: raise UnknownError(stdout) if c4grad is not None: qcvars["CURRENT GRADIENT"] = c4grad qcvars[f"{input_model.model.method.upper()[3:]} TOTAL GRADIENT"] = c4grad if c4hess is not None: qcvars[f"{input_model.model.method.upper()[3:]} TOTAL HESSIAN"] = c4hess qcvars["CURRENT HESSIAN"] = c4hess if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() build_out(qcvars) atprop = build_atomicproperties(qcvars) output_data = { "schema_version": 1, "extras": {"outfiles": outfiles, **input_model.extras}, "properties": atprop, "provenance": Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour"), "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: 'ResultInput') -> 'Result': stdout = outfiles.pop("stdout") # nwmol, if it exists, is dinky, just a clue to geometry of nwchem results # qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest(input_model.molecule, stdout, **outfiles) #ORIGpsivar, nwhess, nwgrad, nwmol, version, errorTMP = harvester.harvest(qmol, nwchemrec['stdout'], **nwfiles) qcvars, nwhess, nwgrad, nwmol, version, errorTMP = harvest( input_model.molecule, stdout, **outfiles) if nwgrad is not None: qcvars['CURRENT GRADIENT'] = nwgrad if nwhess is not None: qcvars['CURRENT HESSIAN'] = nwhess retres = qcvars[f'CURRENT {input_model.driver.upper()}'] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { 'schema_name': 'qcschema_output', 'schema_version': 1, 'extras': { 'outfiles': outfiles, }, 'properties': {}, 'provenance': Provenance(creator="NWChem", version=self.get_version(), routine="nwchem"), 'return_result': retres, 'stdout': stdout, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data['extras']['qcvars'] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data['success'] = True return Result(**{**input_model.dict(), **output_data})
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] stdout = outfiles.pop("stdout") # c4mol, if it exists, is dinky, just a clue to geometry of cfour results qcvars, c4hess, c4grad, c4mol, version, errorTMP = harvest( input_model.molecule, stdout, **outfiles) if c4grad is not None: qcvars["CURRENT GRADIENT"] = c4grad if c4hess is not None: qcvars["CURRENT HESSIAN"] = c4hess retres = qcvars[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { "schema_name": "qcschema_output", "schema_version": 1, "extras": { "outfiles": outfiles }, "properties": {}, "provenance": Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour"), "return_result": retres, "stdout": stdout, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: AtomicInput) -> AtomicResult: # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results qcvars, gamessgrad, gamessmol = harvest(input_model.molecule, stdout, **outfiles) if gamessgrad is not None: qcvars["CURRENT GRADIENT"] = gamessgrad if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] build_out(qcvars) atprop = build_atomicproperties(qcvars) output_data = { "schema_version": 1, "molecule": gamessmol, "extras": {"outfiles": outfiles, **input_model.extras}, "properties": atprop, "provenance": Provenance(creator="GAMESS", version=self.get_version(), routine="rungms"), "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in unnp(qcvars, flat=True).items() } return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> AtomicResult: # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") method = input_model.model.method.lower() method = method[4:] if method.startswith("nwc-") else method # Read the NWChem stdout file and, if needed, the hess or grad files # July 2021: nwmol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always atin orientation try: qcvars, nwhess, nwgrad, nwmol, version, module, errorTMP = harvest( input_model.molecule, method, stdout, **outfiles) except Exception: raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) try: if nwgrad is not None: qcvars[f"{method.upper()} TOTAL GRADIENT"] = nwgrad qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: qcvars[f"{method.upper()} TOTAL HESSIAN"] = nwhess qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] except KeyError: raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.tolist() # Get the formatted properties build_out(qcvars) atprop = build_atomicproperties(qcvars) provenance = Provenance(creator="NWChem", version=self.get_version(), routine="nwchem").dict() if module is not None: provenance["module"] = module # Format them inout an output output_data = { "schema_version": 1, "molecule": nwmol, # overwrites with outfile Cartesians in case fix_*=F "extras": { **input_model.extras }, "native_files": {k: v for k, v in outfiles.items() if v is not None}, "properties": atprop, "provenance": provenance, "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output( self, outfiles: Dict[str, str], input_model: "AtomicInput" ) -> AtomicResult: # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") method = input_model.model.method.lower() method = method[4:] if method.startswith("nwc-") else method # Read the NWChem stdout file and, if needed, the hess or grad files # LW 7Jul21: I allow exceptions to be raised so that we can detect errors # in the parsing of output files qcvars, nwhess, nwgrad, nwmol, version, module, errorTMP = harvest( input_model.molecule, method, stdout, **outfiles) try: if nwgrad is not None: qcvars[f"{method.upper()} TOTAL GRADIENT"] = nwgrad qcvars["CURRENT GRADIENT"] = nwgrad if nwhess is not None: qcvars[f"{method.upper()} TOTAL HESSIAN"] = nwhess qcvars["CURRENT HESSIAN"] = nwhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] except KeyError as e: raise UnknownError( "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr + "\nTRACEBACK:\n" + "".join(traceback.format_exception(*sys.exc_info()))) if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.tolist() # Get the formatted properties build_out(qcvars) atprop = build_atomicproperties(qcvars) provenance = Provenance(creator="NWChem", version=self.get_version(), routine="nwchem").dict() if module is not None: provenance["module"] = module # Format them inout an output output_data = { "schema_version": 1, "extras": { "outfiles": outfiles, **input_model.extras }, "properties": atprop, "provenance": provenance, "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
def _parse_logfile_common(self, outtext: str, input_dict: Dict[str, Any]): """ Parses fields from log file that are not parsed from QCSCRATCH in parse_output """ properties = {} provenance = Provenance(creator="QChem", version=self.get_version(), routine="qchem").dict() mobj = re.search(r"This is a multi-thread run using ([0-9]+) threads", outtext) if mobj: provenance["nthreads"] = int(mobj.group(1)) mobj = re.search(r"Total job time:\s*" + NUMBER + r"s\(wall\)", outtext) if mobj: provenance["wall_time"] = float(mobj.group(1)) mobj = re.search( r"Archival summary:\s*\n[0-9]+\\[0-9+]\\([\w\.\-]+)\\", outtext) if mobj: provenance["hostname"] = mobj.group(1) mobj = re.search( r"\n\s*There are\s+(\d+) alpha and\s+(\d+) beta electrons\s*\n", outtext) if mobj: properties["calcinfo_nalpha"] = int(mobj.group(1)) properties["calcinfo_nbeta"] = int(mobj.group(2)) mobj = re.search( r"\n\s*There are\s+\d+ shells and\s+(\d+) basis functions\s*\n", outtext) if mobj: properties["calcinfo_nbasis"] = int(mobj.group(1)) mobj = re.search( r"\n\s*RI-MP2 CORRELATION ENERGY\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) if mobj: properties["mp2_correlation_energy"] = float(mobj.group(1)) mobj = re.search( r"\n\s*RI-MP2 SINGLES ENERGY\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) if mobj: properties["mp2_singles_energy"] = float(mobj.group(1)) mobj_aaaa = re.search( r"\n\s*RI-MP2 ENERGY \(aa\|aa\)\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) mobj_bbbb = re.search( r"\n\s*RI-MP2 ENERGY \(bb\|bb\)\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) if mobj_aaaa and mobj_bbbb: properties["mp2_same_spin_correlation_energy"] = float( mobj_aaaa.group(1)) + float(mobj_bbbb.group(1)) mobj_aabb = re.search( r"\n\s*RI-MP2 ENERGY \(aa\|bb\)\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) mobj_bbaa = re.search( r"\n\s*RI-MP2 ENERGY \(bb\|aa\)\s+=\s+" + NUMBER + r"\s+au\s*\n", outtext) if mobj_aaaa and mobj_bbbb: properties["mp2_opposite_spin_correlation_energy"] = float( mobj_aabb.group(1)) + float(mobj_bbaa.group(1)) properties["calcinfo_natom"] = len(input_dict["molecule"]["symbols"]) mobj = re.search( r"\n\s*(\d+)\s+" + NUMBER + "\s+" + NUMBER + r"\s+Convergence criterion met\s*\n", outtext) if mobj: properties["scf_iterations"] = int(mobj.group(1)) mobj = re.search( r"\n\s+Dipole Moment \(Debye\)\s*\n\s+X\s+" + NUMBER + r"\s+Y\s+" + NUMBER + r"\s+Z\s+" + NUMBER + r"\s*\n", outtext, ) if mobj: cf = constants.conversion_factor("debye", "e * bohr") properties["scf_dipole_moment"] = [ float(mobj.group(i)) * cf for i in range(1, 4) ] return properties, provenance
def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs adcc """ self.found(raise_error=True) import adcc import psi4 mol = input_model.molecule model = input_model.model conv_tol = input_model.keywords.get("conv_tol", 1e-6) if input_model.driver not in ["energy", "properties"]: raise InputError( f"Driver {input_model.driver} not implemented for ADCC.") if isinstance(input_model.model.basis, BasisSet): raise InputError( "QCSchema BasisSet for model.basis not implemented. Use string basis name." ) if not input_model.model.basis: raise InputError("Model must contain a basis set.") psi4_molecule = psi4.core.Molecule.from_schema( dict(mol.dict(), fix_symmetry="c1")) psi4.core.clean() psi4.core.be_quiet() psi4.set_options({ "basis": model.basis, "scf_type": "pk", "e_convergence": conv_tol / 100, "d_convergence": conv_tol / 10, # 'maxiter': max_iter, "reference": "RHF" if mol.molecular_multiplicity == 1 else "UHF", }) _, wfn = psi4.energy("HF", return_wfn=True, molecule=psi4_molecule) adcc.set_n_threads(config.ncores) compute_success = False try: adcc_state = adcc.run_adc(wfn, method=model.method, **input_model.keywords) compute_success = adcc_state.converged except adcc.InputError as e: raise InputError(str(e)) except Exception as e: raise UnknownError(str(e)) input_data = input_model.dict(encoding="json") output_data = input_data.copy() output_data["success"] = compute_success if compute_success: output_data["return_result"] = adcc_state.excitation_energy[0] extract_props = input_model.driver == "properties" qcvars = adcc_state.to_qcvars(recurse=True, properties=extract_props) atprop = build_atomicproperties(qcvars) output_data["extras"]["qcvars"] = qcvars output_data["properties"] = atprop provenance = Provenance(creator="adcc", version=self.get_version(), routine="adcc").dict() provenance["nthreads"] = adcc.get_n_threads() output_data["provenance"] = provenance return AtomicResult(**output_data)
def parse_output(self, outfiles: Dict[str, str], input_model: AtomicInput) -> AtomicResult: # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") method = input_model.model.method.lower() method = method[4:] if method.startswith("gms-") else method # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results try: qcvars, gamesshess, gamessgrad, gamessmol, module = harvest( input_model.molecule, method, stdout, **outfiles) except Exception as e: raise UnknownError( "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr + "\nTRACEBACK:\n" + "".join(traceback.format_exception(*sys.exc_info()))) try: if gamessgrad is not None: qcvars[f"{method.upper()} TOTAL GRADIENT"] = gamessgrad qcvars["CURRENT GRADIENT"] = gamessgrad if gamesshess is not None: qcvars[f"{method.upper()} TOTAL HESSIAN"] = gamesshess qcvars["CURRENT HESSIAN"] = gamesshess if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] except KeyError as e: raise UnknownError( "STDOUT:\n" + stdout + "\nSTDERR:\n" + stderr + "\nTRACEBACK:\n" + "".join(traceback.format_exception(*sys.exc_info()))) build_out(qcvars) atprop = build_atomicproperties(qcvars) provenance = Provenance(creator="GAMESS", version=self.get_version(), routine="rungms").dict() if module is not None: provenance["module"] = module output_data = { "schema_version": 1, "molecule": gamessmol, "extras": { "outfiles": outfiles, **input_model.extras }, "properties": atprop, "provenance": provenance, "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
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_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})
def parse_output( self, outfiles, input_model: "AtomicInput" ) -> "AtomicResult": # lgtm: [py/similar-function] # Get the stdout from the calculation (required) stdout = outfiles["moldft"]["stdout"] if "molresponse" in outfiles.keys(): stdout += outfiles["molresponse"]["stdout"] print("within parse_output scf_info.json", outfiles["moldft"]["outfiles"]["scf_info.json"]) print("within parse output calc_info", outfiles["moldft"]["outfiles"]["calc_info.json"]) # Read the MADNESj stdout file and, if needed, the hess or grad files qcvars, madhess, madgrad, madmol, version, errorTMP = harvest( input_model.molecule, outfiles) ## pop the files because I think I need to outfiles.pop("moldft") if "molresponse" in outfiles.keys(): outfiles.pop("molresponse") if madgrad is not None: qcvars["CURRENT GRADIENT"] = madgrad if madhess is not None: qcvars["CURRENT HESSIAN"] = madhess # Normalize the output as a float or list of floats if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"RETURN_ENERGY"] else: retres = qcvars["RETURN_ENERGY"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.tolist() # Get the formatted properties qcprops = extract_formatted_properties(qcvars) # Format them inout an output output_data = { "schema_name": "qcschema_output", "schema_version": 1, "extras": { "outfiles": outfiles, **input_model.extras }, "properties": qcprops, "provenance": Provenance(creator="MADNESS", version=self.get_version(), routine="madness"), "return_result": retres, "stdout": stdout, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(qcvars, flat=True).items() } output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
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})
def test_repr_provenance(): prov = Provenance(creator="qcel", version="v0.3.2") assert "qcel" in str(prov) assert "qcel" in repr(prov)
def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs OpenMM on given structure, inputs, in vacuum. """ self.found(raise_error=True) from simtk import openmm from simtk import unit with capture_stdout(): import openforcefield.topology as offtop # Failure flag ret_data = {"success": False} # generate basis, not given if not input_data.model.basis: raise InputError("Method must contain a basis set.") # Make sure we are using smirnoff or antechamber basis = input_data.model.basis.lower() if basis in ["smirnoff", "antechamber"]: with capture_stdout(): # try and make the molecule from the cmiles cmiles = None if input_data.molecule.extras: cmiles = input_data.molecule.extras.get( "canonical_isomeric_explicit_hydrogen_mapped_smiles", None) if cmiles is None: cmiles = input_data.molecule.extras.get( "cmiles", {} ).get( "canonical_isomeric_explicit_hydrogen_mapped_smiles", None) if cmiles is not None: off_mol = offtop.Molecule.from_mapped_smiles( mapped_smiles=cmiles) # add the conformer conformer = unit.Quantity(value=np.array( input_data.molecule.geometry), unit=unit.bohr) off_mol.add_conformer(conformer) else: # Process molecule with RDKit rdkit_mol = RDKitHarness._process_molecule_rdkit( input_data.molecule) # Create an Open Force Field `Molecule` from the RDKit Molecule off_mol = offtop.Molecule(rdkit_mol) # now we need to create the system openmm_system = self._generate_openmm_system( molecule=off_mol, method=input_data.model.method, keywords=input_data.keywords) else: raise InputError( "Accepted bases are: {'smirnoff', 'antechamber', }") # Need an integrator for simulation even if we don't end up using it really integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds) # Set platform to CPU explicitly platform = openmm.Platform.getPlatformByName("CPU") # Set number of threads to use # if `nthreads` is `None`, OpenMM default of all logical cores on # processor will be used nthreads = config.ncores if nthreads is None: nthreads = os.environ.get("OPENMM_CPU_THREADS") if nthreads: properties = {"Threads": str(nthreads)} else: properties = {} # Initialize context context = openmm.Context(openmm_system, integrator, platform, properties) # Set positions from our Open Force Field `Molecule` context.setPositions(off_mol.conformers[0]) # Compute the energy of the configuration state = context.getState(getEnergy=True) # Get the potential as a simtk.unit.Quantity, put into units of hartree q = state.getPotentialEnergy( ) / unit.hartree / unit.AVOGADRO_CONSTANT_NA ret_data["properties"] = {"return_energy": q} # Execute driver if input_data.driver == "energy": ret_data["return_result"] = ret_data["properties"]["return_energy"] elif input_data.driver == "gradient": # Compute the forces state = context.getState(getForces=True) # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3) gradient = state.getForces(asNumpy=True) # Convert to hartree/bohr and reformat as 1D array q = (gradient / (unit.hartree / unit.bohr) ).reshape(-1) / unit.AVOGADRO_CONSTANT_NA # Force to gradient ret_data["return_result"] = -1 * q else: raise InputError( f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}." ) ret_data["success"] = True ret_data["extras"] = input_data.extras # Move several pieces up a level ret_data["provenance"] = Provenance(creator="openmm", version=openmm.__version__, nthreads=nthreads) return AtomicResult(**{**input_data.dict(), **ret_data})
def parse_output(self, outfiles: Dict[str, str], input_model: 'ResultInput') -> 'Result': stdout = outfiles.pop("stdout") for fl, contents in outfiles.items(): if contents is not None: # LOG text += f'\n DFTD3 scratch file {fl} has been read.\n' pass # parse energy output (could go further and break into E6, E8, E10 and Cn coeff) real = np.array(input_model.molecule.real) full_nat = real.shape[0] real_nat = np.sum(real) for ln in stdout.splitlines(): if re.match(' Edisp /kcal,au', ln): ene = Decimal(ln.split()[3]) elif re.match(r" E6\(ABC\) \" :", ln): # c. v3.2.0 raise ResourceError( "Cannot process ATM results from DFTD3 prior to v3.2.1.") elif re.match(r""" E6\(ABC\) /kcal,au:""", ln): atm = Decimal(ln.split()[-1]) elif re.match(' normal termination of dftd3', ln): break else: if not ((real_nat == 1) and (input_model.driver == 'gradient')): raise UnknownError( 'Unsuccessful run. Possibly -D variant not available in dftd3 version.' ) # parse gradient output # * DFTD3 crashes on one-atom gradients. Avoid the error (above) and just force the correct result (below). if outfiles['dftd3_gradient'] is not None: srealgrad = outfiles['dftd3_gradient'].replace('D', 'E') realgrad = np.fromstring(srealgrad, count=3 * real_nat, sep=' ').reshape((-1, 3)) elif real_nat == 1: realgrad = np.zeros((1, 3)) if outfiles['dftd3_abc_gradient'] is not None: srealgrad = outfiles['dftd3_abc_gradient'].replace('D', 'E') realgradabc = np.fromstring(srealgrad, count=3 * real_nat, sep=' ').reshape((-1, 3)) elif real_nat == 1: realgradabc = np.zeros((1, 3)) if input_model.driver == 'gradient': ireal = np.argwhere(real).reshape((-1)) fullgrad = np.zeros((full_nat, 3)) rg = realgradabc if (input_model.extras['info']['dashlevel'] == 'atmgr') else realgrad try: fullgrad[ireal, :] = rg except NameError as exc: raise UnknownError( 'Unsuccessful gradient collection.') from exc qcvkey = input_model.extras['info']['fctldash'].upper() calcinfo = [] if input_model.extras['info']['dashlevel'] == 'atmgr': calcinfo.append(qcel.Datum('CURRENT ENERGY', 'Eh', atm)) calcinfo.append( qcel.Datum('DISPERSION CORRECTION ENERGY', 'Eh', atm)) calcinfo.append( qcel.Datum('3-BODY DISPERSION CORRECTION ENERGY', 'Eh', atm)) calcinfo.append( qcel.Datum( 'AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION ENERGY', 'Eh', atm)) if input_model.driver == 'gradient': calcinfo.append( qcel.Datum('CURRENT GRADIENT', 'Eh/a0', fullgrad)) calcinfo.append( qcel.Datum('DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) calcinfo.append( qcel.Datum('3-BODY DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) calcinfo.append( qcel.Datum( 'AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) else: calcinfo.append(qcel.Datum('CURRENT ENERGY', 'Eh', ene)) calcinfo.append( qcel.Datum('DISPERSION CORRECTION ENERGY', 'Eh', ene)) calcinfo.append( qcel.Datum('2-BODY DISPERSION CORRECTION ENERGY', 'Eh', ene)) if qcvkey: calcinfo.append( qcel.Datum(f'{qcvkey} DISPERSION CORRECTION ENERGY', 'Eh', ene)) if input_model.driver == 'gradient': calcinfo.append( qcel.Datum('CURRENT GRADIENT', 'Eh/a0', fullgrad)) calcinfo.append( qcel.Datum('DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) calcinfo.append( qcel.Datum('2-BODY DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) if qcvkey: calcinfo.append( qcel.Datum(f'{qcvkey} DISPERSION CORRECTION GRADIENT', 'Eh/a0', fullgrad)) #LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo}) calcinfo = {info.label: info.data for info in calcinfo} #calcinfo = qcel.util.unnp(calcinfo, flat=True) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision calcinfo = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(calcinfo, flat=True).items() } # jobrec['properties'] = {"return_energy": ene} # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"]) retres = calcinfo[f'CURRENT {input_model.driver.upper()}'] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { 'extras': input_model.extras, 'properties': {}, 'provenance': Provenance(creator="DFTD3", version=self.get_version(), routine=__name__ + '.' + sys._getframe().f_code.co_name), 'return_result': retres, 'stdout': stdout, } # yapf: disable output_data["extras"]['local_keywords'] = input_model.extras['info'] output_data["extras"]['qcvars'] = calcinfo output_data['success'] = True return Result(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> "AtomicResult": stdout = outfiles.pop("stdout") for fl, contents in outfiles.items(): if contents is not None: # LOG text += f'\n MP2D scratch file {fl} has been read.\n' pass # parse energy output (could go further and break into UCHF, CKS) real = np.array(input_model.molecule.real) full_nat = real.shape[0] real_nat = np.sum(real) for ln in stdout.splitlines(): if re.match(" MP2D dispersion correction Eh", ln): ene = Decimal(ln.split()[4]) elif re.match("Atomic Coordinates in Angstroms", ln): break else: if not ((real_nat == 1) and (input_model.driver == "gradient")): raise UnknownError("Unknown issue occured.") # parse gradient output if outfiles["mp2d_gradient"] is not None: srealgrad = outfiles["mp2d_gradient"] realgrad = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3)) if input_model.driver == "gradient": ireal = np.argwhere(real).reshape((-1)) fullgrad = np.zeros((full_nat, 3)) try: fullgrad[ireal, :] = realgrad except NameError as exc: raise UnknownError( "Unsuccessful gradient collection.") from exc qcvkey = input_model.extras["info"]["fctldash"].upper() calcinfo = [] calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene)) calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", ene)) calcinfo.append( qcel.Datum("2-BODY DISPERSION CORRECTION ENERGY", "Eh", ene)) if qcvkey: calcinfo.append( qcel.Datum(f"{qcvkey} DISPERSION CORRECTION ENERGY", "Eh", ene)) if input_model.driver == "gradient": calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad)) calcinfo.append( qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) calcinfo.append( qcel.Datum("2-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) if qcvkey: calcinfo.append( qcel.Datum(f"{qcvkey} DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) # LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo}) calcinfo = {info.label: info.data for info in calcinfo} # calcinfo = qcel.util.unnp(calcinfo, flat=True) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision calcinfo = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in calcinfo.items() } # jobrec['properties'] = {"return_energy": ene} # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"]) retres = calcinfo[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { "extras": input_model.extras, "properties": {}, "provenance": Provenance(creator="MP2D", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name), "return_result": retres, "stdout": stdout, } output_data["extras"]["local_keywords"] = input_model.extras["info"] output_data["extras"]["qcvars"] = calcinfo output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> "AtomicResult": Grimme_h2kcal = 627.509541 stdout = outfiles.pop("stdout") for fl, contents in outfiles.items(): if contents is not None: # LOG text += f'\n DFTD3 scratch file {fl} has been read.\n' pass # parse energy output (could go further and break into E6, E8, E10 and Cn coeff) real = np.array(input_model.molecule.real) full_nat = real.shape[0] real_nat = np.sum(real) for ln in stdout.splitlines(): if re.match(" Edisp /kcal,au", ln): ene = Decimal(ln.split()[3]) elif re.match(r" E6\(ABC\) \" :", ln): # c. v3.2.0 raise ResourceError("Cannot process ATM results from DFTD3 prior to v3.2.1.") elif re.match(r""" E6\(ABC\) /kcal,au:""", ln): atm = Decimal(ln.split()[-1]) elif re.match(" analysis of pair-wise terms", ln): D3pairs = np.zeros((full_nat, full_nat)) # Iterate over block start = stdout.splitlines().index(ln) + 2 for l in stdout.splitlines()[start:]: data = l.replace("-", " -").split() # print(data) if len(data) == 0: break atom1 = int(data[0]) - 1 atom2 = int(data[1]) - 1 Edisp = Decimal(data[-1]) D3pairs[atom1, atom2] = Edisp / Decimal(Grimme_h2kcal) D3pairs[atom2, atom1] = D3pairs[atom1, atom2] elif re.match(" normal termination of dftd3", ln): break else: if not ((real_nat == 1) and (input_model.driver == "gradient")): raise UnknownError( f"Unsuccessful run. Check input, particularly geometry in [a0]. Model: {input_model.model}" ) # parse gradient output # * DFTD3 crashes on one-atom gradients. Avoid the error (above) and just force the correct result (below). if outfiles["dftd3_gradient"] is not None: srealgrad = outfiles["dftd3_gradient"].replace("D", "E") realgrad = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3)) elif real_nat == 1: realgrad = np.zeros((1, 3)) if outfiles["dftd3_abc_gradient"] is not None: srealgrad = outfiles["dftd3_abc_gradient"].replace("D", "E") realgradabc = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3)) elif real_nat == 1: realgradabc = np.zeros((1, 3)) if input_model.driver == "gradient": ireal = np.argwhere(real).reshape((-1)) fullgrad = np.zeros((full_nat, 3)) rg = realgradabc if (input_model.extras["info"]["dashlevel"] == "atmgr") else realgrad try: fullgrad[ireal, :] = rg except NameError as exc: raise UnknownError("Unsuccessful gradient collection.") from exc qcvkey = input_model.extras["info"]["fctldash"].upper() calcinfo = [] if input_model.extras["info"]["dashlevel"] == "atmgr": calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", atm)) calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", atm)) calcinfo.append(qcel.Datum("3-BODY DISPERSION CORRECTION ENERGY", "Eh", atm)) calcinfo.append(qcel.Datum("AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION ENERGY", "Eh", atm)) if input_model.driver == "gradient": calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad)) calcinfo.append(qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) calcinfo.append(qcel.Datum("3-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) calcinfo.append( qcel.Datum("AXILROD-TELLER-MUTO 3-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad) ) else: calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene)) calcinfo.append(qcel.Datum("DISPERSION CORRECTION ENERGY", "Eh", ene)) calcinfo.append(qcel.Datum("2-BODY DISPERSION CORRECTION ENERGY", "Eh", ene)) if qcvkey: calcinfo.append(qcel.Datum(f"{qcvkey} DISPERSION CORRECTION ENERGY", "Eh", ene)) if input_model.driver == "gradient": calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad)) calcinfo.append(qcel.Datum("DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) calcinfo.append(qcel.Datum("2-BODY DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) if qcvkey: calcinfo.append(qcel.Datum(f"{qcvkey} DISPERSION CORRECTION GRADIENT", "Eh/a0", fullgrad)) # LOGtext += qcel.datum.print_variables({info.label: info for info in calcinfo}) calcinfo = {info.label: info.data for info in calcinfo} # calcinfo = qcel.util.unnp(calcinfo, flat=True) # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision calcinfo = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcel.util.unnp(calcinfo, flat=True).items() } # jobrec['properties'] = {"return_energy": ene} # jobrec["molecule"]["real"] = list(jobrec["molecule"]["real"]) retres = calcinfo[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { "extras": input_model.extras, "properties": {}, "provenance": Provenance( creator="DFTD3", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name ), "return_result": retres, "stdout": stdout, } output_data["extras"]["local_keywords"] = input_model.extras["info"] output_data["extras"]["qcvars"] = calcinfo if input_model.keywords.get("save_pairwise_dispersion") is True: output_data["extras"]["qcvars"]["PAIRWISE DISPERSION CORRECTION ANALYSIS"] = D3pairs output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult": """ Runs OpenMM on given structure, inputs, in vacuum. """ self.found(raise_error=True) from simtk import openmm from simtk import unit import openforcefield.topology as offtop # Failure flag ret_data = {"success": False} # generate basis, not given if not input_data.model.basis: basis = self._generate_basis(input_data) ret_data["basis"] = basis # get number of threads to use from `TaskConfig.ncores`; otherwise, try environment variable nthreads = config.ncores if nthreads is None: nthreads = os.environ.get("OPENMM_CPU_THREADS") # Set workdir to scratch # Location resolution order config.scratch_dir, /tmp parent = config.scratch_directory with temporary_directory(parent=parent, suffix="_openmm_scratch") as tmpdir: # Grab molecule, forcefield jmol = input_data.molecule # TODO: If urls are supported by # `openforcefield.typing.engines.smirnoff.ForceField` already, we # can eliminate the `offxml` and `url` distinction # URL processing can happen there instead if getattr(input_data.model, "offxml", None): # we were given a file path or relative path offxml = input_data.model.offxml # Load an Open Force Field `ForceField` off_forcefield = self._get_off_forcefield(offxml, offxml) elif getattr(input_data.model, "url", None): # we were given a url with urllib.request.urlopen(input_data.model.url) as req: xml = req.read() # Load an Open Force Field `ForceField` off_forcefield = self._get_off_forcefield(xml.decode(), xml) else: raise InputError("OpenMM requires either `model.offxml` or `model.url` to be set") # Process molecule with RDKit rdkit_mol = RDKitHarness._process_molecule_rdkit(jmol) # Create an Open Force Field `Molecule` from the RDKit Molecule off_mol = offtop.Molecule(rdkit_mol) # Create OpenMM system in vacuum from forcefield, molecule off_top = off_mol.to_topology() openmm_system = self._get_openmm_system(off_forcefield, off_top) # Need an integrator for simulation even if we don't end up using it really integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds) # Set platform to CPU explicitly platform = openmm.Platform.getPlatformByName("CPU") # Set number of threads to use # if `nthreads` is `None`, OpenMM default of all logical cores on # processor will be used if nthreads: properties = {"Threads": str(nthreads)} else: properties = {} # Initialize context context = openmm.Context(openmm_system, integrator, platform, properties) # Set positions from our Open Force Field `Molecule` context.setPositions(off_mol.conformers[0]) # Compute the energy of the configuration state = context.getState(getEnergy=True) # Get the potential as a simtk.unit.Quantity, put into units of hartree q = state.getPotentialEnergy() / unit.hartree ret_data["properties"] = {"return_energy": q.value_in_unit(q.unit)} # Execute driver if input_data.driver == "energy": ret_data["return_result"] = ret_data["properties"]["return_energy"] elif input_data.driver == "gradient": # Get number of atoms n_atoms = len(jmol.symbols) # Compute the forces state = context.getState(getForces=True) # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3) gradient = state.getForces(asNumpy=True) # Convert to hartree/bohr and reformat as 1D array q = (gradient / (unit.hartree / unit.bohr)).reshape([n_atoms * 3]) ret_data["return_result"] = q.value_in_unit(q.unit) else: raise InputError( f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}." ) ret_data["success"] = True # Move several pieces up a level ret_data["provenance"] = Provenance(creator="openmm", version=openmm.__version__, nthreads=nthreads) return AtomicResult(**{**input_data.dict(), **ret_data})
def parse_output( self, outfiles: Dict[str, str], input_model: AtomicInput ) -> AtomicResult: # lgtm: [py/similar-function] stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") method = input_model.model.method.lower() method = method[3:] if method.startswith("c4-") else method # c4mol, if it exists, is dinky, just a clue to geometry of cfour results try: # July 2021: c4mol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always atin orientation qcvars, c4hess, c4grad, c4mol, version, module, errorTMP = harvest( input_model.molecule, method, stdout, **outfiles ) except Exception: raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) if errorTMP != "": raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) try: if c4grad is not None: qcvars["CURRENT GRADIENT"] = c4grad qcvars[f"{method.upper()} TOTAL GRADIENT"] = c4grad if c4hess is not None: qcvars[f"{method.upper()} TOTAL HESSIAN"] = c4hess qcvars["CURRENT HESSIAN"] = c4hess if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] except KeyError: raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) # TODO: "xalloc(): memory allocation failed!" if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() build_out(qcvars) atprop = build_atomicproperties(qcvars) provenance = Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour").dict() if module is not None: provenance["module"] = module output_data = { "schema_version": 1, "molecule": c4mol, # overwrites with outfile Cartesians in case fix_*=F "extras": {**input_model.extras}, "native_files": {k: v for k, v in outfiles.items() if v is not None}, "properties": atprop, "provenance": provenance, "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # Decimal --> str preserves precision # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> "AtomicResult": stdout = outfiles.pop("stdout") # parse energy output (could go further and break into E6, E8, E10 and Cn coeff) real = np.array(input_model.molecule.real) full_nat = real.shape[0] real_nat = np.sum(real) for ln in stdout.splitlines(): if re.match(" Egcp:", ln): ene = Decimal(ln.split()[1]) elif re.match(" normal termination of gCP", ln): break else: if self._defaults["name"] == "GCP" and not ( (real_nat == 1) and (input_model.driver == "gradient")): raise UnknownError( f"Unsuccessful run. Check input, particularly geometry in [a0]. Model: {input_model.model}" ) # parse gradient output if outfiles["gcp_gradient"] is not None: srealgrad = outfiles["gcp_gradient"].replace("D", "E") realgrad = np.fromstring(srealgrad, count=3 * real_nat, sep=" ").reshape((-1, 3)) elif real_nat == 1: realgrad = np.zeros((1, 3)) if input_model.driver == "gradient": ireal = np.argwhere(real).reshape((-1)) fullgrad = np.zeros((full_nat, 3)) try: fullgrad[ireal, :] = realgrad except NameError as exc: raise UnknownError( "Unsuccessful gradient collection.") from exc qcvkey = input_model.model.method.upper() calcinfo = [] calcinfo.append(qcel.Datum("CURRENT ENERGY", "Eh", ene)) calcinfo.append(qcel.Datum("GCP CORRECTION ENERGY", "Eh", ene)) if qcvkey: calcinfo.append( qcel.Datum(f"{qcvkey} GCP CORRECTION ENERGY", "Eh", ene)) if input_model.driver == "gradient": calcinfo.append(qcel.Datum("CURRENT GRADIENT", "Eh/a0", fullgrad)) calcinfo.append( qcel.Datum("GCP CORRECTION GRADIENT", "Eh/a0", fullgrad)) if qcvkey: calcinfo.append( qcel.Datum(f"{qcvkey} GCP CORRECTION GRADIENT", "Eh/a0", fullgrad)) calcinfo = {info.label: info.data for info in calcinfo} # Decimal --> str preserves precision calcinfo = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in calcinfo.items() } retres = calcinfo[f"CURRENT {input_model.driver.upper()}"] if isinstance(retres, Decimal): retres = float(retres) elif isinstance(retres, np.ndarray): retres = retres.ravel().tolist() output_data = { "extras": input_model.extras, "properties": {}, "provenance": Provenance(creator="GCP", version=self.get_version(), routine=__name__ + "." + sys._getframe().f_code.co_name), "return_result": retres, "stdout": stdout, } output_data["extras"]["qcvars"] = calcinfo output_data["success"] = True return AtomicResult(**{**input_model.dict(), **output_data})
def parse_output(self, outfiles: Dict[str, str], input_model: AtomicInput) -> AtomicResult: # Get the stdout from the calculation (required) stdout = outfiles.pop("stdout") stderr = outfiles.pop("stderr") method = input_model.model.method.lower() method = method[4:] if method.startswith("gms-") else method # gamessmol, if it exists, is dinky, just a clue to geometry of gamess results try: # July 2021: gamessmol & vector returns now atin/outfile orientation depending on fix_com,orientation=T/F. previously always outfile orientation qcvars, gamesshess, gamessgrad, gamessmol, module = harvest( input_model.molecule, method, stdout, **outfiles) # TODO: "EXECUTION OF GAMESS TERMINATED -ABNORMALLY-" in dexe["stdout"]: except Exception: raise UnknownError(error_stamp(outfiles["input"], stdout, stderr)) try: if gamessgrad is not None: qcvars[f"{method.upper()} TOTAL GRADIENT"] = gamessgrad qcvars["CURRENT GRADIENT"] = gamessgrad if gamesshess is not None: qcvars[f"{method.upper()} TOTAL HESSIAN"] = gamesshess qcvars["CURRENT HESSIAN"] = gamesshess if input_model.driver.upper() == "PROPERTIES": retres = qcvars[f"CURRENT ENERGY"] else: retres = qcvars[f"CURRENT {input_model.driver.upper()}"] except KeyError: if "EXETYP=CHECK" in stdout and "EXECUTION OF GAMESS TERMINATED NORMALLY" in stdout: # check run that completed normally # * on one hand, it's still an error return_result-wise # * but on the other hand, often the reason for the job is to get gamessmol, so let it return success=T below retres = 0.0 else: raise UnknownError( error_stamp(outfiles["input"], stdout, stderr)) build_out(qcvars) atprop = build_atomicproperties(qcvars) provenance = Provenance(creator="GAMESS", version=self.get_version(), routine="rungms").dict() if module is not None: provenance["module"] = module output_data = { "schema_version": 1, "molecule": gamessmol, # overwrites with outfile Cartesians in case fix_*=F "extras": { **input_model.extras }, "native_files": {k: v for k, v in outfiles.items() if v is not None}, "properties": atprop, "provenance": provenance, "return_result": retres, "stderr": stderr, "stdout": stdout, "success": True, } # got to even out who needs plump/flat/Decimal/float/ndarray/list # * formerly unnp(qcvars, flat=True).items() output_data["extras"]["qcvars"] = { k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } return AtomicResult(**{**input_model.dict(), **output_data})