def make_hn( hcount, distance ) -> Tuple[RestrictedHartreeFockObjective, of.MolecularData, np.ndarray, np.ndarray, np.ndarray]: shcount = str(hcount) sdistance = str(distance) cwd = os.path.dirname(os.path.realpath(__file__)) hn_path = str( Path(cwd + "/molecular_data/hydrogen_chains/h_" + shcount + "_sto-3g/bond_distance_" + sdistance + "/")) hdf5name = str("H" + shcount + "_sto-3g_singlet_linear_r-" + sdistance + ".hdf5") molfile = os.path.join(hn_path, hdf5name) molecule = of.MolecularData(filename=molfile) molecule.load() S = np.load(os.path.join(hn_path, 'overlap.npy')) Hcore = np.load(os.path.join(hn_path, 'h_core.npy')) TEI = np.load(os.path.join(hn_path, 'tei.npy')) _, X = sp.linalg.eigh(Hcore, S) obi = of.general_basis_change(Hcore, X, (1, 0)) tbi = np.einsum('psqr', of.general_basis_change(TEI, X, (1, 0, 1, 0))) molecular_hamiltonian = generate_hamiltonian(obi, tbi, molecule.nuclear_repulsion) rhf_objective = RestrictedHartreeFockObjective(molecular_hamiltonian, molecule.n_electrons) scipy_result = rhf_minimization(rhf_objective) return rhf_objective, molecule, scipy_result.x, obi, tbi
def make_h3_2_5() -> Tuple[RestrictedHartreeFockObjective, of.MolecularData, np.ndarray, np.ndarray, np.ndarray]: # load the molecule from moelcular data h3_2_5_path = os.path.join( hfvqe.__path__[0], 'molecular_data/hydrogen_chains/h_3_p_sto-3g/bond_distance_2.5') molfile = os.path.join(h3_2_5_path, 'H3_plus_sto-3g_singlet_linear_r-2.5.hdf5') molecule = of.MolecularData(filename=molfile) molecule.load() S = np.load(os.path.join(h3_2_5_path, 'overlap.npy')) Hcore = np.load(os.path.join(h3_2_5_path, 'h_core.npy')) TEI = np.load(os.path.join(h3_2_5_path, 'tei.npy')) _, X = sp.linalg.eigh(Hcore, S) obi = of.general_basis_change(Hcore, X, (1, 0)) tbi = np.einsum('psqr', of.general_basis_change(TEI, X, (1, 0, 1, 0))) molecular_hamiltonian = generate_hamiltonian(obi, tbi, molecule.nuclear_repulsion) rhf_objective = RestrictedHartreeFockObjective(molecular_hamiltonian, molecule.n_electrons) scipy_result = rhf_minimization(rhf_objective) return rhf_objective, molecule, scipy_result.x, obi, tbi
def test_rhf_min(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) overlap = molecule.overlap_integrals mo_obi = molecule.one_body_integrals mo_tbi = molecule.two_body_integrals rotation_mat = molecule.canonical_orbitals.T.dot(overlap) obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) hff = HartreeFockFunctional(one_body_integrals=obi, two_body_integrals=tbi, overlap=overlap, n_electrons=molecule.n_electrons, model='rhf', nuclear_repulsion=molecule.nuclear_repulsion) result = rhf_minimization(hff) assert isinstance(result, OptimizeResult) result2 = rhf_minimization(hff, initial_guess=np.array([0]), sp_options={ 'maxiter': 100, 'disp': False }) assert isinstance(result2, OptimizeResult) assert np.isclose(result2.fun, result.fun)
def make_h3_2_5(molecular_data_directory=None) \ -> Tuple[RestrictedHartreeFockObjective, of.MolecularData, np.ndarray, np.ndarray, np.ndarray]: if molecular_data_directory is None: molecular_data_directory = _MOLECULAR_DATA_DIRECTORY h3_2_5_path = f'{molecular_data_directory}/hydrogen_chains/h_3_p_sto-3g/bond_distance_2.5' molfile = f'{h3_2_5_path}/H3_plus_sto-3g_singlet_linear_r-2.5.hdf5' molecule = of.MolecularData(filename=molfile) molecule.load() S = np.load(os.path.join(h3_2_5_path, 'overlap.npy')) Hcore = np.load(os.path.join(h3_2_5_path, 'h_core.npy')) TEI = np.load(os.path.join(h3_2_5_path, 'tei.npy')) _, X = sp.linalg.eigh(Hcore, S) obi = of.general_basis_change(Hcore, X, (1, 0)) tbi = np.einsum('psqr', of.general_basis_change(TEI, X, (1, 0, 1, 0))) molecular_hamiltonian = generate_hamiltonian(obi, tbi, molecule.nuclear_repulsion) rhf_objective = RestrictedHartreeFockObjective(molecular_hamiltonian, molecule.n_electrons) scipy_result = rhf_minimization(rhf_objective) return rhf_objective, molecule, scipy_result.x, obi, tbi
def test_gradient_lih(): filename = os.path.join(DATA_DIRECTORY, "H1-Li1_sto-3g_singlet_1.45.hdf5") molecule = MolecularData(filename=filename) overlap = molecule.overlap_integrals mo_obi = molecule.one_body_integrals mo_tbi = molecule.two_body_integrals rotation_mat = molecule.canonical_orbitals.T.dot(overlap) obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) hff = HartreeFockFunctional(one_body_integrals=obi, two_body_integrals=tbi, overlap=overlap, n_electrons=molecule.n_electrons, model='rhf', nuclear_repulsion=molecule.nuclear_repulsion) params = np.random.randn(hff.nocc * hff.nvirt) u = sp.linalg.expm( rhf_params_to_matrix(params, hff.num_orbitals, occ=hff.occ, virt=hff.virt)) grad_dim = hff.nocc * hff.nvirt initial_opdm = np.diag([1] * hff.nocc + [0] * hff.nvirt) final_opdm = u.dot(initial_opdm).dot(u.conj().T) grad = hff.rhf_global_gradient(params, final_opdm) # get finite difference gradient finite_diff_grad = np.zeros(grad_dim) epsilon = 0.0001 for i in range(grad_dim): params_epsilon = params.copy() params_epsilon[i] += epsilon u = sp.linalg.expm( rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt)) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_plus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) params_epsilon[i] -= 2 * epsilon u = sp.linalg.expm( rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt)) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_minus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) finite_diff_grad[i] = (energy_plus_epsilon - energy_minus_epsilon) / (2 * epsilon) assert np.allclose(finite_diff_grad, grad, atol=epsilon)
def make_rhf_objective(molecule: of.MolecularData): S, Hcore, TEI = get_ao_integrals(molecule) _, X = sp.linalg.eigh(Hcore, S) obi = of.general_basis_change(Hcore, X, (1, 0)) tbi = np.einsum('psqr', of.general_basis_change(TEI, X, (1, 0, 1, 0))) molecular_hamiltonian = generate_hamiltonian(obi, tbi, molecule.nuclear_repulsion) rhf_objective = RestrictedHartreeFockObjective(molecular_hamiltonian, molecule.n_electrons) return rhf_objective, S, Hcore, TEI, obi, tbi
def test_rhf_func_generator(): filename = os.path.join(DATA_DIRECTORY, "H1-Li1_sto-3g_singlet_1.45.hdf5") molecule = MolecularData(filename=filename) overlap = molecule.overlap_integrals mo_obi = molecule.one_body_integrals mo_tbi = molecule.two_body_integrals rotation_mat = molecule.canonical_orbitals.T.dot(overlap) obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) hff = HartreeFockFunctional(one_body_integrals=obi, two_body_integrals=tbi, overlap=overlap, n_electrons=molecule.n_electrons, model='rhf', nuclear_repulsion=molecule.nuclear_repulsion) unitary, energy, gradient = rhf_func_generator(hff) assert isinstance(unitary, Callable) assert isinstance(energy, Callable) assert isinstance(gradient, Callable) params = np.random.randn(hff.nocc * hff.nvirt) u = unitary(params) assert np.allclose(u.conj().T.dot(u), np.eye(hff.num_orbitals)) assert isinstance(energy(params), float) assert isinstance(gradient(params), np.ndarray) _, _, _, opdm_func = rhf_func_generator(hff, get_opdm_func=True) assert isinstance(opdm_func, Callable) assert isinstance(opdm_func(params), np.ndarray) assert np.isclose(opdm_func(params).shape[0], hff.num_orbitals) _, energy, _ = rhf_func_generator(hff, init_occ_vec=np.array([1, 1, 1, 1, 0, 0])) assert isinstance(energy(params), float)
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)