From 2cd1c55a5fd5253ddb92db64ec98a4be315f45eb Mon Sep 17 00:00:00 2001 From: "Kevin J. Sung" Date: Mon, 13 May 2024 23:53:37 -0400 Subject: [PATCH] add spinful givens ansatz operator gate --- python/ffsim/qiskit/__init__.py | 2 ++ python/ffsim/qiskit/gates/__init__.py | 6 +++- python/ffsim/qiskit/gates/givens_ansatz.py | 40 ++++++++++++++++++++-- tests/python/qiskit/givens_ansatz_test.py | 35 +++++++++++++++++-- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/python/ffsim/qiskit/__init__.py b/python/ffsim/qiskit/__init__.py index b2ebb1913..493f9d0f8 100644 --- a/python/ffsim/qiskit/__init__.py +++ b/python/ffsim/qiskit/__init__.py @@ -14,6 +14,7 @@ from ffsim.qiskit.gates import ( DiagCoulombEvolutionJW, + GivensAnsatzOperatorJW, GivensAnsatzOperatorSpinlessJW, OrbitalRotationJW, OrbitalRotationSpinlessJW, @@ -38,6 +39,7 @@ __all__ = [ "DiagCoulombEvolutionJW", "DropNegligible", + "GivensAnsatzOperatorJW", "GivensAnsatzOperatorSpinlessJW", "MergeOrbitalRotations", "OrbitalRotationJW", diff --git a/python/ffsim/qiskit/gates/__init__.py b/python/ffsim/qiskit/gates/__init__.py index b7ce72826..1ef9b508a 100644 --- a/python/ffsim/qiskit/gates/__init__.py +++ b/python/ffsim/qiskit/gates/__init__.py @@ -11,7 +11,10 @@ """Qiskit fermionic quantum gates.""" from ffsim.qiskit.gates.diag_coulomb import DiagCoulombEvolutionJW -from ffsim.qiskit.gates.givens_ansatz import GivensAnsatzOperatorSpinlessJW +from ffsim.qiskit.gates.givens_ansatz import ( + GivensAnsatzOperatorJW, + GivensAnsatzOperatorSpinlessJW, +) from ffsim.qiskit.gates.orbital_rotation import ( OrbitalRotationJW, OrbitalRotationSpinlessJW, @@ -26,6 +29,7 @@ __all__ = [ "DiagCoulombEvolutionJW", + "GivensAnsatzOperatorJW", "GivensAnsatzOperatorSpinlessJW", "OrbitalRotationJW", "OrbitalRotationSpinlessJW", diff --git a/python/ffsim/qiskit/gates/givens_ansatz.py b/python/ffsim/qiskit/gates/givens_ansatz.py index aac263431..e4aa81241 100644 --- a/python/ffsim/qiskit/gates/givens_ansatz.py +++ b/python/ffsim/qiskit/gates/givens_ansatz.py @@ -28,12 +28,46 @@ from ffsim.variational import GivensAnsatzOperator -class GivensAnsatzOperatorSpinlessJW(Gate): +class GivensAnsatzOperatorJW(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", 2 * givens_ansatz_operator.norb, [], label=label + ) + + def _define(self): + """Gate decomposition.""" + qubits = QuantumRegister(self.num_qubits) + circuit = QuantumCircuit(qubits, name=self.name) + norb = len(qubits) // 2 + alpha_qubits = qubits[:norb] + beta_qubits = qubits[norb:] + for instruction in _givens_ansatz_jw(alpha_qubits, self.givens_ansatz_operator): + circuit.append(instruction) + for instruction in _givens_ansatz_jw(beta_qubits, self.givens_ansatz_operator): + circuit.append(instruction) + self.definition = circuit + + +class GivensAnsatzOperatorSpinlessJW(Gate): + """Givens rotation ansatz operator under the Jordan-Wigner transformation, spinless. + + Like :class:`GivensAnsatzOperatorJW` but only acts on a single spin species. + """ + def __init__( self, givens_ansatz_operator: GivensAnsatzOperator, *, label: str | None = None ): @@ -52,7 +86,9 @@ 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 + _givens_ansatz_jw(qubits, self.givens_ansatz_operator), + qubits=qubits, + name=self.name, ) diff --git a/tests/python/qiskit/givens_ansatz_test.py b/tests/python/qiskit/givens_ansatz_test.py index d8aa78a70..278a7a1c9 100644 --- a/tests/python/qiskit/givens_ansatz_test.py +++ b/tests/python/qiskit/givens_ansatz_test.py @@ -25,14 +25,43 @@ def brickwork(norb: int, n_layers: int): 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): +@pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) +def test_random_givens_ansatz_operator_spinful(norb: int, nelec: tuple[int, int]): """Test random Givens rotation ansatz gives correct output state.""" rng = np.random.default_rng() + dim = ffsim.dim(norb, nelec) + for _ in range(3): + n_layers = 2 * 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.GivensAnsatzOperatorJW(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) + + +@pytest.mark.parametrize("norb, nocc", ffsim.testing.generate_norb_nocc(range(5))) +def test_random_givens_ansatz_operator_spinless(norb: int, nocc: int): + """Test random spinless 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 + n_layers = 2 * 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)