Skip to content

Commit 58e6b71

Browse files
authored
Simplify QubitizationWalkOperator: remove specializations for control/adjoint and use defaults instead (#1481)
* simplify walk operator * add ctrl systems for various bloqs * special adjoint wrapper for bloqs with specialized ctrl * fix ctrl sys for `CZ` and `CY`: target register names of uncontrolled and controlled versions do not match * nb * fix bug in select hubbard (`0` ctrl not supported by decomp, so fallback to default) * move new adjoint bloq to `mcmt.specialized_ctrl` * add tests for adjoint * imports and mypy * `Y, Z`: explicitly implement ctrl sys (avoids AutoPartition in decomp) * docstring `AdjointWithSpecializedCtrl` * add notebook * format * simplify ctrl sys for CZ, CY * hubbard: not impl error on cv=0
1 parent 580ae65 commit 58e6b71

13 files changed

+460
-86
lines changed

qualtran/bloqs/basic_gates/swap.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,15 @@ def adjoint(self) -> 'Bloq':
105105
return self
106106

107107
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:
108-
if ctrl_spec != CtrlSpec():
109-
return super().get_ctrl_system(ctrl_spec=ctrl_spec)
110-
111-
cswap = TwoBitCSwap()
112-
113-
def adder(
114-
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
115-
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
116-
(ctrl,) = ctrl_soqs
117-
ctrl, x, y = bb.add(cswap, ctrl=ctrl, x=in_soqs['x'], y=in_soqs['y'])
118-
return [ctrl], [x, y]
108+
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs
119109

120-
return cswap, adder
110+
return get_ctrl_system_1bit_cv_from_bloqs(
111+
self,
112+
ctrl_spec,
113+
current_ctrl_bit=None,
114+
bloq_with_ctrl=TwoBitCSwap(),
115+
ctrl_reg_name='ctrl',
116+
)
121117

122118

123119
@bloq_example
@@ -201,6 +197,13 @@ def wire_symbol(self, reg: Optional['Register'], idx: Tuple[int, ...] = ()) -> '
201197
else:
202198
return TextBox('×')
203199

200+
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:
201+
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs
202+
203+
return get_ctrl_system_1bit_cv_from_bloqs(
204+
self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='ctrl'
205+
)
206+
204207

205208
@bloq_example
206209
def _cswap_bit() -> TwoBitCSwap:

qualtran/bloqs/basic_gates/y_gate.py

+7
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ def wire_symbol(
173173
return TextBox('Y')
174174
raise ValueError(f"Unknown register {reg}.")
175175

176+
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
177+
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs
178+
179+
return get_ctrl_system_1bit_cv_from_bloqs(
180+
self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='ctrl'
181+
)
182+
176183

177184
@bloq_example
178185
def _cy_gate() -> CYGate:

qualtran/bloqs/basic_gates/z_basis.py

+7
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,13 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
340340
return Circle()
341341
raise ValueError(f'Unknown wire symbol register name: {reg.name}')
342342

343+
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
344+
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs
345+
346+
return get_ctrl_system_1bit_cv_from_bloqs(
347+
self, ctrl_spec, current_ctrl_bit=1, bloq_with_ctrl=self, ctrl_reg_name='q1'
348+
)
349+
343350

344351
@bloq_example
345352
def _cz() -> CZ:

qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ class SelectHubbard(SelectOracle):
9090
def __attrs_post_init__(self):
9191
if self.x_dim != self.y_dim:
9292
raise NotImplementedError("Currently only supports the case where x_dim=y_dim.")
93+
if self.control_val == 0:
94+
raise NotImplementedError(
95+
"control_val=0 not supported, use `SelectHubbard(x, y).controlled(CtrlSpec(cvs=0))` instead"
96+
)
9397

9498
@cached_property
9599
def control_registers(self) -> Tuple[Register, ...]:
@@ -191,18 +195,24 @@ def __str__(self):
191195
return s
192196

193197
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
194-
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv
198+
from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs
195199

196-
return get_ctrl_system_1bit_cv(
200+
return get_ctrl_system_1bit_cv_from_bloqs(
197201
self,
198202
ctrl_spec=ctrl_spec,
199203
current_ctrl_bit=self.control_val,
200-
get_ctrl_bloq_and_ctrl_reg_name=lambda cv: (
201-
attrs.evolve(self, control_val=cv),
202-
'control',
203-
),
204+
bloq_with_ctrl=attrs.evolve(self, control_val=1),
205+
ctrl_reg_name='control',
206+
)
207+
208+
def adjoint(self) -> 'Bloq':
209+
from qualtran.bloqs.mcmt.specialized_ctrl import (
210+
AdjointWithSpecializedCtrl,
211+
SpecializeOnCtrlBit,
204212
)
205213

214+
return AdjointWithSpecializedCtrl(self, specialize_on_ctrl=SpecializeOnCtrlBit.ONE)
215+
206216

207217
@bloq_example
208218
def _sel_hubb() -> SelectHubbard:

qualtran/bloqs/chemistry/hubbard_model/qubitization/select_hubbard_test.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
from unittest.mock import ANY
1415

1516
import pytest
1617

1718
from qualtran.bloqs.chemistry.hubbard_model.qubitization.select_hubbard import (
1819
_sel_hubb,
1920
SelectHubbard,
2021
)
21-
from qualtran.cirq_interop.t_complexity_protocol import t_complexity
22+
from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost
2223

2324

2425
def test_sel_hubb_auto(bloq_autotester):
@@ -28,8 +29,19 @@ def test_sel_hubb_auto(bloq_autotester):
2829
@pytest.mark.parametrize('dim', [*range(2, 10)])
2930
def test_select_t_complexity(dim):
3031
select = SelectHubbard(x_dim=dim, y_dim=dim, control_val=1)
31-
cost = t_complexity(select)
32+
cost = get_cost_value(select, QECGatesCost())
3233
N = 2 * dim * dim
3334
logN = 2 * (dim - 1).bit_length() + 1
34-
assert cost.t == 10 * N + 14 * logN - 8
35-
assert cost.rotations == 0
35+
assert cost == GateCounts(
36+
cswap=2 * logN, and_bloq=5 * (N // 2) - 2, measurement=5 * (N // 2) - 2, clifford=ANY
37+
)
38+
assert cost.total_t_count() == 10 * N + 14 * logN - 8
39+
40+
41+
def test_adjoint_controlled():
42+
bloq = _sel_hubb()
43+
44+
adj_ctrl_bloq = bloq.controlled().adjoint()
45+
ctrl_adj_bloq = bloq.adjoint().controlled()
46+
47+
assert adj_ctrl_bloq == ctrl_adj_bloq
+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "0",
6+
"metadata": {},
7+
"source": [
8+
"## Bloqs with specialized controlled implementations\n",
9+
"\n",
10+
"In some cases, a bloq may have a specialized singly-controlled version (e.g. `LCUBlockEncoding`).\n",
11+
"Qualtran provides a convenience methods `get_ctrl_system_1bit_cv` and `get_ctrl_system_1bit_cv_from_bloqs` to override the `get_ctrl_system`. These methods ensure that multiply-controlled bloqs are correctly reduced to the provided singly-controlled variants.\n",
12+
"\n",
13+
"- `get_ctrl_system_1bit_cv_from_bloqs` - Override when a specialized controlled-by-1 implementation is available.\n",
14+
"- `get_ctrl_system_1bit_cv` - Override when both specialized controlled-by-1 and controlled-by-0 implementations are available.\n",
15+
"\n",
16+
"The following demonstrates an example for a bloq implementing $T^\\dagger X T$, where the controlled version only needs to control the $X$."
17+
]
18+
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": null,
22+
"id": "1",
23+
"metadata": {},
24+
"outputs": [],
25+
"source": [
26+
"import attrs\n",
27+
"from qualtran import Bloq, BloqBuilder, Soquet, SoquetT, Signature, CtrlSpec, AddControlledT\n",
28+
"from qualtran.bloqs.basic_gates import TGate, XGate, CNOT\n",
29+
"\n",
30+
"\n",
31+
"@attrs.frozen\n",
32+
"class BloqWithSpecializedCtrl(Bloq):\n",
33+
" \"\"\"Bloq implementing $T^\\dagger X T$\"\"\"\n",
34+
" is_controlled: bool = False\n",
35+
"\n",
36+
" @property\n",
37+
" def signature(self) -> 'Signature':\n",
38+
" n_ctrls = 1 if self.is_controlled else 0\n",
39+
" return Signature.build(ctrl=n_ctrls, q=1)\n",
40+
" \n",
41+
" def build_composite_bloq(self, bb: 'BloqBuilder', q: 'Soquet', **soqs) -> dict[str, 'SoquetT']:\n",
42+
" ctrl = soqs.pop('ctrl', None)\n",
43+
" \n",
44+
" q = bb.add(TGate(), q=q)\n",
45+
" if self.is_controlled:\n",
46+
" ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)\n",
47+
" else:\n",
48+
" ctrl, q = bb.add(XGate(), ctrl=ctrl, target=q)\n",
49+
" q = bb.add(TGate().adjoint(), q=q)\n",
50+
" \n",
51+
" out_soqs = {'q': q}\n",
52+
" if ctrl:\n",
53+
" out_soqs |= {'ctrl': ctrl}\n",
54+
" return out_soqs\n",
55+
" \n",
56+
" def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:\n",
57+
" from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs\n",
58+
"\n",
59+
" return get_ctrl_system_1bit_cv_from_bloqs(\n",
60+
" self,\n",
61+
" ctrl_spec,\n",
62+
" current_ctrl_bit=1 if self.is_controlled else None,\n",
63+
" bloq_with_ctrl=attrs.evolve(self, is_controlled=True),\n",
64+
" ctrl_reg_name='ctrl',\n",
65+
" )"
66+
]
67+
},
68+
{
69+
"cell_type": "code",
70+
"execution_count": null,
71+
"id": "2",
72+
"metadata": {},
73+
"outputs": [],
74+
"source": [
75+
"from qualtran.drawing import show_bloq, show_call_graph\n",
76+
"\n",
77+
"bloq = BloqWithSpecializedCtrl().controlled().controlled()\n",
78+
"show_bloq(bloq.decompose_bloq().flatten())\n",
79+
"show_call_graph(bloq)"
80+
]
81+
},
82+
{
83+
"cell_type": "markdown",
84+
"id": "3",
85+
"metadata": {},
86+
"source": [
87+
"## Propagating the Adjoint\n",
88+
"\n",
89+
"In the above bloq, calling controlled on the adjoint does not push the controls into the bloq, and therefore does not use the specialized implementation provided."
90+
]
91+
},
92+
{
93+
"cell_type": "code",
94+
"execution_count": null,
95+
"id": "4",
96+
"metadata": {},
97+
"outputs": [],
98+
"source": [
99+
"BloqWithSpecializedCtrl().adjoint().controlled()"
100+
]
101+
},
102+
{
103+
"cell_type": "markdown",
104+
"id": "5",
105+
"metadata": {},
106+
"source": [
107+
"This can be fixed by overriding the adjoint using a special wrapper for this case - `AdjointWithSpecializedCtrl`. This is a subclass of the default `Adjoint` metabloq, and ensures that single-qubit controls are pushed into the underlying bloq."
108+
]
109+
},
110+
{
111+
"cell_type": "code",
112+
"execution_count": null,
113+
"id": "6",
114+
"metadata": {},
115+
"outputs": [],
116+
"source": [
117+
"@attrs.frozen\n",
118+
"class BloqWithSpecializedCtrlWithAdjoint(Bloq):\n",
119+
" \"\"\"Bloq implementing $T^\\dagger X T$\"\"\"\n",
120+
" is_controlled: bool = False\n",
121+
"\n",
122+
" @property\n",
123+
" def signature(self) -> 'Signature':\n",
124+
" n_ctrls = 1 if self.is_controlled else 0\n",
125+
" return Signature.build(ctrl=n_ctrls, q=1)\n",
126+
" \n",
127+
" def build_composite_bloq(self, bb: 'BloqBuilder', q: 'Soquet', **soqs) -> dict[str, 'SoquetT']:\n",
128+
" ctrl = soqs.pop('ctrl', None)\n",
129+
" \n",
130+
" q = bb.add(TGate(), q=q)\n",
131+
" if self.is_controlled:\n",
132+
" ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q)\n",
133+
" else:\n",
134+
" ctrl, q = bb.add(XGate(), ctrl=ctrl, target=q)\n",
135+
" q = bb.add(TGate().adjoint(), q=q)\n",
136+
" \n",
137+
" out_soqs = {'q': q}\n",
138+
" if ctrl:\n",
139+
" out_soqs |= {'ctrl': ctrl}\n",
140+
" return out_soqs\n",
141+
" \n",
142+
" def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:\n",
143+
" from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs\n",
144+
"\n",
145+
" return get_ctrl_system_1bit_cv_from_bloqs(\n",
146+
" self,\n",
147+
" ctrl_spec,\n",
148+
" current_ctrl_bit=1 if self.is_controlled else None,\n",
149+
" bloq_with_ctrl=attrs.evolve(self, is_controlled=True),\n",
150+
" ctrl_reg_name='ctrl',\n",
151+
" )\n",
152+
"\n",
153+
" def adjoint(self):\n",
154+
" from qualtran.bloqs.mcmt.specialized_ctrl import AdjointWithSpecializedCtrl, SpecializeOnCtrlBit\n",
155+
" \n",
156+
" return AdjointWithSpecializedCtrl(self, SpecializeOnCtrlBit.ONE)"
157+
]
158+
},
159+
{
160+
"cell_type": "code",
161+
"execution_count": null,
162+
"id": "7",
163+
"metadata": {},
164+
"outputs": [],
165+
"source": [
166+
"BloqWithSpecializedCtrlWithAdjoint().adjoint().controlled()"
167+
]
168+
},
169+
{
170+
"cell_type": "code",
171+
"execution_count": null,
172+
"id": "8",
173+
"metadata": {},
174+
"outputs": [],
175+
"source": [
176+
"assert BloqWithSpecializedCtrlWithAdjoint().adjoint().controlled() == BloqWithSpecializedCtrlWithAdjoint(is_controlled=True).adjoint()"
177+
]
178+
}
179+
],
180+
"metadata": {
181+
"kernelspec": {
182+
"display_name": "Python 3 (ipykernel)",
183+
"language": "python",
184+
"name": "python3"
185+
},
186+
"language_info": {
187+
"codemirror_mode": {
188+
"name": "ipython",
189+
"version": 3
190+
},
191+
"file_extension": ".py",
192+
"mimetype": "text/x-python",
193+
"name": "python",
194+
"nbconvert_exporter": "python",
195+
"pygments_lexer": "ipython3",
196+
"version": "3.11.9"
197+
}
198+
},
199+
"nbformat": 4,
200+
"nbformat_minor": 5
201+
}

0 commit comments

Comments
 (0)