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_variable( cls: core.Wavefunction, key: str) -> Union[float, core.Matrix, np.ndarray]: """Return copy of scalar or array QCVariable *key* from *self* :class:`psi4.core.Wavefunction`. Returns ------- float or numpy.ndarray or Matrix Scalar variables are returned as floats. Array variables not naturally 2D (like multipoles) are returned as :class:`numpy.ndarray` of natural dimensionality. Other array variables are returned as :py:class:`~psi4.core.Matrix` and may have an extra dimension with symmetry information. Example ------- >>> g, wfn = psi4.gradient("hf/cc-pvdz", return_wfn=True) >>> wfn.variable("CURRENT ENERGY") -100.00985995185668 >>> wfn.variable("CURRENT DIPOLE") array([ 0. , 0. , -0.83217802]) >>> wfn.variable("CURRENT GRADIENT") <psi4.core.Matrix object at 0x12d884fc0> >>> wfn.variable("CURRENT GRADIENT").np array([[ 6.16297582e-33, 6.16297582e-33, -9.41037138e-02], [-6.16297582e-33, -6.16297582e-33, 9.41037138e-02]]) """ key = _qcvar_warnings(key) if cls.has_scalar_variable(key): return cls.scalar_variable(key) elif cls.has_array_variable(key): return _qcvar_reshape_get(key, cls.array_variable(key)) else: raise KeyError("psi4.core.Wavefunction.variable: Requested variable " + key + " was not set!\n")
def compute_gradient(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> core.Matrix: """Compute dispersion gradient based on engine, dispersion level, and parameters in `self`. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- Matrix (nat, 3) dispersion gradient [Eh/a0]. """ if self.engine in ['dftd3', 'mp2d']: resi = AtomicInput( **{ 'driver': 'gradient', 'model': { 'method': self.fctldash, 'basis': '(auto)', }, 'keywords': { 'level_hint': self.dashlevel, 'params_tweaks': self.dashparams, 'dashcoeff_supplement': self.dashcoeff_supplement, 'verbose': 1, }, 'molecule': molecule.to_schema(dtype=2), 'provenance': p4util.provenance_stamp(__name__), }) jobrec = qcng.compute( resi, self.engine, raise_error=True, local_options={"scratch_directory": core.IOManager.shared_object().get_default_path()}) dashd_part = core.Matrix.from_array(jobrec.extras['qcvars']['DISPERSION CORRECTION GRADIENT']) if wfn is not None: for k, qca in jobrec.extras['qcvars'].items(): if "CURRENT" not in k: wfn.set_variable(k, float(qca) if isinstance(qca, str) else qca) if self.fctldash in ['hf3c', 'pbeh3c']: jobrec = qcng.compute( resi, "gcp", raise_error=True, local_options={"scratch_directory": core.IOManager.shared_object().get_default_path()}) gcp_part = core.Matrix.from_array(jobrec.return_result) dashd_part.add(gcp_part) return dashd_part else: return self.disp.compute_gradient(molecule)
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 _core_wavefunction_set_variable( cls: core.Wavefunction, key: str, val: Union[core.Matrix, np.ndarray, float]) -> None: """Sets scalar or array QCVariable *key* to *val* on *cls*.""" if isinstance(val, core.Matrix): if cls.has_scalar_variable(key): raise ValidationError( "psi4.core.Wavefunction.set_variable: Target variable " + key + " already a scalar variable!") else: cls.set_array_variable(key, val) elif isinstance(val, np.ndarray): if cls.has_scalar_variable(key): raise ValidationError( "psi4.core.Wavefunction.set_variable: Target variable " + key + " already a scalar variable!") else: cls.set_array_variable( key, core.Matrix.from_array(_qcvar_reshape_set(key, val))) else: if cls.has_array_variable(key): raise ValidationError( "psi4.core.Wavefunction.set_variable: Target variable " + key + " already an array variable!") else: cls.set_scalar_variable(key, val)
def compute_hessian(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> core.Matrix: """Compute dispersion Hessian based on engine, dispersion level, and parameters in `self`. Uses finite difference, as no dispersion engine has analytic second derivatives. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- Matrix (3*nat, 3*nat) dispersion Hessian [Eh/a0/a0]. """ optstash = p4util.OptionsState(['PRINT'], ['PARENT_SYMMETRY']) core.set_global_option('PRINT', 0) core.print_out( "\n\n Analytical Dispersion Hessians are not supported by dftd3 or gcp.\n" ) core.print_out( " Computing the Hessian through finite difference of gradients.\n\n" ) # Setup the molecule molclone = molecule.clone() molclone.reinterpret_coordentry(False) molclone.fix_orientation(True) molclone.fix_com(True) # Record undisplaced symmetry for projection of diplaced point groups core.set_global_option("PARENT_SYMMETRY", molecule.schoenflies_symbol()) findif_meta_dict = driver_findif.hessian_from_gradients_geometries( molclone, -1) for displacement in findif_meta_dict["displacements"].values(): geom_array = np.reshape(displacement["geometry"], (-1, 3)) molclone.set_geometry(core.Matrix.from_array(geom_array)) molclone.update_geometry() displacement["gradient"] = self.compute_gradient( molclone).np.ravel().tolist() H = driver_findif.assemble_hessian_from_gradients(findif_meta_dict, -1) if wfn is not None: wfn.set_variable('DISPERSION CORRECTION HESSIAN', H) optstash.restore() return core.Matrix.from_array(H)
def _core_wavefunction_del_variable(cls: core.Wavefunction, key: str) -> None: """Removes scalar or array QCVariable *key* from *cls* if present.""" if cls.has_scalar_variable(key): cls.del_scalar_variable(key) elif cls.has_array_variable(key): cls.del_array_variable(key)
def _core_wavefunction_has_variable(cls: core.Wavefunction, key: str) -> bool: """Whether scalar or array QCVariable *key* has been set on *self* :class:`psi4.core.Wavefunction`.""" return cls.has_scalar_variable(key) or cls.has_array_variable(key)
def _core_wavefunction_to_file(wfn: core.Wavefunction, filename: str = None) -> Dict: """Converts a Wavefunction object to a base class Parameters ---------- wfn A Wavefunction or inherited class filename An optional filename to write the data to Returns ------- dict A dictionary and NumPy representation of the Wavefunction. """ # collect the wavefunction's variables in a dictionary indexed by varaible type # some of the data types have to be made numpy-friendly first if wfn.basisset().name().startswith("anonymous"): raise ValidationError( "Cannot serialize wavefunction with custom basissets.") wfn_data = { 'molecule': wfn.molecule().to_dict(), 'matrix': { 'Ca': wfn.Ca().to_array() if wfn.Ca() else None, 'Cb': wfn.Cb().to_array() if wfn.Cb() else None, 'Da': wfn.Da().to_array() if wfn.Da() else None, 'Db': wfn.Db().to_array() if wfn.Db() else None, 'Fa': wfn.Fa().to_array() if wfn.Fa() else None, 'Fb': wfn.Fb().to_array() if wfn.Fb() else None, 'H': wfn.H().to_array() if wfn.H() else None, 'S': wfn.S().to_array() if wfn.S() else None, 'X': wfn.lagrangian().to_array() if wfn.lagrangian() else None, 'aotoso': wfn.aotoso().to_array() if wfn.aotoso() else None, 'gradient': wfn.gradient().to_array() if wfn.gradient() else None, 'hessian': wfn.hessian().to_array() if wfn.hessian() else None }, 'vector': { 'epsilon_a': wfn.epsilon_a().to_array() if wfn.epsilon_a() else None, 'epsilon_b': wfn.epsilon_b().to_array() if wfn.epsilon_b() else None, 'frequencies': wfn.frequencies().to_array() if wfn.frequencies() else None }, 'dimension': { 'doccpi': wfn.doccpi().to_tuple(), 'frzcpi': wfn.frzcpi().to_tuple(), 'frzvpi': wfn.frzvpi().to_tuple(), 'nalphapi': wfn.nalphapi().to_tuple(), 'nbetapi': wfn.nbetapi().to_tuple(), 'nmopi': wfn.nmopi().to_tuple(), 'nsopi': wfn.nsopi().to_tuple(), 'soccpi': wfn.soccpi().to_tuple() }, 'int': { 'nalpha': wfn.nalpha(), 'nbeta': wfn.nbeta(), 'nfrzc': wfn.nfrzc(), 'nirrep': wfn.nirrep(), 'nmo': wfn.nmo(), 'nso': wfn.nso(), 'print': wfn.get_print(), }, 'string': { 'name': wfn.name(), 'module': wfn.module(), 'basisname': wfn.basisset().name() }, 'boolean': { 'PCM_enabled': wfn.PCM_enabled(), 'same_a_b_dens': wfn.same_a_b_dens(), 'same_a_b_orbs': wfn.same_a_b_orbs(), 'density_fitted': wfn.density_fitted(), 'basispuream': wfn.basisset().has_puream() }, 'float': { 'energy': wfn.energy(), 'efzc': wfn.efzc(), 'dipole_field_x': wfn.get_dipole_field_strength()[0], 'dipole_field_y': wfn.get_dipole_field_strength()[1], 'dipole_field_z': wfn.get_dipole_field_strength()[2] }, 'floatvar': wfn.scalar_variables(), 'matrixarr': {k: v.to_array() for k, v in wfn.array_variables().items()} } # yapf: disable if filename is not None: if not filename.endswith('.npy'): filename += '.npy' np.save(filename, wfn_data, allow_pickle=True) return wfn_data
def compute_energy(self, molecule: core.Molecule, wfn: core.Wavefunction = None) -> float: """Compute dispersion energy based on engine, dispersion level, and parameters in `self`. Parameters ---------- molecule System for which to compute empirical dispersion correction. wfn Location to set QCVariables Returns ------- float Dispersion energy [Eh]. Notes ----- :psivar:`DISPERSION CORRECTION ENERGY` Disp always set. Overridden in SCF finalization, but that only changes for "-3C" methods. :psivar:`fctl DISPERSION CORRECTION ENERGY` Set if :py:attr:`fctldash` nonempty. """ if self.engine in ['dftd3', 'mp2d']: resi = AtomicInput( **{ 'driver': 'energy', 'model': { 'method': self.fctldash, 'basis': '(auto)', }, 'keywords': { 'level_hint': self.dashlevel, 'params_tweaks': self.dashparams, 'dashcoeff_supplement': self.dashcoeff_supplement, 'pair_resolved': self.save_pairwise_disp, 'verbose': 1, }, 'molecule': molecule.to_schema(dtype=2), 'provenance': p4util.provenance_stamp(__name__), }) jobrec = qcng.compute( resi, self.engine, raise_error=True, local_options={ "scratch_directory": core.IOManager.shared_object().get_default_path() }) dashd_part = float( jobrec.extras['qcvars']['DISPERSION CORRECTION ENERGY']) if wfn is not None: for k, qca in jobrec.extras['qcvars'].items(): wfn.set_variable( k, float(qca) if isinstance(qca, str) else qca) # Pass along the pairwise dispersion decomposition if we need it if self.save_pairwise_disp is True: wfn.set_variable( "PAIRWISE DISPERSION CORRECTION ANALYSIS", jobrec.extras['qcvars'] ["2-BODY PAIRWISE DISPERSION CORRECTION ANALYSIS"]) if self.fctldash in ['hf3c', 'pbeh3c']: jobrec = qcng.compute( resi, "gcp", raise_error=True, local_options={ "scratch_directory": core.IOManager.shared_object().get_default_path() }) gcp_part = jobrec.return_result dashd_part += gcp_part return dashd_part else: ene = self.disp.compute_energy(molecule) core.set_variable('DISPERSION CORRECTION ENERGY', ene) if self.fctldash: core.set_variable( f"{self.fctldash} DISPERSION CORRECTION ENERGY", ene) return ene
def fcidump(wfn: core.Wavefunction, fname: str = 'INTDUMP', oe_ints: Optional[List] = None): """Save integrals to file in FCIDUMP format as defined in Comp. Phys. Commun. 54 75 (1989), https://doi.org/10.1016/0010-4655(89)90033-7 . Additional one-electron integrals, including orbital energies, can also be saved. This latter format can be used with the HANDE QMC code but is not standard. Parameters ---------- wfn Set of molecule, basis, orbitals from which to generate FCIDUMP file. fname Name of the integrals file, defaults to INTDUMP. oe_ints List of additional one-electron integrals to save to file. So far only EIGENVALUES is a valid option. Raises ------ ValidationError When SCF wavefunction is not RHF. Examples -------- >>> # [1] Save one- and two-electron integrals to standard FCIDUMP format >>> E, wfn = energy('scf', return_wfn=True) >>> fcidump(wfn) >>> # [2] Save orbital energies, one- and two-electron integrals. >>> E, wfn = energy('scf', return_wfn=True) >>> fcidump(wfn, oe_ints=['EIGENVALUES']) """ # Get some options reference = core.get_option('SCF', 'REFERENCE') ints_tolerance = core.get_global_option('INTS_TOLERANCE') # Some sanity checks if reference not in ['RHF', 'UHF']: raise ValidationError( 'FCIDUMP not implemented for {} references\n'.format(reference)) if oe_ints is None: oe_ints = [] molecule = wfn.molecule() docc = wfn.doccpi() frzcpi = wfn.frzcpi() frzvpi = wfn.frzvpi() active_docc = docc - frzcpi active_socc = wfn.soccpi() active_mopi = wfn.nmopi() - frzcpi - frzvpi nbf = active_mopi.sum() if wfn.same_a_b_orbs() else 2 * active_mopi.sum() nirrep = wfn.nirrep() nelectron = 2 * active_docc.sum() + active_socc.sum() irrep_map = _irrep_map(wfn) wfn_irrep = 0 for h, n_socc in enumerate(active_socc): if n_socc % 2 == 1: wfn_irrep ^= h core.print_out('Writing integrals in FCIDUMP format to ' + fname + '\n') # Generate FCIDUMP header header = '&FCI\n' header += 'NORB={:d},\n'.format(nbf) header += 'NELEC={:d},\n'.format(nelectron) header += 'MS2={:d},\n'.format(wfn.nalpha() - wfn.nbeta()) header += 'UHF=.{}.,\n'.format(not wfn.same_a_b_orbs()).upper() orbsym = '' for h in range(active_mopi.n()): for n in range(frzcpi[h], frzcpi[h] + active_mopi[h]): orbsym += '{:d},'.format(irrep_map[h]) if not wfn.same_a_b_orbs(): orbsym += '{:d},'.format(irrep_map[h]) header += 'ORBSYM={}\n'.format(orbsym) header += 'ISYM={:d},\n'.format(irrep_map[wfn_irrep]) header += '&END\n' with open(fname, 'w') as intdump: intdump.write(header) # Get an IntegralTransform object check_iwl_file_from_scf_type(core.get_global_option('SCF_TYPE'), wfn) spaces = [core.MOSpace.all()] trans_type = core.IntegralTransform.TransformationType.Restricted if not wfn.same_a_b_orbs(): trans_type = core.IntegralTransform.TransformationType.Unrestricted ints = core.IntegralTransform(wfn, spaces, trans_type) ints.transform_tei(core.MOSpace.all(), core.MOSpace.all(), core.MOSpace.all(), core.MOSpace.all()) core.print_out('Integral transformation complete!\n') DPD_info = { 'instance_id': ints.get_dpd_id(), 'alpha_MO': ints.DPD_ID('[A>=A]+'), 'beta_MO': 0 } if not wfn.same_a_b_orbs(): DPD_info['beta_MO'] = ints.DPD_ID("[a>=a]+") # Write TEI to fname in FCIDUMP format core.fcidump_tei_helper(nirrep, wfn.same_a_b_orbs(), DPD_info, ints_tolerance, fname) # Read-in OEI and write them to fname in FCIDUMP format # Indexing functions to translate from zero-based (C and Python) to # one-based (Fortran) mo_idx = lambda x: x + 1 alpha_mo_idx = lambda x: 2 * x + 1 beta_mo_idx = lambda x: 2 * (x + 1) with open(fname, 'a') as intdump: core.print_out('Writing frozen core operator in FCIDUMP format to ' + fname + '\n') if reference == 'RHF': PSIF_MO_FZC = 'MO-basis Frozen-Core Operator' moH = core.Matrix(PSIF_MO_FZC, wfn.nmopi(), wfn.nmopi()) moH.load(core.IO.shared_object(), psif.PSIF_OEI) mo_slice = core.Slice(frzcpi, frzcpi + active_mopi) MO_FZC = moH.get_block(mo_slice, mo_slice) offset = 0 for h, block in enumerate(MO_FZC.nph): il = np.tril_indices(block.shape[0]) for index, x in np.ndenumerate(block[il]): row = mo_idx(il[0][index] + offset) col = mo_idx(il[1][index] + offset) if (abs(x) > ints_tolerance): intdump.write('{:28.20E}{:4d}{:4d}{:4d}{:4d}\n'.format( x, row, col, 0, 0)) offset += block.shape[0] # Additional one-electron integrals as requested in oe_ints # Orbital energies core.print_out('Writing orbital energies in FCIDUMP format to ' + fname + '\n') if 'EIGENVALUES' in oe_ints: eigs_dump = write_eigenvalues( wfn.epsilon_a().get_block(mo_slice).to_array(), mo_idx) intdump.write(eigs_dump) else: PSIF_MO_A_FZC = 'MO-basis Alpha Frozen-Core Oper' moH_A = core.Matrix(PSIF_MO_A_FZC, wfn.nmopi(), wfn.nmopi()) moH_A.load(core.IO.shared_object(), psif.PSIF_OEI) mo_slice = core.Slice(frzcpi, active_mopi) MO_FZC_A = moH_A.get_block(mo_slice, mo_slice) offset = 0 for h, block in enumerate(MO_FZC_A.nph): il = np.tril_indices(block.shape[0]) for index, x in np.ndenumerate(block[il]): row = alpha_mo_idx(il[0][index] + offset) col = alpha_mo_idx(il[1][index] + offset) if (abs(x) > ints_tolerance): intdump.write('{:28.20E}{:4d}{:4d}{:4d}{:4d}\n'.format( x, row, col, 0, 0)) offset += block.shape[0] PSIF_MO_B_FZC = 'MO-basis Beta Frozen-Core Oper' moH_B = core.Matrix(PSIF_MO_B_FZC, wfn.nmopi(), wfn.nmopi()) moH_B.load(core.IO.shared_object(), psif.PSIF_OEI) mo_slice = core.Slice(frzcpi, active_mopi) MO_FZC_B = moH_B.get_block(mo_slice, mo_slice) offset = 0 for h, block in enumerate(MO_FZC_B.nph): il = np.tril_indices(block.shape[0]) for index, x in np.ndenumerate(block[il]): row = beta_mo_idx(il[0][index] + offset) col = beta_mo_idx(il[1][index] + offset) if (abs(x) > ints_tolerance): intdump.write('{:28.20E}{:4d}{:4d}{:4d}{:4d}\n'.format( x, row, col, 0, 0)) offset += block.shape[0] # Additional one-electron integrals as requested in oe_ints # Orbital energies core.print_out('Writing orbital energies in FCIDUMP format to ' + fname + '\n') if 'EIGENVALUES' in oe_ints: alpha_eigs_dump = write_eigenvalues( wfn.epsilon_a().get_block(mo_slice).to_array(), alpha_mo_idx) beta_eigs_dump = write_eigenvalues( wfn.epsilon_b().get_block(mo_slice).to_array(), beta_mo_idx) intdump.write(alpha_eigs_dump + beta_eigs_dump) # Dipole integrals #core.print_out('Writing dipole moment OEI in FCIDUMP format to ' + fname + '\n') # Traceless quadrupole integrals #core.print_out('Writing traceless quadrupole moment OEI in FCIDUMP format to ' + fname + '\n') # Frozen core + nuclear repulsion energy core.print_out( 'Writing frozen core + nuclear repulsion energy in FCIDUMP format to ' + fname + '\n') e_fzc = ints.get_frozen_core_energy() e_nuc = molecule.nuclear_repulsion_energy( wfn.get_dipole_field_strength()) intdump.write('{:28.20E}{:4d}{:4d}{:4d}{:4d}\n'.format( e_fzc + e_nuc, 0, 0, 0, 0)) core.print_out( 'Done generating {} with integrals in FCIDUMP format.\n'.format(fname))