Skip to content

Commit

Permalink
RzViaPhaseGradient (#1147)
Browse files Browse the repository at this point in the history
* `Rz` via controlled-add-or-sub into phase gradient

* docstring: reference

* add tensor test

* fix bug
  • Loading branch information
anurudhp authored Aug 1, 2024
1 parent fa132a3 commit 5db8857
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 49 deletions.
60 changes: 22 additions & 38 deletions qualtran/bloqs/chemistry/quad_fermion/givens_bloq.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 7 additions & 11 deletions qualtran/bloqs/chemistry/quad_fermion/givens_bloq_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
111 changes: 111 additions & 0 deletions qualtran/bloqs/rotations/rz_via_phase_gradient.py
Original file line number Diff line number Diff line change
@@ -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
87 changes: 87 additions & 0 deletions qualtran/bloqs/rotations/rz_via_phase_gradient_test.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions qualtran/serialization/resolver_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 5db8857

Please sign in to comment.