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

Simulate QuantumCircuit without QObj #1717

Merged
merged 33 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0993b72
add AerCircuit and a path to run simulation without QObj
hhorii Feb 7, 2023
4111ce8
Remove AerOp from AerCircuit
hhorii Feb 8, 2023
d83ea88
Merge remote-tracking branch 'upstream/main' into add_own_assemble
hhorii Feb 13, 2023
3c0e6cc
fix lint errors
hhorii Feb 13, 2023
2223888
revert qobj deprecation
hhorii Feb 13, 2023
c9585f5
fix executor specification in AerBackend
hhorii Feb 13, 2023
b0f66dc
fix bugs in direct execution
hhorii Feb 13, 2023
d26aeb5
set final operation in AerCircuit
hhorii Feb 14, 2023
76ed979
fix lint errors
hhorii Feb 14, 2023
922c6af
use AerJob and add reno
hhorii Feb 14, 2023
87a99fb
support directly-added qubits
hhorii Feb 14, 2023
f156b90
fix qobj execution
hhorii Feb 14, 2023
a0014ff
fix qobj execution, contd.
hhorii Feb 14, 2023
a60363b
fix set state with a matrix
hhorii Feb 14, 2023
cc735ac
inline malloc in matrix
hhorii Feb 14, 2023
184380a
rename malloc_array in AER::Vector to resolve conflicts of signature
hhorii Feb 14, 2023
eb1d739
rename methods to handle QuantumCircuit in AerBackend and deprecate A…
hhorii Feb 21, 2023
4eef01b
simplify AerCircuit
hhorii Feb 22, 2023
5b537f7
avoid pickling of AerCircuit in assemble_circuits
hhorii Feb 22, 2023
0745eb8
back _execute_circuits as abstract method
hhorii Feb 22, 2023
ea586c5
Merge remote-tracking branch 'upstream/main' into add_own_assemble
hhorii Feb 22, 2023
48dc673
Merge branch 'main' into add_own_assemble
hhorii Feb 23, 2023
46e1da1
introduce AER::Config to reduce JSON overheads for configuration
hhorii Feb 27, 2023
7d95adb
Merge remote-tracking branch 'upstream/main' into add_own_assemble
hhorii Feb 27, 2023
1137082
fix a bug in get_value to load config
hhorii Feb 27, 2023
a6ee7c3
enable AerConfig
hhorii Feb 27, 2023
a68df4c
fix lint
hhorii Feb 27, 2023
2b1c077
deprecate backend.run(validate=True) and clean codes in AerBackend
hhorii Mar 1, 2023
8f9629a
Merge branch 'main' into add_own_assemble
hhorii Mar 1, 2023
ff7deba
add assertion in test_auto_method
hhorii Mar 7, 2023
4748360
fix bugs in last commits
hhorii Mar 7, 2023
e75676d
correct test cases to test auto option
hhorii Mar 7, 2023
5827b7d
Merge branch 'main' into add_own_assemble
hhorii Mar 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 226 additions & 1 deletion qiskit_aer/backends/aer_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
"""

import itertools
from copy import copy
from typing import List

from qiskit.circuit import QuantumCircuit, Clbit
from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression
from qiskit.extensions import Initialize
from qiskit.providers.options import Options
from qiskit.pulse import Schedule, ScheduleBlock
from qiskit.circuit.controlflow import (
WhileLoopOp,
Expand All @@ -25,6 +28,10 @@
BreakLoopOp,
ContinueLoopOp)
from qiskit.compiler import transpile
from qiskit.qobj import QobjExperimentHeader
from qiskit_aer.aererror import AerError
# pylint: disable=import-error, no-name-in-module
from qiskit_aer.backends.controller_wrappers import AerCircuit, AerConfig
from .backend_utils import circuit_optypes
from ..library.control_flow_instructions import AerMark, AerJump

Expand Down Expand Up @@ -325,3 +332,221 @@ def compile_circuit(circuits, basis_gates=None, optypes=None):
compile a circuit that have control-flow instructions
"""
return AerCompiler().compile(circuits, basis_gates, optypes)


def generate_aer_config(
circuits: List[QuantumCircuit],
backend_options: Options,
**run_options
) -> AerConfig:
"""generates a configuration to run simulation.

Args:
circuits: circuit(s) to be converted
backend_options: backend options
run_options: run options

Returns:
AerConfig to run Aer
"""
num_qubits = max(circuit.num_qubits for circuit in circuits)
memory_slots = max(circuit.num_clbits for circuit in circuits)

config = AerConfig()
config.memory_slots = memory_slots
config.n_qubits = num_qubits
Comment on lines +356 to +357
Copy link
Member

Choose a reason for hiding this comment

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

Not that it really matters since this class really isn't user facing. I'm surprised you didn't just add these to the constructor for AerConfig

for key, value in backend_options.__dict__.items():
Copy link
Member

Choose a reason for hiding this comment

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

We should really have an API for doing this natively in the Options class so we can either do iter(options) or options.items() directly. I'll open an issue on terra to add this.

Copy link
Member

Choose a reason for hiding this comment

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

if hasattr(config, key) and value is not None:
setattr(config, key, value)
for key, value in run_options.items():
if hasattr(config, key) and value is not None:
setattr(config, key, value)
return config


def assemble_circuit(circuit: QuantumCircuit):
"""assemble circuit object mapped to AER::Circuit"""

num_qubits = circuit.num_qubits
num_memory = circuit.num_clbits
max_conditional_idx = 0

qreg_sizes = []
creg_sizes = []
global_phase = float(circuit.global_phase)

for qreg in circuit.qregs:
qreg_sizes.append([qreg.name, qreg.size])
for creg in circuit.cregs:
creg_sizes.append([creg.name, creg.size])

is_conditional = any(
getattr(inst.operation, "condition", None) for inst in circuit.data
)

header = QobjExperimentHeader(
n_qubits=num_qubits,
qreg_sizes=qreg_sizes,
memory_slots=num_memory,
creg_sizes=creg_sizes,
name=circuit.name,
global_phase=global_phase,
)

if circuit.metadata is not None:
header.metadata = circuit.metadata

qubit_indices = {qubit: idx for idx, qubit in enumerate(circuit.qubits)}
clbit_indices = {clbit: idx for idx, clbit in enumerate(circuit.clbits)}

aer_circ = AerCircuit()
aer_circ.set_header(header)
aer_circ.num_qubits = num_qubits
aer_circ.num_memory = num_memory
aer_circ.global_phase_angle = global_phase

for inst in circuit.data:
# To convert to a qobj-style conditional, insert a bfunc prior
# to the conditional instruction to map the creg ?= val condition
# onto a gating register bit.
conditional_reg = -1
if hasattr(inst.operation, "condition") and inst.operation.condition:
ctrl_reg, ctrl_val = inst.operation.condition
mask = 0
val = 0
if isinstance(ctrl_reg, Clbit):
mask = 1 << clbit_indices[ctrl_reg]
val = (ctrl_val & 1) << clbit_indices[ctrl_reg]
else:
for clbit, idx in clbit_indices.items():
if clbit in ctrl_reg:
mask |= 1 << idx
val |= ((ctrl_val >> list(ctrl_reg).index(clbit)) & 1) << idx
conditional_reg = num_memory + max_conditional_idx
aer_circ.bfunc(f"0x{mask:X}", f"0x{val:X}", "==", conditional_reg)
max_conditional_idx += 1

_assemble_op(aer_circ, inst, qubit_indices, clbit_indices,
is_conditional, conditional_reg)

return aer_circ


def _assemble_op(aer_circ, inst, qubit_indices, clbit_indices, is_conditional, conditional_reg):
operation = inst.operation
qubits = [qubit_indices[qubit] for qubit in inst.qubits]
clbits = [clbit_indices[clbit] for clbit in inst.clbits]
name = operation.name
label = operation.label
params = operation.params if hasattr(operation, "params") else None
copied = False

for i, param in enumerate(params):
if (isinstance(param, ParameterExpression) and len(param.parameters) > 0):
if not copied:
params = copy(params)
copied = True
params[i] = 0.0

if name in {'ccx', 'ccz', 'cp', 'cswap', 'csx', 'cx', 'cy', 'cz', 'delay', 'ecr',
'h', 'id', 'mcp', 'mcphase', 'mcr', 'mcrx', 'mcry', 'mcrz', 'mcswap',
'mcsx', 'mcu', 'mcu1', 'mcu2', 'mcu3', 'mcx', 'mcx_gray', 'mcy', 'mcz',
'p', 'r', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzx', 'rzz', 's', 'sdg', 'swap',
'sx', 'sxdg', 't', 'tdg', 'u', 'x', 'y', 'z', 'u1', 'u2', 'u3',
'cu', 'cu1', 'cu2', 'cu3'}:
aer_circ.gate(name, qubits, params, [], conditional_reg, label if label else name)
elif name == 'measure':
if is_conditional:
aer_circ.measure(qubits, clbits, clbits)
else:
aer_circ.measure(qubits, clbits, [])
elif name == 'reset':
aer_circ.reset(qubits)
elif name == 'diagonal':
aer_circ.diagonal(qubits, params, label if label else 'diagonal')
elif name == 'unitary':
hhorii marked this conversation as resolved.
Show resolved Hide resolved
aer_circ.unitary(qubits, params[0], conditional_reg, label if label else 'unitary')
elif name == 'pauli':
aer_circ.gate(name, qubits, [], params, conditional_reg, label if label else name)
elif name == 'initialize':
aer_circ.initialize(qubits, params)
elif name == 'roerror':
aer_circ.roerror(qubits, params)
elif name == 'multiplexer':
aer_circ.multiplexer(qubits, params, conditional_reg, label if label else name)
elif name == 'kraus':
aer_circ.kraus(qubits, params, conditional_reg)
elif name in {'save_statevector', 'save_statevector_dict', 'save_clifford',
'save_probabilities', 'save_probabilities_dict', 'save_matrix_product_state',
'save_unitary', 'save_superop', 'save_density_matrix', 'save_state',
'save_stabilizer'}:
aer_circ.save_state(qubits, name, operation._subtype, label if label else name)
elif name in {'save_amplitudes', 'save_amplitudes_sq'}:
aer_circ.save_amplitudes(qubits, name, params, operation._subtype,
label if label else name)
elif name in ('save_expval', 'save_expval_var'):
paulis = []
coeff_reals = []
coeff_imags = []
for pauli, coeff in operation.params:
paulis.append(pauli)
coeff_reals.append(coeff[0])
coeff_imags.append(coeff[1])
aer_circ.save_expval(qubits, name, paulis, coeff_reals, coeff_imags, operation._subtype,
label if label else name)
elif name == 'set_statevector':
aer_circ.set_statevector(qubits, params)
elif name == 'set_unitary':
aer_circ.set_unitary(qubits, params)
elif name == 'set_density_matrix':
aer_circ.set_density_matrix(qubits, params)
elif name == 'set_stabilizer':
aer_circ.set_clifford(qubits, params)
elif name == 'set_superop':
aer_circ.set_superop(qubits, params)
elif name == 'set_matrix_product_state':
aer_circ.set_matrix_product_state(qubits, params)
elif name == 'superop':
aer_circ.superop(qubits, params[0], conditional_reg)
elif name == 'barrier':
pass
elif name == 'jump':
aer_circ.jump(qubits, params, conditional_reg)
elif name == 'mark':
aer_circ.mark(qubits, params)
elif name == 'qerror_loc':
aer_circ.set_qerror_loc(qubits, label if label else name, conditional_reg)
elif name in ('for_loop', 'while_loop', 'if_else'):
raise AerError('control-flow instructions must be converted '
f'to jump and mark instructions: {name}')

else:
raise AerError(f'unknown instruction: {name}')


def assemble_circuits(
circuits: List[QuantumCircuit]
) -> List[AerCircuit]:
"""converts a list of Qiskit circuits into circuits mapped AER::Circuit

Args:
circuits: circuit(s) to be converted

Returns:
circuits to be run on the Aer backends

Examples:

.. code-block:: python

from qiskit.circuit import QuantumCircuit
from qiskit_aer.backends.aer_compiler import assemble_circuits
# Create a circuit to be simulated
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Generate AerCircuit from the input circuit
aer_qc_list = assemble_circuits(circuits=[qc])
"""
return [assemble_circuit(circuit) for circuit in circuits]
22 changes: 14 additions & 8 deletions qiskit_aer/backends/aer_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

from ..version import __version__
from .aerbackend import AerBackend, AerError
from .backend_utils import (cpp_execute, available_methods,
available_devices,
from .backend_utils import (cpp_execute_circuits, cpp_execute_qobj,
available_methods, available_devices,
MAX_QUBITS_STATEVECTOR,
BASIS_GATES)
# pylint: disable=import-error, no-name-in-module
Expand Down Expand Up @@ -642,7 +642,7 @@ def __repr__(self):
if noise_model is None or noise_model.is_ideal():
return display
pad = ' ' * (len(self.__class__.__name__) + 1)
return '{}\n{}noise_model={})'.format(display[:-1], pad, repr(noise_model))
return f'{display[:-1]}\n{pad}noise_model={repr(noise_model)})'

def name(self):
"""Format backend name string for simulator"""
Expand Down Expand Up @@ -682,7 +682,7 @@ def from_backend(cls, backend, **options):

# Customize configuration name
name = configuration.backend_name
configuration.backend_name = 'aer_simulator({})'.format(name)
configuration.backend_name = f'aer_simulator({name})'
else:
raise TypeError(
"The backend argument requires a BackendV2 or BackendV1 object, "
Expand Down Expand Up @@ -730,7 +730,13 @@ def configuration(self):
config.backend_name = self.name()
return config

def _execute(self, qobj):
def _execute_circuits(self, aer_circuits, noise_model, config):
"""Execute circuits on the backend.
"""
ret = cpp_execute_circuits(self._controller, aer_circuits, noise_model, config)
return ret

def _execute_qobj(self, qobj):
"""Execute a qobj on the backend.

Args:
Expand All @@ -739,7 +745,7 @@ def _execute(self, qobj):
Returns:
dict: return a dictionary of results.
"""
return cpp_execute(self._controller, qobj)
return cpp_execute_qobj(self._controller, qobj)

def set_option(self, key, value):
if key == "custom_instructions":
Expand All @@ -748,8 +754,8 @@ def set_option(self, key, value):
if key == "method":
if (value is not None and value not in self.available_methods()):
raise AerError(
"Invalid simulation method {}. Available methods"
" are: {}".format(value, self.available_methods()))
f"Invalid simulation method {value}. Available methods"
f" are: {self.available_methods()}")
self._set_method_config(value)
super().set_option(key, value)
if key in ["method", "noise_model", "basis_gates"]:
Expand Down
Loading