From f15f85ee3a5b1c727f593c054487eda10cc169d2 Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Mon, 13 May 2024 23:46:16 -0400 Subject: [PATCH] add spinless givens ansatz gate --- python/ffsim/qiskit/__init__.py | 2 + python/ffsim/qiskit/gates/__init__.py | 2 + python/ffsim/qiskit/gates/givens_ansatz.py | 68 ++++++++++++++++++++++ python/ffsim/variational/givens.py | 1 + tests/python/qiskit/givens_ansatz_test.py | 55 +++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 python/ffsim/qiskit/gates/givens_ansatz.py create mode 100644 tests/python/qiskit/givens_ansatz_test.py diff --git a/python/ffsim/qiskit/__init__.py b/python/ffsim/qiskit/__init__.py index 0f69f84f9..b2ebb1913 100644 --- a/python/ffsim/qiskit/__init__.py +++ b/python/ffsim/qiskit/__init__.py @@ -14,6 +14,7 @@ from ffsim.qiskit.gates import ( DiagCoulombEvolutionJW, + GivensAnsatzOperatorSpinlessJW, OrbitalRotationJW, OrbitalRotationSpinlessJW, PrepareHartreeFockJW, @@ -37,6 +38,7 @@ __all__ = [ "DiagCoulombEvolutionJW", "DropNegligible", + "GivensAnsatzOperatorSpinlessJW", "MergeOrbitalRotations", "OrbitalRotationJW", "OrbitalRotationSpinlessJW", diff --git a/python/ffsim/qiskit/gates/__init__.py b/python/ffsim/qiskit/gates/__init__.py index 09470c8a2..b7ce72826 100644 --- a/python/ffsim/qiskit/gates/__init__.py +++ b/python/ffsim/qiskit/gates/__init__.py @@ -11,6 +11,7 @@ """Qiskit fermionic quantum gates.""" from ffsim.qiskit.gates.diag_coulomb import DiagCoulombEvolutionJW +from ffsim.qiskit.gates.givens_ansatz import GivensAnsatzOperatorSpinlessJW from ffsim.qiskit.gates.orbital_rotation import ( OrbitalRotationJW, OrbitalRotationSpinlessJW, @@ -25,6 +26,7 @@ __all__ = [ "DiagCoulombEvolutionJW", + "GivensAnsatzOperatorSpinlessJW", "OrbitalRotationJW", "OrbitalRotationSpinlessJW", "PrepareHartreeFockJW", diff --git a/python/ffsim/qiskit/gates/givens_ansatz.py b/python/ffsim/qiskit/gates/givens_ansatz.py new file mode 100644 index 000000000..aac263431 --- /dev/null +++ b/python/ffsim/qiskit/gates/givens_ansatz.py @@ -0,0 +1,68 @@ +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Givens rotation ansatz gate.""" + +from __future__ import annotations + +import itertools +import math +from collections.abc import Iterator, Sequence + +from qiskit.circuit import ( + CircuitInstruction, + Gate, + QuantumCircuit, + QuantumRegister, + Qubit, +) +from qiskit.circuit.library import XXPlusYYGate + +from ffsim.variational import GivensAnsatzOperator + + +class GivensAnsatzOperatorSpinlessJW(Gate): + """Givens rotation ansatz operator under the Jordan-Wigner transformation. + + See :class:`ffsim.GivensAnsatzOperator` for a description of this gate's unitary. + """ + + def __init__( + self, givens_ansatz_operator: GivensAnsatzOperator, *, label: str | None = None + ): + """Create a new Givens ansatz operator gate. + + Args: + givens_ansatz_operator: The Givens rotation ansatz operator. + label: The label of the gate. + """ + self.givens_ansatz_operator = givens_ansatz_operator + super().__init__( + "givens_ansatz_jw", givens_ansatz_operator.norb, [], label=label + ) + + def _define(self): + """Gate decomposition.""" + qubits = QuantumRegister(self.num_qubits) + self.definition = QuantumCircuit.from_instructions( + _givens_ansatz_jw(qubits, self.givens_ansatz_operator), qubits=qubits + ) + + +def _givens_ansatz_jw( + qubits: Sequence[Qubit], givens_ansatz_operator: GivensAnsatzOperator +) -> Iterator[CircuitInstruction]: + for (i, j), theta in zip( + itertools.cycle(givens_ansatz_operator.interaction_pairs), + givens_ansatz_operator.thetas, + ): + yield CircuitInstruction( + XXPlusYYGate(2 * theta, -0.5 * math.pi), (qubits[i], qubits[j]) + ) diff --git a/python/ffsim/variational/givens.py b/python/ffsim/variational/givens.py index effb1a631..1efd84a45 100644 --- a/python/ffsim/variational/givens.py +++ b/python/ffsim/variational/givens.py @@ -43,6 +43,7 @@ class GivensAnsatzOperator: norb: int interaction_pairs: list[tuple[int, int]] thetas: np.ndarray + # TODO add phis for complex phases def _apply_unitary_( self, vec: np.ndarray, norb: int, nelec: tuple[int, int], copy: bool diff --git a/tests/python/qiskit/givens_ansatz_test.py b/tests/python/qiskit/givens_ansatz_test.py new file mode 100644 index 000000000..d8aa78a70 --- /dev/null +++ b/tests/python/qiskit/givens_ansatz_test.py @@ -0,0 +1,55 @@ +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for Givens rotation ansatz gate.""" + +from __future__ import annotations + +import numpy as np +import pytest +from qiskit.quantum_info import Statevector + +import ffsim + + +def brickwork(norb: int, n_layers: int): + for i in range(n_layers): + for j in range(i % 2, norb - 1, 2): + yield (j, j + 1) + + +@pytest.mark.parametrize("norb, nocc", ffsim.testing.generate_norb_nocc(range(5))) +def test_random_givens_ansatz_operator(norb: int, nocc: int): + """Test random Givens rotation ansatz gives correct output state.""" + rng = np.random.default_rng() + nelec = (nocc, 0) + dim = ffsim.dim(norb, nelec) + for _ in range(3): + n_layers = norb + interaction_pairs = list(brickwork(norb, n_layers)) + thetas = rng.uniform(-np.pi, np.pi, size=len(interaction_pairs)) + givens_ansatz_op = ffsim.GivensAnsatzOperator(norb, interaction_pairs, thetas) + gate = ffsim.qiskit.GivensAnsatzOperatorSpinlessJW(givens_ansatz_op) + + small_vec = ffsim.random.random_statevector(dim, seed=rng) + big_vec = ffsim.qiskit.ffsim_vec_to_qiskit_vec( + small_vec, norb=norb, nelec=nelec + ) + + statevec = Statevector(big_vec).evolve(gate) + result = ffsim.qiskit.qiskit_vec_to_ffsim_vec( + np.array(statevec), norb=norb, nelec=nelec + ) + + expected = ffsim.apply_unitary( + small_vec, givens_ansatz_op, norb=norb, nelec=nelec + ) + + np.testing.assert_allclose(result, expected)