diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py index ed9ef43432fb..1b04e7e9f85d 100644 --- a/qiskit/quantum_info/operators/operator.py +++ b/qiskit/quantum_info/operators/operator.py @@ -199,7 +199,7 @@ def from_label(cls, label): return op @classmethod - def from_circuit(cls, circuit, ignore_set_layout=False, layout=None): + def from_circuit(cls, circuit, ignore_set_layout=False, layout=None, final_layout=None): """Create a new Operator object from a :class:`.QuantumCircuit` While a :class:`~.QuantumCircuit` object can passed directly as ``data`` @@ -224,6 +224,9 @@ def from_circuit(cls, circuit, ignore_set_layout=False, layout=None): of a layout contained in the ``circuit`` input. If specified the virtual bits in the :class:`~.Layout` must be present in the ``circuit`` input. + final_layout (Layout): If specified this kwarg can be used to represent the + output permutation caused by swap insertions during the routing stage + of the transpiler. Returns: Operator: An operator representing the input circuit """ @@ -239,6 +242,10 @@ def from_circuit(cls, circuit, ignore_set_layout=False, layout=None): initial_layout=layout, input_qubit_mapping={qubit: index for index, qubit in enumerate(circuit.qubits)}, ) + if final_layout is None: + if not ignore_set_layout and layout is not None: + final_layout = getattr(layout, "final_layout", None) + qargs = None # If there was a layout specified (either from the circuit # or via user input) use that to set qargs to permute qubits @@ -252,6 +259,16 @@ def from_circuit(cls, circuit, ignore_set_layout=False, layout=None): # Convert circuit to an instruction instruction = circuit.to_instruction() op._append_instruction(instruction, qargs=qargs) + # If final layout is set permute output indices based on layout + if final_layout is not None: + # TODO: Do this without the intermediate Permutation object by just + # operating directly on the array directly + from qiskit.circuit.library import Permutation # pylint: disable=cyclic-import + + final_physical_to_virtual = final_layout.get_physical_bits() + perm_pattern = [final_layout._v2p[v] for v in circuit.qubits] + perm_op = Operator(Permutation(len(final_physical_to_virtual), perm_pattern)) + op &= perm_op return op def is_unitary(self, atol=None, rtol=None): diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index 54a1c1de4aa4..5557f052ba31 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -135,6 +135,7 @@ def __call__(self, circuit, property_set=None): result_circuit._layout = TranspileLayout( initial_layout=self.property_set["layout"], input_qubit_mapping=self.property_set["original_qubit_indices"], + final_layout=self.property_set["final_layout"], ) if self.property_set["clbit_write_latency"] is not None: result_circuit._clbit_write_latency = self.property_set["clbit_write_latency"] diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index ccaca4923412..eb29c6c80fd1 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -19,7 +19,7 @@ """ from dataclasses import dataclass -from typing import Dict +from typing import Dict, Optional from qiskit.circuit.quantumregister import Qubit, QuantumRegister from qiskit.transpiler.exceptions import LayoutError @@ -374,3 +374,4 @@ class TranspileLayout: initial_layout: Layout input_qubit_mapping: Dict[Qubit, int] + final_layout: Optional[Layout] = None diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index 5eb7b4a834eb..512fc7483cf3 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -102,6 +102,7 @@ def run(self, dag): order = current_layout.reorder_bits(new_dag.qubits) new_dag.compose(subdag, qubits=order) + self.property_set["final_layout"] = current_layout return new_dag def _fake_run(self, dag): diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index c616169485d0..1cb7196c1abf 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -155,8 +155,8 @@ def run(self, dag): mapped_gates.extend(gates_mapped) + self.property_set["final_layout"] = current_state.layout if self.fake_run: - self.property_set["final_layout"] = current_state.layout return dag # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index 284e38078c27..dbe497fdca8c 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -133,6 +133,7 @@ def run(self, circuit, output_name=None, callback=None): circuit._layout = TranspileLayout( initial_layout=self.property_set["layout"], input_qubit_mapping=self.property_set["original_qubit_indices"], + final_layout=self.property_set["final_layout"], ) circuit._clbit_write_latency = self.property_set["clbit_write_latency"] circuit._conditional_latency = self.property_set["conditional_latency"] diff --git a/releasenotes/notes/final_layout-8178327a57b8b96a.yaml b/releasenotes/notes/final_layout-8178327a57b8b96a.yaml new file mode 100644 index 000000000000..445f75a53065 --- /dev/null +++ b/releasenotes/notes/final_layout-8178327a57b8b96a.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + The :meth:`.Operator.from_circuit` constructor method now will reverse + the output permutation caused by the routing/swap mapping stage of the + transpiler. By default if a transpiled circuit had Swap gates inserted + the output matrix will have that permutation reversed so the returned + matrix will be equivalent to the original un-transpiled circuit. If you'd + like to disable this default behavior the ``ignore_set_layout`` keyword + argument can be set to ``True`` to do this (in addition to previous behavior + of ignoring the initial layout from transpilation). If you'd like to + manually set a final layout you can use the new ``final_layout`` keyword + argument to pass in a :class:`~.Layout` object to use for the output + permutation. diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py index 449e0648df3a..9973f5320ac4 100644 --- a/test/python/quantum_info/operators/test_operator.py +++ b/test/python/quantum_info/operators/test_operator.py @@ -749,6 +749,77 @@ def test_from_circuit_constructor_reverse_embedded_layout(self): global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True) self.assertTrue(global_phase_equivalent) + def test_from_circuit_constructor_reverse_embedded_layout_from_transpile(self): + """Test initialization from a circuit with an embedded final layout.""" + # Test tensor product of 1-qubit gates + circuit = QuantumCircuit(3) + circuit.h(0) + circuit.x(1) + circuit.ry(np.pi / 2, 2) + output = transpile(circuit, initial_layout=[2, 1, 0]) + op = Operator.from_circuit(output) + y90 = (1 / np.sqrt(2)) * np.array([[1, -1], [1, 1]]) + target = np.kron(y90, np.kron(self.UX, self.UH)) + global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True) + self.assertTrue(global_phase_equivalent) + + def test_from_circuit_constructor_reverse_embedded_layout_from_transpile_with_registers(self): + """Test initialization from a circuit with an embedded final layout.""" + # Test tensor product of 1-qubit gates + qr = QuantumRegister(3, name="test_reg") + circuit = QuantumCircuit(qr) + circuit.h(0) + circuit.x(1) + circuit.ry(np.pi / 2, 2) + output = transpile(circuit, initial_layout=[2, 1, 0]) + op = Operator.from_circuit(output) + y90 = (1 / np.sqrt(2)) * np.array([[1, -1], [1, 1]]) + target = np.kron(y90, np.kron(self.UX, self.UH)) + global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True) + self.assertTrue(global_phase_equivalent) + + def test_from_circuit_constructor_reverse_embedded_layout_and_final_layout(self): + """Test initialization from a circuit with an embedded final layout.""" + # Test tensor product of 1-qubit gates + qr = QuantumRegister(3, name="test_reg") + circuit = QuantumCircuit(qr) + circuit.h(2) + circuit.x(1) + circuit.ry(np.pi / 2, 0) + circuit._layout = TranspileLayout( + Layout({circuit.qubits[2]: 0, circuit.qubits[1]: 1, circuit.qubits[0]: 2}), + {qubit: index for index, qubit in enumerate(circuit.qubits)}, + Layout({circuit.qubits[0]: 1, circuit.qubits[1]: 2, circuit.qubits[2]: 0}), + ) + circuit.swap(0, 1) + circuit.swap(1, 2) + op = Operator.from_circuit(circuit) + y90 = (1 / np.sqrt(2)) * np.array([[1, -1], [1, 1]]) + target = np.kron(y90, np.kron(self.UX, self.UH)) + global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True) + self.assertTrue(global_phase_equivalent) + + def test_from_circuit_constructor_reverse_embedded_layout_and_manual_final_layout(self): + """Test initialization from a circuit with an embedded final layout.""" + # Test tensor product of 1-qubit gates + qr = QuantumRegister(3, name="test_reg") + circuit = QuantumCircuit(qr) + circuit.h(2) + circuit.x(1) + circuit.ry(np.pi / 2, 0) + circuit._layout = TranspileLayout( + Layout({circuit.qubits[2]: 0, circuit.qubits[1]: 1, circuit.qubits[0]: 2}), + {qubit: index for index, qubit in enumerate(circuit.qubits)}, + ) + final_layout = Layout({circuit.qubits[0]: 1, circuit.qubits[1]: 2, circuit.qubits[2]: 0}) + circuit.swap(0, 1) + circuit.swap(1, 2) + op = Operator.from_circuit(circuit, final_layout=final_layout) + y90 = (1 / np.sqrt(2)) * np.array([[1, -1], [1, 1]]) + target = np.kron(y90, np.kron(self.UX, self.UH)) + global_phase_equivalent = matrix_equal(op.data, target, ignore_phase=True) + self.assertTrue(global_phase_equivalent) + def test_from_circuit_constructor_reverse_embedded_layout_ignore_set_layout(self): """Test initialization from a circuit with an ignored embedded reverse layout.""" # Test tensor product of 1-qubit gates