Skip to content

Commit

Permalink
Merge pull request #1412 from chriseclectic/stable/0.10
Browse files Browse the repository at this point in the history
Backports for Aer 0.10.1
  • Loading branch information
chriseclectic authored Dec 17, 2021
2 parents 61b028b + 0ca6d1a commit 04ab1d5
Show file tree
Hide file tree
Showing 17 changed files with 411 additions and 237 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.10.0'
release = '0.10.1'

# -- General configuration ---------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion qiskit/providers/aer/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.10.0
0.10.1
80 changes: 58 additions & 22 deletions qiskit/providers/aer/backends/aer_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
Compier to convert Qiskit control-flow to Aer backend.
"""
from qiskit.circuit import QuantumCircuit
from qiskit.pulse import Schedule, ScheduleBlock
from qiskit.circuit.controlflow import (
WhileLoopOp,
ForLoopOp,
IfElseOp,
BreakLoopOp,
ContinueLoopOp)
from qiskit.compiler import transpile
from .backend_utils import circuit_optypes
from ..library.control_flow_instructions import AerMark, AerJump


Expand All @@ -29,37 +31,71 @@ class AerCompiler:
def __init__(self):
self._last_flow_id = -1

def compile(self, circuits, basis_gates=None):
def compile(self, circuits, basis_gates=None, optypes=None):
"""compile a circuit that have control-flow instructions.
Args:
circuits (QuantumCircuit or list): The QuantumCircuit (or list
of QuantumCircuit objects) to be compiled
basis_gates (list): basis gates to decompose sub-circuits(default: None).
circuits (QuantumCircuit or list): The QuantumCircuits to be compiled
basis_gates (list): basis gates to decompose sub-circuits
(default: None).
optypes (list): list of instruction type sets for each circuit
(default: None).
Returns:
QuantumCircuit or list: QuantumCircuit (or list
of QuantumCircuit objects) without control-flow instructions
list: A list QuantumCircuit without control-flow
if optypes is None.
tuple: A tuple of a list of quantum circuits and list of
compiled circuit optypes for each circuit if
optypes kwarg is not None.
"""
if basis_gates:
basis_gates = basis_gates + ['mark', 'jump']
if isinstance(circuits, list):
return [transpile(self._inline_circuit(circ, None, None),
basis_gates=basis_gates) if self._is_dynamic(circ)
else circ for circ in circuits]
if isinstance(circuits, (QuantumCircuit, Schedule, ScheduleBlock)):
circuits = [circuits]
if optypes is None:
compiled_optypes = len(circuits) * [None]
else:
return (transpile(self._inline_circuit(circuits, None, None),
basis_gates=basis_gates) if self._is_dynamic(circuits)
else circuits)

def _is_dynamic(self, circuit):
"""check whether a circuit contains control-flow instructions
"""
# Make a shallow copy incase we modify it
compiled_optypes = list(optypes)
if isinstance(circuits, list):
basis_gates = basis_gates + ['mark', 'jump']
compiled_circuits = []
for idx, circuit in enumerate(circuits):
if self._is_dynamic(circuit, compiled_optypes[idx]):
compiled_circ = transpile(
self._inline_circuit(circuit, None, None),
basis_gates=basis_gates
)
compiled_circuits.append(compiled_circ)
# Recompute optype for compiled circuit
compiled_optypes[idx] = circuit_optypes(compiled_circ)
else:
compiled_circuits.append(circuit)
if optypes is None:
return compiled_circuits
return compiled_circuits, compiled_optypes

if optypes is None:
return circuits
return circuits, optypes

@staticmethod
def _is_dynamic(circuit, optype=None):
"""check whether a circuit contains control-flow instructions"""
if not isinstance(circuit, QuantumCircuit):
return False

controlflow_types = (
WhileLoopOp, ForLoopOp, IfElseOp, BreakLoopOp, ContinueLoopOp
)

# Check via optypes
if isinstance(optype, set):
return bool(optype.intersection(controlflow_types))

# Check via iteration
for inst, _, _ in circuit.data:
if isinstance(inst, (WhileLoopOp, ForLoopOp, IfElseOp, BreakLoopOp, ContinueLoopOp)):
if isinstance(inst, controlflow_types):
return True

return False

def _inline_circuit(self, circ, continue_label, break_label):
Expand Down Expand Up @@ -187,8 +223,8 @@ def _inline_if_else_op(self, inst, continue_label, break_label, parent, qargs, c
parent.append(AerMark(if_end_label, true_body.num_qubits), qargs, [])


def compile_circuit(circuits, basis_gates=None):
def compile_circuit(circuits, basis_gates=None, optypes=None):
"""
compile a circuit that have control-flow instructions
"""
return AerCompiler().compile(circuits, basis_gates)
return AerCompiler().compile(circuits, basis_gates, optypes)
111 changes: 71 additions & 40 deletions qiskit/providers/aer/backends/aerbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import warnings
from abc import ABC, abstractmethod

from qiskit.circuit import QuantumCircuit, ParameterExpression
from qiskit.circuit import QuantumCircuit, ParameterExpression, Delay
from qiskit.compiler import assemble
from qiskit.providers import BackendV1 as Backend
from qiskit.providers.models import BackendStatus
Expand All @@ -32,8 +32,9 @@
from ..aererror import AerError
from ..jobs import AerJob, AerJobSet, split_qobj
from ..noise.noise_model import NoiseModel, QuantumErrorLocation
from ..noise.errors.quantum_error import QuantumChannelInstruction
from .aer_compiler import compile_circuit
from .backend_utils import format_save_type
from .backend_utils import format_save_type, circuit_optypes

# Logger
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -332,26 +333,44 @@ def _format_results(output):

def _assemble(self, circuits, parameter_binds=None, **run_options):
"""Assemble one or more Qobj for running on the simulator"""
# Remove quantum error instructions from circuits and add to
# run option noise model
circuits, run_options = self._assemble_noise_model(circuits, **run_options)

# This conditional check can be removed when we remove passing
# qobj to run
if isinstance(circuits, (QasmQobj, PulseQobj)):
qobj = circuits
elif parameter_binds:
# Handle parameter binding
parameterizations = self._convert_binds(circuits, parameter_binds)
assemble_binds = []
assemble_binds.append({param: 1 for bind in parameter_binds for param in bind})

qobj = assemble(compile_circuit(circuits, self.configuration().basis_gates),
self,
parameter_binds=assemble_binds,
parameterizations=parameterizations)
else:
qobj = assemble(compile_circuit(circuits, self.configuration().basis_gates), self)
# Generate optypes for circuit
# Generate opsets of instructions
if isinstance(circuits, (QuantumCircuit, Schedule, ScheduleBlock)):
circuits = [circuits]
optypes = [circuit_optypes(circ) for circ in circuits]

# Compile Qasm3 instructions
circuits, optypes = compile_circuit(
circuits,
basis_gates=self.configuration().basis_gates,
optypes=optypes)

# run option noise model
circuits, optypes, run_options = self._assemble_noise_model(
circuits, optypes, **run_options)

if parameter_binds:
# Handle parameter binding
parameterizations = self._convert_binds(circuits, parameter_binds)
assemble_binds = []
assemble_binds.append({param: 1 for bind in parameter_binds for param in bind})

qobj = assemble(
circuits,
backend=self,
parameter_binds=assemble_binds,
parameterizations=parameterizations)
else:
qobj = assemble(circuits, backend=self)

# Add optypes to qobj
# We convert to strings to avoid pybinding of types
qobj.config.optypes = [
set(i.__name__ for i in optype) if optype else set()
for optype in optypes]

# Add options
for key, val in self.options.__dict__.items():
Expand All @@ -364,13 +383,13 @@ def _assemble(self, circuits, parameter_binds=None, **run_options):

return qobj

def _assemble_noise_model(self, circuits, **run_options):
def _assemble_noise_model(self, circuits, optypes, **run_options):
"""Move quantum error instructions from circuits to noise model"""
if isinstance(circuits, (QasmQobj, PulseQobj)):
return circuits, run_options

if isinstance(circuits, (QuantumCircuit, Schedule, ScheduleBlock)):
circuits = [circuits]
run_circuits = [circuits]
else:
# Make a shallow copy so we can modify list elements if required
run_circuits = copy.copy(circuits)

# Flag for if we need to make a deep copy of the noise model
# This avoids unnecessarily copying the noise model for circuits
Expand All @@ -379,24 +398,35 @@ def _assemble_noise_model(self, circuits, **run_options):
noise_model = run_options.get(
'noise_model', getattr(self.options, 'noise_model', None))

# Add custom pass noise only to QuantumCircuit objects
if noise_model and all(isinstance(circ, QuantumCircuit) for circ in circuits):
# Add custom pass noise only to QuantumCircuit objects that contain delay
# instructions since this is the only instruction handled by the noise pass
# at present
if noise_model and all(isinstance(circ, QuantumCircuit) for circ in run_circuits):
npm = noise_model._pass_manager()
if npm is not None:
circuits = npm.run(circuits)
if isinstance(circuits, QuantumCircuit):
circuits = [circuits]
# Get indicies of circuits that need noise transpiling
transpile_idxs = [
idx for idx, optype in enumerate(optypes) if Delay in optype
]

# Transpile only the required circuits
transpiled_circuits = npm.run([run_circuits[i] for i in transpile_idxs])
if isinstance(transpiled_circuits, QuantumCircuit):
transpiled_circuits = [transpiled_circuits]

# Update the circuits with transpiled ones
for idx, circ in zip(transpile_idxs, transpiled_circuits):
run_circuits[idx] = circ
optypes[idx] = circuit_optypes(circ)

# Check if circuits contain quantum error instructions
run_circuits = []
for circ in circuits:
if isinstance(circ, (Schedule, ScheduleBlock)):
run_circuits.append(circ)
else:
for idx, circ in enumerate(run_circuits):
if QuantumChannelInstruction in optypes[idx] and not isinstance(
circ, (Schedule, ScheduleBlock)):
updated_circ = False
new_data = []
for inst, qargs, cargs in circ.data:
if inst.name == "quantum_channel":
if isinstance(inst, QuantumChannelInstruction):
updated_circ = True
if not updated_noise:
# Deep copy noise model on first update
Expand All @@ -407,7 +437,9 @@ def _assemble_noise_model(self, circuits, **run_options):
updated_noise = True
# Extract error and replace with place holder
qerror = inst._quantum_error
new_data.append((QuantumErrorLocation(qerror), qargs, cargs))
qerror_loc = QuantumErrorLocation(qerror)
new_data.append((qerror_loc, qargs, cargs))
optypes[idx].add(QuantumErrorLocation)
# Add error to noise model
if qerror.id not in noise_model._default_quantum_errors:
noise_model.add_all_qubit_quantum_error(qerror, qerror.id)
Expand All @@ -416,16 +448,15 @@ def _assemble_noise_model(self, circuits, **run_options):
if updated_circ:
new_circ = circ.copy()
new_circ.data = new_data
run_circuits.append(new_circ)
else:
run_circuits.append(circ)
run_circuits[idx] = new_circ
optypes[idx].discard(QuantumChannelInstruction)

# If we modified the existing noise model, add it to the run options
if updated_noise:
run_options['noise_model'] = noise_model

# Return the possibly updated circuits and noise model
return run_circuits, run_options
return run_circuits, optypes, run_options

def _get_executor(self, **run_options):
"""Get the executor"""
Expand Down
11 changes: 11 additions & 0 deletions qiskit/providers/aer/backends/backend_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,14 @@ def func(data):
return {key: func(val) for key, val in data.items()}

return func(data)


def circuit_optypes(circuit):
"""Return set of all operation types and parent types in a circuit."""
if not isinstance(circuit, QuantumCircuit):
return set()
optypes = set()
for inst, _, _ in circuit._data:
optypes.update(type(inst).mro())
optypes.discard(object)
return optypes
31 changes: 20 additions & 11 deletions qiskit/providers/aer/jobs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,21 @@ def _split_qobj(qobj, max_size, qobj_id, seed):
return qobjs


def _check_custom_instruction(experiments):

# check if save instruction exist
for exp in experiments:
for inst in exp.instructions:
if "save" in inst.name:
return True
return False
def _check_custom_instruction(experiments, optypes=None):
"""Return True if circuits contain instructions that cant be split"""
# Check via optype list if available
if optypes is not None:
# Optypes store class names as strings
return any(
{"SaveData", "Snapshot"}.intersection(optype)
for optype in optypes
)

# Otherwise iterate over instruction names
return any(
"save_" in inst.name or "snapshot" in inst.name
for exp in experiments for inst in exp.instructions
)


def _set_seed(qobj_list, seed):
Expand Down Expand Up @@ -162,11 +169,13 @@ def split_qobj(qobj, max_size=None, max_shot_size=None, qobj_id=None):
Returns:
List: A list of qobjs.
"""
optypes = getattr(qobj.config, 'optypes', None)
split_qobj_list = []
if (max_shot_size is not None and max_shot_size > 0):
if _check_custom_instruction(qobj.experiments):
raise JobError("`max_shot_size` option cannot"
"be used with circuits containing save instructions.")
if _check_custom_instruction(qobj.experiments, optypes):
raise JobError(
"`max_shot_size` option cannot be used with circuits"
" containing save or snapshot instructions.")

_seed = getattr(qobj.config, "seed_simulator", 0)
if hasattr(qobj.config, "noise_model"):
Expand Down
6 changes: 2 additions & 4 deletions qiskit/providers/aer/noise/errors/quantum_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.library.generalized_gates import PauliGate
from qiskit.circuit.library.standard_gates import IGate
from qiskit.compiler import assemble
from qiskit.exceptions import QiskitError
from qiskit.extensions import UnitaryGate
from qiskit.quantum_info.operators.base_operator import BaseOperator
Expand Down Expand Up @@ -453,13 +452,12 @@ def error_term(self, position):

def to_dict(self):
"""Return the current error as a dictionary."""
qobj = assemble(self.circuits)
instructions = [exp.to_dict()['instructions'] for exp in qobj.experiments]
error = {
"type": "qerror",
"id": self.id,
"operations": [],
"instructions": instructions,
"instructions": [[op[0].assemble().to_dict() for op in circ.data]
for circ in self._circs],
"probabilities": list(self.probabilities)
}
return error
Expand Down
Loading

0 comments on commit 04ab1d5

Please sign in to comment.