Skip to content

Commit 1c38094

Browse files
committed
Fix default options for GraphMapper
1 parent a860498 commit 1c38094

File tree

4 files changed

+131
-34
lines changed

4 files changed

+131
-34
lines changed

projectq/cengines/_gate_manager.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@
2828
# ==============================================================================
2929

3030

31+
class defaults(object):
32+
"""
33+
Class containing default values for some options
34+
"""
35+
36+
delta = 0.001
37+
max_lifetime = 5
38+
near_term_layer_depth = 1
39+
W = 0.5
40+
41+
42+
# ==============================================================================
43+
44+
3145
def _topological_sort(dag):
3246
"""
3347
Returns a generator of nodes in topologically sorted order.
@@ -154,7 +168,7 @@ def nearest_neighbours_cost_fun(gates_dag, mapping, distance_matrix, swap,
154168
Returns:
155169
Score of current swap operations
156170
"""
157-
#pylint: disable=unused-argument
171+
# pylint: disable=unused-argument
158172
return _sum_distance_over_gates(gates_dag.front_layer_2qubit, mapping,
159173
distance_matrix)
160174

@@ -212,7 +226,7 @@ def look_ahead_parallelism_cost_fun(gates_dag, mapping, distance_matrix, swap,
212226
- Weighting factor (see cost function formula)
213227
"""
214228
decay = opts['decay']
215-
near_term_weight = opts['W']
229+
near_term_weight = opts.get('W', defaults.W)
216230

217231
n_front = len(gates_dag.front_layer_2qubit)
218232
n_near = len(gates_dag.near_term_layer)
@@ -668,8 +682,9 @@ def __init__(self, graph, decay_opts=None):
668682
if decay_opts is None:
669683
decay_opts = {}
670684
self.dag = CommandDAG()
671-
self._decay = DecayManager(decay_opts.get('delta', 0.001),
672-
decay_opts.get('max_lifetime', 5))
685+
self._decay = DecayManager(
686+
decay_opts.get('delta', defaults.delta),
687+
decay_opts.get('max_lifetime', defaults.max_lifetime))
673688
self._stats = {
674689
'simul_exec': [],
675690
'2qubit_gates_loc': {},
@@ -754,7 +769,9 @@ def generate_swaps(self,
754769
self._decay.clear()
755770
opts['decay'] = self._decay
756771

757-
self.dag.calculate_near_term_layer(current_mapping)
772+
self.dag.calculate_near_term_layer(
773+
current_mapping,
774+
opts.get('near_term_layer_depth', defaults.near_term_layer_depth))
758775

759776
mapping = current_mapping.copy()
760777
swaps = []
@@ -836,7 +853,8 @@ def _add_to_execute_list(node):
836853
has_command_to_execute = True
837854
self._stats['simul_exec'][-1] += 1
838855
_add_to_execute_list(node)
839-
elif node.logical_id0 in mapping and node.logical_id1 in mapping:
856+
elif (node.logical_id0 in mapping
857+
and node.logical_id1 in mapping):
840858
if self.graph.has_edge(mapping[node.logical_id0],
841859
mapping[node.logical_id1]):
842860
has_command_to_execute = True
@@ -938,7 +956,8 @@ def _generate_one_swap_step(self, mapping, cost_fun, opts):
938956

939957
# Rank swap candidates using the provided cost function
940958
scores = []
941-
for logical_id0, backend_id0, logical_id1, backend_id1 in swap_candidates:
959+
for (logical_id0, backend_id0, logical_id1,
960+
backend_id1) in swap_candidates:
942961
new_mapping = mapping.copy()
943962

944963
_apply_swap_to_mapping(new_mapping, logical_id0, logical_id1,

projectq/cengines/_gate_manager_test.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,17 @@ def test_command_dag_near_term_layer(command_dag):
511511
command_dag.add_command(cmd14)
512512
dag_node12 = search_cmd(command_dag, cmd12)
513513
dag_node34 = search_cmd(command_dag, cmd34)
514+
dag_node23b = search_cmd(command_dag, cmd23b)
515+
dag_node46 = search_cmd(command_dag, cmd46)
514516

515-
command_dag.calculate_near_term_layer({i: i for i in range(7)})
517+
command_dag.calculate_near_term_layer({i: i for i in range(7)}, depth=1)
516518
assert command_dag.near_term_layer == [dag_node12, dag_node34]
517519

520+
command_dag.calculate_near_term_layer({i: i for i in range(7)}, depth=2)
521+
assert command_dag.near_term_layer == [
522+
dag_node12, dag_node34, dag_node23b, dag_node46
523+
]
524+
518525

519526
def test_command_dag_calculate_interaction_list(command_dag):
520527
cmd01 = gen_cmd(0, 1)

projectq/cengines/_graphmapper.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
Mapper for a quantum circuit to an arbitrary connected graph.
1616
1717
Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed
18-
to be applied in parallel if they act on disjoint qubit(s) and any pair
19-
of qubits can perform a 2 qubit gate (all-to-all connectivity)
18+
to be applied in parallel if they act on disjoint qubit(s) and any pair of
19+
qubits can perform a 2 qubit gate (all-to-all connectivity)
20+
2021
Output: Quantum circuit in which qubits are placed in 2-D square grid in which
21-
only nearest neighbour qubits can perform a 2 qubit gate. The mapper
22-
uses Swap gates in order to move qubits next to each other.
22+
only nearest neighbour qubits can perform a 2 qubit gate. The mapper uses Swap
23+
gates in order to move qubits next to each other.
2324
"""
2425
from copy import deepcopy
2526

@@ -39,12 +40,12 @@
3940
if sys.version_info[0] >= 3 and sys.version_info[1] > 6: # pragma: no cover
4041

4142
def uniquify_list(seq):
42-
#pylint: disable=missing-function-docstring
43+
# pylint: disable=missing-function-docstring
4344
return list(dict.fromkeys(seq))
4445
else: # pragma: no cover
4546

4647
def uniquify_list(seq):
47-
#pylint: disable=missing-function-docstring
48+
# pylint: disable=missing-function-docstring
4849
seen = set()
4950
seen_add = seen.add
5051
return [x for x in seq if x not in seen and not seen_add(x)]
@@ -53,6 +54,18 @@ def uniquify_list(seq):
5354
# ==============================================================================
5455

5556

57+
class defaults(object):
58+
"""
59+
Class containing default values for some options
60+
"""
61+
#: Defaults to :py:func:`.look_ahead_parallelism_cost_fun`
62+
cost_fun = look_ahead_parallelism_cost_fun
63+
max_swap_steps = 30
64+
65+
66+
# ==============================================================================
67+
68+
5669
class GraphMapperError(Exception):
5770
"""Base class for all exceptions related to the GraphMapper."""
5871

@@ -78,7 +91,7 @@ def _add_qubits_to_mapping_fcfs(current_mapping, graph, new_logical_qubit_ids,
7891
7992
Returns: A new mapping
8093
"""
81-
#pylint: disable=unused-argument
94+
# pylint: disable=unused-argument
8295

8396
mapping = deepcopy(current_mapping)
8497
currently_used_nodes = sorted([v for _, v in mapping.items()])
@@ -337,26 +350,20 @@ def __init__(self,
337350
* - Key
338351
- Type
339352
- Description
340-
* - cost_fun
341-
- ``function``
342-
- | Cost function to be called when generating a new
343-
| list of swap operations.
344-
| Defaults to :py:func:`.look_ahead_parallelism_cost_fun`
345353
* - decay_opts
346354
- ``dict``
347355
- | Options to pass onto the :py:class:`.DecayManager`
348356
constructor
349-
| Defaults to ``{'delta': 0.001, 'max_lifetime': 5}``.
350-
* - opts
357+
| (see :py:class:`._gate_manager.defaults`)
358+
* - swap_opts
351359
- ``dict``
352-
- | Extra options to pass onto the cost function
353-
| (see :py:meth:`.MultiQubitGateManager.generate_swaps`)
354-
| Defaults to ``{'W': 0.5}``.
355-
* - max_swap_steps
356-
- ``int``
357-
- | Maximum number of swap steps per mapping
358-
| (see :py:meth:`.MultiQubitGateManager.generate_swaps`)
359-
| Defaults to 30
360+
- | Extra options used when generating a list of swap
361+
| operations.
362+
| Acceptable keys: W, cost_fun, near_term_layer_depth,
363+
| max_swap_steps
364+
| (see :py:meth:`.GateManager.generate_swaps`,
365+
| :py:class:`._graphmapper.defaults` and
366+
| :py:class:`._gate_manager.defaults`)
360367
"""
361368
BasicMapperEngine.__init__(self)
362369

@@ -542,12 +549,14 @@ def _run(self):
542549
if not self.qubit_manager.size():
543550
return
544551

552+
# NB: default values are taken care of at place of access
553+
swap_opts = self._opts.get('swap_opts', {})
554+
545555
swaps, all_swapped_qubits = self.qubit_manager.generate_swaps(
546556
self._current_mapping,
547-
cost_fun=self._opts.get('cost_fun',
548-
look_ahead_parallelism_cost_fun),
549-
opts=self._opts.get('opts', {'W': 0.5}),
550-
max_steps=self._opts.get('max_swap_steps', 30))
557+
cost_fun=swap_opts.get('cost_fun', defaults.cost_fun),
558+
opts=swap_opts,
559+
max_steps=swap_opts.get('max_swap_steps', defaults.max_swap_steps))
551560

552561
if swaps:
553562
# Get a list of the qubits we need to allocate just to perform the

projectq/cengines/_graphmapper_test.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,68 @@ def test_run_and_receive(simple_graph, simple_mapper):
535535
assert mapper.num_mappings == 1
536536

537537

538+
@pytest.mark.parametrize("opts", [{}, {
539+
'swap_opts': {
540+
}
541+
}, {
542+
'swap_opts': {
543+
'W': 0.5,
544+
}
545+
}, {
546+
'swap_opts': {
547+
'W': 0.5,
548+
'near_term_layer_depth': 2
549+
}
550+
}])
551+
def test_run_and_receive_with_opts(simple_graph, opts):
552+
mapper = graphm.GraphMapper(graph=simple_graph,
553+
add_qubits_to_mapping="fcfs",
554+
opts=opts)
555+
backend = DummyEngine(save_commands=True)
556+
backend.is_last_engine = True
557+
mapper.next_engine = backend
558+
559+
qb, allocate_cmds = allocate_all_qubits_cmd(mapper)
560+
561+
gates = [
562+
Command(None, X, qubits=([qb[0]], ), controls=[qb[1]]),
563+
Command(None, X, qubits=([qb[1]], ), controls=[qb[2]]),
564+
Command(None, X, qubits=([qb[1]], ), controls=[qb[5]]),
565+
Command(None, X, qubits=([qb[2]], ), controls=[qb[3]]),
566+
Command(None, X, qubits=([qb[5]], ), controls=[qb[3]]),
567+
Command(None, X, qubits=([qb[3]], ), controls=[qb[4]]),
568+
Command(None, X, qubits=([qb[3]], ), controls=[qb[6]]),
569+
Command(None, X, qubits=([qb[4]], ), controls=[qb[6]]),
570+
]
571+
deallocate_cmds = [
572+
Command(engine=None, gate=Deallocate, qubits=([qb[1]], ))
573+
]
574+
575+
allocated_qubits_ref = set([0, 2, 3, 4, 5, 6])
576+
577+
all_cmds = list(itertools.chain(allocate_cmds, gates, deallocate_cmds))
578+
mapper.receive(all_cmds)
579+
qb_flush = WeakQubitRef(engine=None, idx=-1)
580+
cmd_flush = Command(engine=None, gate=FlushGate(), qubits=([qb_flush], ))
581+
mapper.receive([cmd_flush])
582+
assert mapper.qubit_manager.size() == 0
583+
assert len(backend.received_commands) == len(all_cmds) + 1
584+
assert mapper._currently_allocated_ids == allocated_qubits_ref
585+
586+
mapping = dict(enumerate(range(len(simple_graph))))
587+
del mapping[1]
588+
assert mapper.current_mapping == mapping
589+
590+
cmd9 = Command(None, X, qubits=([qb[0]], ), controls=[qb[6]])
591+
mapper.receive([cmd9, cmd_flush])
592+
assert mapper._currently_allocated_ids == allocated_qubits_ref
593+
for idx in allocated_qubits_ref:
594+
assert idx in mapper.current_mapping
595+
assert mapper.qubit_manager.size() == 0
596+
assert len(mapper.current_mapping) == 6
597+
assert mapper.num_mappings == 1
598+
599+
538600
def test_send_two_qubit_gate_before_swap(simple_mapper):
539601
qb, all_cmds = allocate_all_qubits_cmd(simple_mapper[0])
540602

0 commit comments

Comments
 (0)