Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve initial qubit mapping algorithm #144

Merged
merged 7 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions recirq/quantum_chess/circuit_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import cirq

import recirq.quantum_chess.controlled_iswap as controlled_iswap
import recirq.quantum_chess.initial_mapping_utils as imu
import recirq.quantum_chess.swap_updater as su

ADJACENCY = [(0, 1), (0, -1), (1, 0), (-1, 0)]

Expand Down Expand Up @@ -305,6 +307,49 @@ def transform(self, circuit: cirq.Circuit) -> cirq.Circuit:
return circuit.transform_qubits(lambda q: self.mapping[q])


class DynamicLookAheadHeuristicCircuitTransformer(CircuitTransformer):
"""Optimizer that transforms a circuit to satify a device's constraints.

This implements the initial mapping algorithm and the SWAP-based update
algorithm proposed by the paper "A Dynamic Look-Ahead Heuristic for the
Qubit Mapping Problem of NISQ Computer":
https://ieeexplore.ieee.org/abstract/document/8976109.

The initial mapping algorithm first maps the center of the logical qubits
graph to the center of the physical qubits graph. It then traverses the
logical qubits in a breadth-first traversal order starting from the center
of the logical qubits graph. For each logical qubit, it finds the physical
qubit that minimizes the nearest neighbor distance for the leftmost gates.

The SWAP-based update algorithm uses a heuristic cost function of a SWAP
operation called maximum consecutive positive effect (MCPE) to greedily
look ahead in each moment for SWAP operations that will reduce the nearest
neighbor distance for the largest number of gates in the current look-ahead
window.

Reference:
P. Zhu, Z. Guan and X. Cheng, "A Dynamic Look-Ahead Heuristic for the
Qubit Mapping Problem of NISQ Computers," in IEEE Transactions on Computer-
Aided Design of Integrated Circuits and Systems, vol. 39, no. 12, pp. 4721-
4735, Dec. 2020, doi: 10.1109/TCAD.2020.2970594.
"""
def __init__(self, device: cirq.Device):
super().__init__()
self.device = device

def transform(self, circuit: cirq.Circuit) -> cirq.Circuit:
"""Returns a transformed circuit.

The transformed circuit satisfies all physical adjacency constraints
of the device.

Args:
circuit: The circuit to transform.
"""
initial_mapping = imu.calculate_initial_mapping(self.device, circuit)
updater = su.SwapUpdater(circuit, self.device.qubit_set(), initial_mapping)
return cirq.Circuit(updater.add_swaps())

class SycamoreDecomposer(cirq.PointOptimizer):
"""Optimizer that decomposes all three qubit operations into
sqrt-ISWAPs.
Expand Down
82 changes: 51 additions & 31 deletions recirq/quantum_chess/circuit_transformer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,62 +30,82 @@
d1 = cirq.NamedQubit('d1')


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_single_qubit_ops(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_single_qubit_ops(transformer, device):
c = cirq.Circuit(cirq.X(a1), cirq.X(a2), cirq.X(a3))
transformer.qubit_mapping(c)
c = transformer.transform(c)
device.validate_circuit(c)
t = transformer(device)
device.validate_circuit(t.transform(c))
wingers marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_single_qubit_with_two_qubits(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_single_qubit_and_two_qubits_ops(transformer, device):
c = cirq.Circuit(cirq.X(a1), cirq.X(a2), cirq.X(a3),
cirq.ISWAP(a3, a4) ** 0.5)
transformer.qubit_mapping(c)
device.validate_circuit(transformer.transform(c))
t = transformer(device)
device.validate_circuit(t.transform(c))


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_three_split_moves(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_three_split_moves(transformer, device):
c = cirq.Circuit(qm.split_move(a1, a2, b1), qm.split_move(a2, a3, b3),
qm.split_move(b1, c1, c2))
transformer.qubit_mapping(c)
device.validate_circuit(transformer.transform(c))
t = transformer(device)
device.validate_circuit(t.transform(c))


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_disconnected(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_disconnected(transformer, device):
c = cirq.Circuit(qm.split_move(a1, a2, a3), qm.split_move(a3, a4, d1),
qm.split_move(b1, b2, b3), qm.split_move(c1, c2, c3))
transformer.qubit_mapping(c)
device.validate_circuit(transformer.transform(c))
t = transformer(device)
device.validate_circuit(t.transform(c))


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_move_around_square(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_move_around_square(transformer, device):
c = cirq.Circuit(qm.normal_move(a1, a2), qm.normal_move(a2, b2),
qm.normal_move(b2, b1), qm.normal_move(b1, a1))
transformer.qubit_mapping(c)
device.validate_circuit(transformer.transform(c))
t = transformer(device)
device.validate_circuit(t.transform(c))


@pytest.mark.parametrize('transformer',
[ct.ConnectivityHeuristicCircuitTransformer,
ct.DynamicLookAheadHeuristicCircuitTransformer])
@pytest.mark.parametrize('device',
(cirq.google.Sycamore23, cirq.google.Sycamore))
def test_split_then_merge(device):
transformer = ct.ConnectivityHeuristicCircuitTransformer(device)
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_split_then_merge(transformer, device):
c = cirq.Circuit(qm.split_move(a1, a2, b1), qm.split_move(a2, a3, b3),
qm.split_move(b1, c1, c2), qm.normal_move(c1, d1),
qm.normal_move(a3, a4), qm.merge_move(a4, d1, a1))
transformer.qubit_mapping(c)
device.validate_circuit(transformer.transform(c))
t = transformer(device)
device.validate_circuit(t.transform(c))


@pytest.mark.parametrize('device',
[cirq.google.Sycamore23, cirq.google.Sycamore])
def test_split_then_merge_trapezoid(device):
c = cirq.Circuit(qm.split_move(a1, a2, b1), qm.normal_move(a2, a3),
qm.merge_move(a3, b1, b3))
t = ct.DynamicLookAheadHeuristicCircuitTransformer(device)
device.validate_circuit(t.transform(c))
Loading