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

Added visualization for qubit permutations for routed circuits #5848

Merged
merged 11 commits into from
Sep 12, 2022
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@
prepare_two_qubit_state_using_cz,
prepare_two_qubit_state_using_sqrt_iswap,
RouteCQC,
routed_circuit_with_mapping,
SqrtIswapTargetGateset,
single_qubit_matrix_to_gates,
single_qubit_matrix_to_pauli_rotations,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/ops/gate_operation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ def all_subclasses(cls):
cirq.Pauli,
# Private gates.
cirq.transformers.analytical_decompositions.two_qubit_to_fsim._BGate,
cirq.transformers.routing.visualize_routed_circuit._SwapPrintGate,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't include an internal class in the global cirq namespace.

Copy link
Contributor Author

@ammareltigani ammareltigani Sep 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like it has to be included under skip_classes in this file or else I get the error
AssertionError: <class 'cirq.transformers.routing.visualize_routed_circuit._SwapPrintGate'> has no json file, please add a json file or add to the list of classes to be skipped if there is a reason this gate should not round trip to a gate via creating an operation.

cirq.ops.raw_types._InverseCompositeGate,
cirq.circuits.qasm_output.QasmTwoQubitGate,
cirq.ops.MSGate,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
LineInitialMapper,
MappingManager,
RouteCQC,
routed_circuit_with_mapping,
)

from cirq.transformers.target_gatesets import (
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/transformers/routing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
from cirq.transformers.routing.mapping_manager import MappingManager
from cirq.transformers.routing.line_initial_mapper import LineInitialMapper
from cirq.transformers.routing.route_circuit_cqc import RouteCQC
from cirq.transformers.routing.visualize_routed_circuit import routed_circuit_with_mapping
78 changes: 78 additions & 0 deletions cirq-core/cirq/transformers/routing/visualize_routed_circuit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2022 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Dict, Optional, Tuple, TYPE_CHECKING
from cirq import circuits, ops

if TYPE_CHECKING:
import cirq


class _SwapPrintGate(ops.Gate):
"""A gate that displays the string representation of each qubits on the circuit."""

def __init__(self, qubits: Tuple[Tuple['cirq.Qid', 'cirq.Qid'], ...]) -> None:
self.qubits = qubits

def num_qubits(self):
return len(self.qubits)

def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs') -> Tuple[str, ...]:
return tuple(f'{str(q[1])}' for q in self.qubits)


def routed_circuit_with_mapping(
routed_circuit: 'cirq.AbstractCircuit',
initial_map: Optional[Dict['cirq.Qid', 'cirq.Qid']] = None,
) -> 'cirq.AbstractCircuit':
"""Returns the same circuits with information about the permutation of qubits after each swap.

Args:
routed_circuit: a routed circuit that potentially has inserted swaps tagged with a
RoutingSwapTag.
initial_map: the initial mapping from logical to physical qubits. If this is not specified
then the identity mapping of the qubits in routed_circuit will be used as initial_map.

Raises:
ValueError: if a non-SWAP gate is tagged with a RoutingSwapTag.
"""
all_qubits = sorted(routed_circuit.all_qubits())
qdict = {q: q for q in all_qubits}
if initial_map is None:
initial_map = qdict.copy()
inverse_map = {v: k for k, v in initial_map.items()}

def swap_print_moment() -> 'cirq.Operation':
return _SwapPrintGate(
tuple(zip(qdict.values(), [inverse_map[x] for x in qdict.values()]))
).on(*all_qubits)

ret_circuit = circuits.Circuit(swap_print_moment())
for m in routed_circuit:
swap_in_moment = False
for op in m:
if ops.RoutingSwapTag() in op.tags:
tanujkhattar marked this conversation as resolved.
Show resolved Hide resolved
if type(op.gate) != ops.swap_gates.SwapPowGate:
raise ValueError(
"Invalid circuit. A non-SWAP gate cannot be tagged a RoutingSwapTag."
)
swap_in_moment = True
q1, q2 = op.qubits
qdict[q1], qdict[q2] = qdict[q2], qdict[q1]

ret_circuit.append(m)
if swap_in_moment:
ret_circuit.append(swap_print_moment())

return ret_circuit
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2022 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest
import cirq


def test_routed_circuit_with_mapping_simple():
q = cirq.LineQubit.range(2)
circuit = cirq.Circuit([cirq.Moment(cirq.SWAP(q[0], q[1]).with_tags(cirq.RoutingSwapTag()))])
expected_diagram = """
0: ───q(0)───×[cirq.RoutingSwapTag()]───q(1)───
│ │ │
1: ───q(1)───×──────────────────────────q(0)───"""
cirq.testing.assert_has_diagram(cirq.routed_circuit_with_mapping(circuit), expected_diagram)

expected_diagram_with_initial_mapping = """
0: ───a───×[cirq.RoutingSwapTag()]───b───
│ │ │
1: ───b───×──────────────────────────a───"""
cirq.testing.assert_has_diagram(
cirq.routed_circuit_with_mapping(
circuit, {cirq.NamedQubit("a"): q[0], cirq.NamedQubit("b"): q[1]}
),
expected_diagram_with_initial_mapping,
)

# if swap is untagged should not affect the mapping
circuit = cirq.Circuit([cirq.Moment(cirq.SWAP(q[0], q[1]))])
expected_diagram = """
0: ───q(0)───×───
│ │
1: ───q(1)───×───"""
cirq.testing.assert_has_diagram(cirq.routed_circuit_with_mapping(circuit), expected_diagram)

circuit = cirq.Circuit(
[
cirq.Moment(cirq.X(q[0]).with_tags(cirq.RoutingSwapTag())),
cirq.Moment(cirq.SWAP(q[0], q[1])),
]
)
with pytest.raises(
ValueError, match="Invalid circuit. A non-SWAP gate cannot be tagged a RoutingSwapTag."
):
cirq.routed_circuit_with_mapping(circuit)


def test_routed_circuit_with_mapping_multi_swaps():
q = cirq.LineQubit.range(6)
circuit = cirq.Circuit(
[
cirq.Moment(cirq.CNOT(q[3], q[4])),
cirq.Moment(cirq.CNOT(q[5], q[4]), cirq.CNOT(q[2], q[3])),
cirq.Moment(
cirq.CNOT(q[2], q[1]), cirq.SWAP(q[4], q[3]).with_tags(cirq.RoutingSwapTag())
),
cirq.Moment(
cirq.SWAP(q[0], q[1]).with_tags(cirq.RoutingSwapTag()),
cirq.SWAP(q[3], q[2]).with_tags(cirq.RoutingSwapTag()),
),
cirq.Moment(cirq.CNOT(q[2], q[1])),
cirq.Moment(cirq.CNOT(q[1], q[0])),
]
)
expected_diagram = """
0: ───q(0)──────────────────────────────────────q(0)───×[cirq.RoutingSwapTag()]───q(1)───────X───
│ │ │ │ │
1: ───q(1)───────────X──────────────────────────q(1)───×──────────────────────────q(0)───X───@───
│ │ │ │ │
2: ───q(2)───────@───@──────────────────────────q(2)───×──────────────────────────q(4)───@───────
│ │ │ │ │
3: ───q(3)───@───X───×──────────────────────────q(4)───×[cirq.RoutingSwapTag()]───q(2)───────────
│ │ │ │ │
4: ───q(4)───X───X───×[cirq.RoutingSwapTag()]───q(3)──────────────────────────────q(3)───────────
│ │ │ │
5: ───q(5)───────@──────────────────────────────q(5)──────────────────────────────q(5)───────────
"""
cirq.testing.assert_has_diagram(cirq.routed_circuit_with_mapping(circuit), expected_diagram)