From 69ccb0ddd4e041ee78994f90c98a2d4ec610e841 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:09:04 +0000 Subject: [PATCH 01/11] Minor changes to default values and documentation --- src/qibochem/driver/molecule.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qibochem/driver/molecule.py b/src/qibochem/driver/molecule.py index a5995b4..f17930a 100644 --- a/src/qibochem/driver/molecule.py +++ b/src/qibochem/driver/molecule.py @@ -72,7 +72,7 @@ def __init__(self, geometry=None, charge=0, multiplicity=1, basis=None, xyz_file # For HF embedding self.active = active #: Iterable of molecular orbitals included in the active space - self.frozen = None + self.frozen = None #: Iterable representing the occupied molecular orbitals removed from the simulation self.inactive_energy = None self.embed_oei = None @@ -341,11 +341,11 @@ def hamiltonian( if ham_type is None: ham_type = "sym" if oei is None: - oei = self.oei + oei = self.oei if self.embed_oei is None else self.embed_oei if tei is None: - tei = self.tei + tei = self.tei if self.embed_tei is None else self.embed_tei if constant is None: - constant = 0.0 + constant = 0.0 if self.inactive_energy is None else self.inactive_energy if ferm_qubit_map is None: ferm_qubit_map = "jw" From 97965512b5f3119d677634d0a8e33d4afc89e6b2 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:34:48 +0000 Subject: [PATCH 02/11] Streamline current HF embedding testing --- tests/test_molecule.py | 556 ++++++++++++++++++++--------------------- 1 file changed, 276 insertions(+), 280 deletions(-) diff --git a/tests/test_molecule.py b/tests/test_molecule.py index e3038b1..d587585 100644 --- a/tests/test_molecule.py +++ b/tests/test_molecule.py @@ -1,280 +1,276 @@ -""" -Test Molecule class functions -""" - -from pathlib import Path - -import numpy as np -import openfermion -import pytest -from qibo import gates, models -from qibo.hamiltonians import SymbolicHamiltonian -from qibo.symbols import X, Y, Z - -from qibochem.driver import hamiltonian -from qibochem.driver.molecule import Molecule -from qibochem.measurement.expectation import expectation - - -def test_run_pyscf(): - """PySCF driver""" - # Hardcoded benchmark results - # Change to run PySCF directly during a test? - h2_ref_energy = -1.117349035 - h2_ref_hcore = np.array([[-1.14765024, -1.00692423], [-1.00692423, -1.14765024]]) - - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.run_pyscf() - assert h2.e_hf == pytest.approx(h2_ref_energy) - assert np.allclose(h2.hcore, h2_ref_hcore) - - -def test_run_pyscf_molecule_xyz(): - """Pyscf driver with xyz file""" - file_path = Path("tests", "data", "lih.xyz") - if not file_path.is_file(): - with open(file_path, "w") as file_handler: - file_handler.write("2\n0 1\nLi 0.0 0.0 0.0\nH 0.0 0.0 1.2\n") - lih_ref_energy = -7.83561582555692 - lih = Molecule(xyz_file=file_path) - lih.run_pyscf() - - assert lih.e_hf == pytest.approx(lih_ref_energy) - - -def test_run_pyscf_molecule_xyz_charged(): - file_path = Path("tests", "data", "h2.xyz") - if not file_path.is_file(): - with open(file_path, "w") as file_handler: - file_handler.write("2\n \nH 0.0 0.0 0.0\nH 0.0 0.0 0.7\n") - h2_ref_energy = -1.117349035 - h2 = Molecule(xyz_file=file_path) - h2.run_pyscf() - - assert h2.e_hf == pytest.approx(h2_ref_energy) - - -def test_molecule_custom_basis(): - mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))], 0, 1, "6-31g") - mol.run_pyscf() - assert np.isclose(mol.e_hf, -7.94129296352493) - - -def test_hf_embedding_1(): - mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) - mol.run_pyscf() - ref_oei = mol.oei - ref_tei = mol.tei - mol.hf_embedding() - embed_oei = mol.embed_oei - embed_tei = mol.embed_tei - assert np.allclose(embed_oei, ref_oei) - assert np.allclose(embed_tei, ref_tei) - - -def test_hf_embedding_2(): - mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) - mol.run_pyscf() - mol.frozen = [0] - mol.active = [1, 2] - mol.hf_embedding() - assert mol.n_active_orbs == 4 - assert mol.n_active_e == 2 - - -def test_fermionic_hamiltonian(): - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.run_pyscf() - h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) - - # a^\dagger_0 a_0 - ref_one_body_tensor = np.array( - [ - [-1.27785301, 0.0, 0.0, 0.0], - [0.0, -1.27785301, 0.0, 0.0], - [0.0, 0.0, -0.4482997, 0.0], - [0.0, 0.0, 0.0, -0.4482997], - ] - ) - - assert np.isclose(h2_ferm_ham[()], 0.7559674441714287) - assert np.allclose(h2_ferm_ham.one_body_tensor, ref_one_body_tensor) - - -def test_fermionic_hamiltonian_2(): - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7414))]) - h2.run_pyscf() - - h2_ferm_ham_1 = h2.hamiltonian("f", h2.oei, h2.tei) - h2_qub_ham_jw_1 = h2.hamiltonian("q", h2.oei, h2.tei, ferm_qubit_map="jw") - h2_qub_ham_bk_1 = h2.hamiltonian("q", h2.oei, h2.tei, ferm_qubit_map="bk") - # h2_mol_ham has format of InteractionOperator - h2_mol_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) - h2_ferm_ham_2 = openfermion.transforms.get_fermion_operator(h2_mol_ham) - h2_qub_ham_jw_2 = openfermion.jordan_wigner(h2_mol_ham) - h2_qub_ham_bk_2 = openfermion.bravyi_kitaev(h2_mol_ham) - - assert h2_ferm_ham_2.isclose(h2_ferm_ham_1) - assert h2_qub_ham_jw_2.isclose(h2_qub_ham_jw_1) - assert h2_qub_ham_bk_2.isclose(h2_qub_ham_bk_1) - - -def test_parse_pauli_string_1(): - pauli_string = ((0, "X"), (1, "Y")) - qibo_pauli_string = hamiltonian.parse_pauli_string(pauli_string, 0.5) - ref_pauli_string = "0.5*X0*Y1" - assert str(qibo_pauli_string) == ref_pauli_string - - -def test_parse_pauli_string_2(): - qibo_pauli_string = hamiltonian.parse_pauli_string(None, 0.1) - assert str(qibo_pauli_string) == "0.1" - - -def test_qubit_hamiltonian(): - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.run_pyscf() - h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) - h2_qubit_ham_jw = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "jw") - h2_qubit_ham_bk = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "bk") - ref_h2_qubit_ham_jw = { - (): -0.04207897647782238, - ((0, "Z"),): 0.17771287465139918, - ((1, "Z"),): 0.1777128746513992, - ((2, "Z"),): -0.24274280513140478, - ((3, "Z"),): -0.24274280513140478, - ((0, "Z"), (1, "Z")): 0.17059738328801052, - ((0, "Z"), (2, "Z")): 0.12293305056183809, - ((0, "Z"), (3, "Z")): 0.16768319457718972, - ((1, "Z"), (2, "Z")): 0.16768319457718972, - ((1, "Z"), (3, "Z")): 0.12293305056183809, - ((2, "Z"), (3, "Z")): 0.17627640804319608, - ((0, "X"), (1, "X"), (2, "Y"), (3, "Y")): -0.04475014401535165, - ((0, "X"), (1, "Y"), (2, "Y"), (3, "X")): 0.04475014401535165, - ((0, "Y"), (1, "X"), (2, "X"), (3, "Y")): 0.04475014401535165, - ((0, "Y"), (1, "Y"), (2, "X"), (3, "X")): -0.04475014401535165, - } - ref_h2_qubit_ham_bk = { - ((0, "Z"),): 0.17771287465139923, - (): -0.04207897647782244, - ((0, "Z"), (1, "Z")): 0.17771287465139918, - ((2, "Z"),): -0.24274280513140484, - ((1, "Z"), (2, "Z"), (3, "Z")): -0.24274280513140484, - ((0, "Y"), (1, "Z"), (2, "Y")): 0.04475014401535165, - ((0, "X"), (1, "Z"), (2, "X")): 0.04475014401535165, - ((0, "X"), (1, "Z"), (2, "X"), (3, "Z")): 0.04475014401535165, - ((0, "Y"), (1, "Z"), (2, "Y"), (3, "Z")): 0.04475014401535165, - ((1, "Z"),): 0.17059738328801052, - ((0, "Z"), (2, "Z")): 0.12293305056183809, - ((0, "Z"), (1, "Z"), (2, "Z")): 0.16768319457718972, - ((0, "Z"), (1, "Z"), (2, "Z"), (3, "Z")): 0.16768319457718972, - ((0, "Z"), (2, "Z"), (3, "Z")): 0.12293305056183809, - ((1, "Z"), (3, "Z")): 0.17627640804319608, - } - - jw_array = np.array([terms for terms in h2_qubit_ham_jw.terms.values()]) - bk_array = np.array([terms for terms in h2_qubit_ham_bk.terms.values()]) - - ref_jw_array = np.array([terms for terms in ref_h2_qubit_ham_jw.values()]) - ref_bk_array = np.array([terms for terms in ref_h2_qubit_ham_bk.values()]) - - assert np.allclose(jw_array, ref_jw_array) - assert np.allclose(bk_array, ref_bk_array) - - # incorrect mapping circuit - with pytest.raises(KeyError): - hamiltonian.qubit_hamiltonian(h2_ferm_ham, "incorrect") - - -def test_symbolic_hamiltonian(): - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.run_pyscf() - h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) - h2_qubit_ham = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "jw") - h2_sym_ham = hamiltonian.symbolic_hamiltonian(h2_qubit_ham) - ref_ham = ( - -0.0420789764778224 - - 0.0447501440153516 * X(0) * X(1) * Y(2) * Y(3) - + 0.0447501440153516 * X(0) * Y(1) * Y(2) * X(3) - + 0.0447501440153516 * Y(0) * X(1) * X(2) * Y(3) - - 0.0447501440153516 * Y(0) * Y(1) * X(2) * X(3) - + 0.177712874651399 * Z(0) - + 0.170597383288011 * Z(0) * Z(1) - + 0.122933050561838 * Z(0) * Z(2) - + 0.16768319457719 * Z(0) * Z(3) - + 0.177712874651399 * Z(1) - + 0.16768319457719 * Z(1) * Z(2) - + 0.122933050561838 * Z(1) * Z(3) - - 0.242742805131405 * Z(2) - + 0.176276408043196 * Z(2) * Z(3) - - 0.242742805131405 * Z(3) - ) - ref_sym_ham = SymbolicHamiltonian(ref_ham) - - assert np.allclose(h2_sym_ham.matrix, ref_sym_ham.matrix) - - -def test_hamiltonian_input_error(): - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.e_nuc = 0.0 - h2.oei = np.random.rand(4, 4) - h2.tei = np.random.rand(4, 4, 4, 4) - with pytest.raises(NameError): - h2.hamiltonian("ihpc") - - -# Commenting out since not actively supporting PSI4 at the moment -# @pytest.mark.skip(reason="Psi4 doesn't offer pip install, so needs to be installed through conda or manually.") -# def test_run_psi4(): -# """PSI4 driver""" -# # Hardcoded benchmark results -# h2_ref_energy = -1.117349035 -# h2_ref_hcore = np.array([[-1.14765024, -1.00692423], [-1.00692423, -1.14765024]]) -# -# h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) -# h2.run_psi4() -# -# assert h2.e_hf == pytest.approx(h2_ref_energy) -# assert np.allclose(h2.hcore, h2_ref_hcore) - - -def test_expectation_value(): - """Tests generation of molecular Hamiltonian and its expectation value using a JW-HF circuit""" - # Hardcoded benchmark results - h2_ref_energy = -1.117349035 - - h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) - h2.run_pyscf() - - # JW-HF circuit - circuit = models.Circuit(h2.nso) - circuit.add(gates.X(_i) for _i in range(h2.nelec)) - # Molecular Hamiltonian and the HF expectation value - hamiltonian = h2.hamiltonian() - hf_energy = expectation(circuit, hamiltonian) - - # assert h2.e_hf == pytest.approx(hf_energy) - assert h2_ref_energy == pytest.approx(hf_energy) - - -def test_eigenvalues(): - dummy = Molecule() - # FermionOperator test: - ferm_ham = sum( - openfermion.FermionOperator(f"{_i}^ {_i}") + openfermion.FermionOperator(f"{_i} {_i}^") for _i in range(4) - ) - ham_matrix = openfermion.get_sparse_operator(ferm_ham).toarray() - assert np.allclose(dummy.eigenvalues(ferm_ham), sorted(np.linalg.eigvals(ham_matrix))[:6]) - - # QubitOperator test: - qubit_ham = openfermion.QubitOperator("Z0 Z1") - ham_matrix = openfermion.get_sparse_operator(qubit_ham).toarray() - assert np.allclose(dummy.eigenvalues(qubit_ham), sorted(np.kron(np.array([1.0, -1.0]), np.array([1.0, -1.0])))[:2]) - - # SymbolicHamiltonian test: - sym_ham = SymbolicHamiltonian(Z(0) * Z(1)) - assert np.allclose(dummy.eigenvalues(sym_ham), sorted(np.kron(np.array([1.0, -1.0]), np.array([1.0, -1.0])))) - - # Unknown Hamiltonian type - with pytest.raises(TypeError): - dummy.eigenvalues(0.0) +""" +Test Molecule class functions +""" + +from pathlib import Path + +import numpy as np +import openfermion +import pytest +from qibo import gates, models +from qibo.hamiltonians import SymbolicHamiltonian +from qibo.symbols import X, Y, Z + +from qibochem.driver import hamiltonian +from qibochem.driver.molecule import Molecule +from qibochem.measurement.expectation import expectation + + +def test_run_pyscf(): + """PySCF driver""" + # Hardcoded benchmark results + # Change to run PySCF directly during a test? + h2_ref_energy = -1.117349035 + h2_ref_hcore = np.array([[-1.14765024, -1.00692423], [-1.00692423, -1.14765024]]) + + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.run_pyscf() + assert h2.e_hf == pytest.approx(h2_ref_energy) + assert np.allclose(h2.hcore, h2_ref_hcore) + + +def test_run_pyscf_molecule_xyz(): + """Pyscf driver with xyz file""" + file_path = Path("tests", "data", "lih.xyz") + if not file_path.is_file(): + with open(file_path, "w") as file_handler: + file_handler.write("2\n0 1\nLi 0.0 0.0 0.0\nH 0.0 0.0 1.2\n") + lih_ref_energy = -7.83561582555692 + lih = Molecule(xyz_file=file_path) + lih.run_pyscf() + + assert lih.e_hf == pytest.approx(lih_ref_energy) + + +def test_run_pyscf_molecule_xyz_charged(): + file_path = Path("tests", "data", "h2.xyz") + if not file_path.is_file(): + with open(file_path, "w") as file_handler: + file_handler.write("2\n \nH 0.0 0.0 0.0\nH 0.0 0.0 0.7\n") + h2_ref_energy = -1.117349035 + h2 = Molecule(xyz_file=file_path) + h2.run_pyscf() + + assert h2.e_hf == pytest.approx(h2_ref_energy) + + +def test_molecule_custom_basis(): + mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))], 0, 1, "6-31g") + mol.run_pyscf() + assert np.isclose(mol.e_hf, -7.94129296352493) + + +def test_hf_embedding(): + mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) + mol.run_pyscf() + ref_oei = mol.oei + ref_tei = mol.tei + mol.hf_embedding() + embed_oei = mol.embed_oei + embed_tei = mol.embed_tei + assert np.allclose(embed_oei, ref_oei) + assert np.allclose(embed_tei, ref_tei) + + mol.frozen = [0] + mol.active = [1, 2] + mol.hf_embedding() + assert mol.n_active_orbs == 4 + assert mol.n_active_e == 2 + + +def test_fermionic_hamiltonian(): + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.run_pyscf() + h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) + + # a^\dagger_0 a_0 + ref_one_body_tensor = np.array( + [ + [-1.27785301, 0.0, 0.0, 0.0], + [0.0, -1.27785301, 0.0, 0.0], + [0.0, 0.0, -0.4482997, 0.0], + [0.0, 0.0, 0.0, -0.4482997], + ] + ) + + assert np.isclose(h2_ferm_ham[()], 0.7559674441714287) + assert np.allclose(h2_ferm_ham.one_body_tensor, ref_one_body_tensor) + + +def test_fermionic_hamiltonian_2(): + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7414))]) + h2.run_pyscf() + + h2_ferm_ham_1 = h2.hamiltonian("f", h2.oei, h2.tei) + h2_qub_ham_jw_1 = h2.hamiltonian("q", h2.oei, h2.tei, ferm_qubit_map="jw") + h2_qub_ham_bk_1 = h2.hamiltonian("q", h2.oei, h2.tei, ferm_qubit_map="bk") + # h2_mol_ham has format of InteractionOperator + h2_mol_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) + h2_ferm_ham_2 = openfermion.transforms.get_fermion_operator(h2_mol_ham) + h2_qub_ham_jw_2 = openfermion.jordan_wigner(h2_mol_ham) + h2_qub_ham_bk_2 = openfermion.bravyi_kitaev(h2_mol_ham) + + assert h2_ferm_ham_2.isclose(h2_ferm_ham_1) + assert h2_qub_ham_jw_2.isclose(h2_qub_ham_jw_1) + assert h2_qub_ham_bk_2.isclose(h2_qub_ham_bk_1) + + +def test_parse_pauli_string_1(): + pauli_string = ((0, "X"), (1, "Y")) + qibo_pauli_string = hamiltonian.parse_pauli_string(pauli_string, 0.5) + ref_pauli_string = "0.5*X0*Y1" + assert str(qibo_pauli_string) == ref_pauli_string + + +def test_parse_pauli_string_2(): + qibo_pauli_string = hamiltonian.parse_pauli_string(None, 0.1) + assert str(qibo_pauli_string) == "0.1" + + +def test_qubit_hamiltonian(): + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.run_pyscf() + h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) + h2_qubit_ham_jw = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "jw") + h2_qubit_ham_bk = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "bk") + ref_h2_qubit_ham_jw = { + (): -0.04207897647782238, + ((0, "Z"),): 0.17771287465139918, + ((1, "Z"),): 0.1777128746513992, + ((2, "Z"),): -0.24274280513140478, + ((3, "Z"),): -0.24274280513140478, + ((0, "Z"), (1, "Z")): 0.17059738328801052, + ((0, "Z"), (2, "Z")): 0.12293305056183809, + ((0, "Z"), (3, "Z")): 0.16768319457718972, + ((1, "Z"), (2, "Z")): 0.16768319457718972, + ((1, "Z"), (3, "Z")): 0.12293305056183809, + ((2, "Z"), (3, "Z")): 0.17627640804319608, + ((0, "X"), (1, "X"), (2, "Y"), (3, "Y")): -0.04475014401535165, + ((0, "X"), (1, "Y"), (2, "Y"), (3, "X")): 0.04475014401535165, + ((0, "Y"), (1, "X"), (2, "X"), (3, "Y")): 0.04475014401535165, + ((0, "Y"), (1, "Y"), (2, "X"), (3, "X")): -0.04475014401535165, + } + ref_h2_qubit_ham_bk = { + ((0, "Z"),): 0.17771287465139923, + (): -0.04207897647782244, + ((0, "Z"), (1, "Z")): 0.17771287465139918, + ((2, "Z"),): -0.24274280513140484, + ((1, "Z"), (2, "Z"), (3, "Z")): -0.24274280513140484, + ((0, "Y"), (1, "Z"), (2, "Y")): 0.04475014401535165, + ((0, "X"), (1, "Z"), (2, "X")): 0.04475014401535165, + ((0, "X"), (1, "Z"), (2, "X"), (3, "Z")): 0.04475014401535165, + ((0, "Y"), (1, "Z"), (2, "Y"), (3, "Z")): 0.04475014401535165, + ((1, "Z"),): 0.17059738328801052, + ((0, "Z"), (2, "Z")): 0.12293305056183809, + ((0, "Z"), (1, "Z"), (2, "Z")): 0.16768319457718972, + ((0, "Z"), (1, "Z"), (2, "Z"), (3, "Z")): 0.16768319457718972, + ((0, "Z"), (2, "Z"), (3, "Z")): 0.12293305056183809, + ((1, "Z"), (3, "Z")): 0.17627640804319608, + } + + jw_array = np.array([terms for terms in h2_qubit_ham_jw.terms.values()]) + bk_array = np.array([terms for terms in h2_qubit_ham_bk.terms.values()]) + + ref_jw_array = np.array([terms for terms in ref_h2_qubit_ham_jw.values()]) + ref_bk_array = np.array([terms for terms in ref_h2_qubit_ham_bk.values()]) + + assert np.allclose(jw_array, ref_jw_array) + assert np.allclose(bk_array, ref_bk_array) + + # incorrect mapping circuit + with pytest.raises(KeyError): + hamiltonian.qubit_hamiltonian(h2_ferm_ham, "incorrect") + + +def test_symbolic_hamiltonian(): + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.run_pyscf() + h2_ferm_ham = hamiltonian.fermionic_hamiltonian(h2.oei, h2.tei, h2.e_nuc) + h2_qubit_ham = hamiltonian.qubit_hamiltonian(h2_ferm_ham, "jw") + h2_sym_ham = hamiltonian.symbolic_hamiltonian(h2_qubit_ham) + ref_ham = ( + -0.0420789764778224 + - 0.0447501440153516 * X(0) * X(1) * Y(2) * Y(3) + + 0.0447501440153516 * X(0) * Y(1) * Y(2) * X(3) + + 0.0447501440153516 * Y(0) * X(1) * X(2) * Y(3) + - 0.0447501440153516 * Y(0) * Y(1) * X(2) * X(3) + + 0.177712874651399 * Z(0) + + 0.170597383288011 * Z(0) * Z(1) + + 0.122933050561838 * Z(0) * Z(2) + + 0.16768319457719 * Z(0) * Z(3) + + 0.177712874651399 * Z(1) + + 0.16768319457719 * Z(1) * Z(2) + + 0.122933050561838 * Z(1) * Z(3) + - 0.242742805131405 * Z(2) + + 0.176276408043196 * Z(2) * Z(3) + - 0.242742805131405 * Z(3) + ) + ref_sym_ham = SymbolicHamiltonian(ref_ham) + + assert np.allclose(h2_sym_ham.matrix, ref_sym_ham.matrix) + + +def test_hamiltonian_input_error(): + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.e_nuc = 0.0 + h2.oei = np.random.rand(4, 4) + h2.tei = np.random.rand(4, 4, 4, 4) + with pytest.raises(NameError): + h2.hamiltonian("ihpc") + + +# Commenting out since not actively supporting PSI4 at the moment +# @pytest.mark.skip(reason="Psi4 doesn't offer pip install, so needs to be installed through conda or manually.") +# def test_run_psi4(): +# """PSI4 driver""" +# # Hardcoded benchmark results +# h2_ref_energy = -1.117349035 +# h2_ref_hcore = np.array([[-1.14765024, -1.00692423], [-1.00692423, -1.14765024]]) +# +# h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) +# h2.run_psi4() +# +# assert h2.e_hf == pytest.approx(h2_ref_energy) +# assert np.allclose(h2.hcore, h2_ref_hcore) + + +def test_expectation_value(): + """Tests generation of molecular Hamiltonian and its expectation value using a JW-HF circuit""" + # Hardcoded benchmark results + h2_ref_energy = -1.117349035 + + h2 = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + h2.run_pyscf() + + # JW-HF circuit + circuit = models.Circuit(h2.nso) + circuit.add(gates.X(_i) for _i in range(h2.nelec)) + # Molecular Hamiltonian and the HF expectation value + hamiltonian = h2.hamiltonian() + hf_energy = expectation(circuit, hamiltonian) + + # assert h2.e_hf == pytest.approx(hf_energy) + assert h2_ref_energy == pytest.approx(hf_energy) + + +def test_eigenvalues(): + dummy = Molecule() + # FermionOperator test: + ferm_ham = sum( + openfermion.FermionOperator(f"{_i}^ {_i}") + openfermion.FermionOperator(f"{_i} {_i}^") for _i in range(4) + ) + ham_matrix = openfermion.get_sparse_operator(ferm_ham).toarray() + assert np.allclose(dummy.eigenvalues(ferm_ham), sorted(np.linalg.eigvals(ham_matrix))[:6]) + + # QubitOperator test: + qubit_ham = openfermion.QubitOperator("Z0 Z1") + ham_matrix = openfermion.get_sparse_operator(qubit_ham).toarray() + assert np.allclose(dummy.eigenvalues(qubit_ham), sorted(np.kron(np.array([1.0, -1.0]), np.array([1.0, -1.0])))[:2]) + + # SymbolicHamiltonian test: + sym_ham = SymbolicHamiltonian(Z(0) * Z(1)) + assert np.allclose(dummy.eigenvalues(sym_ham), sorted(np.kron(np.array([1.0, -1.0]), np.array([1.0, -1.0])))) + + # Unknown Hamiltonian type + with pytest.raises(TypeError): + dummy.eigenvalues(0.0) From 96d5334825f4dc6f0333503898b2a10e2b46a62a Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:58:31 +0000 Subject: [PATCH 03/11] Add code for active argument (only) --- src/qibochem/driver/molecule.py | 77 ++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/src/qibochem/driver/molecule.py b/src/qibochem/driver/molecule.py index f17930a..4079e30 100644 --- a/src/qibochem/driver/molecule.py +++ b/src/qibochem/driver/molecule.py @@ -264,34 +264,87 @@ def _inactive_fock_matrix(self, frozen): inactive_fock[_p][_q] += 2 * self.tei[_orb][_p][_q][_orb] - self.tei[_orb][_p][_orb][_q] return inactive_fock - def hf_embedding(self, active=None, frozen=None): + def _active_space(self, active, frozen): """ - Turns on HF embedding for a given active/frozen space, and fills in the class attributes: - ``inactive_energy``, ``embed_oei``, and ``embed_tei``. + Helper function to check the input for active/frozen space and define the default values + for them where necessary Args: active: Iterable representing the active-space for quantum simulation frozen: Iterable representing the occupied orbitals to be removed from the simulation + + Returns: + _active, _frozen: Iterables representing the active/frozen space """ - # Default arguments for active and frozen + n_orbs = self.norb + n_occ_orbs = self.nalpha + + _active, _frozen = None, None if active is None: + if frozen is None: + # Default active: Full set of orbitals, frozen: empty list + _active = list(range(n_orbs)) + _frozen = [] + else: + # Check that active argument is valid + assert max(active) < n_orbs and min(active) >= 0, "Active space must be between 0 and the number of MOs" + _active = active + # active, frozen arguments both given + if frozen is not None: + # Check that active/frozen arguments don't overlap + assert not (set(active) & set(frozen)), "Active and frozen space cannot overlap" + # Frozen space must be occupied orbitals + assert max(frozen) + 1 < n_occ_orbs and min(frozen) >= 0, "Frozen orbital must be occupied orbitals" + # All occupied orbitals have to be in active or frozen + assert all( + _occ in set(active + frozen) for _occ in range(n_occ_orbs) + ), "All occupied orbitals have to be in either the active or frozen space" + # Hopefully no more problems with the input + _frozen = frozen + # frozen argument not given + else: + # Default frozen: All occupied orbitals not in active + _frozen = [_i for _i in range(n_occ_orbs) if _i not in _active] + + """ if self.active is None: - active = list(range(self.norb)) + _active = list(range(self.norb)) else: - active = self.active + _active = self.active if frozen is None: if self.frozen is None: - frozen = [_i for _i in range(self.nalpha) if _i not in active] + _frozen = [_i for _i in range(self.nalpha) if _i not in _active] else: - frozen = self.frozen + _frozen = self.frozen # Check that arguments are valid - assert max(active) < self.norb and min(active) >= 0, "Active space must be between 0 " "and the number of MOs" - if frozen: - assert not (set(active) & set(frozen)), "Active and frozen space cannot overlap" - assert max(frozen) + 1 < self.nelec // 2 and min(frozen) >= 0, ( + assert max(_active) < self.norb and min(_active) >= 0, "Active space must be between 0 " "and the number of MOs" + if _frozen: + assert not (set(_active) & set(_frozen)), "Active and frozen space cannot overlap" + assert max(_frozen) + 1 < self.nelec // 2 and min(_frozen) >= 0, ( "Frozen orbitals must" " be occupied orbitals" ) + """ + return _active, _frozen + + def hf_embedding(self, active=None, frozen=None): + """ + Turns on HF embedding for a given active/frozen space, and fills in the class attributes: + ``inactive_energy``, ``embed_oei``, and ``embed_tei``. + + Args: + active: Iterable representing the active-space for quantum simulation + frozen: Iterable representing the occupied orbitals to be removed from the simulation + """ + # Default arguments for active and frozen if no arguments given + if active is None and frozen is None: + _active, _frozen = self._active_space(self.active, self.frozen) + else: + # active/frozen arguments given, process them using _active_space similarly + _active, _frozen = self._active_space(active, frozen) + # Update the class attributes with the checked arguments + self.active = _active + self.frozen = _frozen # Build the inactive Fock matrix first inactive_fock = self._inactive_fock_matrix(frozen) From 801699936ad09515dfefc1a590cfd18b57d3545e Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:29:16 +0000 Subject: [PATCH 04/11] Finish function to define active space rigorously which hopefully works... --- src/qibochem/driver/molecule.py | 47 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/qibochem/driver/molecule.py b/src/qibochem/driver/molecule.py index 4079e30..63b16b3 100644 --- a/src/qibochem/driver/molecule.py +++ b/src/qibochem/driver/molecule.py @@ -281,50 +281,41 @@ def _active_space(self, active, frozen): _active, _frozen = None, None if active is None: + # No arguments given if frozen is None: # Default active: Full set of orbitals, frozen: empty list _active = list(range(n_orbs)) _frozen = [] + # Only frozen argument given + else: + if frozen: + # Non-empty frozen space must be occupied orbitals + assert max(frozen) + 1 < n_occ_orbs and min(frozen) >= 0, "Frozen orbital must be occupied orbitals" + _frozen = frozen + # Default active: All orbitals not in frozen + _active = [_i for _i in range(n_orbs) if _i not in _frozen] + # active argument given else: # Check that active argument is valid assert max(active) < n_orbs and min(active) >= 0, "Active space must be between 0 and the number of MOs" _active = active - # active, frozen arguments both given - if frozen is not None: + # frozen argument not given + if frozen is None: + # Default frozen: All occupied orbitals not in active + _frozen = [_i for _i in range(n_occ_orbs) if _i not in _active] + # active, frozen arguments both given: + else: # Check that active/frozen arguments don't overlap assert not (set(active) & set(frozen)), "Active and frozen space cannot overlap" - # Frozen space must be occupied orbitals - assert max(frozen) + 1 < n_occ_orbs and min(frozen) >= 0, "Frozen orbital must be occupied orbitals" + if frozen: + # Non-empty frozen space must be occupied orbitals + assert max(frozen) + 1 < n_occ_orbs and min(frozen) >= 0, "Frozen orbital must be occupied orbitals" # All occupied orbitals have to be in active or frozen assert all( _occ in set(active + frozen) for _occ in range(n_occ_orbs) ), "All occupied orbitals have to be in either the active or frozen space" # Hopefully no more problems with the input _frozen = frozen - # frozen argument not given - else: - # Default frozen: All occupied orbitals not in active - _frozen = [_i for _i in range(n_occ_orbs) if _i not in _active] - - """ - if self.active is None: - _active = list(range(self.norb)) - else: - _active = self.active - if frozen is None: - if self.frozen is None: - _frozen = [_i for _i in range(self.nalpha) if _i not in _active] - else: - _frozen = self.frozen - - # Check that arguments are valid - assert max(_active) < self.norb and min(_active) >= 0, "Active space must be between 0 " "and the number of MOs" - if _frozen: - assert not (set(_active) & set(_frozen)), "Active and frozen space cannot overlap" - assert max(_frozen) + 1 < self.nelec // 2 and min(_frozen) >= 0, ( - "Frozen orbitals must" " be occupied orbitals" - ) - """ return _active, _frozen def hf_embedding(self, active=None, frozen=None): From 0ed41320e40dfb6c98c06c32272cf8b580dd696e Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:32:09 +0000 Subject: [PATCH 05/11] Add tests for new _active_space function --- tests/test_molecule.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_molecule.py b/tests/test_molecule.py index d587585..c2c4768 100644 --- a/tests/test_molecule.py +++ b/tests/test_molecule.py @@ -60,6 +60,36 @@ def test_molecule_custom_basis(): assert np.isclose(mol.e_hf, -7.94129296352493) +def test_define_active_space(): + mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) + mol.nalpha = 2 + mol.norb = 6 + # Default arguments: Nothing given + assert mol._active_space(None, None) == (list(range(mol.norb)), []) + # Default frozen argument if active given + assert mol._active_space([1, 2, 5], None) == ([1, 2, 5], [0]) + # Default active argument if frozen given + assert mol._active_space(None, [0]) == (list(range(1, 6)), [0]) + # active, frozen arguments both given + assert mol._active_space([0, 1, 2, 3], []) == (list(range(4)), []) + + +def test_define_active_space_assertions(): + mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) + mol.nalpha = 2 + mol.norb = 6 + + # Invalid active argument + with pytest.raises(AssertionError): + _ = mol._active_space([10], None) + # Invalid frozen argument + with pytest.raises(AssertionError): + _ = mol._active_space(None, [100]) + # active/frozen spaces overlap + with pytest.raises(AssertionError): + _ = mol._active_space([0, 1], [0]) + + def test_hf_embedding(): mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) mol.run_pyscf() From 2ef5fd48e9677b32663015d1723ba193895c6cc1 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:49:23 +0000 Subject: [PATCH 06/11] Bugfix: Correct hf_embedding function Oops, forgot to do so and test locally before pushing --- src/qibochem/driver/molecule.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/qibochem/driver/molecule.py b/src/qibochem/driver/molecule.py index 63b16b3..a9615d5 100644 --- a/src/qibochem/driver/molecule.py +++ b/src/qibochem/driver/molecule.py @@ -338,22 +338,20 @@ def hf_embedding(self, active=None, frozen=None): self.frozen = _frozen # Build the inactive Fock matrix first - inactive_fock = self._inactive_fock_matrix(frozen) + inactive_fock = self._inactive_fock_matrix(self.frozen) # Calculate the inactive Fock energy # Only want frozen part of original OEI and inactive Fock matrix - _oei = self.oei[np.ix_(frozen, frozen)] - _inactive_fock = inactive_fock[np.ix_(frozen, frozen)] + _oei = self.oei[np.ix_(self.frozen, self.frozen)] + _inactive_fock = inactive_fock[np.ix_(self.frozen, self.frozen)] self.inactive_energy = np.einsum("ii->", _oei + _inactive_fock) # Keep only the active part - self.embed_oei = inactive_fock[np.ix_(active, active)] - self.embed_tei = self.tei[np.ix_(active, active, active, active)] + self.embed_oei = inactive_fock[np.ix_(self.active, self.active)] + self.embed_tei = self.tei[np.ix_(self.active, self.active, self.active, self.active)] - # Update class attributes - self.active = active - self.frozen = frozen - self.n_active_orbs = 2 * len(active) + # Update other class attributes + self.n_active_orbs = 2 * len(self.active) self.n_active_e = self.nelec - 2 * len(self.frozen) def hamiltonian( From 5f4a8f4d3101748664bb2f4c61764e907bdfa31f Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Thu, 8 Feb 2024 07:52:04 +0000 Subject: [PATCH 07/11] Simplify hf_embedding test Change LiH to H2, check removal of all virtual orbitals --- tests/test_molecule.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/test_molecule.py b/tests/test_molecule.py index c2c4768..58a9b6b 100644 --- a/tests/test_molecule.py +++ b/tests/test_molecule.py @@ -91,21 +91,18 @@ def test_define_active_space_assertions(): def test_hf_embedding(): - mol = Molecule([("Li", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 1.2))]) + mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) mol.run_pyscf() - ref_oei = mol.oei - ref_tei = mol.tei - mol.hf_embedding() - embed_oei = mol.embed_oei - embed_tei = mol.embed_tei - assert np.allclose(embed_oei, ref_oei) - assert np.allclose(embed_tei, ref_tei) - - mol.frozen = [0] - mol.active = [1, 2] - mol.hf_embedding() - assert mol.n_active_orbs == 4 + # Remove all virtual orbitals from the active space + mol.hf_embedding(active=[0]) + # Check that the class attributes have been updated correctly + assert mol.frozen == [] + assert mol.n_active_orbs == 2 assert mol.n_active_e == 2 + # OEI/TEI (in MO basis) for the occupied orbitals should remain unchanged + dim = mol.n_active_orbs // 2 + assert np.allclose(mol.embed_oei, mol.oei[:dim, :dim]) + assert np.allclose(mol.embed_tei, mol.tei[:dim, :dim, :dim, :dim]) def test_fermionic_hamiltonian(): From 4f1464ae35ab4bbb9d80eb09c657e155b0fa0a58 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:32:49 +0000 Subject: [PATCH 08/11] Add tutorial for HF embedding into docs (draft) --- doc/source/tutorials/ansatz.rst | 1 + doc/source/tutorials/molecule.rst | 34 ++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/doc/source/tutorials/ansatz.rst b/doc/source/tutorials/ansatz.rst index ae3ff31..3352179 100644 --- a/doc/source/tutorials/ansatz.rst +++ b/doc/source/tutorials/ansatz.rst @@ -88,6 +88,7 @@ The following example demonstrates how the energy of the H2 molecule is affected | 0.2 | 0.673325849299 | ----------------------------- +.. _UCC Ansatz: Unitary Coupled Cluster Ansatz ------------------------------ diff --git a/doc/source/tutorials/molecule.rst b/doc/source/tutorials/molecule.rst index 198c21f..4df364f 100644 --- a/doc/source/tutorials/molecule.rst +++ b/doc/source/tutorials/molecule.rst @@ -46,9 +46,41 @@ Qibochem offers the functionality to interface with `PySCF ` # Running PySCF h2.run_pyscf() -The default level of theory is HF/STO-3G, and upon executing the PySCF driver for a given molecule, several molecular quantities are calculated and stored in the Molecule class. +The default level of theory is HF/STO-3G, and upon executing the PySCF driver for a given molecule, several molecular quantities are calculated and stored in the ``Molecule`` class. These include the: * converged Hartree-Fock energy * optimized molecular orbital (MO) coefficients * one- and two-electron integrals + + +Embedding the quantum electronic structure calculation +------------------------------------------------------ + +In quantum chemistry, most *ab initio* calculations start with a Hartree-Fock (HF) calculation. +The obtained HF wave function is then used as a starting point to apply post-HF methods to improve upon the treatment of electron correlation. +An example of a post-HF method that can be run on a quantum computer is the :ref:`Unitary Coupled Cluster method `. +Unfortunately, the current level of quantum hardware are unable to run these methods for molecules that are too large. + +One possible approach to reduce the hardware required is to embed the quantum simulation into a classically computed environment. +(see `Rossmanek et al. `_) +Essentially, the occupancy of the core *1s* orbitals for the heavy atoms in a molecule is effectively constant; the same might hold for higher virtual orbitals. +As such, these orbitals can be removed from the simulation of the molecule without there being a significant change in the molecular properties of interest. +The remaining orbitals left in the simulation are then known as the active space of the molecule. + +An example of how to apply this using Qibochem for the LiH molecule is given below. + +.. code-block:: python + + from qibochem.driver.molecule import Molecule + + # Inline definition of H2 + h2 = Molecule([('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.74804))]) + # Running PySCF + h2.run_pyscf() + frozen = [0] # The electrons on the 1s orbital of Li in LiH are frozen - removed from the quantum simulation + h2.hf_embedding(frozen=frozen) + +The code block above will re-calculate the one- and two-electron integrals for the given active space, and store the result back in the ``Molecule`` class. + +.. Note: Not sure if this is the best place to describe HF embedding? But where else would be good? From f45545e72338e82364846bb965e15dbd2c20d996 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:42:44 +0000 Subject: [PATCH 09/11] Edit Hamiltonian tutorial for HF embedding --- doc/source/tutorials/hamiltonian.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/tutorials/hamiltonian.rst b/doc/source/tutorials/hamiltonian.rst index 3546cc7..99a6970 100644 --- a/doc/source/tutorials/hamiltonian.rst +++ b/doc/source/tutorials/hamiltonian.rst @@ -74,6 +74,7 @@ Lastly, to carry out quantum simulations of the molecular electronic structure u -0.107280411608667 - 0.0454153248117008*X0*X1*Y2*Y3 + 0.0454153248117008*X0*Y1*Y2*X3 + 0.0454153248117008*Y0*X1*X2*Y3 - 0.0454153248117008*Y0*Y1*X2*X3 + 0.170182611817142*Z0 + 0.16830546187935*Z0*Z1 + 0.120163790529127*Z0*Z2 + 0.165579115340828*Z0*Z3 + 0.170182611817142*Z1 + 0.165579115340828*Z1*Z2 + 0.120163790529127*Z1*Z3 - 0.219750654392482*Z2 + 0.174043557614182*Z2*Z3 - 0.219750654392482*Z3 By default, the molecular Hamiltonian is returned as a ``SymbolicHamiltonian``, `i.e.` if no arguments are given in :code:`mol.hamiltonian()`. +In addition, if HF embedding has been applied, the embedded values of the one-/two- electron integrals will be used to construct the molecular Hamiltonian as well. Otherwise, using the ``"f"``/``"ferm"`` and ``"q"``/``"qubit"`` arguments will return the molecular Hamiltonian as an OpenFermion ``FermionOperator`` and ``QubitOperator`` respectively. Additional information about the data structure of these two classes can be found `here `_. From b2831bb1f0a89ecff6a795e36c2b72c8335a9cd8 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:56:01 +0000 Subject: [PATCH 10/11] Add tests for code coverage --- tests/test_molecule.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_molecule.py b/tests/test_molecule.py index 58a9b6b..198b45b 100644 --- a/tests/test_molecule.py +++ b/tests/test_molecule.py @@ -72,6 +72,8 @@ def test_define_active_space(): assert mol._active_space(None, [0]) == (list(range(1, 6)), [0]) # active, frozen arguments both given assert mol._active_space([0, 1, 2, 3], []) == (list(range(4)), []) + # active, frozen arguments both given + assert mol._active_space([1, 2, 3], [0]) == (list(range(1, 4)), [0]) def test_define_active_space_assertions(): @@ -91,10 +93,10 @@ def test_define_active_space_assertions(): def test_hf_embedding(): - mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))]) + mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.7))], active=[0]) mol.run_pyscf() # Remove all virtual orbitals from the active space - mol.hf_embedding(active=[0]) + mol.hf_embedding() # Check that the class attributes have been updated correctly assert mol.frozen == [] assert mol.n_active_orbs == 2 From 665138697401b9279ed8d98017be8c0a38415c29 Mon Sep 17 00:00:00 2001 From: Wong Zi Cheng <70616433+chmwzc@users.noreply.github.com> Date: Wed, 14 Feb 2024 02:34:19 +0000 Subject: [PATCH 11/11] Update Molecule API documentation --- src/qibochem/driver/molecule.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/qibochem/driver/molecule.py b/src/qibochem/driver/molecule.py index a9615d5..9cba67a 100644 --- a/src/qibochem/driver/molecule.py +++ b/src/qibochem/driver/molecule.py @@ -363,16 +363,23 @@ def hamiltonian( ferm_qubit_map=None, ): """ - Builds a molecular Hamiltonian using the one-/two- electron integrals + Builds a molecular Hamiltonian using the one-/two- electron integrals. If HF embedding has been applied, + (i.e. the ``embed_oei``, ``embed_tei``, and ``inactive_energy`` attributes are all not ``None``), the + corresponding values for the molecular integrals will be used instead. Args: ham_type: Format of molecular Hamiltonian returned. The available options are: ``("f", "ferm")``: OpenFermion ``FermionOperator``, ``("q", "qubit")``: OpenFermion ``QubitOperator``, or ``("s", "sym")``: Qibo ``SymbolicHamiltonian`` (default) - oei: 1-electron integrals. Default: ``self.oei`` (MO basis) - tei: 2-electron integrals in 2ndQ notation. Default: ``self.tei`` (MO basis) - constant: For inactive Fock energy if embedding used. Default: 0.0 + oei: 1-electron integrals (in the MO basis). The default value is the ``oei`` class attribute , unless + the ``embed_oei`` attribute exists and is not ``None``, then ``embed_oei`` is used. + tei: 2-electron integrals in the second-quantization notation (and MO basis). The default value is the + ``tei`` class attribute , unless the ``embed_tei`` attribute exists and is not ``None``, then ``embed_tei`` + is used. + constant: Constant value to be added to the electronic energy. Mainly used for adding the inactive Fock + energy if HF embedding was applied. Default: 0.0, unless the ``inactive_energy`` class attribute exists + and is not ``None``, then ``inactive_energy`` is used. ferm_qubit_map: Which fermion to qubit transformation to use. Must be either ``jw`` (default) or ``bk``