Skip to content

Commit 80dfbac

Browse files
committed
Merge remote-tracking branch 'origin/main' into 2024-04/generic-costs-framework
2 parents 8fa6e2a + 433a288 commit 80dfbac

75 files changed

Lines changed: 702 additions & 498 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,20 @@ jobs:
9999
pip install --no-deps -e .
100100
- run: |
101101
check/pylint
102+
103+
mypy:
104+
runs-on: ubuntu-latest
105+
steps:
106+
- uses: actions/checkout@v3
107+
with:
108+
fetch-depth: 0
109+
- uses: actions/setup-python@v4
110+
with:
111+
python-version: "3.10"
112+
- name: Install dependencies
113+
run: |
114+
python -m pip install --upgrade pip
115+
pip install -r dev_tools/requirements/envs/dev.env.txt
116+
pip install --no-deps -e .
117+
- run: |
118+
check/mypy

dev_tools/conf/mypy.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ show_error_codes = true
33
plugins = duet.typing, numpy.typing.mypy_plugin
44
allow_redefinition = true
55
check_untyped_defs = true
6+
67
# Disabling function override checking
78
# Qualtran has many places where kwargs are used
89
# with the intention to override in subclasses in ways mypy does not like

qualtran/_infra/bloq.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@
2727

2828
from qualtran import (
2929
AddControlledT,
30+
Adjoint,
3031
BloqBuilder,
3132
CompositeBloq,
3233
CtrlSpec,
34+
GateWithRegisters,
3335
Register,
3436
Signature,
3537
Soquet,

qualtran/_infra/composite_bloq.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import cirq
5050

5151
from qualtran.cirq_interop._cirq_to_bloq import CirqQuregInT, CirqQuregT
52-
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
5352
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator
5453
from qualtran.simulation.classical_sim import ClassicalValT
5554

@@ -139,16 +138,19 @@ def as_cirq_op(
139138
"""Return a cirq.CircuitOperation containing a cirq-exported version of this cbloq."""
140139
import cirq
141140

142-
circuit, out_quregs = self.to_cirq_circuit(qubit_manager=qubit_manager, **cirq_quregs)
141+
circuit, out_quregs = self.to_cirq_circuit_and_quregs(
142+
qubit_manager=qubit_manager, cirq_quregs=cirq_quregs
143+
)
143144
return cirq.CircuitOperation(circuit), out_quregs
144145

145-
def to_cirq_circuit(
146-
self, qubit_manager: Optional['cirq.QubitManager'] = None, **cirq_quregs: 'CirqQuregInT'
146+
def to_cirq_circuit_and_quregs(
147+
self, qubit_manager: Optional['cirq.QubitManager'] = None, **cirq_quregs
147148
) -> Tuple['cirq.FrozenCircuit', Dict[str, 'CirqQuregT']]:
148-
"""Convert this CompositeBloq to a `cirq.Circuit`.
149+
"""Convert this CompositeBloq to a `cirq.Circuit` and output qubit registers.
149150
150151
Args:
151-
qubit_manager: A `cirq.QubitManager` to allocate new qubits.
152+
qubit_manager: A `cirq.QubitManager` to allocate new qubits. If not provided,
153+
uses `cirq.SimpleQubitManager()` by default.
152154
**cirq_quregs: Mapping from left register names to Cirq qubit arrays.
153155
154156
Returns:
@@ -166,6 +168,30 @@ def to_cirq_circuit(
166168
self.signature, cirq_quregs, self._binst_graph, qubit_manager=qubit_manager
167169
)
168170

171+
def to_cirq_circuit(
172+
self,
173+
*,
174+
qubit_manager: Optional['cirq.QubitManager'] = None,
175+
cirq_quregs: Optional[Mapping[str, 'CirqQuregInT']] = None,
176+
) -> 'cirq.FrozenCircuit':
177+
"""Convert this CompositeBloq to a `cirq.Circuit`.
178+
179+
Args:
180+
qubit_manager: A `cirq.QubitManager` to allocate new qubits. If not provided,
181+
uses `cirq.SimpleQubitManager()` by default.
182+
cirq_quregs: Mapping from left register names to Cirq qubit arrays. If not provided,
183+
uses `get_named_qubits(self.signature.lefts())` by default.
184+
185+
Returns:
186+
circuit: The cirq.FrozenCircuit version of this composite bloq.
187+
"""
188+
from qualtran._infra.gate_with_registers import get_named_qubits
189+
190+
if cirq_quregs is None:
191+
cirq_quregs = get_named_qubits(self.signature.lefts())
192+
193+
return self.to_cirq_circuit_and_quregs(qubit_manager=qubit_manager, **cirq_quregs)[0]
194+
169195
@classmethod
170196
def from_cirq_circuit(cls, circuit: 'cirq.Circuit') -> 'CompositeBloq':
171197
"""Construct a composite bloq from a Cirq circuit.

qualtran/_infra/composite_bloq_test.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
)
4141
from qualtran._infra.composite_bloq import _create_binst_graph, _get_dangling_soquets
4242
from qualtran._infra.data_types import BoundedQUInt, QAny, QBit, QFxp, QUInt
43-
from qualtran._infra.gate_with_registers import get_named_qubits
4443
from qualtran.bloqs.basic_gates import CNOT, IntEffect, ZeroEffect
4544
from qualtran.bloqs.for_testing.atom import TestAtom, TestTwoBitOp
4645
from qualtran.bloqs.for_testing.many_registers import TestMultiTypedRegister, TestQFxp
@@ -164,7 +163,9 @@ def test_map_soqs():
164163

165164
def test_bb_composite_bloq():
166165
cbloq_auto = TestTwoCNOT().decompose_bloq()
167-
circuit, _ = cbloq_auto.to_cirq_circuit(q1=[cirq.LineQubit(1)], q2=[cirq.LineQubit(2)])
166+
circuit, _ = cbloq_auto.to_cirq_circuit_and_quregs(
167+
q1=[cirq.LineQubit(1)], q2=[cirq.LineQubit(2)]
168+
)
168169
cirq.testing.assert_has_diagram(
169170
circuit,
170171
desired="""\
@@ -174,6 +175,16 @@ def test_bb_composite_bloq():
174175
""",
175176
)
176177

178+
circuit = cbloq_auto.to_cirq_circuit()
179+
cirq.testing.assert_has_diagram(
180+
circuit,
181+
desired="""\
182+
q1: ───@───X───
183+
│ │
184+
q2: ───X───@───
185+
""",
186+
)
187+
177188

178189
def test_bloq_builder():
179190
signature = Signature.build(x=1, y=1)
@@ -344,7 +355,7 @@ def test_complicated_target_register():
344355
# note: this includes the two `Dangling` generations.
345356
assert len(list(nx.topological_generations(binst_graph))) == 2 * 3 + 2
346357

347-
circuit, _ = cbloq.to_cirq_circuit(None, **get_named_qubits(bloq.signature.lefts()))
358+
circuit = cbloq.to_cirq_circuit()
348359
cirq.testing.assert_has_diagram(
349360
circuit,
350361
"""\

qualtran/_infra/controlled.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ def wire_symbol(self, reg: Register, idx: Tuple[int, ...] = tuple()) -> 'WireSym
445445
return self.ctrl_spec.wire_symbol(i, reg, idx)
446446

447447
def adjoint(self) -> 'Bloq':
448-
return self.subbloq.adjoint().controlled(self.ctrl_spec)
448+
return self.subbloq.adjoint().controlled(ctrl_spec=self.ctrl_spec)
449449

450450
def pretty_name(self) -> str:
451451
return f'C[{self.subbloq.pretty_name()}]'

qualtran/_infra/controlled_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_ctrl_bloq_as_cirq_op():
100100

101101
def _test_cirq_equivalence(bloq: Bloq, gate: cirq.Gate):
102102
left_quregs = get_named_qubits(bloq.signature.lefts())
103-
circuit1, right_quregs = bloq.as_composite_bloq().to_cirq_circuit(None, **left_quregs)
103+
circuit1 = bloq.as_composite_bloq().to_cirq_circuit(cirq_quregs=left_quregs)
104104
circuit2 = cirq.Circuit(
105105
gate.on(*merge_qubits(bloq.signature, **get_named_qubits(bloq.signature)))
106106
)
@@ -124,7 +124,7 @@ def _test_cirq_equivalence(bloq: Bloq, gate: cirq.Gate):
124124
bloq = Controlled(Swap(5), CtrlSpec(qdtypes=QUInt(4), cvs=0b0101))
125125
quregs = get_named_qubits(bloq.signature)
126126
ctrl, x, y = quregs['ctrl'], quregs['x'], quregs['y']
127-
circuit1, _ = bloq.decompose_bloq().to_cirq_circuit(None, **quregs)
127+
circuit1 = bloq.decompose_bloq().to_cirq_circuit(cirq_quregs=quregs)
128128
circuit2 = cirq.Circuit(
129129
cirq.SWAP(x[i], y[i]).controlled_by(*ctrl, control_values=[0, 1, 0, 1]) for i in range(5)
130130
)
@@ -415,7 +415,7 @@ def test_controlled_diagrams():
415415
q: ──────X^0.25───''',
416416
)
417417

418-
ctrl_0_gate = XPowGate(0.25).controlled(CtrlSpec(cvs=0))
418+
ctrl_0_gate = XPowGate(0.25).controlled(ctrl_spec=CtrlSpec(cvs=0))
419419
cirq.testing.assert_has_diagram(
420420
cirq.Circuit(ctrl_0_gate.on_registers(**get_named_qubits(ctrl_0_gate.signature))),
421421
'''
@@ -424,7 +424,7 @@ def test_controlled_diagrams():
424424
q: ──────X^0.25───''',
425425
)
426426

427-
multi_ctrl_gate = XPowGate(0.25).controlled(CtrlSpec(cvs=[0, 1]))
427+
multi_ctrl_gate = XPowGate(0.25).controlled(ctrl_spec=CtrlSpec(cvs=[0, 1]))
428428
cirq.testing.assert_has_diagram(
429429
cirq.Circuit(multi_ctrl_gate.on_registers(**get_named_qubits(multi_ctrl_gate.signature))),
430430
'''
@@ -435,7 +435,7 @@ def test_controlled_diagrams():
435435
q: ─────────X^0.25───''',
436436
)
437437

438-
ctrl_bloq = Swap(2).controlled(CtrlSpec(cvs=[0, 1]))
438+
ctrl_bloq = Swap(2).controlled(ctrl_spec=CtrlSpec(cvs=[0, 1]))
439439
cirq.testing.assert_has_diagram(
440440
cirq.Circuit(ctrl_bloq.on_registers(**get_named_qubits(ctrl_bloq.signature))),
441441
'''

qualtran/_infra/gate_with_registers.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import abc
1616
from typing import (
1717
Any,
18+
cast,
1819
Collection,
1920
Dict,
2021
Iterable,
@@ -27,6 +28,7 @@
2728
Union,
2829
)
2930

31+
import attrs
3032
import cirq
3133
import numpy as np
3234
from numpy.typing import NDArray
@@ -38,7 +40,7 @@
3840
if TYPE_CHECKING:
3941
import quimb.tensor as qtn
4042

41-
from qualtran import CtrlSpec, SoquetT
43+
from qualtran import AddControlledT, BloqBuilder, CtrlSpec, SoquetT
4244
from qualtran.cirq_interop import CirqQuregT
4345
from qualtran.drawing import WireSymbol
4446

@@ -357,8 +359,8 @@ def on_registers(
357359
) -> cirq.Operation:
358360
return self.on(*merge_qubits(self.signature, **qubit_regs))
359361

360-
def __pow__(self, power: int) -> 'Bloq':
361-
bloq = self if power > 0 else self.adjoint()
362+
def __pow__(self, power: int) -> 'GateWithRegisters':
363+
bloq = self if power > 0 else cast(GateWithRegisters, self.adjoint())
362364
if abs(power) == 1:
363365
return bloq
364366
if all(reg.side == Side.THRU for reg in self.signature):
@@ -381,15 +383,6 @@ def _get_ctrl_spec(
381383
already accepts a `CtrlSpec` and simply returns it OR a Cirq-style API which accepts
382384
parameters expected by `cirq.Gate.controlled()` and converts them to a `CtrlSpec` object.
383385
384-
Users implementing custom `GateWithRegisters.controlled()` overrides can use this helper
385-
to generate a CtrlSpec from the cirq-style API and thus easily support both Cirq & Bloq
386-
APIs. For example
387-
388-
>>> class CustomGWR(GateWithRegisters):
389-
>>> def controlled(self, *args, **kwargs) -> 'Bloq':
390-
>>> ctrl_spec = self._get_ctrl_spec(*args, **kwargs)
391-
>>> # Use ctrl_spec to construct a controlled version of `self`.
392-
393386
Args:
394387
num_controls: Cirq style API to specify control specification -
395388
Total number of control qubits.
@@ -477,10 +470,6 @@ def controlled(
477470
bloq. Bloqs authors can declare their own, custom controlled versions by overriding
478471
`Bloq.get_ctrl_system` in the bloq.
479472
480-
If overriding the `GWR.controlled()` method directly, Bloq authors can use the
481-
`self._get_ctrl_spec` helper to construct a `CtrlSpec` object from the input parameters of
482-
`GWR.controlled()` and use it to return a custom controlled version of this Bloq.
483-
484473
485474
Args:
486475
num_controls: Cirq style API to specify control specification -
@@ -548,3 +537,62 @@ def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.Circ
548537

549538
wire_symbols[0] = self.__class__.__name__
550539
return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols)
540+
541+
542+
class SpecializedSingleQubitControlledGate(GateWithRegisters):
543+
"""Add a specialized single-qubit controlled version of a Bloq.
544+
545+
`control_val` is an optional single-bit control. When `control_val` is provided,
546+
the `control_registers` property should return a single named qubit register,
547+
and otherwise return an empty tuple.
548+
549+
Example usage:
550+
551+
@attrs.frozen
552+
class MyGate(SpecializedSingleQubitControlledGate):
553+
control_val: Optional[int] = None
554+
555+
@property
556+
def control_registers() -> Tuple[Register, ...]:
557+
return () if self.control_val is None else (Register('control', QBit()),)
558+
"""
559+
560+
control_val: Optional[int]
561+
562+
@property
563+
@abc.abstractmethod
564+
def control_registers(self) -> Tuple[Register, ...]:
565+
...
566+
567+
def get_single_qubit_controlled_bloq(
568+
self, control_val: int
569+
) -> 'SpecializedSingleQubitControlledGate':
570+
"""Override this to provide a custom controlled bloq"""
571+
return attrs.evolve(self, control_val=control_val) # type: ignore[misc]
572+
573+
def get_ctrl_system(
574+
self, ctrl_spec: Optional['CtrlSpec'] = None
575+
) -> Tuple['Bloq', 'AddControlledT']:
576+
if ctrl_spec is None:
577+
ctrl_spec = CtrlSpec()
578+
579+
if self.control_val is None and ctrl_spec.shapes in [((),), ((1,),)]:
580+
control_val = int(ctrl_spec.cvs[0].item())
581+
cbloq = self.get_single_qubit_controlled_bloq(control_val)
582+
583+
if not hasattr(cbloq, 'control_registers'):
584+
raise TypeError("{cbloq} should have attribute `control_registers`")
585+
586+
(ctrl_reg,) = cbloq.control_registers
587+
588+
def adder(
589+
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: dict[str, 'SoquetT']
590+
) -> tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
591+
soqs = {ctrl_reg.name: ctrl_soqs[0]} | in_soqs
592+
soqs = bb.add_d(cbloq, **soqs)
593+
ctrl_soqs = [soqs.pop(ctrl_reg.name)]
594+
return ctrl_soqs, soqs.values()
595+
596+
return cbloq, adder
597+
598+
return super().get_ctrl_system(ctrl_spec)

qualtran/_infra/gate_with_registers_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import Dict, TYPE_CHECKING
15+
from typing import Dict, Iterator, TYPE_CHECKING
1616

1717
import cirq
1818
import numpy as np
@@ -46,7 +46,7 @@ def signature(self) -> Signature:
4646
regs = Signature([r1, r2, r3])
4747
return regs
4848

49-
def decompose_from_registers(self, *, context, **quregs) -> cirq.OP_TREE:
49+
def decompose_from_registers(self, *, context, **quregs) -> Iterator[cirq.OP_TREE]:
5050
yield cirq.H.on_each(quregs['r1'])
5151
yield cirq.X.on_each(quregs['r2'])
5252
yield cirq.X.on_each(quregs['r3'])

qualtran/bloqs/arithmetic/addition.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@
1414
import itertools
1515
import math
1616
from functools import cached_property
17-
from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union
17+
from typing import (
18+
Any,
19+
Dict,
20+
Iterable,
21+
Iterator,
22+
List,
23+
Optional,
24+
Sequence,
25+
Set,
26+
Tuple,
27+
TYPE_CHECKING,
28+
Union,
29+
)
1830

1931
import cirq
2032
import numpy as np
@@ -197,7 +209,7 @@ def _right_building_block(self, inp, out, anc, depth):
197209

198210
def decompose_from_registers(
199211
self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] # type: ignore[type-var]
200-
) -> cirq.OP_TREE:
212+
) -> Iterator[cirq.OP_TREE]:
201213
# reverse the order of qubits for big endian-ness.
202214
input_bits = quregs['a'][::-1]
203215
output_bits = quregs['b'][::-1]

0 commit comments

Comments
 (0)