Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qualtran/bloqs/arithmetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
LinearDepthHalfLessThanEqual,
SingleQubitCompare,
)
from qualtran.bloqs.arithmetic.controlled_add_or_subtract import ControlledAddOrSubtract
from qualtran.bloqs.arithmetic.controlled_addition import CAdd
from qualtran.bloqs.arithmetic.conversions import (
SignedIntegerToTwosComplement,
Expand Down
3 changes: 3 additions & 0 deletions qualtran/bloqs/arithmetic/addition.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,9 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT':

return counts

def __str__(self):
return f'AddK(k={self.k})'


@bloq_example(generalizer=ignore_split_join)
def _add_k() -> AddK:
Expand Down
5 changes: 5 additions & 0 deletions qualtran/bloqs/arithmetic/addition_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,8 @@ def test_controlled_add_from_add():
assert ctrl == 0
assert a == 5
assert b == 15


def test_add_k_str():
add_k = AddK(QUInt(5), k=3)
assert str(add_k) == "AddK(k=3)"
3 changes: 3 additions & 0 deletions qualtran/bloqs/arithmetic/bitwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ def on_classical_vals(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT']:
x %= 2**self.dtype.bitsize
return {'x': x}

def __str__(self):
return f'BitwiseNot({self.dtype.num_bits})'


@bloq_example
def _bitwise_not() -> BitwiseNot:
Expand Down
5 changes: 5 additions & 0 deletions qualtran/bloqs/arithmetic/bitwise_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,8 @@ def test_bitwisenot_classical_action(dtype, bitsize):
else:
valid_range = range(2**bitsize)
qlt_testing.assert_consistent_classical_action(b, x=valid_range)


def test_bitwise_not_str():
bloq = BitwiseNot(QUInt(5))
assert str(bloq) == "BitwiseNot(5)"
2 changes: 1 addition & 1 deletion qualtran/bloqs/basic_gates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .identity import Identity
from .on_each import OnEach
from .power import Power
from .rotation import CRz, CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate
from .rotation import CRy, CRz, CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate
from .s_gate import SGate
from .su2_rotation import SU2RotationGate
from .swap import CSwap, Swap, TwoBitCSwap, TwoBitSwap
Expand Down
2 changes: 1 addition & 1 deletion qualtran/bloqs/basic_gates/global_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def _add_ctrled(
return bloq, _add_ctrled

def __str__(self) -> str:
return f'GPhase({self.coefficient})'
return f'GPhase({self.exponent})'


@bloq_example
Expand Down
5 changes: 5 additions & 0 deletions qualtran/bloqs/basic_gates/global_phase_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,8 @@ def test_t_complexity():

def test_global_phase(bloq_autotester):
bloq_autotester(_global_phase)


def test_global_phase_str():
bloq = GlobalPhase(exponent=0.5)
assert str(bloq) == "GPhase(0.5)"
2 changes: 1 addition & 1 deletion qualtran/bloqs/basic_gates/on_each.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
return super().wire_symbol(reg, idx)

def __str__(self):
return f'{self.gate}{self.n}'
return f'{self.gate}(oneach={self.n})'
5 changes: 5 additions & 0 deletions qualtran/bloqs/basic_gates/on_each_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@ def test_call_graph():
c1 = x_on_each.bloq_counts(generalizer=ignore_split_join)
c2 = x_on_each.decompose_bloq().bloq_counts(generalizer=ignore_split_join)
assert c1 == c2


def test_on_each_str():
on_each = OnEach(10, XGate())
assert str(on_each) == "X(oneach=10)"
24 changes: 22 additions & 2 deletions qualtran/bloqs/basic_gates/power.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cached_property
from typing import Dict, TYPE_CHECKING
from typing import Dict, Optional, Tuple, TYPE_CHECKING

import attrs
import numpy as np
from attrs import frozen

from qualtran import Bloq, BloqBuilder, GateWithRegisters, Side, Signature, SoquetT
from qualtran import Bloq, BloqBuilder, GateWithRegisters, Register, Side, Signature, SoquetT
from qualtran.drawing import LarrowTextBox, RarrowTextBox, Text, TextBox, WireSymbol
from qualtran.symbolics import is_symbolic, SymbolicInt

if TYPE_CHECKING:
Expand Down Expand Up @@ -86,3 +88,21 @@ def _circuit_diagram_info_(
wire_symbols = [f'{symbol}^{self.power}' for symbol in info.wire_symbols]

return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols)

def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':

if reg is None:
sub_title = self.bloq.wire_symbol(None, idx)
if not isinstance(sub_title, Text):
raise ValueError(
f"{self.bloq} should return a `Text` object for reg=None wire symbol."
)
if sub_title.text == '':
return Text('')

return Text(f'{sub_title.text}**{self.power}')

ws = self.bloq.wire_symbol(reg, idx)
if isinstance(ws, (Text, TextBox, RarrowTextBox, LarrowTextBox)):
return attrs.evolve(ws, text=f'{ws.text}**{self.power}')
return ws
Comment thread
mpharrigan marked this conversation as resolved.
39 changes: 39 additions & 0 deletions qualtran/bloqs/basic_gates/power_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,42 @@ def to_cirq_circuit(bloq: GateWithRegisters) -> cirq.Circuit:
@pytest.mark.slow
def test_no_circular_import():
subprocess.check_call(['python', '-c', 'from qualtran.bloqs.basic_gates import power'])


def test_power_wire_symbol():
from qualtran import Signature
from qualtran.drawing import LarrowTextBox, RarrowTextBox, Text, TextBox

class MockBloq(GateWithRegisters):
@property
def signature(self):
return Signature([])

def wire_symbol(self, reg, idx=tuple()):
if reg is None:
return Text("Mock")
if reg.name == 'a':
return Text("A")
if reg.name == 'b':
return TextBox("B")
if reg.name == 'c':
return LarrowTextBox("C")
if reg.name == 'd':
return RarrowTextBox("D")

bloq = MockBloq()
power_bloq = Power(bloq, 3)

assert power_bloq.wire_symbol(None) == Text("Mock**3")

from qualtran import QUInt, Register

reg_a = Register('a', QUInt(1))
reg_b = Register('b', QUInt(1))
reg_c = Register('c', QUInt(1))
reg_d = Register('d', QUInt(1))

assert power_bloq.wire_symbol(reg_a) == Text("A**3")
assert power_bloq.wire_symbol(reg_b) == TextBox("B**3")
assert power_bloq.wire_symbol(reg_c) == LarrowTextBox("C**3")
assert power_bloq.wire_symbol(reg_d) == RarrowTextBox("D**3")
92 changes: 78 additions & 14 deletions qualtran/bloqs/basic_gates/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"""

from functools import cached_property
from typing import Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING, Union
from typing import Dict, Iterable, Optional, Sequence, Tuple, Type, TYPE_CHECKING, Union

import attrs
import cirq
Expand Down Expand Up @@ -163,7 +163,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
return TextBox(f'Z^{self.exponent}')

def __str__(self):
return f'Z**{self.exponent}'
return f'Z(pow={self.exponent})'


@bloq_example
Expand Down Expand Up @@ -231,7 +231,7 @@ def adjoint(self) -> 'CZPowGate':
return attrs.evolve(self, exponent=-self.exponent)

def __str__(self):
return f'CZ**{self.exponent}'
return f'CZ(pow={self.exponent})'


@bloq_example
Expand Down Expand Up @@ -306,7 +306,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
return TextBox(f'X^{self.exponent}')

def __str__(self):
return f'X**{self.exponent}'
return f'X(pow={self.exponent})'


@bloq_example
Expand Down Expand Up @@ -381,7 +381,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
return TextBox(f'Y^{self.exponent}')

def __str__(self):
return f'Y**{self.exponent}'
return f'Y(pow={self.exponent})'


@bloq_example
Expand Down Expand Up @@ -487,6 +487,29 @@ def _rz() -> Rz:

_RZ_DOC = BloqDocSpec(bloq_cls=Rz, examples=[_rz], call_graph_example=None)

_PowClsT = Union[Type[XPowGate], Type[YPowGate], Type[ZPowGate]]


def _controlled_rp_circuit(
bb: 'BloqBuilder',
/,
*,
single_q_pow_cls: _PowClsT,
angle: SymbolicFloat,
eps: SymbolicFloat,
ctrl: 'Soquet',
q: 'Soquet',
) -> Dict[str, 'SoquetT']:
from qualtran.bloqs.basic_gates import CNOT

t = angle / np.pi
q = bb.add(single_q_pow_cls(t / 2, eps=eps / 2), q=q)
ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)
q = bb.add(single_q_pow_cls(-t / 2, eps=eps / 2), q=q)
ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)

return {'ctrl': ctrl, 'q': q}


@frozen
class CRz(Bloq):
Expand Down Expand Up @@ -532,15 +555,9 @@ def signature(self) -> 'Signature':
def build_composite_bloq(
self, bb: 'BloqBuilder', ctrl: 'Soquet', q: 'Soquet'
) -> Dict[str, 'SoquetT']:
from qualtran.bloqs.basic_gates import CNOT

t = self.angle / np.pi
q = bb.add(ZPowGate(t / 2, eps=self.eps / 2), q=q)
ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)
q = bb.add(ZPowGate(-t / 2, eps=self.eps / 2), q=q)
ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)

return {'ctrl': ctrl, 'q': q}
return _controlled_rp_circuit(
bb, single_q_pow_cls=ZPowGate, angle=self.angle, eps=self.eps, ctrl=ctrl, q=q
)

def __str__(self):
return f'CRz({self.angle})'
Expand Down Expand Up @@ -677,6 +694,20 @@ def as_pl_op(self, wires: 'Wires') -> 'Operation':
def adjoint(self) -> 'Ry':
return attrs.evolve(self, angle=-self.angle)

def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
if ctrl_spec != CtrlSpec():
return super().get_ctrl_system(ctrl_spec)

from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs

return get_ctrl_system_1bit_cv_from_bloqs(
bloq=self,
ctrl_spec=ctrl_spec,
current_ctrl_bit=None,
bloq_with_ctrl=CRy(self.angle, eps=self.eps),
ctrl_reg_name='ctrl',
)
Comment on lines +697 to +709
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This is a great addition! For consistency with Ry and Rz, it would be good to also add a specialized get_ctrl_system for Rx. This would involve creating a CRx bloq, similar to how CRy was added in this PR. The _controlled_rp_circuit helper is already generic enough to support this.

You could add the following CRx class:

@frozen
class CRx(Bloq):
    r"""A controlled Rx rotation."""
    angle: SymbolicFloat
    eps: SymbolicFloat = 1e-11

    @cached_property
    def signature(self) -> 'Signature':
        return Signature.build(ctrl=1, q=1)

    def build_composite_bloq(
        self, bb: 'BloqBuilder', ctrl: 'Soquet', q: 'Soquet'
    ) -> Dict[str, 'SoquetT']:
        return _controlled_rp_circuit(
            bb, single_q_pow_cls=XPowGate, angle=self.angle, eps=self.eps, ctrl=ctrl, q=q
        )

    def __str__(self):
        return f'CRx({self.angle})'

And then add this method to the Rx class:

    def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
        if ctrl_spec != CtrlSpec():
            return super().get_ctrl_system(ctrl_spec)

        from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs

        return get_ctrl_system_1bit_cv_from_bloqs(
            bloq=self,
            ctrl_spec=ctrl_spec,
            current_ctrl_bit=None,
            bloq_with_ctrl=CRx(self.angle, eps=self.eps),
            ctrl_reg_name='ctrl',
        )

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can do this in a follow up


def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text('')
Expand All @@ -696,3 +727,36 @@ def _rx() -> Rx:
def _ry() -> Ry:
ry = Ry(angle=np.pi / 4.0)
return ry


@frozen
class CRy(Bloq):
r"""A controlled Ry rotation.

Args:
angle: The rotation angle in radians.
eps: The precision of the rotation. This parameter is for bookkeeping and does
not affect e.g. the tensor representation of this gate. When synthesizing
a rotation from a discrete gate set, you must fix a precision `eps`.

Registers:
ctrl: Whether the rotation is active.
q: The qubit on which we optionally perform the rotation.
"""

angle: SymbolicFloat
eps: SymbolicFloat = 1e-11

@cached_property
def signature(self) -> 'Signature':
return Signature.build(ctrl=1, q=1)

def build_composite_bloq(
self, bb: 'BloqBuilder', ctrl: 'Soquet', q: 'Soquet'
) -> Dict[str, 'SoquetT']:
return _controlled_rp_circuit(
bb, single_q_pow_cls=YPowGate, angle=self.angle, eps=self.eps, ctrl=ctrl, q=q
)

def __str__(self):
return f'CRy({self.angle})'
39 changes: 33 additions & 6 deletions qualtran/bloqs/basic_gates/rotation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
ZGate,
ZPowGate,
)
from qualtran.bloqs.basic_gates.rotation import _crz, _cz_pow, _rx, _ry, _rz, _z_pow, CRz
from qualtran.bloqs.basic_gates.rotation import _crz, _cz_pow, _rx, _ry, _rz, _z_pow, CRy, CRz
from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost
from qualtran.resource_counting.classify_bloqs import bloq_is_rotation, bloq_is_t_like

Expand Down Expand Up @@ -156,6 +156,7 @@ def test_t_like_rotation_gates():
assert not bloq_is_rotation(Rx(angle))
assert not bloq_is_rotation(Ry(angle))
assert not bloq_is_rotation(Rz(angle))
assert not bloq_is_rotation(CRy(angle))
assert bloq_is_t_like(Rx(angle))
assert bloq_is_t_like(Ry(angle))
assert bloq_is_t_like(Rz(angle))
Expand Down Expand Up @@ -257,15 +258,17 @@ def test_pl_interop():


def test_str():
assert str(ZPowGate()) == "Z**1.0"
assert str(XPowGate()) == "X**1.0"
assert str(YPowGate()) == "Y**1.0"
assert str(ZPowGate()) == "Z(pow=1.0)"
assert str(XPowGate()) == "X(pow=1.0)"
assert str(YPowGate()) == "Y(pow=1.0)"
assert str(_ry()) == "Ry(0.7853981633974483)"
assert str(_rx()) == "Rx(0.7853981633974483)"
assert str(_rz()) == "Rz(a)"

assert str(CZPowGate(1.0)) == 'CZ**1.0'
assert str(CZPowGate(0.9)) == 'CZ**0.9'
assert str(CZPowGate(1.0)) == 'CZ(pow=1.0)'
assert str(CZPowGate(0.9)) == 'CZ(pow=0.9)'

assert str(CRz(1.0)) == 'CRz(1.0)'


def test_rx(bloq_autotester):
Expand All @@ -278,3 +281,27 @@ def test_ry(bloq_autotester):

def test_rz(bloq_autotester):
bloq_autotester(_rz)


def test_cry():
from qualtran.bloqs.basic_gates.rotation import CRy

rs = np.random.RandomState(52)
angle = rs.uniform(0, 2 * np.pi)

u1 = CRy(angle=angle).tensor_contract()
u2 = cirq.unitary(cirq.Ry(rads=angle).controlled())
u3 = Controlled(Ry(angle=angle), CtrlSpec()).tensor_contract()
np.testing.assert_allclose(u1, u2, atol=1e-8)
np.testing.assert_allclose(u1, u3, atol=1e-8)

ry = Ry(angle=angle)
assert ry.controlled() == CRy(angle=angle)

ctrl_bloq = Controlled(ry.as_composite_bloq(), CtrlSpec()).decompose_bloq()
(cry_inst,) = list(ctrl_bloq.bloq_instances)
assert cry_inst.bloq == CRy(angle=angle)

# testing the specialized ctrl
ctrl, bloq_with_ctrl = ry.get_ctrl_system(CtrlSpec())
assert ctrl == CRy(angle=angle)
Loading
Loading