Skip to content

Commit

Permalink
Merge b94959d into 963bff3
Browse files Browse the repository at this point in the history
  • Loading branch information
damiansteiger committed Jul 19, 2018
2 parents 963bff3 + b94959d commit cff1c0f
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 53 deletions.
9 changes: 5 additions & 4 deletions projectq/backends/_sim/_simulator_test.py
Expand Up @@ -87,7 +87,9 @@ def receive(self, command_list):
if qubit.id == -1:
continue
elif qubit.id not in self.current_mapping:
self.current_mapping[qubit.id] = qubit.id + 1
previous_map = self.current_mapping
previous_map[qubit.id] = qubit.id + 1
self.current_mapping = previous_map
self._send_cmd_with_mapped_ids(cmd)

return TrivialMapper()
Expand Down Expand Up @@ -682,8 +684,7 @@ def receive(command_list):
eng = MainEngine(sim, [mapper])
qubit0 = eng.allocate_qubit()
qubit1 = eng.allocate_qubit()
mapper.current_mapping = dict()
mapper.current_mapping[qubit0[0].id] = qubit1[0].id
mapper.current_mapping[qubit1[0].id] = qubit0[0].id
mapper.current_mapping = {qubit0[0].id: qubit1[0].id,
qubit1[0].id: qubit0[0].id}
assert (sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) ==
qubit1 + qubit0)
10 changes: 9 additions & 1 deletion projectq/cengines/_basicmapper.py
Expand Up @@ -38,7 +38,15 @@ class BasicMapperEngine(BasicEngine):

def __init__(self):
BasicEngine.__init__(self)
self.current_mapping = None
self._current_mapping = None

@property
def current_mapping(self):
return deepcopy(self._current_mapping)

@current_mapping.setter
def current_mapping(self, current_mapping):
self._current_mapping = current_mapping

def _send_cmd_with_mapped_ids(self, cmd):
"""
Expand Down
2 changes: 1 addition & 1 deletion projectq/cengines/_ibm5qubitmapper.py
Expand Up @@ -164,7 +164,7 @@ def _store(self, cmd):
new_max = 0
if len(self.current_mapping) > 0:
new_max = max(self.current_mapping.values()) + 1
self.current_mapping[target] = new_max
self._current_mapping[target] = new_max
self._cmds.append(cmd)

def receive(self, command_list):
Expand Down
2 changes: 1 addition & 1 deletion projectq/cengines/_linearmapper.py
Expand Up @@ -497,7 +497,7 @@ def _send_possible_commands(self):
# Note: Deallocate gates get send everytime by self._run()
self._currently_allocated_ids.remove(cmd.qubits[0][0].id)
active_ids.remove(cmd.qubits[0][0].id)
self.current_mapping.pop(cmd.qubits[0][0].id)
self._current_mapping.pop(cmd.qubits[0][0].id)
else:
new_stored_commands.append(cmd)
else:
Expand Down
4 changes: 2 additions & 2 deletions projectq/cengines/_linearmapper_test.py
Expand Up @@ -458,7 +458,7 @@ def test_send_possible_commands_allocate():
assert len(backend.received_commands) == 0
assert mapper._stored_commands == [cmd0]
# in mapping:
mapper.current_mapping[0] = 3
mapper.current_mapping = {0: 3}
mapper._send_possible_commands()
assert len(mapper._stored_commands) == 0
# Only self._run() sends Allocate gates
Expand All @@ -482,7 +482,7 @@ def test_send_possible_commands_deallocate():
assert len(backend.received_commands) == 0
assert mapper._stored_commands == [cmd0]
# allocated:
mapper.current_mapping[0] = 3
mapper.current_mapping = {0: 3}
mapper._currently_allocated_ids.add(0)
mapper._send_possible_commands()
# Only self._run() send Deallocate gates
Expand Down
2 changes: 1 addition & 1 deletion projectq/cengines/_manualmapper.py
Expand Up @@ -56,5 +56,5 @@ def receive(self, command_list):
ids += [qb.id for qb in cmd.control_qubits]
for ID in ids:
if ID not in self.current_mapping:
self.current_mapping[ID] = self.map(ID)
self._current_mapping[ID] = self.map(ID)
self._send_cmd_with_mapped_ids(cmd)
117 changes: 94 additions & 23 deletions projectq/cengines/_twodmapper.py
Expand Up @@ -40,7 +40,7 @@ class GridMapper(BasicMapperEngine):
"""
Mapper to a 2-D grid graph.
Physical qubits on the grid are numbered in row-major order. E.g. for
Mapped qubits on the grid are numbered in row-major order. E.g. for
3 rows and 2 columns:
0 - 1
Expand All @@ -49,14 +49,18 @@ class GridMapper(BasicMapperEngine):
| |
4 - 5
The numbers are the mapped qubit ids.
The numbers are the mapped qubit ids. The backend might number
the qubits on the grid differently (e.g. not row-major), we call these
backend qubit ids. If the backend qubit ids are not row-major, one can
pass a dictionary translating from our row-major mapped ids to these
backend ids.
Note: The algorithm sorts twice inside each column and once inside each
row.
Attributes:
current_mapping: Stores the mapping: key is logical qubit id, value
is mapped qubit id from 0,...,self.num_qubits
is backend qubit id.
storage(int): Number of gate it caches before mapping.
num_rows(int): Number of rows in the grid
num_columns(int): Number of columns in the grid
Expand All @@ -70,7 +74,8 @@ class GridMapper(BasicMapperEngine):
mappings which have been applied
"""
def __init__(self, num_rows, num_columns, storage=1000,
def __init__(self, num_rows, num_columns, mapped_ids_to_backend_ids=None,
storage=1000,
optimization_function=lambda x: return_swap_depth(x),
num_optimization_steps=50):
"""
Expand All @@ -79,6 +84,15 @@ def __init__(self, num_rows, num_columns, storage=1000,
Args:
num_rows(int): Number of rows in the grid
num_columns(int): Number of columns in the grid.
mapped_ids_to_backend_ids(dict): Stores a mapping from mapped ids
which are 0,...,self.num_qubits-1
in row-major order on the grid to
the corresponding qubit ids of the
backend. Key: mapped id. Value:
corresponding backend id.
Default is None which means
backend ids are identical to
mapped ids.
storage: Number of gates to temporarily store
optimization_function: Function which takes a list of swaps and
returns a cost value. Mapper chooses a
Expand All @@ -87,11 +101,32 @@ def __init__(self, num_rows, num_columns, storage=1000,
num_optimization_steps(int): Number of different permutations to
of the matching to try and minimize
the cost.
Raises:
RuntimeError: if incorrect `mapped_ids_to_backend_ids` parameter
"""
BasicMapperEngine.__init__(self)
self.num_rows = num_rows
self.num_columns = num_columns
self.num_qubits = num_rows * num_columns
# Internally we use the mapped ids until sending a command.
# Before sending we use this map to translate to backend ids:
self._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids
if self._mapped_ids_to_backend_ids is None:
self._mapped_ids_to_backend_ids = dict()
for i in range(self.num_qubits):
self._mapped_ids_to_backend_ids[i] = i
if (not (set(self._mapped_ids_to_backend_ids.keys()) ==
set(list(range(self.num_qubits)))) or not (
len(set(self._mapped_ids_to_backend_ids.values())) ==
self.num_qubits)):
raise RuntimeError("Incorrect mapped_ids_to_backend_ids parameter")
self._backend_ids_to_mapped_ids = dict()
for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items():
self._backend_ids_to_mapped_ids[backend_id] = mapped_id
# As we use internally the mapped ids which are in row-major order,
# we have an internal current mapping which maps from logical ids to
# these mapped ids:
self._current_row_major_mapping = deepcopy(self.current_mapping)
self.storage = storage
self.optimization_function = optimization_function
self.num_optimization_steps = num_optimization_steps
Expand All @@ -107,6 +142,8 @@ def __init__(self, num_rows, num_columns, storage=1000,
# deallocated:
self._currently_allocated_ids = set()
# Change between 2D and 1D mappings (2D is a snake like 1D chain)
# Note it translates to our mapped ids in row major order and not
# backend ids which might be different.
self._map_2d_to_1d = dict()
self._map_1d_to_2d = dict()
for row_index in range(self.num_rows):
Expand All @@ -126,6 +163,21 @@ def __init__(self, num_rows, num_columns, storage=1000,
self.depth_of_swaps = dict()
self.num_of_swaps_per_mapping = dict()

@property
def current_mapping(self):
return deepcopy(self._current_mapping)

@current_mapping.setter
def current_mapping(self, current_mapping):
self._current_mapping = current_mapping
if current_mapping is None:
self._current_row_major_mapping = None
else:
self._current_row_major_mapping = dict()
for logical_id, backend_id in current_mapping.items():
self._current_row_major_mapping[logical_id] = (
self._backend_ids_to_mapped_ids[backend_id])

def is_available(self, cmd):
"""
Only allows 1 or two qubit gates.
Expand All @@ -151,15 +203,16 @@ def _return_new_mapping(self):
square grid.
Returns: A new mapping as a dict. key is logical qubit id,
value is placement id
value is mapped id
"""
# Change old mapping to 1D in order to use LinearChain heuristic
if self.current_mapping:
if self._current_row_major_mapping:
old_mapping_1d = dict()
for logical_id, mapped_id in self.current_mapping.items():
for logical_id, mapped_id in (
self._current_row_major_mapping.items()):
old_mapping_1d[logical_id] = self._map_2d_to_1d[mapped_id]
else:
old_mapping_1d = self.current_mapping
old_mapping_1d = self._current_row_major_mapping

new_mapping_1d = LinearMapper.return_new_mapping(
num_qubits=self.num_qubits,
Expand Down Expand Up @@ -379,10 +432,11 @@ def _send_possible_commands(self):
"""
Sends the stored commands possible without changing the mapping.
Note: self.current_mapping must exist already
Note: self._current_row_major_mapping (hence also self.current_mapping)
must exist already
"""
active_ids = deepcopy(self._currently_allocated_ids)
for logical_id in self.current_mapping:
for logical_id in self._current_row_major_mapping:
# So that loop doesn't stop before AllocateGate applied
active_ids.add(logical_id)

Expand All @@ -393,7 +447,7 @@ def _send_possible_commands(self):
new_stored_commands += self._stored_commands[i:]
break
if isinstance(cmd.gate, AllocateQubitGate):
if cmd.qubits[0][0].id in self.current_mapping:
if cmd.qubits[0][0].id in self._current_row_major_mapping:
self._currently_allocated_ids.add(cmd.qubits[0][0].id)
# Note: Allocate gates get send everytime by self._run()
else:
Expand All @@ -403,7 +457,8 @@ def _send_possible_commands(self):
# Note: Deallocate gates get send everytime by self._run()
self._currently_allocated_ids.remove(cmd.qubits[0][0].id)
active_ids.remove(cmd.qubits[0][0].id)
self.current_mapping.pop(cmd.qubits[0][0].id)
self._current_row_major_mapping.pop(cmd.qubits[0][0].id)
self._current_mapping.pop(cmd.qubits[0][0].id)
else:
new_stored_commands.append(cmd)
else:
Expand All @@ -414,7 +469,8 @@ def _send_possible_commands(self):
if qubit.id not in active_ids:
send_gate = False
break
mapped_ids.add(self.current_mapping[qubit.id])
mapped_ids.add(
self._current_row_major_mapping[qubit.id])
# Check that mapped ids are nearest neighbour on 2D grid
if len(mapped_ids) == 2:
qb0, qb1 = sorted(list(mapped_ids))
Expand All @@ -424,6 +480,10 @@ def _send_possible_commands(self):
elif qb1 - qb0 == 1 and qb1 % self.num_columns != 0:
send_gate = True
if send_gate:
# Note: This sends the cmd correctly with the backend ids
# as it looks up the mapping in self.current_mapping
# and not our internal mapping
# self._current_row_major_mapping
self._send_cmd_with_mapped_ids(cmd)
else:
for qureg in cmd.all_qubits:
Expand All @@ -445,15 +505,16 @@ def _run(self):
num_of_stored_commands_before = len(self._stored_commands)
if not self.current_mapping:
self.current_mapping = dict()
new_mapping = self._return_new_mapping()
new_row_major_mapping = self._return_new_mapping()
# Allocate all mapped qubit ids
mapped_ids_used = set()
for logical_id in self._currently_allocated_ids:
mapped_ids_used.add(self.current_mapping[logical_id])
mapped_ids_used.add(self._current_row_major_mapping[logical_id])
not_allocated_ids = set(range(self.num_qubits)).difference(
mapped_ids_used)
for mapped_id in not_allocated_ids:
qb = WeakQubitRef(engine=self, idx=mapped_id)
qb = WeakQubitRef(engine=self,
idx=self._mapped_ids_to_backend_ids[mapped_id])
cmd = Command(engine=self, gate=AllocateQubitGate(),
qubits=([qb],))
self.send([cmd])
Expand All @@ -471,9 +532,10 @@ def _run(self):
permutations.append(self._rng.sample(matchings_numbers,
self.num_rows))
for permutation in permutations:
trial_swaps = self.return_swaps(old_mapping=self.current_mapping,
new_mapping=new_mapping,
permutation=permutation)
trial_swaps = self.return_swaps(
old_mapping=self._current_row_major_mapping,
new_mapping=new_row_major_mapping,
permutation=permutation)
if swaps is None:
swaps = trial_swaps
lowest_cost = self.optimization_function(trial_swaps)
Expand All @@ -482,8 +544,10 @@ def _run(self):
lowest_cost = self.optimization_function(trial_swaps)
# Send swap operations to arrive at new_mapping:
for qubit_id0, qubit_id1 in swaps:
q0 = WeakQubitRef(engine=self, idx=qubit_id0)
q1 = WeakQubitRef(engine=self, idx=qubit_id1)
q0 = WeakQubitRef(engine=self,
idx=self._mapped_ids_to_backend_ids[qubit_id0])
q1 = WeakQubitRef(engine=self,
idx=self._mapped_ids_to_backend_ids[qubit_id1])
cmd = Command(engine=self, gate=Swap, qubits=([q0], [q1]))
self.send([cmd])
# Register statistics:
Expand All @@ -498,17 +562,24 @@ def _run(self):
else:
self.num_of_swaps_per_mapping[len(swaps)] += 1
# Change to new map:
self._current_row_major_mapping = new_row_major_mapping
new_mapping = dict()
for logical_id, mapped_id in new_row_major_mapping.items():
new_mapping[logical_id] = (
self._mapped_ids_to_backend_ids[mapped_id])
self.current_mapping = new_mapping

# Send possible gates:
self._send_possible_commands()
# Deallocate all mapped qubit ids not used for storing information:
mapped_ids_used = set()
for logical_id in self._currently_allocated_ids:
mapped_ids_used.add(self.current_mapping[logical_id])
mapped_ids_used.add(self._current_row_major_mapping[logical_id])
not_allocated_ids = set(range(self.num_qubits)).difference(
mapped_ids_used)
for mapped_id in not_allocated_ids:
qb = WeakQubitRef(engine=self, idx=mapped_id)
qb = WeakQubitRef(engine=self,
idx=self._mapped_ids_to_backend_ids[mapped_id])
cmd = Command(engine=self, gate=DeallocateQubitGate(),
qubits=([qb],))
self.send([cmd])
Expand Down

0 comments on commit cff1c0f

Please sign in to comment.