def run_json_qcschema(json_data, clean, json_serialization, keep_wfn=False): """ An implementation of the QC JSON Schema (molssi-qc-schema.readthedocs.io/en/latest/index.html#) implementation in Psi4. Parameters ---------- json_data : JSON Please see molssi-qc-schema.readthedocs.io/en/latest/spec_components.html for further details. Notes ----- !Warning! This function is experimental and likely to change in the future. Please report any suggestions or uses of this function on github.com/MolSSI/QC_JSON_Schema. Examples -------- """ # Clean a few things _clean_psi_environ(clean) # This is currently a forced override if json_data["schema_name"] in ["qc_schema_input", "qcschema_input"]: json_data["schema_name"] = "qcschema_input" else: raise KeyError("Schema name of '{}' not understood".format(json_data["schema_name"])) if json_data["schema_version"] != 1: raise KeyError("Schema version of '{}' not understood".format(json_data["schema_version"])) if json_data.get("nthreads", False) is not False: core.set_num_threads(json_data["nthreads"], quiet=True) # Build molecule if "schema_name" in json_data["molecule"]: molschemus = json_data["molecule"] # dtype >=2 else: molschemus = json_data # dtype =1 mol = core.Molecule.from_schema(molschemus) # Update molecule geometry as we orient and fix_com json_data["molecule"]["geometry"] = mol.geometry().np.ravel().tolist() # Set options kwargs = json_data["keywords"].pop("function_kwargs", {}) p4util.set_options(json_data["keywords"]) # Setup the computation method = json_data["model"]["method"] core.set_global_option("BASIS", json_data["model"]["basis"]) kwargs.update({"return_wfn": True, "molecule": mol}) # Handle special properties case if json_data["driver"] == "properties": if "properties" in json_data["model"]: kwargs["properties"] = [x.lower() for x in json_data["model"]["properties"]] extra = set(kwargs["properties"]) - can_do_properties_ if len(extra): raise KeyError("Did not understand property key %s." % kwargs["properties"]) else: kwargs["properties"] = list(can_do_properties_) # Actual driver run val, wfn = methods_dict_[json_data["driver"]](method, **kwargs) # Pull out a standard set of SCF properties if "extras" not in json_data: json_data["extras"] = {} json_data["extras"]["qcvars"] = {} if json_data["extras"].get("wfn_qcvars_only", False): psi_props = wfn.variables() else: psi_props = core.variables() for k, v in psi_props.items(): if k not in json_data["extras"]["qcvars"]: json_data["extras"]["qcvars"][k] = _serial_translation(v, json=json_serialization) # Still a bit of a mess at the moment add in local vars as well. for k, v in wfn.variables().items(): if k not in json_data["extras"]["qcvars"]: json_data["extras"]["qcvars"][k] = _serial_translation(v, json=json_serialization) # Handle the return result if json_data["driver"] == "energy": json_data["return_result"] = val elif json_data["driver"] in ["gradient", "hessian"]: json_data["return_result"] = _serial_translation(val, json=json_serialization) elif json_data["driver"] == "properties": ret = {} mtd = json_data["model"]["method"].upper() # Dipole/quadrupole still special case if "dipole" in kwargs["properties"]: ret["dipole"] = [psi_props[mtd + " DIPOLE " + x] for x in ["X", "Y", "Z"]] if "quadrupole" in kwargs["properties"]: ret["quadrupole"] = [psi_props[mtd + " QUADRUPOLE " + x] for x in ["XX", "XY", "XZ", "YY", "YZ", "ZZ"]] ret.update(_convert_variables(wfn.variables(), context="properties", json=json_serialization)) json_data["return_result"] = ret else: raise KeyError("Did not understand Driver key %s." % json_data["driver"]) props = { "calcinfo_nbasis": wfn.nso(), "calcinfo_nmo": wfn.nmo(), "calcinfo_nalpha": wfn.nalpha(), "calcinfo_nbeta": wfn.nbeta(), "calcinfo_natom": mol.geometry().shape[0], } props.update(_convert_variables(psi_props, context="generics", json=json_serialization)) if not list(set(['CBS NUMBER', 'NBODY NUMBER', 'FINDIF NUMBER']) & set(json_data["extras"]["qcvars"].keys())): props.update(_convert_variables(psi_props, context="scf", json=json_serialization)) # Write out post-SCF keywords if "MP2 CORRELATION ENERGY" in psi_props: props.update(_convert_variables(psi_props, context="mp2", json=json_serialization)) if "CCSD CORRELATION ENERGY" in psi_props: props.update(_convert_variables(psi_props, context="ccsd", json=json_serialization)) if "CCSD(T) CORRELATION ENERGY" in psi_props: props.update(_convert_variables(psi_props, context="ccsd(t)", json=json_serialization)) json_data["properties"] = props json_data["success"] = True if keep_wfn: json_data["wavefunction"] = _convert_wavefunction(wfn) # Reset state _clean_psi_environ(clean) json_data["schema_name"] = "qcschema_output" return json_data
def run_json_qcschema(json_data, clean, json_serialization, keep_wfn=False): """ An implementation of the QC JSON Schema (molssi-qc-schema.readthedocs.io/en/latest/index.html#) implementation in Psi4. Parameters ---------- json_data : JSON Please see molssi-qc-schema.readthedocs.io/en/latest/spec_components.html for further details. Notes ----- !Warning! This function is experimental and likely to change in the future. Please report any suggestions or uses of this function on github.com/MolSSI/QC_JSON_Schema. Examples -------- """ # Clean a few things _clean_psi_environ(clean) # This is currently a forced override if json_data["schema_name"] in ["qc_schema_input", "qcschema_input"]: json_data["schema_name"] = "qcschema_input" else: raise KeyError("Schema name of '{}' not understood".format( json_data["schema_name"])) if json_data["schema_version"] != 1: raise KeyError("Schema version of '{}' not understood".format( json_data["schema_version"])) if json_data.get("nthreads", False) is not False: core.set_num_threads(json_data["nthreads"], quiet=True) # Build molecule if "schema_name" in json_data["molecule"]: molschemus = json_data["molecule"] # dtype >=2 else: molschemus = json_data # dtype =1 mol = core.Molecule.from_schema(molschemus) # Update molecule geometry as we orient and fix_com json_data["molecule"]["geometry"] = mol.geometry().np.ravel().tolist() # Set options kwargs = json_data["keywords"].pop("function_kwargs", {}) p4util.set_options(json_data["keywords"]) # Setup the computation method = json_data["model"]["method"] core.set_global_option("BASIS", json_data["model"]["basis"]) kwargs.update({"return_wfn": True, "molecule": mol}) # Handle special properties case if json_data["driver"] == "properties": if "properties" not in kwargs: kwargs["properties"] = list(default_properties_) # Actual driver run val, wfn = methods_dict_[json_data["driver"]](method, **kwargs) # Pull out a standard set of SCF properties if "extras" not in json_data: json_data["extras"] = {} json_data["extras"]["qcvars"] = {} current_qcvars_only = json_data["extras"].get("current_qcvars_only", False) if json_data["extras"].get("wfn_qcvars_only", False): psi_props = wfn.variables( include_deprecated_keys=(not current_qcvars_only)) else: psi_props = core.variables( include_deprecated_keys=(not current_qcvars_only)) for k, v in psi_props.items(): if k not in json_data["extras"]["qcvars"]: json_data["extras"]["qcvars"][k] = _serial_translation( v, json=json_serialization) # Still a bit of a mess at the moment add in local vars as well. for k, v in wfn.variables().items(): if k not in json_data["extras"]["qcvars"]: # interpreting wfn_qcvars_only as no deprecated qcvars either if not (json_data["extras"].get("wfn_qcvars_only", False) and (any([ k.upper().endswith(" DIPOLE " + cart) for cart in ["X", "Y", "Z"] ]) or any([ k.upper().endswith(" QUADRUPOLE " + cart) for cart in ["XX", "YY", "ZZ", "XY", "XZ", "YZ"] ]) or k.upper() in [ "SOS-MP2 CORRELATION ENERGY", "SOS-MP2 TOTAL ENERGY", "SOS-PI-MP2 CORRELATION ENERGY", "SOS-PI-MP2 TOTAL ENERGY", "SCS-MP3 CORRELATION ENERGY", "SCS-MP3 TOTAL ENERGY", ])): json_data["extras"]["qcvars"][k] = _serial_translation( v, json=json_serialization) # Handle the return result if json_data["driver"] == "energy": json_data["return_result"] = val elif json_data["driver"] in ["gradient", "hessian"]: json_data["return_result"] = _serial_translation( val, json=json_serialization) elif json_data["driver"] == "properties": ret = {} mtd = json_data["model"]["method"].upper() # Dipole/quadrupole still special case if "dipole" in kwargs["properties"]: ret["dipole"] = _serial_translation(psi_props[mtd + " DIPOLE"], json=json_serialization) if "quadrupole" in kwargs["properties"]: ret["quadrupole"] = _serial_translation(psi_props[mtd + " QUADRUPOLE"], json=json_serialization) ret.update( _convert_variables(wfn.variables(), context="properties", json=json_serialization)) json_data["return_result"] = ret else: raise KeyError("Did not understand Driver key %s." % json_data["driver"]) props = { "calcinfo_nbasis": wfn.nso(), "calcinfo_nmo": wfn.nmo(), "calcinfo_nalpha": wfn.nalpha(), "calcinfo_nbeta": wfn.nbeta(), "calcinfo_natom": mol.geometry().shape[0], "nuclear_repulsion_energy": mol.nuclear_repulsion_energy( ), # use this b/c psivar is monomer for SAPT } props.update( _convert_variables(psi_props, context="generics", json=json_serialization)) if not list( set(['CBS NUMBER', 'NBODY NUMBER', 'FINDIF NUMBER']) & set(json_data["extras"]["qcvars"].keys())): props.update( _convert_variables(psi_props, context="scf", json=json_serialization)) # Write out post-SCF keywords if "MP2 CORRELATION ENERGY" in psi_props: props.update( _convert_variables(psi_props, context="mp2", json=json_serialization)) if "CCSD CORRELATION ENERGY" in psi_props: props.update( _convert_variables(psi_props, context="ccsd", json=json_serialization)) if "CCSD(T) CORRELATION ENERGY" in psi_props: props.update( _convert_variables(psi_props, context="ccsd(t)", json=json_serialization)) json_data["properties"] = props json_data["success"] = True json_data["provenance"]["module"] = wfn.module() if keep_wfn: json_data["wavefunction"] = _convert_wavefunction(wfn) files = { "psi4.grad": Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".grad"), "psi4.hess": Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".hess"), # binary "psi4.180.npy": Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".180.npy"), "timer.dat": Path( "timer.dat" ), # ok for `psi4 --qcschema` but no file collected for `qcengine.run_program(..., "psi4")` } json_data["native_files"] = { fl: flpath.read_text() for fl, flpath in files.items() if flpath.exists() } # Reset state _clean_psi_environ(clean) json_data["schema_name"] = "qcschema_output" return json_data
def run_cfour(name, **kwargs): """Function that prepares environment and input files for a calculation calling Stanton and Gauss's CFOUR code. Also processes results back into Psi4 format. This function is not called directly but is instead called by :py:func:`~psi4.driver.energy` or :py:func:`~psi4.driver.optimize` when a Cfour method is requested (through *name* argument). In order to function correctly, the Cfour executable ``xcfour`` must be present in :envvar:`PATH` or :envvar:`PSIPATH`. .. hlist:: :columns: 1 * Many :ref:`PSI Variables <apdx:cfour_psivar>` extracted from the Cfour output * Python dictionary of associated file constants accessible as ``P4C4_INFO['zmat']``, ``P4C4_INFO['output']``, ``P4C4_INFO['grd']``, *etc.* :type name: str :param name: ``'c4-scf'`` || ``'c4-ccsd(t)'`` || ``'cfour'`` || etc. First argument, usually unlabeled. Indicates the computational method to be applied to the system. :type keep: :ref:`boolean <op_py_boolean>` :param keep: ``'on'`` || |dl| ``'off'`` |dr| Indicates whether to delete the Cfour scratch directory upon completion of the Cfour job. :type path: str :param path: Indicates path to Cfour scratch directory (with respect to Psi4 scratch directory). Otherwise, the default is a subdirectory within the Psi4 scratch directory. If specified, GENBAS and/or ZMAT within will be used. :type genbas: str :param genbas: Indicates that contents should be used for GENBAS file. GENBAS is a complicated topic. It is quite unnecessary if the molecule is from a molecule {...} block and basis is set through |Psifours| BASIS keyword. In that case, a GENBAS is written from LibMints and all is well. Otherwise, a GENBAS is looked for in the usual places: PSIPATH, PATH, PSIDATADIR/basis. If path kwarg is specified, also looks there preferentially for a GENBAS. Can also specify GENBAS within an input file through a string and setting the genbas kwarg. Note that due to the input parser's aggression, blank lines need to be replaced by the text blankline. """ lowername = name.lower() internal_p4c4_info = {} return_wfn = kwargs.pop('return_wfn', False) # Make sure the molecule the user provided is the active one molecule = kwargs.pop('molecule', core.get_active_molecule()) molecule.update_geometry() optstash = p4util.OptionsState(['CFOUR', 'TRANSLATE_PSI4']) # Determine calling function and hence dertype calledby = inspect.stack()[1][3] dertype = ['energy', 'gradient', 'hessian'].index(calledby) #print('I am %s called by %s called by %s.\n' % # (inspect.stack()[0][3], inspect.stack()[1][3], inspect.stack()[2][3])) # Save submission directory current_directory = os.getcwd() # Move into job scratch directory psioh = core.IOManager.shared_object() psio = core.IO.shared_object() os.chdir(psioh.get_default_path()) # Construct and move into cfour subdirectory of job scratch directory cfour_tmpdir = kwargs['path'] if 'path' in kwargs else \ 'psi.' + str(os.getpid()) + '.' + psio.get_default_namespace() + \ '.cfour.' + str(uuid.uuid4())[:8] if not os.path.exists(cfour_tmpdir): os.mkdir(cfour_tmpdir) os.chdir(cfour_tmpdir) # Find environment by merging PSIPATH and PATH environment variables lenv = { 'PATH': ':'.join([ os.path.abspath(x) for x in os.environ.get('PSIPATH', '').split(':') if x != '' ]) + ':' + os.environ.get('PATH') + ':' + core.get_datadir() + '/basis', 'GENBAS_PATH': core.get_datadir() + '/basis', 'CFOUR_NUM_CORES': os.environ.get('CFOUR_NUM_CORES'), 'MKL_NUM_THREADS': os.environ.get('MKL_NUM_THREADS'), 'OMP_NUM_THREADS': os.environ.get('OMP_NUM_THREADS'), 'LD_LIBRARY_PATH': os.environ.get('LD_LIBRARY_PATH') } if 'path' in kwargs: lenv['PATH'] = kwargs['path'] + ':' + lenv['PATH'] # Filter out None values as subprocess will fault on them lenv = {k: v for k, v in lenv.items() if v is not None} # Load the GENBAS file genbas_path = qcdb.search_file('GENBAS', lenv['GENBAS_PATH']) if genbas_path: try: shutil.copy2(genbas_path, psioh.get_default_path() + cfour_tmpdir) except shutil.Error: # should only fail if src and dest equivalent pass core.print_out("\n GENBAS loaded from %s\n" % (genbas_path)) core.print_out(" CFOUR to be run from %s\n" % (psioh.get_default_path() + cfour_tmpdir)) else: message = """ GENBAS file for CFOUR interface not found. Either: [1] Supply a GENBAS by placing it in PATH or PSIPATH [1a] Use cfour {} block with molecule and basis directives. [1b] Use molecule {} block and CFOUR_BASIS keyword. [2] Allow Psi4's internal basis sets to convert to GENBAS [2a] Use molecule {} block and BASIS keyword. """ core.print_out(message) core.print_out(' Search path that was tried:\n') core.print_out(lenv['PATH'].replace(':', ', ')) # Generate the ZMAT input file in scratch if 'path' in kwargs and os.path.isfile('ZMAT'): core.print_out(" ZMAT loaded from %s\n" % (psioh.get_default_path() + kwargs['path'] + '/ZMAT')) else: with open('ZMAT', 'w') as cfour_infile: cfour_infile.write(write_zmat(lowername, dertype, molecule)) internal_p4c4_info['zmat'] = open('ZMAT', 'r').read() #core.print_out('\n====== Begin ZMAT input for CFOUR ======\n') #core.print_out(open('ZMAT', 'r').read()) #core.print_out('======= End ZMAT input for CFOUR =======\n\n') #print('\n====== Begin ZMAT input for CFOUR ======') #print(open('ZMAT', 'r').read()) #print('======= End ZMAT input for CFOUR =======\n') if 'genbas' in kwargs: with open('GENBAS', 'w') as cfour_basfile: cfour_basfile.write(kwargs['genbas'].replace( '\nblankline\n', '\n\n')) core.print_out(' GENBAS loaded from kwargs string\n') # Close psi4 output file and reopen with filehandle print('output in', current_directory + '/' + core.outfile_name()) pathfill = '' if os.path.isabs( core.outfile_name()) else current_directory + os.path.sep # Handle threading # OMP_NUM_THREADS from env is in lenv from above # threads from psi4 -n (core.get_num_threads()) is ignored # CFOUR_OMP_NUM_THREADS psi4 option takes precedence, handled below if core.has_option_changed('CFOUR', 'CFOUR_OMP_NUM_THREADS'): lenv['OMP_NUM_THREADS'] = str( core.get_option('CFOUR', 'CFOUR_OMP_NUM_THREADS')) #print("""\n\n<<<<< RUNNING CFOUR ... >>>>>\n\n""") # Call executable xcfour, directing cfour output to the psi4 output file cfour_executable = kwargs['c4exec'] if 'c4exec' in kwargs else 'xcfour' try: retcode = subprocess.Popen([cfour_executable], bufsize=0, stdout=subprocess.PIPE, env=lenv) except OSError as e: sys.stderr.write( 'Program %s not found in path or execution failed: %s\n' % (cfour_executable, e.strerror)) message = ('Program %s not found in path or execution failed: %s\n' % (cfour_executable, e.strerror)) raise ValidationError(message) c4out = '' while True: data = retcode.stdout.readline() data = data.decode('utf-8') if not data: break core.print_out(data) c4out += data internal_p4c4_info['output'] = c4out c4files = {} core.print_out('\n') for item in ['GRD', 'FCMFINAL', 'DIPOL']: try: with open(psioh.get_default_path() + cfour_tmpdir + '/' + item, 'r') as handle: c4files[item] = handle.read() core.print_out(' CFOUR scratch file %s has been read\n' % (item)) core.print_out('%s\n' % c4files[item]) internal_p4c4_info[item.lower()] = c4files[item] except IOError: pass core.print_out('\n') if molecule.name() == 'blank_molecule_psi4_yo': qcdbmolecule = None else: molecule.update_geometry() qcdbmolecule = qcdb.Molecule( molecule.create_psi4_string_from_molecule()) qcdbmolecule.update_geometry() # c4mol, if it exists, is dinky, just a clue to geometry of cfour results psivar, c4grad, c4mol = qcdb.cfour.harvest(qcdbmolecule, c4out, **c4files) # Absorb results into psi4 data structures for key in psivar.keys(): core.set_variable(key.upper(), float(psivar[key])) if qcdbmolecule is None and c4mol is not None: molrec = qcel.molparse.from_string( c4mol.create_psi4_string_from_molecule(), enable_qm=True, missing_enabled_return_qm='minimal', enable_efp=False, missing_enabled_return_efp='none', ) molecule = core.Molecule.from_dict(molrec['qm']) molecule.set_name('blank_molecule_psi4_yo') core.set_active_molecule(molecule) molecule.update_geometry() # This case arises when no Molecule going into calc (cfour {} block) but want # to know the orientation at which grad, properties, etc. are returned (c4mol). # c4mol is dinky, w/o chg, mult, dummies and retains name # blank_molecule_psi4_yo so as to not interfere with future cfour {} blocks if c4grad is not None: mat = core.Matrix.from_list(c4grad) core.set_gradient(mat) #print ' <<< [3] C4-GRD-GRAD >>>' #mat.print() # exit(1) # # Things needed core.so module to do # collect c4out string # read GRD # read FCMFINAL # see if theres an active molecule # # Things delegatable to qcdb # parsing c4out # reading GRD and FCMFINAL strings # reconciling p4 and c4 molecules (orient) # reconciling c4out and GRD and FCMFINAL results # transforming frame of results back to p4 # # Things run_cfour needs to have back # psivar # qcdb.Molecule of c4? # coordinates? # gradient in p4 frame # # Process the cfour output # psivar, c4coord, c4grad = qcdb.cfour.cfour_harvest(c4out) # for key in psivar.keys(): # core.set_variable(key.upper(), float(psivar[key])) # # # Awful Hack - Go Away TODO # if c4grad: # molecule = core.get_active_molecule() # molecule.update_geometry() # # if molecule.name() == 'blank_molecule_psi4_yo': # p4grad = c4grad # p4coord = c4coord # else: # qcdbmolecule = qcdb.Molecule(molecule.create_psi4_string_from_molecule()) # #p4grad = qcdbmolecule.deorient_array_from_cfour(c4coord, c4grad) # #p4coord = qcdbmolecule.deorient_array_from_cfour(c4coord, c4coord) # # with open(psioh.get_default_path() + cfour_tmpdir + '/GRD', 'r') as cfour_grdfile: # c4outgrd = cfour_grdfile.read() # print('GRD\n',c4outgrd) # c4coordGRD, c4gradGRD = qcdb.cfour.cfour_harvest_files(qcdbmolecule, grd=c4outgrd) # # p4mat = core.Matrix.from_list(p4grad) # core.set_gradient(p4mat) # print(' <<< P4 PSIVAR >>>') # for item in psivar: # print(' %30s %16.8f' % (item, psivar[item])) #print(' <<< P4 COORD >>>') #for item in p4coord: # print(' %16.8f %16.8f %16.8f' % (item[0], item[1], item[2])) # print(' <<< P4 GRAD >>>') # for item in c4grad: # print(' %16.8f %16.8f %16.8f' % (item[0], item[1], item[2])) # Clean up cfour scratch directory unless user instructs otherwise keep = yes.match(str(kwargs['keep'])) if 'keep' in kwargs else False os.chdir('..') try: if keep or ('path' in kwargs): core.print_out('\n CFOUR scratch files have been kept in %s\n' % (psioh.get_default_path() + cfour_tmpdir)) else: shutil.rmtree(cfour_tmpdir) except OSError as e: print('Unable to remove CFOUR temporary directory %s' % e, file=sys.stderr) exit(1) # Return to submission directory and reopen output file os.chdir(current_directory) core.print_out('\n') p4util.banner(' Cfour %s %s Results ' % (name.lower(), calledby.capitalize())) core.print_variables() if c4grad is not None: core.get_gradient().print_out() core.print_out('\n') p4util.banner(' Cfour %s %s Results ' % (name.lower(), calledby.capitalize())) core.print_variables() if c4grad is not None: core.get_gradient().print_out() # Quit if Cfour threw error if 'CFOUR ERROR CODE' in core.variables(): raise ValidationError("""Cfour exited abnormally.""") P4C4_INFO.clear() P4C4_INFO.update(internal_p4c4_info) optstash.restore() # new skeleton wavefunction w/mol, highest-SCF basis (just to choose one), & not energy # Feb 2017 hack. Could get proper basis in skel wfn even if not through p4 basis kw if core.get_global_option('BASIS') in ["", "(AUTO)"]: gobas = "sto-3g" else: gobas = core.get_global_option('BASIS') basis = core.BasisSet.build(molecule, "ORBITAL", gobas) if basis.has_ECP(): raise ValidationError("""ECPs not hooked up for Cfour""") wfn = core.Wavefunction(molecule, basis) for k, v in psivar.items(): wfn.set_variable(k.upper(), float(v)) optstash.restore() if dertype == 0: finalquantity = psivar['CURRENT ENERGY'] elif dertype == 1: finalquantity = core.get_gradient() wfn.set_gradient(finalquantity) if finalquantity.rows(0) < 20: core.print_out('CURRENT GRADIENT') finalquantity.print_out() elif dertype == 2: pass #finalquantity = finalhessian #wfn.set_hessian(finalquantity) #if finalquantity.rows(0) < 20: # core.print_out('CURRENT HESSIAN') # finalquantity.print_out() return wfn