Skip to content

Commit a08fbe1

Browse files
authored
Consistent Toffoli.controlled() (#1771)
This fixes the inconsistency raised in #1768 This is a step towards #1556 -- specifically, we now always return `ControlledViaAnd(XGate(), ...)` for all Toffoli cases (as well as for most cases with XGate(); but this PR does not change the behavior of `CNOT`)
1 parent 735e3e3 commit a08fbe1

4 files changed

Lines changed: 154 additions & 41 deletions

File tree

qualtran/_infra/controlled.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
import numpy as np
3434
from numpy.typing import NDArray
3535

36-
from ..symbolics import is_symbolic, prod, Shaped, SymbolicInt
36+
from qualtran.symbolics import is_symbolic, prod, Shaped, SymbolicInt
37+
3738
from .bloq import Bloq, DecomposeNotImplementedError, DecomposeTypeError
3839
from .data_types import CDType, QBit, QCDType, QDType
3940
from .gate_with_registers import GateWithRegisters
@@ -52,18 +53,11 @@
5253
ControlBit: TypeAlias = int
5354
"""A control bit, either 0 or 1."""
5455

56+
_CVInLeafT: TypeAlias = Union[int, np.integer, NDArray[np.integer], Shaped]
57+
_CVInType: TypeAlias = Union[_CVInLeafT, Sequence['_CVInType']]
58+
5559

56-
def _cvs_convert(
57-
cvs: Union[
58-
int,
59-
np.integer,
60-
NDArray[np.integer],
61-
Shaped,
62-
Sequence[Union[int, np.integer]],
63-
Sequence[Sequence[Union[int, np.integer]]],
64-
Sequence[Union[NDArray[np.integer], Shaped]],
65-
]
66-
) -> Tuple[Union[NDArray[np.integer], Shaped], ...]:
60+
def _cvs_convert(cvs: _CVInType) -> Tuple[Union[NDArray[np.integer], Shaped], ...]:
6761
if isinstance(cvs, Shaped):
6862
return (cvs,)
6963
if isinstance(cvs, (int, np.integer)):

qualtran/bloqs/basic_gates/toffoli.ipynb

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,109 @@
141141
"show_counts_sigma(toffoli_sigma)"
142142
]
143143
},
144+
{
145+
"cell_type": "markdown",
146+
"id": "dc13c6f1-86b7-4a0b-9b4a-e7f3ed67e3a6",
147+
"metadata": {},
148+
"source": [
149+
"## Controlled\n",
150+
"\n",
151+
"We use `Toffoli` for specifically two control bits of the `XGate`. Requesting `Toffoli().controlled(ctrl_spec)` will give an instance of the generic `ControlledViaAnd(XGate(), ...)` bloq, which will control the `XGate` on an arbitrary boolean function supported by `CtrlSpec`. The set of control registers present on the resulting bloq will 1) the control registers needed for the provided `ctrl_spec` and 2) one additional `QBit()[2]` register for the original Toffoli control bits."
152+
]
153+
},
154+
{
155+
"cell_type": "code",
156+
"execution_count": null,
157+
"id": "d4e0647b-ce06-47eb-9e18-7d49e375a9ed",
158+
"metadata": {},
159+
"outputs": [],
160+
"source": [
161+
"from qualtran import CtrlSpec\n",
162+
"print(repr(Toffoli().controlled()))\n",
163+
"show_bloq(Toffoli().controlled(), 'musical_score')"
164+
]
165+
},
166+
{
167+
"cell_type": "code",
168+
"execution_count": null,
169+
"id": "7fffec98-d1fe-42d5-8878-4117ecc881bc",
170+
"metadata": {},
171+
"outputs": [],
172+
"source": [
173+
"print(repr(Toffoli().controlled(CtrlSpec(cvs=0))))\n",
174+
"show_bloq(Toffoli().controlled(CtrlSpec(cvs=0)), 'musical_score')"
175+
]
176+
},
177+
{
178+
"cell_type": "markdown",
179+
"id": "4a58a0e7-1219-4bc6-ae2b-8df71d7d85d5",
180+
"metadata": {},
181+
"source": [
182+
"### Complex control specs\n",
183+
"\n",
184+
"Per above, `Toffoli.controlled()` can support arbitrary control specs."
185+
]
186+
},
144187
{
145188
"cell_type": "code",
146189
"execution_count": null,
147-
"id": "ad6ba003",
190+
"id": "59d97e1f-b957-432f-b0de-edeee84e5b71",
148191
"metadata": {},
149192
"outputs": [],
150-
"source": []
193+
"source": [
194+
"active_six = CtrlSpec(qdtypes=QInt(8), cvs=6)\n",
195+
"six_and_toffoli = Toffoli().controlled(active_six)\n",
196+
"print(repr(six_and_toffoli))\n",
197+
"show_bloq(six_and_toffoli, 'musical_score')"
198+
]
199+
},
200+
{
201+
"cell_type": "markdown",
202+
"id": "afcbb7aa-c098-4c56-8961-e0cfc9661a5c",
203+
"metadata": {},
204+
"source": [
205+
"### Calling controlled gates automatically\n",
206+
"\n",
207+
"The `Toffoli().controlled()` method (and in general, the `.controlled()` method for any bloq object) returns a bloq that implements a controlled version of the original gate. The returned bloq can structure its signature however it chooses. You can see that `Toffoli.controlled()` returns a `ControlledViaAnd(XGate(), ...)` which names its control registers `ctrl{n}` with the final one corresponding to the original Toffoli control bits. \n",
208+
"\n",
209+
"When writing meta-bloqs that need to support adding arbitrarily-controlled versions of Toffoli to a decomposition, you should not rely on any particular signature. `Bloq.get_ctrl_system()` is the method for automatically `bb.add`-ing controlled versions of arbitrary bloqs. We demonstrate that below."
210+
]
211+
},
212+
{
213+
"cell_type": "code",
214+
"execution_count": null,
215+
"id": "afc14f80-44cd-4fc8-a626-427ef8fd4a99",
216+
"metadata": {},
217+
"outputs": [],
218+
"source": [
219+
"from qualtran.bloqs.basic_gates import IntState, IntEffect\n",
220+
"\n",
221+
"bb = BloqBuilder()\n",
222+
"# q0, q1, target will be original wires for calling Toffoli\n",
223+
"q0 = bb.add_register('q0', QBit())\n",
224+
"q1 = bb.add_register('q1', QBit())\n",
225+
"target = bb.add_register('target', QBit())\n",
226+
"\n",
227+
"# \"six\" is a new wire that we additionally want to control the Toffoli with\n",
228+
"six = bb.add(IntState(val=6, bitsize=8))\n",
229+
"ctrl_spec = CtrlSpec(qdtypes=QUInt(8), cvs=6)\n",
230+
"\n",
231+
"# Instead of using Toffoli().controlled() directly, we use the ctrl_system\n",
232+
"ctof, ctof_adder = Toffoli().get_ctrl_system(ctrl_spec)\n",
233+
"\n",
234+
"# The new control variables are passed in their own list and returned in their own list\n",
235+
"# The existing Toffoli inputs are provided in a dictionary and returned in their own list\n",
236+
"(six,), ((q0, q1), target) = ctof_adder(bb, ctrl_soqs=[six], in_soqs=dict(ctrl=[q0,q1], target=target))\n",
237+
"\n",
238+
"# # below is a brittle way to achieve the same effect for this particular ctrl_spec\n",
239+
"# # it assumes control registers named ctrl1 and ctrl2\n",
240+
"# six_and_toffoli = Toffoli().controlled(ctrl_spec)\n",
241+
"# six, (q0, q1), target = bb.add(six_and_toffoli, ctrl1=six, ctrl2=[q0, q1], q=target)\n",
242+
"\n",
243+
"bb.add(IntEffect(val=6, bitsize=8), val=six)\n",
244+
"program = bb.finalize(q0=q0, q1=q1, target=target)\n",
245+
"show_bloq(program, 'musical_score')"
246+
]
151247
}
152248
],
153249
"metadata": {
@@ -166,7 +262,7 @@
166262
"name": "python",
167263
"nbconvert_exporter": "python",
168264
"pygments_lexer": "ipython3",
169-
"version": "3.10.9"
265+
"version": "3.13.9"
170266
}
171267
},
172268
"nbformat": 4,

qualtran/bloqs/basic_gates/toffoli.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
import itertools
1515
from functools import cached_property
16-
from typing import cast, Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
16+
from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
1717

1818
import numpy as np
1919
from attrs import frozen
@@ -22,6 +22,7 @@
2222
from qualtran import (
2323
Bloq,
2424
bloq_example,
25+
BloqBuilder,
2526
BloqDocSpec,
2627
CompositeBloq,
2728
Connection,
@@ -30,6 +31,7 @@
3031
QBit,
3132
Register,
3233
Signature,
34+
SoquetT,
3335
)
3436

3537
if TYPE_CHECKING:
@@ -38,7 +40,7 @@
3840
from pennylane.operation import Operation
3941
from pennylane.wires import Wires
4042

41-
from qualtran import AddControlledT, BloqBuilder, SoquetT
43+
from qualtran import AddControlledT
4244
from qualtran.cirq_interop import CirqQuregT
4345
from qualtran.drawing import WireSymbol
4446
from qualtran.simulation.classical_sim import ClassicalValT
@@ -137,27 +139,33 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
137139
raise ValueError(f'Unknown wire symbol register name: {reg.name}')
138140

139141
def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']:
140-
from qualtran.bloqs.basic_gates import CNOT
141-
from qualtran.bloqs.mcmt import ControlledViaAnd
142-
143-
if ctrl_spec != CtrlSpec():
144-
return super().get_ctrl_system(ctrl_spec)
145-
146-
cc_cnot = ControlledViaAnd(CNOT(), CtrlSpec(cvs=[1, 1]))
147-
148-
def add_controlled(
149-
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT']
150-
) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
151-
(new_ctrl,) = ctrl_soqs
152-
ctrl0, ctrl1 = cast(NDArray, in_soqs.pop('ctrl'))
153-
154-
(new_ctrl, ctrl0), ctrl1, target = bb.add(
155-
cc_cnot, ctrl2=np.array([new_ctrl, ctrl0]), ctrl=ctrl1, target=in_soqs.pop('target')
142+
from qualtran.bloqs.basic_gates import XGate
143+
144+
# Perform the Logical-AND of `ctrl_spec` with Toffoli's implicit XGate ctrl spec
145+
# of cvs=(1, 1).
146+
anded_ctrl_spec = CtrlSpec(
147+
qdtypes=ctrl_spec.qdtypes + (QBit(),), cvs=ctrl_spec.cvs + (np.array([1, 1]),)
148+
)
149+
ctrl_via_and, adder1 = XGate().get_ctrl_system(anded_ctrl_spec)
150+
151+
# We have to wire up the *new* ctrl registers vs Toffoli's *existing* ctrl register
152+
# while also translating between Toffoli() and ControlledViaAnd(XGate(), cvs=(1,1)).
153+
def adder2(
154+
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
155+
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
156+
# Note: in adder2, `ctrl_soqs` matches the requested `ctrl_spec`
157+
# in adder1, `ctrl_soqs` matches `anded_ctrl_spec`
158+
# Note: in adder2 `in_soqs` matches the signature of Toffoli
159+
# in adder1, `in_soqs` matches the signature of XGate
160+
tof_ctrl = in_soqs['ctrl']
161+
tof_target = in_soqs['target']
162+
(*ctrl_soqs, tof_ctrl), (tof_target,) = adder1(
163+
bb, list(ctrl_soqs) + [tof_ctrl], {'q': tof_target}
156164
)
157165

158-
return [new_ctrl], [np.array([ctrl0, ctrl1]), target]
166+
return ctrl_soqs, [tof_ctrl, tof_target]
159167

160-
return cc_cnot, add_controlled
168+
return ctrl_via_and, adder2
161169

162170

163171
@bloq_example

qualtran/bloqs/basic_gates/toffoli_test.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
import numpy as np
1818

1919
import qualtran.testing as qlt_testing
20-
from qualtran import BloqBuilder, CtrlSpec
21-
from qualtran.bloqs.basic_gates import Toffoli, ZeroState
20+
from qualtran import BloqBuilder, CtrlSpec, QBit, Register, Signature
21+
from qualtran.bloqs.basic_gates import CNOT, Toffoli, XGate, ZeroState
2222
from qualtran.bloqs.basic_gates.toffoli import _toffoli
23-
from qualtran.bloqs.mcmt import And
23+
from qualtran.bloqs.mcmt import And, ControlledViaAnd
2424
from qualtran.drawing.musical_score import Circle, ModPlus
2525
from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost
2626

@@ -106,15 +106,15 @@ def test_ctrl_toffoli_cost():
106106
ctrl_tof = Toffoli().controlled()
107107

108108
_, sigma = ctrl_tof.call_graph()
109-
assert sigma == {Toffoli(): 1, And(): 1, And().adjoint(): 1}
109+
assert sigma == {CNOT(): 1, And(): 2, And().adjoint(): 2}
110110

111111
gc = get_cost_value(ctrl_tof, QECGatesCost())
112-
assert gc == GateCounts(and_bloq=1, toffoli=1, clifford=1, measurement=1)
112+
assert gc == GateCounts(and_bloq=2, clifford=3, measurement=2)
113113

114114
cc_tof = Toffoli().controlled(CtrlSpec(cvs=[1, 1]))
115115

116116
_, sigma = cc_tof.call_graph()
117-
assert sigma == {Toffoli(): 1, And(): 2, And().adjoint(): 2}
117+
assert sigma == {CNOT(): 1, And(): 3, And().adjoint(): 3}
118118

119119

120120
def test_toffoli_controlled():
@@ -123,3 +123,18 @@ def test_toffoli_controlled():
123123

124124
bloq = Controlled(Toffoli().as_composite_bloq(), CtrlSpec())
125125
qlt_testing.assert_valid_bloq_decomposition(bloq)
126+
127+
128+
def test_toffoli_controlled_2():
129+
# https://github.com/quantumlib/Qualtran/issues/1768
130+
131+
c0t = Toffoli().controlled(CtrlSpec(QBit(), cvs=0))
132+
c1t = Toffoli().controlled(CtrlSpec(QBit(), cvs=1))
133+
134+
assert c0t == ControlledViaAnd(XGate(), CtrlSpec(qdtypes=(QBit(), QBit()), cvs=(0, [1, 1])))
135+
assert c1t == ControlledViaAnd(XGate(), CtrlSpec(qdtypes=(QBit(), QBit()), cvs=(1, [1, 1])))
136+
137+
assert c0t.signature == c1t.signature
138+
assert c0t.signature == Signature(
139+
[Register('ctrl1', QBit()), Register('ctrl2', QBit(), shape=(2,)), Register('q', QBit())]
140+
)

0 commit comments

Comments
 (0)