Skip to content

Commit 157cd1a

Browse files
authored
Polish line placement example (#705)
- Fix Circuit.insert not commuting xmon CZs past other xmon CZs when there is space available (instead of stopping at the first sign of a blockage) - Fix W(~=1.0)^t not being displayed as X^-t - Add new_device and qubit_map parameters to optimized_for_xmon - Rename from 'line_placement.py' to 'place_on_bristlecone.py' - 'line_on_device' now just returns a tuple of qubits with a souped-up __str__ - Extend example to include creating a circuit and optimizing it - Refactor LineSequence class into a List[GridQubit] type declaration - Refactor LinePlacement class into GridQubitLineTuple, which is just a tuple with a special __str__ - Modify several line placement tests to check results instead of the way they were achieved - Combine 'line_placement_on_device' and 'line_on_device' Fixes #463
1 parent 7d0ca0d commit 157cd1a

19 files changed

+329
-508
lines changed

cirq/circuits/circuit.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ def __repr__(self):
234234
moment_lines = ('\n ' + repr(moment) for moment in self._moments)
235235
if self._device == devices.UnconstrainedDevice:
236236
return 'Circuit([{}])'.format(','.join(moment_lines))
237-
return 'Circuit([{}], device={})'.format(','.join(moment_lines),
238-
self._device)
237+
return 'Circuit([{}], device={!r})'.format(','.join(moment_lines),
238+
self._device)
239239

240240
def __str__(self):
241241
return self.to_text_diagram()
@@ -386,19 +386,19 @@ def prev_moment_operating_on(
386386
(end_moment_index - k - 1
387387
for k in range(max_distance)))
388388

389-
def _prev_moment_blocking(
389+
def _prev_moment_available(
390390
self,
391391
op: ops.Operation,
392392
end_moment_index: int) -> Optional[int]:
393-
if not self._moments:
394-
return None
395-
393+
last_available = end_moment_index
396394
k = end_moment_index
397395
while k > 0:
398396
k -= 1
399-
if not self._can_add_op_at(k, op):
400-
return k
401-
return None
397+
if not self._can_commute_past(k, op):
398+
return last_available
399+
if self._can_add_op_at(k, op):
400+
last_available = k
401+
return last_available
402402

403403
def operation_at(self,
404404
qubit: ops.QubitId,
@@ -493,17 +493,17 @@ def _pick_or_create_inserted_op_moment_index(
493493
return splitter_index
494494

495495
if strategy is InsertStrategy.INLINE:
496-
if (self._can_add_op_at(splitter_index - 1, op) and
497-
0 <= splitter_index - 1 < len(self._moments)):
496+
if (0 <= splitter_index - 1 < len(self._moments) and
497+
self._can_add_op_at(splitter_index - 1, op)):
498498
return splitter_index - 1
499499

500500
return self._pick_or_create_inserted_op_moment_index(
501501
splitter_index, op, InsertStrategy.NEW)
502502

503503
if strategy is InsertStrategy.EARLIEST:
504504
if self._can_add_op_at(splitter_index, op):
505-
p = self._prev_moment_blocking(op, splitter_index)
506-
return p + 1 if p is not None else 0
505+
p = self._prev_moment_available(op, splitter_index)
506+
return p or 0
507507

508508
return self._pick_or_create_inserted_op_moment_index(
509509
splitter_index, op, InsertStrategy.INLINE)
@@ -525,6 +525,11 @@ def _can_add_op_at(self,
525525
operation,
526526
self._moments[moment_index])
527527

528+
def _can_commute_past(self,
529+
moment_index: int,
530+
operation: ops.Operation) -> bool:
531+
return not self._moments[moment_index].operates_on(operation.qubits)
532+
528533
def insert(
529534
self,
530535
index: int,

cirq/circuits/circuit_test.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,15 +1978,35 @@ def test_validates_while_editing():
19781978
def test_respects_additional_adjacency_constraints():
19791979
c = cirq.Circuit(device=cg.Foxtail)
19801980
c.append(cg.Exp11Gate().on(cirq.GridQubit(0, 0),
1981-
cirq.GridQubit(0, 1)))
1981+
cirq.GridQubit(0, 1)))
19821982
c.append(cg.Exp11Gate().on(cirq.GridQubit(1, 0),
1983-
cirq.GridQubit(1, 1)),
1983+
cirq.GridQubit(1, 1)),
19841984
strategy=cirq.InsertStrategy.EARLIEST)
19851985
assert c == cirq.Circuit([
19861986
cirq.Moment([cg.Exp11Gate().on(cirq.GridQubit(0, 0),
1987-
cirq.GridQubit(0, 1))]),
1987+
cirq.GridQubit(0, 1))]),
19881988
cirq.Moment([cg.Exp11Gate().on(cirq.GridQubit(1, 0),
1989-
cirq.GridQubit(1, 1))]),
1989+
cirq.GridQubit(1, 1))]),
1990+
], device=cg.Foxtail)
1991+
1992+
1993+
def test_commutes_past_adjacency_constraints():
1994+
c = cirq.Circuit([
1995+
cirq.Moment(),
1996+
cirq.Moment(),
1997+
cirq.Moment([
1998+
cg.Exp11Gate().on(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))])
1999+
],
2000+
device=cg.Foxtail)
2001+
c.append(cg.Exp11Gate().on(cirq.GridQubit(1, 0),
2002+
cirq.GridQubit(1, 1)),
2003+
strategy=cirq.InsertStrategy.EARLIEST)
2004+
assert c == cirq.Circuit([
2005+
cirq.Moment([cg.Exp11Gate().on(cirq.GridQubit(1, 0),
2006+
cirq.GridQubit(1, 1))]),
2007+
cirq.Moment(),
2008+
cirq.Moment([cg.Exp11Gate().on(cirq.GridQubit(0, 0),
2009+
cirq.GridQubit(0, 1))]),
19902010
], device=cg.Foxtail)
19912011

19922012

cirq/google/optimize.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,37 @@
1313
# limitations under the License.
1414

1515
"""A combination of several optimizations targeting XmonDevice."""
16+
from typing import Optional, Callable, cast
1617

17-
from cirq import circuits
18+
from cirq import circuits, ops, devices
1819
from cirq.google import (
1920
convert_to_xmon_gates,
2021
merge_rotations,
2122
merge_interactions,
2223
eject_full_w,
2324
eject_z,
24-
)
25+
xmon_device)
2526

2627
_TOLERANCE = 1e-5
2728

2829
_OPTIMIZERS = [
2930
convert_to_xmon_gates.ConvertToXmonGates(),
3031

31-
merge_interactions.MergeInteractions(tolerance=_TOLERANCE),
32+
merge_interactions.MergeInteractions(tolerance=_TOLERANCE,
33+
allow_partial_czs=False),
3234
merge_rotations.MergeRotations(tolerance=_TOLERANCE),
3335
eject_full_w.EjectFullW(tolerance=_TOLERANCE),
3436
eject_z.EjectZ(tolerance=_TOLERANCE),
3537
circuits.DropNegligible(tolerance=_TOLERANCE),
3638
]
3739

3840

39-
def optimized_for_xmon(circuit: circuits.Circuit) -> circuits.Circuit:
41+
def optimized_for_xmon(
42+
circuit: circuits.Circuit,
43+
new_device: Optional[xmon_device.XmonDevice] = None,
44+
qubit_map: Callable[[ops.QubitId], devices.GridQubit] =
45+
lambda e: cast(devices.GridQubit, e),
46+
) -> circuits.Circuit:
4047
"""Optimizes a circuit with XmonDevice in mind.
4148
4249
Starts by converting the circuit's operations to the xmon gate set, then
@@ -45,6 +52,9 @@ def optimized_for_xmon(circuit: circuits.Circuit) -> circuits.Circuit:
4552
4653
Args:
4754
circuit: The circuit to optimize.
55+
new_device: The device the optimized circuit should be targeted at. If
56+
set to None, the circuit's current device is used.
57+
qubit_map: Transforms the qubits (e.g. so that they are GridQubits).
4858
4959
Returns:
5060
The optimized circuit.
@@ -53,6 +63,7 @@ def optimized_for_xmon(circuit: circuits.Circuit) -> circuits.Circuit:
5363
for optimizer in _OPTIMIZERS:
5464
optimizer.optimize_circuit(copy)
5565

56-
return circuits.Circuit().from_ops(
57-
copy.all_operations(),
58-
strategy=circuits.InsertStrategy.EARLIEST)
66+
return circuits.Circuit.from_ops(
67+
(op.transform_qubits(qubit_map) for op in copy.all_operations()),
68+
strategy=circuits.InsertStrategy.EARLIEST,
69+
device=new_device or copy.device)

cirq/google/optimize_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,33 @@ def test_ccz():
5656
assert len(after) <= 22
5757
assert_circuits_with_terminal_measurements_are_equivalent(
5858
before, after, atol=1e-4)
59+
60+
61+
def test_adjacent_cz_get_split_apart():
62+
before = cirq.Circuit([cirq.Moment([
63+
cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)),
64+
cirq.CZ(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1))])])
65+
66+
after = cg.optimized_for_xmon(before,
67+
new_device=cg.Foxtail)
68+
69+
assert after == cirq.Circuit([
70+
cirq.Moment([
71+
cg.Exp11Gate().on(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))]),
72+
cirq.Moment([
73+
cg.Exp11Gate().on(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1))])],
74+
device=cg.Foxtail)
75+
76+
77+
def test_remap_qubits():
78+
before = cirq.Circuit([cirq.Moment([
79+
cirq.CZ(cirq.LineQubit(0), cirq.LineQubit(1))])])
80+
81+
after = cg.optimized_for_xmon(before,
82+
new_device=cg.Foxtail,
83+
qubit_map=lambda q: cirq.GridQubit(q.x, 0))
84+
85+
assert after == cirq.Circuit([
86+
cirq.Moment([
87+
cg.Exp11Gate().on(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0))])],
88+
device=cg.Foxtail)

cirq/google/xmon_gates.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -353,30 +353,33 @@ def trace_distance_bound(self):
353353
return 1
354354
return abs(self.half_turns) * 3.5
355355

356-
def _text_symbol(self, args: ops.TextDiagramInfoArgs) -> str:
356+
def text_diagram_info(self, args: ops.TextDiagramInfoArgs
357+
) -> ops.TextDiagramInfo:
357358
e = 0 if args.precision is None else 10**-args.precision
359+
half_turns = self.half_turns
358360
if isinstance(self.axis_half_turns, value.Symbol):
359-
return 'W({})'.format(self.axis_half_turns)
360-
if abs(self.axis_half_turns) <= e:
361-
return 'X'
362-
if abs(self.axis_half_turns - 0.5) <= e:
363-
return 'Y'
364-
if args.precision is not None:
365-
return 'W({{:.{}}})'.format(args.precision).format(
361+
s = 'W({})'.format(self.axis_half_turns)
362+
elif abs(self.axis_half_turns) <= e:
363+
s = 'X'
364+
elif (abs(self.axis_half_turns - 1) <= e and
365+
isinstance(half_turns, float)):
366+
s = 'X'
367+
half_turns = -half_turns
368+
elif abs(self.axis_half_turns - 0.5) <= e:
369+
s = 'Y'
370+
elif args.precision is not None:
371+
s = 'W({{:.{}}})'.format(args.precision).format(
366372
self.axis_half_turns)
367373
else:
368-
return 'W({})'.format(self.axis_half_turns)
369-
370-
def text_diagram_info(self, args: ops.TextDiagramInfoArgs
371-
) -> ops.TextDiagramInfo:
372-
return ops.TextDiagramInfo((self._text_symbol(args),),
373-
self.half_turns)
374+
s = 'W({})'.format(self.axis_half_turns)
375+
return ops.TextDiagramInfo((s,), half_turns)
374376

375377
def __str__(self):
376-
base = self._text_symbol(ops.TextDiagramInfoArgs.UNINFORMED_DEFAULT)
377-
if self.half_turns == 1:
378-
return base
379-
return '{}^{}'.format(base, self.half_turns)
378+
info = self.text_diagram_info(
379+
ops.TextDiagramInfoArgs.UNINFORMED_DEFAULT)
380+
if info.exponent == 1:
381+
return info.wire_symbols[0]
382+
return '{}^{}'.format(info.wire_symbols[0], info.exponent)
380383

381384
def __repr__(self):
382385
return ('ExpWGate(half_turns={}, axis_half_turns={})'.format(

cirq/google/xmon_gates_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ def test_w_eq():
287287
cg.ExpWGate(half_turns=7.75, axis_half_turns=11.25))
288288

289289

290+
def test_w_str():
291+
assert str(cg.ExpWGate()) == 'X'
292+
assert str(cg.ExpWGate(axis_half_turns=0.99999, half_turns=0.5)) == 'X^-0.5'
293+
assert str(cg.ExpWGate(axis_half_turns=0.5, half_turns=0.25)) == 'Y^0.25'
294+
assert str(cg.ExpWGate(axis_half_turns=0.25,
295+
half_turns=0.5)) == 'W(0.25)^0.5'
296+
297+
298+
290299
def test_w_to_proto():
291300
assert proto_matches_text(
292301
cg.ExpWGate(half_turns=Symbol('k'),

cirq/line/placement/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
LinePlacementStrategy,
2323
)
2424
from cirq.line.placement.sequence import (
25-
LinePlacement,
25+
GridQubitLineTuple,
2626
)
2727
from cirq.line.placement.line import (
28-
line_placement_on_device,
2928
line_on_device,
3029
)

cirq/line/placement/anneal.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
EDGE,
2727
)
2828
from cirq.line.placement.sequence import (
29-
LinePlacement,
29+
GridQubitLineTuple,
3030
LineSequence
3131
)
3232
from cirq.contrib import optimization
@@ -52,8 +52,8 @@ def __init__(self, device: XmonDevice, seed=None) -> None:
5252
def search(
5353
self,
5454
trace_func: Callable[
55-
[List[List[GridQubit]], float, float, float, bool],
56-
None] = None) -> List[List[GridQubit]]:
55+
[List[LineSequence], float, float, float, bool],
56+
None] = None) -> List[LineSequence]:
5757
"""Issues new linear sequence search.
5858
5959
Each call to this method starts new search.
@@ -340,8 +340,12 @@ class AnnealSequenceSearchStrategy(place_strategy.LinePlacementStrategy):
340340
efficiency improvements.
341341
"""
342342

343-
def __init__(self, trace_func: Callable[
344-
[List[List[GridQubit]], float, float, float, bool], None] = None,
343+
def __init__(self,
344+
trace_func: Callable[[List[LineSequence],
345+
float,
346+
float,
347+
float,
348+
bool], None] = None,
345349
seed: int = None) -> None:
346350
"""Linearized sequence search using simulated annealing method.
347351
@@ -361,7 +365,7 @@ def __init__(self, trace_func: Callable[
361365
self.trace_func = trace_func
362366
self.seed = seed
363367

364-
def place_line(self, device: XmonDevice, length: int) -> LinePlacement:
368+
def place_line(self, device: XmonDevice, length: int) -> GridQubitLineTuple:
365369
"""Runs line sequence search.
366370
367371
Args:
@@ -373,8 +377,7 @@ def place_line(self, device: XmonDevice, length: int) -> LinePlacement:
373377
method.
374378
"""
375379
seqs = AnnealSequenceSearch(device, self.seed).search(self.trace_func)
376-
return LinePlacement(device, length,
377-
[LineSequence(seq) for seq in seqs])
380+
return GridQubitLineTuple.best_of(seqs, length)
378381

379382

380383
def index_2d(seqs: List[List[Any]], target: Any) -> Tuple[int, int]:

cirq/line/placement/anneal_test.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -386,19 +386,14 @@ def _verify_valid_state(qubits: List[GridQubit], state: _STATE):
386386
assert not c_set
387387

388388

389-
@mock.patch('cirq.line.placement.anneal.AnnealSequenceSearch')
390-
def test_anneal_search_method_calls(search):
389+
def test_anneal_search_method_calls():
391390
q00, q01 = GridQubit(0, 0), GridQubit(0, 1)
392391
device = _create_device([q00, q01])
393392
length = 1
394393
seed = 1
395-
search_instance = search.return_value
396394

397395
method = AnnealSequenceSearchStrategy(None, seed)
398-
method.place_line(device, length)
399-
400-
search.assert_called_once_with(device, seed)
401-
search_instance.search.assert_called_once_with(None)
396+
assert len(method.place_line(device, length)) == length
402397

403398

404399
def test_index_2d():

0 commit comments

Comments
 (0)