def decompose(hf_file, mapping="jordan_wigner", core=None, active=None): r"""Decomposes the molecular Hamiltonian into a linear combination of Pauli operators using OpenFermion tools. This function uses OpenFermion functions to build the second-quantized electronic Hamiltonian of the molecule and map it to the Pauli basis using the Jordan-Wigner or Bravyi-Kitaev transformation. Args: hf_file (str): absolute path to the hdf5-formatted file with the Hartree-Fock electronic structure mapping (str): Specifies the transformation to map the fermionic Hamiltonian to the Pauli basis. Input values can be ``'jordan_wigner'`` or ``'bravyi_kitaev'``. core (list): indices of core orbitals, i.e., the orbitals that are not correlated in the many-body wave function active (list): indices of active orbitals, i.e., the orbitals used to build the correlated many-body wave function Returns: QubitOperator: an instance of OpenFermion's ``QubitOperator`` **Example** >>> decompose('./pyscf/sto-3g/h2', mapping='bravyi_kitaev') (-0.04207897696293986+0j) [] + (0.04475014401986122+0j) [X0 Z1 X2] + (0.04475014401986122+0j) [X0 Z1 X2 Z3] +(0.04475014401986122+0j) [Y0 Z1 Y2] + (0.04475014401986122+0j) [Y0 Z1 Y2 Z3] +(0.17771287459806262+0j) [Z0] + (0.17771287459806265+0j) [Z0 Z1] +(0.1676831945625423+0j) [Z0 Z1 Z2] + (0.1676831945625423+0j) [Z0 Z1 Z2 Z3] +(0.12293305054268105+0j) [Z0 Z2] + (0.12293305054268105+0j) [Z0 Z2 Z3] +(0.1705973832722409+0j) [Z1] + (-0.2427428049645989+0j) [Z1 Z2 Z3] +(0.1762764080276107+0j) [Z1 Z3] + (-0.2427428049645989+0j) [Z2] """ # loading HF data from the hdf5 file molecule = MolecularData(filename=hf_file.strip()) # getting the terms entering the second-quantized Hamiltonian terms_molecular_hamiltonian = molecule.get_molecular_hamiltonian( occupied_indices=core, active_indices=active ) # generating the fermionic Hamiltonian fermionic_hamiltonian = get_fermion_operator(terms_molecular_hamiltonian) mapping = mapping.strip().lower() if mapping not in ("jordan_wigner", "bravyi_kitaev"): raise TypeError( "The '{}' transformation is not available. \n " "Please set 'mapping' to 'jordan_wigner' or 'bravyi_kitaev'.".format(mapping) ) # fermionic-to-qubit transformation of the Hamiltonian if mapping == "bravyi_kitaev": return bravyi_kitaev(fermionic_hamiltonian) return jordan_wigner(fermionic_hamiltonian)
MolecularMeta = {} molecule = MolecularData(geometry, basis, multiplicity, description=str(round(bond_length, 2))) molecule = run_pyscf(molecule, run_scf=run_scf) # Basic information jim = molecule.name print("===== Molecule {} =====".format(jim)) MolecularMeta['Name'] = jim MolecularMeta['Geo'] = bond_length # Hamiltonian ham_qubit = jordan_wigner(molecule.get_molecular_hamiltonian()) ham_qubit.compress() # Now in QubitOperator form ham_sparse = qubit_operator_sparse(ham_qubit, n_qubits=molecule.n_qubits) print(' HF:', molecule.hf_energy) MolecularMeta['FCI eigenstates'] = run_FCI(molecule, vqd_maxiter + 8) # Generate operator pool if not isinstance(SymbolicAnsatz, Circuit): pool.init(n_orb=molecule.n_orbitals, n_occ=molecule.get_n_alpha_electrons(), n_vir=molecule.n_orbitals - molecule.get_n_alpha_electrons()) opt_eigen_ops = [ ] # list of sublists (index of operators used in each eigenstate ansatz) opt_eigen_params = [
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)