def hessian_write(wfn: core.Wavefunction): if core.get_option('FINDIF', 'HESSIAN_WRITE'): filename = core.get_writer_file_prefix(wfn.molecule().name()) + ".hess" with open(filename, 'wb') as handle: qcdb.hessparse.to_string(np.asarray(wfn.hessian()), handle, dtype='psi4')
def _core_wavefunction_get_scratch_filename(self, filenumber): """ Given a wavefunction and a scratch file number, canonicalizes the name so that files can be consistently written and read """ fname = os.path.split( os.path.abspath(core.get_writer_file_prefix( self.molecule().name())))[1] psi_scratch = core.IOManager.shared_object().get_default_path() return os.path.join(psi_scratch, fname + '.' + str(filenumber))
def gradient_write(wfn: core.Wavefunction): if core.get_option('FINDIF', 'GRADIENT_WRITE'): filename = core.get_writer_file_prefix(wfn.molecule().name()) + ".grad" qcdb.gradparse.to_string(np.asarray(wfn.gradient()), filename, dtype='GRD', mol=wfn.molecule(), energy=wfn.energy())
def _init_addghost_C(self, oldcalc, calc): # print('Adding ghost %s->%s' % (oldcalc, calc)) old_filename = self._fmt_mo_fn(oldcalc) wfn = core.Wavefunction.from_file(old_filename) Ca_occ = wfn.Ca_subset('SO', 'OCC') Cb_occ = wfn.Cb_subset('SO', 'OCC') m1_nso = self.wfn_cache[('m1', 'm', oldcalc.Z)].nso() m2_nso = self.wfn_cache[('m2', 'm', oldcalc.Z)].nso() m1_nalpha = self.wfn_cache[('m1', 'm', oldcalc.Z)].nalpha() m2_nalpha = self.wfn_cache[('m2', 'm', oldcalc.Z)].nalpha() m1_nbeta = self.wfn_cache[('m1', 'm', oldcalc.Z)].nbeta() m2_nbeta = self.wfn_cache[('m2', 'm', oldcalc.Z)].nbeta() Ca_d = np.zeros((m1_nso + m2_nso, m1_nso + m2_nso)) Cb_d = np.zeros((m1_nso + m2_nso, m1_nso + m2_nso)) if calc.V == 'm1': Ca_d[:m1_nso, :m1_nalpha] = Ca_occ Cb_d[:m1_nso, :m1_nbeta] = Cb_occ elif calc.V == 'm2': Ca_d[-m2_nso:, :m2_nalpha] = Ca_occ Cb_d[-m2_nso:, :m2_nbeta] = Cb_occ wfn_new = wfn.to_file() wfn_new['matrix']['Ca'] = Ca_d wfn_new['matrix']['Cb'] = Cb_d wfn_new['dimension']['nsopi'] = (m1_nso + m2_nso, ) wfn_new['dimension']['nmopi'] = (m1_nso + m2_nso, ) wfn_new['int']['nso'] = m1_nso + m2_nso wfn_new['int']['nmo'] = m1_nso + m2_nso wfn_new = core.Wavefunction.from_file(wfn_new) psi_scratch = core.IOManager.shared_object().get_default_path() write_filename = os.path.join( psi_scratch, os.path.split( os.path.abspath(core.get_writer_file_prefix( self.fmt_ns(calc))))[1] + ".180.npy") wfn_new.to_file(write_filename) extras.register_numpy_file(write_filename) core.set_local_option('SCF', 'GUESS', 'READ')
def _init_addghost_C(self, oldcalc, calc): # print('Adding ghost %s->%s' % (oldcalc, calc)) old_filename = self._fmt_mo_fn(oldcalc) data = np.load(old_filename) Ca_occ = core.Matrix.np_read(data, "Ca_occ") Cb_occ = core.Matrix.np_read(data, "Cb_occ") m1_nso = self.wfn_cache[('m1', 'm', oldcalc.Z)].nso() m2_nso = self.wfn_cache[('m2', 'm', oldcalc.Z)].nso() m1_nalpha = self.wfn_cache[('m1', 'm', oldcalc.Z)].nalpha() m2_nalpha = self.wfn_cache[('m2', 'm', oldcalc.Z)].nalpha() m1_nbeta = self.wfn_cache[('m1', 'm', oldcalc.Z)].nbeta() m2_nbeta = self.wfn_cache[('m2', 'm', oldcalc.Z)].nbeta() if calc.V == 'm1': Ca_occ_d = core.Matrix('Ca_occ', (m1_nso + m2_nso), m1_nalpha) Ca_occ_d.np[:m1_nso, :] = Ca_occ.np[:, :] Cb_occ_d = core.Matrix('Cb_occ', (m1_nso + m2_nso), m1_nbeta) Cb_occ_d.np[:m1_nso, :] = Cb_occ.np[:, :] elif calc.V == 'm2': Ca_occ_d = core.Matrix('Ca_occ', (m1_nso + m2_nso), m2_nalpha) Ca_occ_d.np[-m2_nso:, :] = Ca_occ.np[:, :] Cb_occ_d = core.Matrix('Cb_occ', (m1_nso + m2_nso), m2_nbeta) Cb_occ_d.np[-m2_nso:, :] = Cb_occ.np[:, :] data_dict = dict(data) data_dict.update(Ca_occ_d.np_write(prefix='Ca_occ')) data_dict.update(Cb_occ_d.np_write(prefix='Cb_occ')) psi_scratch = core.IOManager.shared_object().get_default_path() write_filename = os.path.join( psi_scratch, os.path.split( os.path.abspath(core.get_writer_file_prefix( self.fmt_ns(calc))))[1] + ".180.npz") np.savez(write_filename, **data_dict) extras.register_numpy_file(write_filename) core.set_local_option('SCF', 'GUESS', 'READ')
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 _fmt_mo_fn(self, calc): # type: (calcid,) -> str # Path to the molecular orbital file for a calc. fname = os.path.split( os.path.abspath(core.get_writer_file_prefix(self.fmt_ns(calc))))[1] return "%s.%s.npy" % (fname, psif.PSIF_SCF_MOS)
def _write_molden( self: core.Wavefunction, filename: Optional[str] = None, do_virtual: Optional[bool] = None, use_natural: bool = False, ): """Writes wavefunction information in *wfn* to *filename* in molden format. Will write natural orbitals from *density* (MO basis) if supplied. Warning! most post-SCF wavefunctions do not build the density as this is often much more costly than the energy. In addition, the wavefunction density attributes (Da and Db) return the SO density and must be transformed to the MO basis to use with this function. .. versionadded:: 0.5 *wfn* parameter passed explicitly :returns: None :type filename: :param filename: Destination file name for MOLDEN file. If unspecified (None), a file name will be generated from the molecule name. :type do_virtual: :param do_virtual: Do write all the MOs to the MOLDEN file (True) or discard the unoccupied MOs (False). Not valid for NO's. If unspecified (None), value taken from :term:`MOLDEN_WITH_VIRTUAL <MOLDEN_WITH_VIRTUAL (GLOBALS)>`. :type use_natural: :param use_natural: Write natural orbitals determined from density on wavefunction. :examples: 1. Molden file with the Kohn-Sham orbitals of a DFT calculation. >>> E, wfn = energy('b3lyp', return_wfn=True) >>> wfn.molden('mycalc.molden') 2. Molden file with the natural orbitals of a CCSD computation. For correlated methods, an energy call will not compute the density. "properties" or "gradient" must be called. >>> E, wfn = properties('ccsd', return_wfn=True) >>> wfn.molden('ccsd_no.molden', use_natural=True) 3. To supply a custom density matrix, manually set the Da and Db of the wavefunction. This is used, for example, to write natural orbitals coming from a root computed by a ``CIWavefunction`` computation, e.g., ``detci``, ``fci``, ``casscf``. The first two arguments of :py:meth:`~psi4.core.CIWavefunction.get_opdm` can be set to ``n, n`` where n => 0 selects the root to write out, provided these roots were computed, see :term:`NUM_ROOTS <NUM_ROOTS (DETCI)>`. The third argument controls the spin (``"A"``, ``"B"`` or ``"SUM"``) and the final boolean option determines whether inactive orbitals are included. >>> E, wfn = energy('detci', return_wfn=True) >>> wfn.Da() = wfn.get_opdm(0, 0, "A", True) >>> wfn.Db() = wfn.get_opdm(0, 0, "B", True) >>> molden(wfn, 'no_root1.molden', use_natural=True) """ if filename is None: filename = core.get_writer_file_prefix( self.molecule().name()) + ".molden" if do_virtual is None: do_virtual = bool(core.get_option("SCF", "MOLDEN_WITH_VIRTUAL")) basisset = self.basisset() mol = self.molecule() # Header and geometry (Atom, Atom #, Z, x, y, z) mol_string = '[Molden Format]\n[Atoms] (AU)\n' for atom in range(mol.natom()): mol_string += f"{mol.symbol(atom):2s} {atom+1:2d} {int(mol.Z(atom)):3d} {mol.x(atom):20.10f} {mol.y(atom):20.10f} {mol.z(atom):20.10f}\n" # Dump basis set mol_string += '[GTO]\n' for atom in range(mol.natom()): mol_string += f" {atom+1:d} 0\n" for rel_shell_idx in range(basisset.nshell_on_center(atom)): abs_shell_idx = basisset.shell_on_center(atom, rel_shell_idx) shell = basisset.shell(abs_shell_idx) mol_string += f" {shell.amchar:s}{shell.nprimitive:5d} 1.00\n" for prim in range(shell.nprimitive): mol_string += f"{shell.exp(prim):20.10f} {shell.original_coef(prim):20.10f}\n" mol_string += '\n' # if use_natural: # Alphas nmopi = self.nmopi() #MO_Da = core.Matrix("MO Alpha Density Matrix", nmopi, nmopi) #MO_Da.transform(self.Da(), self.Ca().transpose()) MO_Da = self.Da_subset("MO") #MO_Da.transform(self.Da(), self.Ca()) NO_Ra = core.Matrix("NO Alpha Rotation Matrix", nmopi, nmopi) occupation_a = core.Vector(nmopi) MO_Da.diagonalize(NO_Ra, occupation_a, core.DiagonalizeOrder.Descending) Ca = core.doublet(self.Ca(), NO_Ra, False, False) epsilon_a = occupation_a # Betas #MO_Db = core.Matrix("MO Beta Density Matrix", nmopi, nmopi) #MO_Db.transform(self.Db(), self.Cb().transpose()) MO_Db = self.Db_subset("MO") NO_Rb = core.Matrix("NO Beta Rotation Matrix", nmopi, nmopi) occupation_b = core.Vector(nmopi) MO_Db.diagonalize(NO_Rb, occupation_b, core.DiagonalizeOrder.Descending) Cb = core.doublet(self.Cb(), NO_Rb, False, False) epsilon_b = occupation_b else: Ca = self.Ca() Cb = self.Cb() occupation_a = self.occupation_a() occupation_b = self.occupation_b() epsilon_a = self.epsilon_a() epsilon_b = self.epsilon_b() # Convert C matrices to AO MO basis. Ca_subset costs information about which symmetry an orbital originally had, which is why we can't use it. aotoso = self.aotoso() Ca_ao_mo = core.doublet(aotoso, Ca, False, False).nph Cb_ao_mo = core.doublet(aotoso, Cb, False, False).nph ao_overlap = self.mintshelper().ao_overlap().np # Convert from Psi4 internal normalization to the unit normalization expected by Molden ao_normalizer = ao_overlap.diagonal()**(-1 / 2) Ca_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T for i in Ca_ao_mo]) Cb_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T for i in Cb_ao_mo]) # Reorder AO x MO matrix to fit Molden conventions ''' Reordering expected by Molden P: x, y, z 5D: D 0, D+1, D-1, D+2, D-2 6D: xx, yy, zz, xy, xz, yz 7F: F 0, F+1, F-1, F+2, F-2, F+3, F-3 10F: xxx, yyy, zzz, xyy, xxy, xxz, xzz, yzz, yyz, xyz 9G: G 0, G+1, G-1, G+2, G-2, G+3, G-3, G+4, G-4 15G: xxxx, yyyy, zzzz, xxxy, xxxz, yyyz, zzzx, zzzy, xxyy, xxzz, yyzz, xxyz, yyxz, zzxy Molden does not handle angular momenta higher than G ''' molden_cartesian_order = [ [2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # p [0, 3, 4, 1, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], # d [0, 4, 5, 3, 9, 6, 1, 8, 7, 2, 0, 0, 0, 0, 0], # f [0, 3, 4, 9, 12, 10, 5, 13, 14, 7, 1, 6, 11, 8, 2] # g ] nirrep = self.nirrep() count = 0 # Keeps track of count for reordering temp_a = Ca_ao_mo.clone() # Placeholders for original AO x MO matrices temp_b = Cb_ao_mo.clone() for i in range(basisset.nshell()): am = basisset.shell(i).am if (am == 1 and basisset.has_puream()) or ( am > 1 and am < 5 and basisset.shell(i).is_cartesian()): for j in range(basisset.shell(i).nfunction): for h in range(nirrep): for k in range(Ca_ao_mo.coldim()[h]): Ca_ao_mo.set(h, count + molden_cartesian_order[am - 1][j], k, temp_a.get(h, count + j, k)) Cb_ao_mo.set(h, count + molden_cartesian_order[am - 1][j], k, temp_b.get(h, count + j, k)) count += basisset.shell(i).nfunction # Dump MO information if basisset.has_puream(): # For historical reasons, D and F can go on the same line, but setting D without F implicitly sets F. G must be on its own. mol_string += '[5D7F]\n[9G]\n\n' ct = mol.point_group().char_table() mol_string += '[MO]\n' mo_dim = self.nmopi() if do_virtual else (self.doccpi() + self.soccpi()) # Alphas. If Alphas and Betas are the same, then only Alphas with double occupation will be written (see line marked "***") mos = [] for h in range(nirrep): for n in range(mo_dim[h]): mos.append((epsilon_a.get(h, n), (h, n))) # Sort mos based on energy def mosSort(element): return element[0] mos.sort(key=mosSort) for i in range(len(mos)): h, n = mos[i][1] mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_a.get(h, n):24.10e}\n Spin= Alpha\n" if self.same_a_b_orbs() and self.epsilon_a() == self.epsilon_b( ) and self.same_a_b_dens(): mol_string += f" Occup= {occupation_a.get(h, n) + occupation_b.get(h, n):24.10e}\n" else: mol_string += f" Occup= {occupation_a.get(h, n):24.10e}\n" for so in range(self.nso()): mol_string += f"{so+1:3d} {Ca_ao_mo.get(h, so, n):24.10e}\n" # Betas mos = [] if not self.same_a_b_orbs( ) or self.epsilon_a() != self.epsilon_b() or not self.same_a_b_dens(): for h in range(nirrep): for n in range(mo_dim[h]): mos.append((self.epsilon_b().get(h, n), (h, n))) mos.sort(key=mosSort) for i in range(len(mos)): h, n = mos[i][1] mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_b.get(h, n):24.10e}\n Spin= Beta\n " \ f"Occup= {occupation_b.get(h, n):24.10e}\n" for so in range(self.nso()): mol_string += f"{so+1:3d} {Cb_ao_mo.get(h, so, n):24.10e}\n" # Write Molden string to file with open(filename, 'w') as fn: fn.write(mol_string)
def _core_wavefunction_get_scratch_filename(self, filenumber): """ Given a wavefunction and a scratch file number, canonicalizes the name so that files can be consistently written and read """ fname = os.path.split(os.path.abspath(core.get_writer_file_prefix(self.molecule().name())))[1] psi_scratch = core.IOManager.shared_object().get_default_path() return os.path.join(psi_scratch, fname + '.' + str(filenumber))