1616import abc
1717import itertools
1818import numbers
19+ import warnings
1920from functools import cached_property
2021from typing import Any , Dict , List , Optional , Sequence , Tuple , TYPE_CHECKING , TypeVar , Union
2122
@@ -70,17 +71,28 @@ def _get_cirq_quregs(signature: Signature, qm: InteropQubitManager):
7071 return ret
7172
7273
73- class CirqGateAsBloqBase (GateWithRegisters , metaclass = abc .ABCMeta ):
74- """A Bloq wrapper around a `cirq.Gate`"""
74+ class CirqGateAsBloqBase (Bloq , metaclass = abc .ABCMeta ):
75+ """A base class to bootstrap a bloq from a `cirq.Gate`.
76+
77+ Bloq authors can inherit from this abstract class and override the `cirq_gate` property
78+ to get a bloq adapted from the cirq gate. Authors can continue to customize the bloq
79+ by overriding methods (like costs, string representations, ...).
80+
81+ Otherwise, this class fulfils the Bloq API by delegating to `cirq.Gate` methods.
82+
83+ This is the base class that provides the functionality for the `CirqGateAsBloq` adapter.
84+ The adapter lets you use any `cirq.Gate` as a bloq immediately (without defining a new class
85+ that inherits from `CirqGateAsBloqBase`), and is used as a fallback in the interoperability
86+ functionality.
87+ """
7588
7689 @property
7790 @abc .abstractmethod
78- def cirq_gate (self ) -> cirq .Gate : ...
91+ def cirq_gate (self ) -> cirq .Gate :
92+ """The `cirq.Gate` to use as the source of truth."""
7993
8094 @cached_property
8195 def signature (self ) -> 'Signature' :
82- if isinstance (self .cirq_gate , Bloq ):
83- return self .cirq_gate .signature
8496 nqubits = cirq .num_qubits (self .cirq_gate )
8597 if nqubits == 1 :
8698 return Signature ([Register ('q' , QBit ())])
@@ -89,14 +101,13 @@ def signature(self) -> 'Signature':
89101 # else
90102 return Signature ([Register ('q' , QBit (), shape = nqubits )])
91103
104+ def decompose_bloq (self ) -> 'CompositeBloq' :
105+ return decompose_from_cirq_style_method (self )
106+
92107 def decompose_from_registers (
93108 self , * , context : cirq .DecompositionContext , ** quregs : CirqQuregT
94109 ) -> cirq .OP_TREE :
95- op = (
96- self .cirq_gate .on_registers (** quregs )
97- if isinstance (self .cirq_gate , GateWithRegisters )
98- else self .cirq_gate .on (* quregs .get ('q' , np .array (())).flatten ())
99- )
110+ op = self .cirq_gate .on (* quregs .get ('q' , np .array (())).flatten ())
100111 try :
101112 return cirq .decompose_once (op )
102113 except TypeError as e :
@@ -112,37 +123,43 @@ def my_tensors(
112123 def as_cirq_op (
113124 self , qubit_manager : 'cirq.QubitManager' , ** in_quregs : 'CirqQuregT'
114125 ) -> Tuple [Union ['cirq.Operation' , None ], Dict [str , 'CirqQuregT' ]]:
115- if isinstance (self .cirq_gate , GateWithRegisters ):
116- return self .cirq_gate .as_cirq_op (qubit_manager , ** in_quregs )
117126 qubits = in_quregs .get ('q' , np .array ([])).flatten ()
118127 return self .cirq_gate .on (* qubits ), in_quregs
119128
120- # Delegate all cirq-style protocols to underlying gate
121- def _unitary_ (self ):
122- return cirq .unitary (self .cirq_gate , default = None )
123-
124- def _circuit_diagram_info_ (
125- self , args : cirq .CircuitDiagramInfoArgs
126- ) -> Optional [cirq .CircuitDiagramInfo ]:
127- return cirq .circuit_diagram_info (self .cirq_gate , default = None )
128-
129- def __str__ (self ):
130- return str (self .cirq_gate )
131-
132129 def __pow__ (self , power ):
133130 return CirqGateAsBloq (gate = cirq .pow (self .cirq_gate , power ))
134131
135132 def adjoint (self ) -> 'Bloq' :
136133 return CirqGateAsBloq (gate = cirq .inverse (self .cirq_gate ))
137134
135+ def __str__ (self ):
136+ return f'cirq.{ self .cirq_gate } '
137+
138138
139139@frozen
140140class CirqGateAsBloq (CirqGateAsBloqBase ):
141+ """An adapter that fulfils the Bloq API by delegating to `cirq.Gate` methods.
142+
143+ - The bloq's signature is one register named "q" of type QBit() with shape (n_qubits,) as
144+ determined by `cirq.num_qubits`.
145+ - Decomposition will go via `cirq.decompose_once`.
146+ - Tensor data is derived from `cirq.unitary`.
147+ - `as_cirq_op` will use the adapted cirq gate directly
148+ - Adjoint and exponentiation go via `cirq.inverse` and `cirq.pow`, respectively.
149+ - The string representation is "cirq.{gate}".
150+
151+ If you'd rather bootstrap your own bloq based on an existing `cirq.Gate`, you can inherit
152+ from `CirqGateAsBloqBase`."""
153+
141154 gate : cirq .Gate
142155
143- def __str__ (self ) -> str :
144- g = min (self .cirq_gate .__class__ .__name__ , str (self .cirq_gate ), key = len )
145- return f'cirq.{ g } '
156+ def __attrs_post_init__ (self ):
157+ if isinstance (self .gate , GateWithRegisters ):
158+ warnings .warn (
159+ f"Tried to use `CirqGateAsBloq` to adapt a `qualtran.GateWithRegisters`, "
160+ f"which already satisfies the Bloq API. Consider using { self .gate } "
161+ f"directly (without the adapter)."
162+ )
146163
147164 @property
148165 def cirq_gate (self ) -> cirq .Gate :
0 commit comments