diff --git a/README.md b/README.md index dc7e398..d0ad654 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ h2.run_pyscf() hamiltonian = h2.hamiltonian() # Build a UCC circuit ansatz for running VQE -circuit = hf_circuit(h2.nso, sum(h2.nelec)) +circuit = hf_circuit(h2.nso, h2.nelec) circuit += ucc_circuit(h2.nso, [0, 1, 2, 3]) # Create and run the VQE, starting with random initial parameters diff --git a/doc/source/api-reference/ansatz.rst b/doc/source/api-reference/ansatz.rst index a271071..c61ffa4 100644 --- a/doc/source/api-reference/ansatz.rst +++ b/doc/source/api-reference/ansatz.rst @@ -23,6 +23,9 @@ Unitary Coupled Cluster .. autofunction:: qibochem.ansatz.ucc.ucc_ansatz +.. autofunction:: qibochem.ansatz.qeb.qeb_circuit + + Basis rotation -------------- diff --git a/doc/source/tutorials/ansatz.rst b/doc/source/tutorials/ansatz.rst index 7b602ae..a9e7fd2 100644 --- a/doc/source/tutorials/ansatz.rst +++ b/doc/source/tutorials/ansatz.rst @@ -138,6 +138,41 @@ An example of how to build a UCC doubles circuit ansatz for the :math:`H_2` mole q3: ... ─────o─RX─RX─o────────────o─RX─ + +UCC with Qubit-Excitation-Based n-tuple Excitation +-------------------------------------------------- + +A CNOT depth-efficient quantum circuit for employing the UCC ansatz, dubbed the Qubit-Excitation-Based (QEB) n-tuple excitations for UCC, was constructed by Yordanov et al. [#f7]_ and Magoulas et al. [#f8]_, avoiding the exponential number of CNOT cascades in those developed before. [#f5]_ The quantum circuits generated for :math:`N` qubits have a reduction of CNOTs from :math:`(2N-1)2^{2N}` to :math:`2^{2N-1}+4N-2`. + +An example for the :math:`H_2` molecule is given here: + + +.. code-block:: python + + from qibochem.driver.molecule import Molecule + from qibochem.ansatz import hf_circuit, qeb_circuit + + mol = Molecule([("H", (0.0, 0.0, 0.0)), ("H", (0.0, 0.0, 0.74804))]) + mol.run_pyscf() + hamiltonian = mol.hamiltonian() + + # Set parameters for the rest of the experiment + n_qubits = mol.nso + n_electrons = mol.nelec + + # Build UCCD circuit + circuit = hf_circuit(n_qubits, n_electrons) # Start with HF circuit + circuit += qeb_circuit(n_qubits, [0, 1, 2, 3]) # Then add the double excitation circuit ansatz + + print(circuit.draw()) + +.. code-block:: output + + q0: ─X─X─────X─o──X─────X─ + q1: ─X─o───X───o────X───o─ + q2: ─────X─|─X─o──X─|─X─── + q3: ─────o─o───RY───o─o─── + .. _Basis rotation ansatz @@ -233,3 +268,7 @@ The orthonormal molecular orbitals :math:`\phi` are optimized by a direct minimi .. [#f6] Piela, L. (2007). 'Ideas of Quantum Chemistry'. Elsevier B. V., the Netherlands. .. [#f7] Clements, W. R. et al., 'Optimal Design for Universal Multiport Interferometers', Optica 3 (2016) 1460. + +.. [#f8] Yordanov Y. S. et al., 'Efficient Quantum Circuits for Quantum Computational Chemistry', Phys Rev A 102 (2020) 062612. + +.. [#f9] Magoulas, I. and Evangelista, F. A., 'CNOT-Efficient Circuits for Arbitrary Rank Many-Body Fermionic and Qubit Excitations', J. Chem. Theory Comput. 19 (2023) 822. diff --git a/src/qibochem/ansatz/__init__.py b/src/qibochem/ansatz/__init__.py index 00609da..cb7d980 100644 --- a/src/qibochem/ansatz/__init__.py +++ b/src/qibochem/ansatz/__init__.py @@ -5,6 +5,7 @@ ) from qibochem.ansatz.hardware_efficient import he_circuit from qibochem.ansatz.hf_reference import hf_circuit +from qibochem.ansatz.qeb import qeb_circuit from qibochem.ansatz.symmetry import symm_preserving_circuit from qibochem.ansatz.ucc import ucc_ansatz, ucc_circuit from qibochem.ansatz.util import generate_excitations, mp2_amplitude, sort_excitations diff --git a/src/qibochem/ansatz/qeb.py b/src/qibochem/ansatz/qeb.py new file mode 100644 index 0000000..739018f --- /dev/null +++ b/src/qibochem/ansatz/qeb.py @@ -0,0 +1,43 @@ +from qibo import Circuit, gates + + +def qeb_circuit(n_qubits, excitation, theta=0.0) -> Circuit: + r""" + Qubit-excitation-based (QEB) circuit corresponding to the unitary coupled-cluster ansatz for a single excitation + + Supports only Jordan-Wigner encoded circuits + + Ref: arXiv:2210.05771 + + Args: + n_qubits: Number of qubits in the quantum circuit + excitation: Iterable of orbitals involved in the excitation; must have an even number of elements + E.g. ``[0, 1, 2, 3]`` represents the excitation of electrons in orbitals ``(0, 1)`` to ``(2, 3)`` + theta: UCC parameter. Defaults to 0.0 + trotter_steps: Number of Trotter steps; i.e. number of times this ansatz is applied with ``theta`` = ``theta / trotter_steps``. Default: 1 + + Returns: + Qibo ``Circuit``: Circuit corresponding to a single UCC excitation + """ + + n_orbitals = len(excitation) + assert n_orbitals % 2 == 0, f"{excitation} must have an even number of items" + + n_tuples = len(excitation) // 2 + i_array = excitation[:n_tuples] + a_array = excitation[n_tuples:] + fwd_gates = [gates.CNOT(i_array[-1], _i) for _i in i_array[-2::-1]] + fwd_gates += [gates.CNOT(a_array[-1], _a) for _a in a_array[-2::-1]] + fwd_gates.append(gates.CNOT(a_array[-1], i_array[-1])) + fwd_gates += [gates.X(_ia) for _ia in excitation if _ia not in (i_array[-1], a_array[-1])] + circuit = Circuit(n_qubits) + circuit.add(gate for gate in fwd_gates) + # MCRY + # multi-controlled RY gate, + # negative controls i, a + # positive control on i_n + mcry_controls = excitation[:-1] + ry_angle = 2.0 * theta + circuit.add(gates.RY(a_array[-1], ry_angle).controlled_by(*mcry_controls)) + circuit.add(gate for gate in fwd_gates[::-1]) + return circuit diff --git a/tests/test_ucc.py b/tests/test_ucc.py index 5b7ef11..8695a2b 100644 --- a/tests/test_ucc.py +++ b/tests/test_ucc.py @@ -10,6 +10,7 @@ from qibo.hamiltonians import SymbolicHamiltonian from qibochem.ansatz import hf_circuit +from qibochem.ansatz.qeb import qeb_circuit from qibochem.ansatz.ucc import expi_pauli, ucc_ansatz, ucc_circuit from qibochem.ansatz.util import generate_excitations, mp2_amplitude, sort_excitations from qibochem.driver import Molecule @@ -95,6 +96,51 @@ def test_ucc_circuit(excitation, mapping, basis_rotations, coeffs): assert len(test_circuit.get_parameters()) == len(basis_rotations) +@pytest.mark.parametrize( + "excitation,mapping,basis_rotations,coeffs", + [ + ([0, 2], None, ("Y0 X2", "X0 Y2"), (0.5, -0.5)), # JW singles + ( + [0, 1, 2, 3], + None, + ( + "X0 X1 Y2 X3", + "Y0 Y1 Y2 X3", + "Y0 X1 X2 X3", + "X0 Y1 X2 X3", + "Y0 X1 Y2 Y3", + "X0 Y1 Y2 Y3", + "X0 X1 X2 Y3", + "Y0 Y1 X2 Y3", + ), + (-0.25, 0.25, 0.25, 0.25, -0.25, -0.25, -0.25, 0.25), + ), # JW doubles + ], +) +def test_qeb_circuit(excitation, mapping, basis_rotations, coeffs): + """Build QEB circuit""" + theta = 0.1 + n_qubits = 4 + + # Build the control array using SymbolicHamiltonian.circuit + # But need to multiply theta by some coefficient introduced by the fermion->qubit mapping + control_circuit = Circuit(n_qubits) + for coeff, basis_rotation in zip(coeffs, basis_rotations): + n_terms = len(basis_rotation) + pauli_term = SymbolicHamiltonian( + symbols.I(n_qubits - 1) + * reduce(lambda x, y: x * y, (getattr(symbols, _op)(int(qubit)) for _op, qubit in basis_rotation.split())) + ) + control_circuit += pauli_term.circuit(-coeff * theta) + control_result = control_circuit(nshots=1) + control_state = control_result.state(True) + + test_circuit = qeb_circuit(n_qubits, excitation, theta=theta) + test_result = test_circuit(nshots=1) + test_state = test_result.state(True) + assert np.allclose(control_state, test_state) + + def test_ucc_ferm_qubit_map_error(): """If unknown fermion to qubit map used""" with pytest.raises(KeyError):