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
2 changes: 1 addition & 1 deletion qualtran/_infra/gate_with_registers.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -

return _wire_symbol_from_gate(self, self.signature, reg, idx)

# Part-2: Cirq-FT style interface can be used to implemented algorithms by Bloq authors.
# Part-2: Cirq-FT style interface can be used to implement algorithms by Bloq authors.

def _num_qubits_(self) -> int:
return total_bits(self.signature)
Expand Down
2 changes: 1 addition & 1 deletion qualtran/bloqs/basic_gates/global_phase_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_unitary():
for alpha in random_state.random(size=10):
coefficient = np.exp(2j * np.pi * alpha)
bloq = GlobalPhase(exponent=2 * alpha)
np.testing.assert_allclose(cirq.unitary(bloq), coefficient)
np.testing.assert_allclose(bloq.tensor_contract(), coefficient)


@pytest.mark.parametrize("cv", [0, 1])
Expand Down
6 changes: 3 additions & 3 deletions qualtran/bloqs/basic_gates/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def adjoint(self) -> 'ZPowGate':
def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text('')
return TextBox(str(self))
return TextBox(f'Z^{self.exponent}')

def __str__(self):
return f'Z**{self.exponent}'
Expand Down Expand Up @@ -302,7 +302,7 @@ def adjoint(self) -> 'XPowGate':
def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text('')
return TextBox(str(self))
return TextBox(f'X^{self.exponent}')

def __str__(self):
return f'X**{self.exponent}'
Expand Down Expand Up @@ -376,7 +376,7 @@ def adjoint(self) -> 'YPowGate':
def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol':
if reg is None:
return Text('')
return TextBox(str(self))
return TextBox(f'Y^{self.exponent}')

def __str__(self):
return f'Y**{self.exponent}'
Expand Down
19 changes: 16 additions & 3 deletions qualtran/bloqs/phase_estimation/text_book_qpe_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
import numpy as np
import pytest

import qualtran.testing as qlt_testing
from qualtran import Signature
from qualtran._infra.gate_with_registers import get_named_qubits
from qualtran.bloqs.basic_gates import ZPowGate
from qualtran.bloqs.chemistry.hubbard_model.qubitization import get_walk_operator_for_hubbard_model
from qualtran.bloqs.for_testing.qubitization_walk_test import get_uniform_pauli_qubitized_walk
from qualtran.bloqs.phase_estimation.lp_resource_state import LPResourceState
from qualtran.bloqs.phase_estimation.qpe_window_state import RectangularWindowState
from qualtran.bloqs.phase_estimation.text_book_qpe import TextbookQPE
from qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import (
HamiltonianSimulationByGQSP,
)
from qualtran.bloqs.phase_estimation import LPResourceState, RectangularWindowState, TextbookQPE
from qualtran.cirq_interop.testing import GateHelper


Expand Down Expand Up @@ -109,3 +112,13 @@ def should_decompose(binst):
# 5. Verify that the estimated phase is correct.
phase = theta * 2 * np.pi
np.testing.assert_allclose(eig_val / qubitization_lambda, np.cos(phase), atol=eps)


def test_qpe_of_gqsp():
# This triggered a bug in the cirq interop.
# https://github.com/quantumlib/Qualtran/issues/1570

walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)
hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7)
textbook_qpe_w_gqsp = TextbookQPE(hubbard_time_evolution_by_gqsp, RectangularWindowState(3))
qlt_testing.assert_valid_bloq_decomposition(textbook_qpe_w_gqsp)
4 changes: 2 additions & 2 deletions qualtran/bloqs/qsp/generalized_qsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ class GeneralizedQSP(GateWithRegisters):
Motlagh and Wiebe. (2023). Theorem 3; Figure 2; Theorem 6.
"""

U: GateWithRegisters
U: 'Bloq'
P: Union[Tuple[complex, ...], Shaped] = field(converter=_to_tuple)
Q: Union[Tuple[complex, ...], Shaped] = field(converter=_to_tuple)
negative_power: SymbolicInt = field(default=0, kw_only=True)
Expand All @@ -302,7 +302,7 @@ def signature(self) -> Signature:
@classmethod
def from_qsp_polynomial(
cls,
U: GateWithRegisters,
U: 'Bloq',
P: Union[NDArray[np.number], Sequence[complex], Shaped],
*,
negative_power: SymbolicInt = 0,
Expand Down
40 changes: 18 additions & 22 deletions qualtran/cirq_interop/_bloq_to_cirq.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@

"""Qualtran Bloqs to Cirq gates/circuits conversion."""

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

import cirq
import networkx as nx
import numpy as np
from numpy.typing import NDArray

from qualtran import (
Bloq,
Expand All @@ -40,7 +38,6 @@
_get_all_and_output_quregs_from_input,
merge_qubits,
split_qubits,
total_bits,
)
from qualtran.cirq_interop._cirq_to_bloq import _QReg, CirqQuregInT, CirqQuregT
from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager
Expand All @@ -58,7 +55,9 @@ def _cirq_style_decompose_from_decompose_bloq(
# Input qubits can get de-allocated by cbloq.to_cirq_circuit_and_quregs, thus mark them as managed.
qm = InteropQubitManager(context.qubit_manager)
qm.manage_qubits(merge_qubits(bloq.signature.lefts(), **in_quregs))
circuit, out_quregs = cbloq.to_cirq_circuit_and_quregs(qubit_manager=qm, **in_quregs)
circuit, out_quregs = _cbloq_to_cirq_circuit(
cbloq.signature, in_quregs, cbloq._binst_graph, qubit_manager=qm
)
qubit_map = {q: q for q in circuit.all_qubits()}
for reg in bloq.signature.rights():
if reg.side == Side.RIGHT:
Expand Down Expand Up @@ -93,11 +92,6 @@ def bloq(self) -> Bloq:
"""The bloq we're wrapping."""
return self._bloq

@cached_property
def signature(self) -> Signature:
"""`GateWithRegisters` registers."""
return self.bloq.signature

@classmethod
def bloq_on(
cls, bloq: Bloq, cirq_quregs: Dict[str, 'CirqQuregT'], qubit_manager: cirq.QubitManager # type: ignore[type-var]
Expand All @@ -120,15 +114,16 @@ def bloq_on(
all_quregs, out_quregs = _get_all_and_output_quregs_from_input(
bloq.signature, qubit_manager, in_quregs=cirq_quregs
)
return BloqAsCirqGate(bloq=bloq).on_registers(**all_quregs), out_quregs
cirq_op = BloqAsCirqGate(bloq=bloq).on(*merge_qubits(bloq.signature, **all_quregs))
return cirq_op, out_quregs

def _num_qubits_(self) -> int:
return total_bits(self.signature)
return self.bloq.signature.n_qubits()

def _decompose_with_context_(
self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None
) -> cirq.OP_TREE:
quregs = split_qubits(self.signature, qubits)
quregs = split_qubits(self.bloq.signature, qubits)
if context is None:
context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
try:
Expand All @@ -142,23 +137,24 @@ def _decompose_with_context_(
def _decompose_(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE:
return self._decompose_with_context_(qubits)

def _has_unitary_(self):
return all(reg.side == Side.THRU for reg in self.bloq.signature)

def _unitary_(self):
if all(reg.side == Side.THRU for reg in self.signature):
if all(reg.side == Side.THRU for reg in self.bloq.signature):
try:
_ = self.bloq.decompose_bloq() # check for decomposability
# If decomposable, return NotImplemented to let the cirq protocol
# try its decomposition-based strategies.
_ = self.bloq.decompose_bloq()
return NotImplemented
except (DecomposeNotImplementedError, DecomposeTypeError):
tensor = self.bloq.tensor_contract()
if tensor.ndim != 2:
return NotImplemented
assert tensor.ndim == 2, "All registers should have been checked to be THRU."
return tensor
except NotImplementedError:
return NotImplemented
return NotImplemented

def on_registers(
self, **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]] # type: ignore[type-var]
) -> cirq.Operation:
return self.on(*merge_qubits(self.signature, **qubit_regs))

def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo:
"""Draw cirq diagrams.

Expand Down
8 changes: 4 additions & 4 deletions qualtran/cirq_interop/_bloq_to_cirq_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from attrs import frozen

from qualtran import Bloq, BloqBuilder, ConnectionT, Signature, Soquet, SoquetT
from qualtran._infra.gate_with_registers import get_named_qubits
from qualtran._infra.gate_with_registers import get_named_qubits, merge_qubits
from qualtran.bloqs.basic_gates import Toffoli, XGate, YGate
from qualtran.bloqs.cryptography.rsa import ModExp
from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd
Expand Down Expand Up @@ -235,8 +235,8 @@ def test_bloq_as_cirq_gate_for_mod_exp():
mod_exp = ModExp.make_for_shor(4, 3)
gate = BloqAsCirqGate(mod_exp)
# Use Cirq's infrastructure to construct an operation and corresponding decomposition.
quregs = get_named_qubits(gate.signature)
op = gate.on_registers(**quregs)
quregs = get_named_qubits(mod_exp.signature)
op = gate.on(*merge_qubits(mod_exp.signature, **quregs))
# cirq.decompose_once(op) delegates to underlying Bloq's decomposition specified in
# `bloq.decompose_bloq()` and wraps resulting composite bloq in a Cirq op-tree. Note
# how `BloqAsCirqGate.decompose_with_registers()` automatically takes care of mapping
Expand Down Expand Up @@ -266,7 +266,7 @@ def test_bloq_as_cirq_gate_for_mod_exp():
decomposed_circuit, out_regs = cbloq.to_cirq_circuit_and_quregs(exponent=quregs['exponent'])
# Whereas when directly applying a cirq gate on qubits to get an operations, we need to
# specify both input and output registers.
circuit = cirq.Circuit(gate.on_registers(**out_regs), decomposed_circuit)
circuit = cirq.Circuit(gate.on(*merge_qubits(cbloq.signature, **out_regs)), decomposed_circuit)
# Notice the newly allocated qubits _C(0) and _C(1) for output register x.
cirq.testing.assert_has_diagram(
circuit,
Expand Down
71 changes: 44 additions & 27 deletions qualtran/cirq_interop/_cirq_to_bloq.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import abc
import itertools
import numbers
import warnings
from functools import cached_property
from typing import Any, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, TypeVar, Union

Expand Down Expand Up @@ -70,17 +71,28 @@ def _get_cirq_quregs(signature: Signature, qm: InteropQubitManager):
return ret


class CirqGateAsBloqBase(GateWithRegisters, metaclass=abc.ABCMeta):
"""A Bloq wrapper around a `cirq.Gate`"""
class CirqGateAsBloqBase(Bloq, metaclass=abc.ABCMeta):
"""A base class to bootstrap a bloq from a `cirq.Gate`.

Bloq authors can inherit from this abstract class and override the `cirq_gate` property
to get a bloq adapted from the cirq gate. Authors can continue to customize the bloq
by overriding methods (like costs, string representations, ...).

Otherwise, this class fulfils the Bloq API by delegating to `cirq.Gate` methods.

This is the base class that provides the functionality for the `CirqGateAsBloq` adapter.
The adapter lets you use any `cirq.Gate` as a bloq immediately (without defining a new class
that inherits from `CirqGateAsBloqBase`), and is used as a fallback in the interoperability
functionality.
"""

@property
@abc.abstractmethod
def cirq_gate(self) -> cirq.Gate: ...
def cirq_gate(self) -> cirq.Gate:
"""The `cirq.Gate` to use as the source of truth."""

@cached_property
def signature(self) -> 'Signature':
if isinstance(self.cirq_gate, Bloq):
return self.cirq_gate.signature
nqubits = cirq.num_qubits(self.cirq_gate)
if nqubits == 1:
return Signature([Register('q', QBit())])
Expand All @@ -89,14 +101,13 @@ def signature(self) -> 'Signature':
# else
return Signature([Register('q', QBit(), shape=nqubits)])

def decompose_bloq(self) -> 'CompositeBloq':
return decompose_from_cirq_style_method(self)

def decompose_from_registers(
self, *, context: cirq.DecompositionContext, **quregs: CirqQuregT
) -> cirq.OP_TREE:
op = (
self.cirq_gate.on_registers(**quregs)
if isinstance(self.cirq_gate, GateWithRegisters)
else self.cirq_gate.on(*quregs.get('q', np.array(())).flatten())
)
op = self.cirq_gate.on(*quregs.get('q', np.array(())).flatten())
try:
return cirq.decompose_once(op)
except TypeError as e:
Expand All @@ -112,37 +123,43 @@ def my_tensors(
def as_cirq_op(
self, qubit_manager: 'cirq.QubitManager', **in_quregs: 'CirqQuregT'
) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]:
if isinstance(self.cirq_gate, GateWithRegisters):
return self.cirq_gate.as_cirq_op(qubit_manager, **in_quregs)
qubits = in_quregs.get('q', np.array([])).flatten()
return self.cirq_gate.on(*qubits), in_quregs

# Delegate all cirq-style protocols to underlying gate
def _unitary_(self):
return cirq.unitary(self.cirq_gate, default=None)

def _circuit_diagram_info_(
self, args: cirq.CircuitDiagramInfoArgs
) -> Optional[cirq.CircuitDiagramInfo]:
return cirq.circuit_diagram_info(self.cirq_gate, default=None)

def __str__(self):
return str(self.cirq_gate)

def __pow__(self, power):
return CirqGateAsBloq(gate=cirq.pow(self.cirq_gate, power))

def adjoint(self) -> 'Bloq':
return CirqGateAsBloq(gate=cirq.inverse(self.cirq_gate))

def __str__(self):
return f'cirq.{self.cirq_gate}'


@frozen
class CirqGateAsBloq(CirqGateAsBloqBase):
"""An adapter that fulfils the Bloq API by delegating to `cirq.Gate` methods.

- The bloq's signature is one register named "q" of type QBit() with shape (n_qubits,) as
determined by `cirq.num_qubits`.
- Decomposition will go via `cirq.decompose_once`.
- Tensor data is derived from `cirq.unitary`.
- `as_cirq_op` will use the adapted cirq gate directly
- Adjoint and exponentiation go via `cirq.inverse` and `cirq.pow`, respectively.
- The string representation is "cirq.{gate}".

If you'd rather bootstrap your own bloq based on an existing `cirq.Gate`, you can inherit
from `CirqGateAsBloqBase`."""

gate: cirq.Gate

def __str__(self) -> str:
g = min(self.cirq_gate.__class__.__name__, str(self.cirq_gate), key=len)
return f'cirq.{g}'
def __attrs_post_init__(self):
if isinstance(self.gate, GateWithRegisters):
warnings.warn(
f"Tried to use `CirqGateAsBloq` to adapt a `qualtran.GateWithRegisters`, "
f"which already satisfies the Bloq API. Consider using {self.gate} "
f"directly (without the adapter)."
)

@property
def cirq_gate(self) -> cirq.Gate:
Expand Down
Loading