def do_make_molecule(self, *args, **kwargs) -> MolecularData: energy = self.compute_energy(method="hf", *args, **kwargs) wfn = self.logs['hf'].wfn molecule = MolecularData(**self.parameters.molecular_data_param) if wfn.nirrep() != 1: wfn = wfn.c1_deep_copy(wfn.basisset()) molecule.one_body_integrals = self.compute_one_body_integrals( ref_wfn=wfn) if "two_body_ordering" not in kwargs: molecule.two_body_integrals = self.compute_two_body_integrals( ref_wfn=wfn) else: molecule.two_body_integrals = self.compute_two_body_integrals( ref_wfn=wfn, ordering=kwargs["two_body_ordering"]) molecule.hf_energy = energy molecule.nuclear_repulsion = wfn.variables( )['NUCLEAR REPULSION ENERGY'] molecule.canonical_orbitals = numpy.asarray(wfn.Ca()) molecule.overlap_integrals = numpy.asarray(wfn.S()) molecule.n_orbitals = molecule.canonical_orbitals.shape[0] molecule.n_qubits = 2 * molecule.n_orbitals molecule.orbital_energies = numpy.asarray(wfn.epsilon_a()) molecule.fock_matrix = numpy.asarray(wfn.Fa()) molecule.save() return molecule
def test_moleculardata_to_restricted_hamiltonian(self): """ Convert an OpenFermion MolecularData object to a fqe.RestrictedHamiltonian """ h1e, h2e, _ = build_lih_data('energy') # dummy geometry geometry = [['Li', [0, 0, 0], ['H', [0, 0, 1.4]]]] charge = 0 multiplicity = 1 molecule = MolecularData(geometry=geometry, basis='sto-3g', charge=charge, multiplicity=multiplicity) molecule.one_body_integrals = h1e molecule.two_body_integrals = numpy.einsum('ijlk', -2 * h2e) molecule.nuclear_repulsion = 0 restricted_ham = openfermion_utils.molecular_data_to_restricted_fqe_op( molecule=molecule) h1e_test, h2e_test = restricted_ham.tensors() self.assertTrue(numpy.allclose(h1e_test, h1e)) self.assertTrue(numpy.allclose(h2e_test, h2e))
def get_ham_from_psi4( wfn, mints, ndocc=None, nact=None, nuclear_repulsion_energy=0, ): """Get a molecular Hamiltonian from a Psi4 calculation. Args: wfn (psi4.core.Wavefunction): Psi4 wavefunction object mints (psi4.core.MintsHelper): Psi4 molecular integrals helper ndocc (int): number of doubly occupied molecular orbitals to include in the saved Hamiltonian. nact (int): number of active molecular orbitals to include in the saved Hamiltonian. nuclear_repulsion_energy (float): The ion-ion interaction energy. Returns: hamiltonian (openfermion.ops.InteractionOperator): the electronic Hamiltonian. """ assert wfn.same_a_b_orbs(), ( "Extraction of Hamiltonian from wavefunction" + "with different alpha and beta orbitals not yet supported :(" ) orbitals = wfn.Ca().to_array(dense=True) if nact is None and ndocc is None: trf_mat = orbitals ndocc = 0 nact = orbitals.shape[1] print( f"Active space selection options were reset to: ndocc = {ndocc} and nact = {nact}" ) elif nact is not None and ndocc is None: assert nact <= orbitals.shape[1] ndocc = 0 trf_mat = orbitals[:, :nact] print( f"Active space selection options were reset to: ndocc = {ndocc} and nact = {nact}" ) elif ndocc is not None and nact is None: assert ndocc <= orbitals.shape[1] nact = orbitals.shape[1] - ndocc trf_mat = orbitals print( f"Active space selection options were reset to: ndocc = {ndocc} and nact = {nact}" ) else: assert orbitals.shape[1] >= nact + ndocc trf_mat = orbitals[:, : nact + ndocc] # Note: code refactored to use Psi4 integral-transformation routines # no more storing the whole two-electron integral tensor when only an # active space is needed one_body_integrals = general_basis_change( np.asarray(mints.ao_kinetic()), orbitals, (1, 0) ) one_body_integrals += general_basis_change( np.asarray(mints.ao_potential()), orbitals, (1, 0) ) # Build the transformation matrices, i.e. the orbitals for which # we want the integrals, as Psi4.core.Matrix objects trf_mat = psi4.core.Matrix.from_array(trf_mat) two_body_integrals = np.asarray(mints.mo_eri(trf_mat, trf_mat, trf_mat, trf_mat)) n_orbitals = trf_mat.shape[1] two_body_integrals.reshape((n_orbitals, n_orbitals, n_orbitals, n_orbitals)) two_body_integrals = np.einsum("psqr", two_body_integrals) # Truncate one_body_integrals[np.absolute(one_body_integrals) < EQ_TOLERANCE] = 0.0 two_body_integrals[np.absolute(two_body_integrals) < EQ_TOLERANCE] = 0.0 occupied_indices = range(ndocc) active_indices = range(ndocc, ndocc + nact) # In order to keep the MolecularData class happy, we need a 'valid' molecule molecular_data = MolecularData( geometry=[("H", (0, 0, 0))], basis="", multiplicity=2 ) molecular_data.one_body_integrals = one_body_integrals molecular_data.two_body_integrals = two_body_integrals molecular_data.nuclear_repulsion = nuclear_repulsion_energy hamiltonian = molecular_data.get_molecular_hamiltonian( occupied_indices, active_indices ) return hamiltonian
def get_ham_from_psi4(wfn, mints, n_active_extract=None, freeze_core_extract=False, orbs=None, nuclear_repulsion_energy=0): """Get a molecular Hamiltonian from a Psi4 calculation. Args: wfn (psi4.core.Wavefunction): Psi4 wavefunction object mints (psi4.core.MintsHelper): Psi4 molecular integrals helper n_active_extract (int): number of molecular orbitals to include in the saved Hamiltonian. If None, includes all orbitals, else you must provide active orbitals in orbs. freeze_core_extract (bool): whether to freeze core orbitals as doubly occupied in the saved Hamiltonian. orbs (psi4.core.Matrix): Psi4 orbitals for active space transformations. Must include all occupied (also core in all cases). nuclear_repulsion_energy (float): The ion-ion interaction energy. Returns: hamiltonian (openfermion.ops.InteractionOperator): the electronic Hamiltonian. Note that the ion-ion electrostatic energy is not included. """ assert wfn.same_a_b_orbs(), "Extraction of Hamiltonian from wavefunction" + \ "with different alpha and beta orbitals not yet supported :(" # Note: code refactored to use Psi4 integral-transformation routines # no more storing the whole two-electron integral tensor when only an # active space is needed orbitals = wfn.Ca().to_array(dense=True) one_body_integrals = general_basis_change(np.asarray(mints.ao_kinetic()), orbitals, (1, 0)) one_body_integrals += general_basis_change( np.asarray(mints.ao_potential()), orbitals, (1, 0)) # Build the transformation matrices, i.e. the orbitals for which # we want the integrals, as Psi4.core.Matrix objects n_core_extract = 0 if freeze_core_extract: n_core_extract = wfn.nfrzc() if n_active_extract is None: trf_mat = wfn.Ca() n_active_extract = wfn.nmo() - n_core_extract else: # If orbs is given, it allows us to perform the two-electron integrals # transformation only in the space of active orbitals. Otherwise, we # transform all orbitals and filter them out in the get_ham_from_integrals # function if orbs is None: trf_mat = wfn.Ca() else: assert (orbs.to_array(dense=True).shape[1] == n_active_extract + n_core_extract) trf_mat = orbs two_body_integrals = np.asarray( mints.mo_eri(trf_mat, trf_mat, trf_mat, trf_mat)) n_orbitals = trf_mat.shape[1] two_body_integrals.reshape( (n_orbitals, n_orbitals, n_orbitals, n_orbitals)) two_body_integrals = np.einsum('psqr', two_body_integrals) # Truncate one_body_integrals[np.absolute(one_body_integrals) < EQ_TOLERANCE] = 0. two_body_integrals[np.absolute(two_body_integrals) < EQ_TOLERANCE] = 0. if n_active_extract is None and not freeze_core_extract: occupied_indices = None active_indices = None else: # Indices of occupied molecular orbitals occupied_indices = range(n_core_extract) # Indices of active molecular orbitals active_indices = range(n_core_extract, n_core_extract + n_active_extract) # In order to keep the MolecularData class happy, we need a 'valid' molecule molecular_data = MolecularData(geometry=[('H', (0, 0, 0))], basis='', multiplicity=2) molecular_data.one_body_integrals = one_body_integrals molecular_data.two_body_integrals = two_body_integrals molecular_data.nuclear_repulsion = nuclear_repulsion_energy hamiltonian = molecular_data.get_molecular_hamiltonian( occupied_indices, active_indices) return (hamiltonian)