Skip to content

Commit 531e3ba

Browse files
committed
New implementation of the GraphMapper (SABRE-like algorithm)
This new implementation is largely based on the SABRE algorithm [1]. Essentially, use cost functions to plan the next list of SWAP operations. [1] https://arxiv.org/abs/1809.02573v2
1 parent 287f19c commit 531e3ba

File tree

3 files changed

+137
-307
lines changed

3 files changed

+137
-307
lines changed

docs/projectq.cengines.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ The ProjectQ compiler engines package.
1414
projectq.cengines.DummyEngine
1515
projectq.cengines.ForwarderEngine
1616
projectq.cengines.GridMapper
17+
projectq.cengines.GraphMapper
1718
projectq.cengines.InstructionFilter
1819
projectq.cengines.IBM5QubitMapper
1920
projectq.cengines.LinearMapper
2021
projectq.cengines.LocalOptimizer
2122
projectq.cengines.ManualMapper
2223
projectq.cengines.MainEngine
23-
projectq.cengines.SwapAndCNOTFlipper
24+
projectq.cengines.SwapAndCNOTFlipper
2425
projectq.cengines.TagRemover
2526

2627

projectq/cengines/_graphmapper.py

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
from projectq.ops import (AllocateQubitGate, Command, DeallocateQubitGate,
3232
FlushGate, Swap)
3333
from projectq.types import WeakQubitRef
34-
from projectq.cengines._graph_path_manager import PathManager
35-
from projectq.cengines._command_list import CommandList
34+
from ._command_list import CommandList
35+
from ._multi_qubit_gate_manager import (MultiQubitGateManager,
36+
look_ahead_parallelism_cost_fun)
3637

3738
# ------------------------------------------------------------------------------
3839

@@ -178,7 +179,7 @@ def _add_qubits_to_mapping(current_mapping, graph, new_logical_qubit_ids,
178179
mapping and that need to be assigned a
179180
backend id
180181
stored_commands (CommandList): list of commands yet to be processed by
181-
the mapper
182+
the mapper
182183
183184
Returns: A new mapping
184185
"""
@@ -256,6 +257,9 @@ class GraphMapper(BasicMapperEngine):
256257
Maps a quantum circuit to an arbitrary connected graph of connected qubits
257258
using Swap gates.
258259
260+
.. seealso::
261+
:py:mod:`projectq.cengines._multi_qubit_gate_manager`
262+
259263
Args:
260264
graph (networkx.Graph) : Arbitrary connected graph
261265
storage (int) Number of gates to temporarily store
@@ -273,8 +277,6 @@ class GraphMapper(BasicMapperEngine):
273277
graph
274278
new_logical_qubit_ids
275279
stored_commands
276-
enable_caching(Bool): Controls whether optimal path caching is
277-
enabled
278280
279281
Attributes:
280282
current_mapping: Stores the mapping: key is logical qubit id, value
@@ -298,26 +300,58 @@ class GraphMapper(BasicMapperEngine):
298300
3) Does not optimize for dirty qubits.
299301
300302
"""
301-
302303
def __init__(self,
303304
graph,
304305
storage=1000,
305306
add_qubits_to_mapping=_add_qubits_to_mapping,
306-
enable_caching=True):
307+
opts={}):
307308
"""
308309
Initialize a GraphMapper compiler engine.
309310
310311
Args:
311312
graph (networkx.Graph): Arbitrary connected graph representing
312313
Qubit connectivity
313314
storage (int): Number of gates to temporarily store
314-
enable_caching (Bool): Controls whether path caching is enabled
315+
generate_swap_opts (dict): extra options to customize swap
316+
operation generation
317+
opts (dict): Extra options (see below)
318+
315319
Raises:
316320
RuntimeError: if the graph is not a connected graph
321+
322+
Note:
323+
``opts`` may contain the following key-values:
324+
325+
.. list-table::
326+
:header-rows: 1
327+
328+
* - Key
329+
- Type
330+
- Description
331+
* - cost_fun
332+
- ``function``
333+
- | Cost function to be called when generating a new
334+
| list of swap operations.
335+
| Defaults to :py:func:`.look_ahead_parallelism_cost_fun`
336+
* - decay_opts
337+
- ``dict``
338+
- | Options to pass onto the :py:class:`.DecayManager`
339+
constructor
340+
| Defaults to ``{'delta': 0.001, 'max_lifetime': 5}``.
341+
* - opts
342+
- ``dict``
343+
- | Extra options to pass onto the cost function
344+
| (see :py:meth:`.MultiQubitGateManager.generate_swaps`)
345+
| Defaults to ``{'W': 0.5}``.
317346
"""
318347
BasicMapperEngine.__init__(self)
319348

320-
self.paths = PathManager(graph, enable_caching)
349+
self.qubit_manager = MultiQubitGateManager(graph=graph,
350+
decay_opts=opts.get(
351+
'decay_opts', {
352+
'delta': 0.001,
353+
'max_lifetime': 5
354+
}))
321355
self.num_qubits = graph.number_of_nodes()
322356
self.storage = storage
323357
# Randomness to pick permutations if there are too many.
@@ -337,6 +371,9 @@ def __init__(self,
337371
# Function to add new logical qubits ids to the mapping
338372
self.set_add_qubits_to_mapping(add_qubits_to_mapping)
339373

374+
self._cost_fun = opts.get('cost_fun', look_ahead_parallelism_cost_fun)
375+
self._opts = opts.get('opts', {'W': 0.5})
376+
340377
# Statistics:
341378
self.num_mappings = 0
342379
self.depth_of_swaps = dict()
@@ -403,10 +440,6 @@ def _process_commands(self):
403440
allocated_qubits = deepcopy(self._currently_allocated_ids)
404441
active_qubits = deepcopy(self._currently_allocated_ids)
405442

406-
# Always start from scratch again
407-
# (does not reset cache or path statistics)
408-
self.paths.clear_paths()
409-
410443
for cmd in self._stored_commands:
411444
if (len(allocated_qubits) == self.num_qubits
412445
and not active_qubits):
@@ -448,19 +481,17 @@ def _process_commands(self):
448481
else:
449482
if not_in_mapping_qubits:
450483
self.current_mapping = self._add_qubits_to_mapping(
451-
self._current_mapping, self.paths.graph,
484+
self._current_mapping, self.qubit_manager.graph,
452485
not_in_mapping_qubits, self._stored_commands)
453486
not_in_mapping_qubits = []
454487

455-
if not self.paths.push_interaction(
456-
self._current_mapping[qubit_ids[0]],
457-
self._current_mapping[qubit_ids[1]]):
458-
break
488+
self.qubit_manager.push_interaction(
489+
qubit_ids[0], qubit_ids[1])
459490

460491
if not_in_mapping_qubits:
461492
self.current_mapping = self._add_qubits_to_mapping(
462-
self._current_mapping, self.paths.graph, not_in_mapping_qubits,
463-
self._stored_commands)
493+
self._current_mapping, self.qubit_manager.graph,
494+
not_in_mapping_qubits, self._stored_commands)
464495

465496
def _send_possible_commands(self):
466497
"""
@@ -485,11 +516,10 @@ def _send_possible_commands(self):
485516
idx=self._current_mapping[cmd.qubits[0][0].id])
486517
self._currently_allocated_ids.add(cmd.qubits[0][0].id)
487518
self.send([
488-
Command(
489-
engine=self,
490-
gate=AllocateQubitGate(),
491-
qubits=([qb0], ),
492-
tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)])
519+
Command(engine=self,
520+
gate=AllocateQubitGate(),
521+
qubits=([qb0], ),
522+
tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)])
493523
])
494524
else:
495525
new_stored_commands.append(cmd)
@@ -502,36 +532,37 @@ def _send_possible_commands(self):
502532
active_ids.remove(cmd.qubits[0][0].id)
503533
self._current_mapping.pop(cmd.qubits[0][0].id)
504534
self.send([
505-
Command(
506-
engine=self,
507-
gate=DeallocateQubitGate(),
508-
qubits=([qb0], ),
509-
tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)])
535+
Command(engine=self,
536+
gate=DeallocateQubitGate(),
537+
qubits=([qb0], ),
538+
tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)])
510539
])
511540
else:
512541
new_stored_commands.append(cmd)
513542
else:
514543
send_gate = True
515-
backend_ids = set()
544+
logical_ids = []
516545
for qureg in cmd.all_qubits:
517546
for qubit in qureg:
547+
logical_ids.append(qubit.id)
548+
518549
if qubit.id not in active_ids:
519550
send_gate = False
520-
break
521-
backend_ids.add(self._current_mapping[qubit.id])
522551

523-
# Check that mapped ids are connected by an edge on the graph
524-
if len(backend_ids) == 2:
525-
send_gate = self.paths.graph.has_edge(*list(backend_ids))
552+
if send_gate:
553+
# Check that mapped ids are connected by an edge on the
554+
# graph
555+
if len(logical_ids) == 2:
556+
send_gate = self.qubit_manager.execute_gate(
557+
self._current_mapping, *logical_ids)
526558

527559
if send_gate:
528560
self._send_cmd_with_mapped_ids(cmd)
529561
else:
530562
# Cannot execute gate -> make sure no other gate will use
531563
# any of those qubits to preserve sequence
532-
for qureg in cmd.all_qubits:
533-
for qubit in qureg:
534-
active_ids.discard(qubit.id)
564+
for logical_id in logical_ids:
565+
active_ids.discard(logical_id)
535566
new_stored_commands.append(cmd)
536567
self._stored_commands = new_stored_commands
537568

@@ -555,7 +586,8 @@ def _run(self):
555586
if not self._stored_commands:
556587
return
557588

558-
swaps = self.paths.generate_swaps()
589+
swaps, all_swapped_qubits = self.qubit_manager.generate_swaps(
590+
self._current_mapping, self._cost_fun, self._opts)
559591

560592
if swaps: # first mapping requires no swaps
561593
backend_ids_used = {
@@ -565,8 +597,7 @@ def _run(self):
565597

566598
# Get a list of the qubits we need to allocate just to perform the
567599
# swaps
568-
not_allocated_ids = set(
569-
self.paths.get_all_nodes()).difference(backend_ids_used)
600+
not_allocated_ids = all_swapped_qubits.difference(backend_ids_used)
570601

571602
# Calculate temporary internal reverse mapping
572603
new_internal_mapping = deepcopy(self._reverse_current_mapping)
@@ -577,10 +608,9 @@ def _run(self):
577608
for backend_id in not_allocated_ids:
578609
qb0 = WeakQubitRef(engine=self, idx=backend_id)
579610
self.send([
580-
Command(
581-
engine=self,
582-
gate=AllocateQubitGate(),
583-
qubits=([qb0], ))
611+
Command(engine=self,
612+
gate=AllocateQubitGate(),
613+
qubits=([qb0], ))
584614
])
585615

586616
# Those qubits are not part of the current mapping, so add them
@@ -635,10 +665,9 @@ def _run(self):
635665
for backend_id in not_needed_anymore:
636666
qb0 = WeakQubitRef(engine=self, idx=backend_id)
637667
self.send([
638-
Command(
639-
engine=self,
640-
gate=DeallocateQubitGate(),
641-
qubits=([qb0], ))
668+
Command(engine=self,
669+
gate=DeallocateQubitGate(),
670+
qubits=([qb0], ))
642671
])
643672

644673
# Calculate new mapping
@@ -668,6 +697,7 @@ def receive(self, command_list):
668697
receive.
669698
"""
670699
for cmd in command_list:
700+
print(cmd)
671701
if isinstance(cmd.gate, FlushGate):
672702
while self._stored_commands:
673703
self._run()
@@ -702,4 +732,4 @@ def __str__(self):
702732
return ("Number of mappings: {}\n" + "Depth of swaps: {}\n\n" +
703733
"Number of swaps per mapping:{}\n\n{}\n\n").format(
704734
self.num_mappings, depth_of_swaps_str,
705-
num_swaps_per_mapping_str, str(self.paths))
735+
num_swaps_per_mapping_str, str(self.qubit_manager))

0 commit comments

Comments
 (0)