3131from projectq .ops import (AllocateQubitGate , Command , DeallocateQubitGate ,
3232 FlushGate , Swap )
3333from 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