Skip to content

Commit

Permalink
Fix LocalNoisePass for circuit returns from noise func (#1455)
Browse files Browse the repository at this point in the history
If the local noise pass function returns a QuantumCircuit instead of an instruction or operator this composes the circuit into the DAG so that it is unrolled, rather than added as an opaque circuit instruction.

Co-authored-by: Matthew Treinish <[email protected]>
Co-authored-by: Toshinari Itoko <[email protected]>
  • Loading branch information
3 people authored Feb 8, 2022
1 parent 8847d0d commit 6461df8
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 10 deletions.
40 changes: 31 additions & 9 deletions qiskit/providers/aer/noise/passes/local_noise_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)

Expand Down
12 changes: 12 additions & 0 deletions releasenotes/notes/fix-local-noise-pass-f94546869a169103.yaml
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/qiskit-aer/issues/1447>`__
for details.
22 changes: 21 additions & 1 deletion test/terra/noise/passes/test_local_noise_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

0 comments on commit 6461df8

Please sign in to comment.