From 5db88570b6359c54db9249494ed01761cc5c5275 Mon Sep 17 00:00:00 2001 From: Anurudh Peduri <7265746+anurudhp@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:07:48 -0700 Subject: [PATCH] RzViaPhaseGradient (#1147) * `Rz` via controlled-add-or-sub into phase gradient * docstring: reference * add tensor test * fix bug --- .../chemistry/quad_fermion/givens_bloq.py | 60 ++++------ .../quad_fermion/givens_bloq_test.py | 18 ++- .../bloqs/rotations/rz_via_phase_gradient.py | 111 ++++++++++++++++++ .../rotations/rz_via_phase_gradient_test.py | 87 ++++++++++++++ qualtran/serialization/resolver_dict.py | 2 + 5 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 qualtran/bloqs/rotations/rz_via_phase_gradient.py create mode 100644 qualtran/bloqs/rotations/rz_via_phase_gradient_test.py diff --git a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py index 645e633f6..3c2a2d24f 100644 --- a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py +++ b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py @@ -51,23 +51,8 @@ from attrs import frozen from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, QBit, QFxp, Signature, SoquetT -from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, Toffoli, XGate -from qualtran.bloqs.rotations.phase_gradient import AddIntoPhaseGrad - - -class RzAddIntoPhaseGradient(AddIntoPhaseGrad): - r"""Temporary controlled adder to give the right complexity for Rz rotations by - phase gradient state addition. - - References: - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization]( - https://arxiv.org/abs/2007.07391). - Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled - rotations - """ - - def bloq_counts(self): - return {Toffoli(): self.x_bitsize - 2} +from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, XGate +from qualtran.bloqs.rotations.rz_via_phase_gradient import RzViaPhaseGradient @frozen @@ -113,8 +98,19 @@ def signature(self) -> Signature: return Signature.build_from_dtypes( target_i=QBit(), target_j=QBit(), - rom_data=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False), - phase_gradient=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False), + rom_data=self.phasegrad_dtype, + phase_gradient=self.phasegrad_dtype, + ) + + @cached_property + def phasegrad_dtype(self): + return QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False) + + @property + def add_into_phasegrad(self) -> RzViaPhaseGradient: + # set up rz-rotation via phase-gradient state + return RzViaPhaseGradient( + angle_dtype=self.phasegrad_dtype, phasegrad_dtype=self.phasegrad_dtype ) def build_composite_bloq( @@ -125,15 +121,6 @@ def build_composite_bloq( rom_data: SoquetT, phase_gradient: SoquetT, ) -> Dict[str, SoquetT]: - # set up rz-rotation via phase-gradient state - add_into_phasegrad_gate = RzAddIntoPhaseGradient( - x_bitsize=self.phasegrad_bitsize, - phase_bitsize=self.phasegrad_bitsize, - right_shift=0, - sign=1, - controlled_by=1, - ) - # clifford block target_i = bb.add(XGate(), q=target_i) target_j = bb.add(SGate(), q=target_j) @@ -144,10 +131,10 @@ def build_composite_bloq( # parallel rz (Can probably be improved with single out of place adder into a single ancilla target_i, rom_data, phase_gradient = bb.add( - add_into_phasegrad_gate, x=rom_data, phase_grad=phase_gradient, ctrl=target_i + self.add_into_phasegrad, q=target_i, angle=rom_data, phase_grad=phase_gradient ) target_j, rom_data, phase_gradient = bb.add( - add_into_phasegrad_gate, x=rom_data, phase_grad=phase_gradient, ctrl=target_j + self.add_into_phasegrad, q=target_j, angle=rom_data, phase_grad=phase_gradient ) # clifford block @@ -242,16 +229,13 @@ def build_composite_bloq( ) # set up rz-rotation on j-bit by phase-gradient state - add_into_phasegrad_gate = RzAddIntoPhaseGradient( - x_bitsize=self.phasegrad_bitsize, - phase_bitsize=self.phasegrad_bitsize, - right_shift=0, - sign=1, - controlled_by=1, - ) target_j, cplx_rom_data, phase_gradient = bb.add( - add_into_phasegrad_gate, x=cplx_rom_data, phase_grad=phase_gradient, ctrl=target_j + real_givens_gate.add_into_phasegrad, + q=target_j, + angle=cplx_rom_data, + phase_grad=phase_gradient, ) + return { 'target_i': target_i, 'target_j': target_j, diff --git a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq_test.py b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq_test.py index a0480965e..1ae077366 100644 --- a/qualtran/bloqs/chemistry/quad_fermion/givens_bloq_test.py +++ b/qualtran/bloqs/chemistry/quad_fermion/givens_bloq_test.py @@ -19,12 +19,13 @@ from scipy.linalg import expm import qualtran.testing as qlt_testing -from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, Toffoli, XGate +from qualtran import QFxp +from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, XGate from qualtran.bloqs.chemistry.quad_fermion.givens_bloq import ( ComplexGivensRotationByPhaseGradient, RealGivensRotationByPhaseGradient, - RzAddIntoPhaseGradient, ) +from qualtran.bloqs.rotations.rz_via_phase_gradient import RzViaPhaseGradient def test_circuit_decomposition_givens(): @@ -72,12 +73,8 @@ def circuit_construction(eta): @pytest.mark.parametrize("x_bitsize", [4, 5, 6, 7]) def test_count_t_cliffords(x_bitsize: int): - add_into_phasegrad_gate = RzAddIntoPhaseGradient( - x_bitsize=x_bitsize, phase_bitsize=x_bitsize, right_shift=0, sign=1, controlled_by=1 - ) - bloq_counts = add_into_phasegrad_gate.bloq_counts() - # produces Toffoli costs given in chemistry papers - assert bloq_counts[Toffoli()] == (x_bitsize - 2) + dtype = QFxp(x_bitsize, x_bitsize, signed=False) + add_into_phasegrad_gate = RzViaPhaseGradient(angle_dtype=dtype, phasegrad_dtype=dtype) gate = RealGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize) gate_counts = gate.bloq_counts() @@ -91,9 +88,8 @@ def test_count_t_cliffords(x_bitsize: int): @pytest.mark.parametrize("x_bitsize", [4, 5, 6, 7]) def test_complex_givens_costs(x_bitsize: int): - add_into_phasegrad_gate = RzAddIntoPhaseGradient( - x_bitsize=x_bitsize, phase_bitsize=x_bitsize, right_shift=0, sign=1, controlled_by=1 - ) + dtype = QFxp(x_bitsize, x_bitsize, signed=False) + add_into_phasegrad_gate = RzViaPhaseGradient(angle_dtype=dtype, phasegrad_dtype=dtype) real_givens_gate = RealGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize) gate = ComplexGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize) gate_counts = gate.bloq_counts() diff --git a/qualtran/bloqs/rotations/rz_via_phase_gradient.py b/qualtran/bloqs/rotations/rz_via_phase_gradient.py new file mode 100644 index 000000000..abe7c707d --- /dev/null +++ b/qualtran/bloqs/rotations/rz_via_phase_gradient.py @@ -0,0 +1,111 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + QBit, + QDType, + QUInt, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic.controlled_add_or_subtract import ControlledAddOrSubtract +from qualtran.bloqs.bookkeeping import Cast +from qualtran.resource_counting import BloqCountT, SympySymbolAllocator + + +@frozen +class RzViaPhaseGradient(Bloq): + r"""Apply a controlled-Rz using a phase gradient state. + + Implements the following unitary action: + + $$ + |\psi\rangle \otimes |x\rangle \mapsto \text{Rz}(4 \pi x) |\psi\rangle \otimes |x\rangle + $$ + + for every state $|\psi\rangle$ and every $x$, or equivalently + + $$ + |b\rangle|x\rangle \mapsto |b\rangle e^{- (-1)^b i x/2} |x\rangle + $$ + + for every $b \in \{0, 1\}$ and every $x$. + + To apply an $\text{Rz}(\theta) = e^{-i Z \theta/2}$, the angle register $x$ should store $\theta/(4\pi)$. + + Args: + angle_dtype: Data type for the `angle_data` register. + phasegrad_dtype: Data type for the phase gradient register. + + Registers: + q: The qubit to apply Rz on. + angle: The rotation angle in radians. + phase_grad: The phase gradient register of sufficient width. + + References: + [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](https://arxiv.org/abs/2007.07391). + Section II-C: Oracles for phasing by cost function. + Appendix A: Addition for controlled rotations. + """ + + angle_dtype: QDType + phasegrad_dtype: QDType + + @property + def signature(self) -> Signature: + return Signature.build_from_dtypes( + q=QBit(), angle=self.angle_dtype, phase_grad=self.phasegrad_dtype + ) + + @property + def _angle_int_dtype(self) -> QUInt: + return QUInt(self.angle_dtype.num_qubits) + + @property + def _phasegrad_int_dtype(self) -> QUInt: + return QUInt(self.phasegrad_dtype.num_qubits) + + def build_composite_bloq( + self, bb: BloqBuilder, q: Soquet, angle: Soquet, phase_grad: Soquet + ) -> dict[str, SoquetT]: + angle = bb.add(Cast(self.angle_dtype, self._angle_int_dtype), reg=angle) + phase_grad = bb.add(Cast(self.phasegrad_dtype, self._phasegrad_int_dtype), reg=phase_grad) + + q, angle, phase_grad = bb.add( + ControlledAddOrSubtract(self._angle_int_dtype, self._phasegrad_int_dtype), + ctrl=q, + a=angle, + b=phase_grad, + ) + + angle = bb.add(Cast(self._angle_int_dtype, self.angle_dtype), reg=angle) + phase_grad = bb.add(Cast(self._phasegrad_int_dtype, self.phasegrad_dtype), reg=phase_grad) + + return {'q': q, 'angle': angle, 'phase_grad': phase_grad} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> set['BloqCountT']: + return {(ControlledAddOrSubtract(self._angle_int_dtype, self._phasegrad_int_dtype), 1)} + + +@bloq_example +def _rz_via_phase_gradient() -> RzViaPhaseGradient: + from qualtran import QFxp + + rz_via_phase_gradient = RzViaPhaseGradient(angle_dtype=QFxp(4, 4), phasegrad_dtype=QFxp(4, 4)) + return rz_via_phase_gradient diff --git a/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py b/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py new file mode 100644 index 000000000..0e905a1e5 --- /dev/null +++ b/qualtran/bloqs/rotations/rz_via_phase_gradient_test.py @@ -0,0 +1,87 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import pytest +import sympy +from attrs import frozen + +from qualtran import Bloq, BloqBuilder, QBit, QFxp, QUInt, Signature, Soquet, SoquetT +from qualtran.bloqs.basic_gates import IntState, Rz, TGate +from qualtran.bloqs.rotations.phase_gradient import PhaseGradientState +from qualtran.bloqs.rotations.rz_via_phase_gradient import ( + _rz_via_phase_gradient, + RzViaPhaseGradient, +) +from qualtran.resource_counting import BloqCount, get_cost_value + + +def test_examples(bloq_autotester): + bloq_autotester(_rz_via_phase_gradient) + + +def test_costs(): + n = sympy.Symbol("n") + dtype = QUInt(n) + bloq = RzViaPhaseGradient(angle_dtype=dtype, phasegrad_dtype=dtype) + # TODO need to improve this to `4 * n - 8` (i.e. Toffoli cost of `n - 2`) + assert get_cost_value(bloq, BloqCount.for_gateset('t')) == {TGate(): 4 * n - 4} + + +@frozen +class TestRz(Bloq): + angle: float + phasegrad_bitsize: int = 4 + + @property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(q=QBit()) + + @property + def dtype(self) -> QFxp: + return QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize) + + @property + def load_angle_bloq(self) -> IntState: + return IntState( + self.dtype.to_fixed_width_int(self.angle / (4 * np.pi), require_exact=False), + bitsize=self.phasegrad_bitsize, + ) + + @property + def load_phasegrad_bloq(self) -> PhaseGradientState: + return PhaseGradientState(self.phasegrad_bitsize) + + def build_composite_bloq(self, bb: 'BloqBuilder', q: Soquet) -> dict[str, 'SoquetT']: + angle = bb.add(self.load_angle_bloq) + phase_grad = bb.add(self.load_phasegrad_bloq) + q, angle, phase_grad = bb.add( + RzViaPhaseGradient(angle_dtype=self.dtype, phasegrad_dtype=self.dtype), + q=q, + angle=angle, + phase_grad=phase_grad, + ) + bb.add(self.load_angle_bloq.adjoint(), val=angle) + bb.add(self.load_phasegrad_bloq.adjoint(), phase_grad=phase_grad) + return {'q': q} + + +@pytest.mark.parametrize("bitsize", [3, 4]) +def test_tensor(bitsize: int): + for coeff in [1, 2]: + theta = 4 * np.pi * coeff / 2**bitsize + + actual = TestRz(angle=theta, phasegrad_bitsize=bitsize).tensor_contract() + expected = Rz(theta).tensor_contract() + + np.testing.assert_allclose(actual, expected, atol=1 / 2**bitsize) diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 0923c610b..17016ca00 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -135,6 +135,7 @@ import qualtran.bloqs.rotations.phasing_via_cost_function import qualtran.bloqs.rotations.programmable_rotation_gate_array import qualtran.bloqs.rotations.quantum_variable_rotation +import qualtran.bloqs.rotations.rz_via_phase_gradient import qualtran.bloqs.state_preparation.prepare_base import qualtran.bloqs.state_preparation.prepare_uniform_superposition import qualtran.bloqs.state_preparation.state_preparation_alias_sampling @@ -379,6 +380,7 @@ "qualtran.bloqs.rotations.quantum_variable_rotation.QvrInterface": qualtran.bloqs.rotations.quantum_variable_rotation.QvrInterface, "qualtran.bloqs.rotations.quantum_variable_rotation.QvrPhaseGradient": qualtran.bloqs.rotations.quantum_variable_rotation.QvrPhaseGradient, "qualtran.bloqs.rotations.quantum_variable_rotation.QvrZPow": qualtran.bloqs.rotations.quantum_variable_rotation.QvrZPow, + "qualtran.bloqs.rotations.rz_via_phase_gradient.RzViaPhaseGradient": qualtran.bloqs.rotations.rz_via_phase_gradient.RzViaPhaseGradient, "qualtran.bloqs.state_preparation.prepare_uniform_superposition.PrepareUniformSuperposition": qualtran.bloqs.state_preparation.prepare_uniform_superposition.PrepareUniformSuperposition, "qualtran.bloqs.state_preparation.prepare_base.PrepareOracle": qualtran.bloqs.state_preparation.prepare_base.PrepareOracle, "qualtran.bloqs.state_preparation.state_preparation_alias_sampling.StatePreparationAliasSampling": qualtran.bloqs.state_preparation.state_preparation_alias_sampling.StatePreparationAliasSampling,