diff --git a/qiskit/providers/aer/noise/passes/local_noise_pass.py b/qiskit/providers/aer/noise/passes/local_noise_pass.py index feaa21af39..f6e1c30bbe 100644 --- a/qiskit/providers/aer/noise/passes/local_noise_pass.py +++ b/qiskit/providers/aer/noise/passes/local_noise_pass.py @@ -14,13 +14,14 @@ """ from typing import Optional, Union, Sequence, Callable, Iterable -from qiskit.circuit import Instruction +from qiskit.circuit import Instruction, QuantumCircuit from qiskit.dagcircuit import DAGCircuit +from qiskit.converters import circuit_to_dag from qiskit.transpiler import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from ..errors import QuantumError, ReadoutError -InstructionLike = Union[Instruction, QuantumError] +InstructionLike = Union[Instruction, QuantumError, QuantumCircuit] class LocalNoisePass(TransformationPass): @@ -100,31 +101,52 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: qubits = [qubit_indices[q] for q in node.qargs] new_op = self._func(node.op, qubits) + if new_op is None: + # Edge case where we are replacing a node with nothing (removing node) if self._method == "replace": dag.remove_op_node(node) continue + if isinstance(new_op, ReadoutError): raise TranspilerError("Insertions of ReadoutError is not yet supported.") - if not isinstance(new_op, Instruction): + + # Initialize new node dag + new_dag = DAGCircuit() + new_dag.add_qubits(node.qargs) + new_dag.add_clbits(node.cargs) + + # If appending re-apply original op node first + if self._method == "append": + new_dag.apply_operation_back(node.op, qargs=node.qargs) + + # If the new op is not a QuantumCircuit or Instruction, attempt + # to conver to an Instruction + if not isinstance(new_op, (QuantumCircuit, Instruction)): try: new_op = new_op.to_instruction() except AttributeError as att_err: raise TranspilerError( "Function must return an object implementing 'to_instruction' method." ) from att_err + + # Validate the instruction matches the number of qubits and clbits of the node if new_op.num_qubits != len(node.qargs): raise TranspilerError( f"Number of qubits of generated op {new_op.num_qubits} != " f"{len(node.qargs)} that of a reference op {node.name}" ) - new_dag = DAGCircuit() - new_dag.add_qubits(node.qargs) - new_dag.add_clbits(node.cargs) - if self._method == "append": - new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs) - new_dag.apply_operation_back(new_op, qargs=node.qargs) + # Add the noise op returned by the function + if isinstance(new_op, QuantumCircuit): + # If the new op is a quantum circuit, compose its DAG with the new dag + # so that it is unrolled rather than added as an opaque instruction + new_dag.compose(circuit_to_dag(new_op), qubits=node.qargs, clbits=node.cargs) + else: + # Otherwise append the instruction returned by the function + new_dag.apply_operation_back(new_op, qargs=node.qargs, cargs=node.cargs) + + # If prepending reapply original op node last if self._method == "prepend": new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs) diff --git a/releasenotes/notes/fix-local-noise-pass-f94546869a169103.yaml b/releasenotes/notes/fix-local-noise-pass-f94546869a169103.yaml new file mode 100644 index 0000000000..df686d3185 --- /dev/null +++ b/releasenotes/notes/fix-local-noise-pass-f94546869a169103.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Fixes an issue with :class:`.LocalNoisePass` for noise functions that + return a :class:`.QuantumCircuit` for the noise op. These were appended + to the DAG as an opaque circuit instruction that must be unrolled to be + simulated. This fix composes them so that the cirucit instructions are + added to the new DAG and can be simulated without additional unrolling + if all circuit instructions are supported by the simulator. + + See `#1447 `__ + for details. diff --git a/test/terra/noise/passes/test_local_noise_pass.py b/test/terra/noise/passes/test_local_noise_pass.py index d4cb211e44..ac0e769d78 100644 --- a/test/terra/noise/passes/test_local_noise_pass.py +++ b/test/terra/noise/passes/test_local_noise_pass.py @@ -16,7 +16,7 @@ from qiskit.providers.aer.noise import ReadoutError, LocalNoisePass from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library.standard_gates import SXGate, HGate +from qiskit.circuit.library.standard_gates import SXGate, HGate, CXGate from qiskit.transpiler import TranspilerError from test.terra.common import QiskitAerTestCase @@ -94,3 +94,23 @@ def out_readout_error(op, qubits): noise_pass = LocalNoisePass(func=out_readout_error, op_types=HGate) with self.assertRaises(TranspilerError): noise_pass(qc) + + def test_append_circuit(self): + qc = QuantumCircuit(2) + qc.cx(0, 1) + + def composite_error(op, qubits): + circ = QuantumCircuit(2) + for q in qubits: + circ.x(q) + return circ + + noise_pass = LocalNoisePass(func=composite_error, op_types=CXGate) + noise_qc = noise_pass(qc) + + expected = QuantumCircuit(2) + expected.cx(0, 1) + expected.x(0) + expected.x(1) + + self.assertEqual(expected, noise_qc)