diff --git a/docs/conf.py b/docs/conf.py index e7d1245fcd..42e2a3ea0d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 --------------------------------------------------- diff --git a/qiskit/providers/aer/VERSION.txt b/qiskit/providers/aer/VERSION.txt index 78bc1abd14..571215736a 100644 --- a/qiskit/providers/aer/VERSION.txt +++ b/qiskit/providers/aer/VERSION.txt @@ -1 +1 @@ -0.10.0 +0.10.1 diff --git a/qiskit/providers/aer/backends/aer_compiler.py b/qiskit/providers/aer/backends/aer_compiler.py index 9bcce4ef23..cfcc966d8a 100644 --- a/qiskit/providers/aer/backends/aer_compiler.py +++ b/qiskit/providers/aer/backends/aer_compiler.py @@ -13,6 +13,7 @@ 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, @@ -20,6 +21,7 @@ BreakLoopOp, ContinueLoopOp) from qiskit.compiler import transpile +from .backend_utils import circuit_optypes from ..library.control_flow_instructions import AerMark, AerJump @@ -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): @@ -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) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 7c84b7d2f1..cc219e9fcb 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -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 @@ -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__) @@ -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(): @@ -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 @@ -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 @@ -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) @@ -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""" diff --git a/qiskit/providers/aer/backends/backend_utils.py b/qiskit/providers/aer/backends/backend_utils.py index db44c0ec5b..110ed4383d 100644 --- a/qiskit/providers/aer/backends/backend_utils.py +++ b/qiskit/providers/aer/backends/backend_utils.py @@ -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 diff --git a/qiskit/providers/aer/jobs/utils.py b/qiskit/providers/aer/jobs/utils.py index cae41eb878..be5a4e8228 100644 --- a/qiskit/providers/aer/jobs/utils.py +++ b/qiskit/providers/aer/jobs/utils.py @@ -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): @@ -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"): diff --git a/qiskit/providers/aer/noise/errors/quantum_error.py b/qiskit/providers/aer/noise/errors/quantum_error.py index ba36c5f74f..7465633598 100644 --- a/qiskit/providers/aer/noise/errors/quantum_error.py +++ b/qiskit/providers/aer/noise/errors/quantum_error.py @@ -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 @@ -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 diff --git a/qiskit/providers/aer/noise/noise_model.py b/qiskit/providers/aer/noise/noise_model.py index c9391afdb7..6bcf58d265 100644 --- a/qiskit/providers/aer/noise/noise_model.py +++ b/qiskit/providers/aer/noise/noise_model.py @@ -22,6 +22,7 @@ from qiskit.circuit import Instruction, Delay from qiskit.providers import BaseBackend, BackendV1, BackendV2 +from qiskit.providers.exceptions import BackendPropertyError from qiskit.providers.models import BackendProperties from qiskit.transpiler import PassManager from .device.models import _excited_population @@ -361,24 +362,30 @@ def from_backend(cls, backend, warnings=warnings) for name, qubits, error in gate_errors: noise_model.add_quantum_error(error, name, qubits, warnings=warnings) - # Add delay errors - if thermal_relaxation and hasattr(properties, "t1") and hasattr(properties, "t2"): - if hasattr(properties, "frequency"): + + if thermal_relaxation: + # Add delay errors via RelaxationNiose pass + try: excited_state_populations = [ _excited_population( freq=properties.frequency(q), temperature=temperature ) for q in range(num_qubits)] - else: + except BackendPropertyError: excited_state_populations = None + try: + delay_pass = RelaxationNoisePass( + t1s=[properties.t1(q) for q in range(num_qubits)], + t2s=[properties.t2(q) for q in range(num_qubits)], + dt=dt, + op_types=Delay, + excited_state_populations=excited_state_populations + ) + noise_model._custom_noise_passes.append(delay_pass) + except BackendPropertyError: + # Device does not have the required T1 or T2 information + # in its properties + pass - delay_pass = RelaxationNoisePass( - t1s=[properties.t1(q) for q in range(num_qubits)], - t2s=[properties.t2(q) for q in range(num_qubits)], - dt=dt, - op_types=Delay, - excited_state_populations=excited_state_populations - ) - noise_model._custom_noise_passes.append(delay_pass) return noise_model def is_ideal(self): # pylint: disable=too-many-return-statements diff --git a/releasenotes/notes/0.10/0-10-1-release-6338690271374e16.yaml b/releasenotes/notes/0.10/0-10-1-release-6338690271374e16.yaml new file mode 100644 index 0000000000..4a3bc27da4 --- /dev/null +++ b/releasenotes/notes/0.10/0-10-1-release-6338690271374e16.yaml @@ -0,0 +1,11 @@ +--- +prelude: > + The Qiskit Aer 0.10.1 patch fixes performance regressions introduced in + Qiskit Aer 0.10.0. +fixes: + - | + Fix performance regression in noisy simulations due to large increase in + serialization overhead for loading noise models from Python into C++ + resulting from unintended nested Python multiprocessing calls. + See `issue 1407 `__ + for details. diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 0a16edc3c7..2f1f35639f 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -332,8 +332,6 @@ class Controller { } return max_memory_mb_; } - // Truncate and remap input qubits - bool enable_truncation_ = true; // The maximum number of threads to use for various levels of parallelization int max_parallel_threads_; @@ -390,8 +388,6 @@ class Controller { //------------------------------------------------------------------------- void Controller::set_config(const json_t &config) { - // Load validation threshold - JSON::get_value(enable_truncation_, "enable_truncation", config); // Load validation threshold JSON::get_value(validation_threshold_, "validation_threshold", config); @@ -846,37 +842,31 @@ Result Controller::execute(const inputdata_t &input_qobj) { // Start QOBJ timer auto timer_start = myclock_t::now(); - Noise::NoiseModel noise_model; - json_t config; + // Initialize QOBJ + Qobj qobj(input_qobj); + auto qobj_time_taken = + std::chrono::duration(myclock_t::now() - timer_start).count(); - // Check for config - if (Parser::get_value(config, "config", input_qobj)) { - // Set config - set_config(config); - // Load noise model - Parser::get_value(noise_model, "noise_model", config); - } + // Set config + set_config(qobj.config); - // Initialize qobj - Qobj qobj; - if (noise_model.has_nonlocal_quantum_errors()) { - // Non-local noise does not work with optimized initialization - qobj = Qobj(input_qobj, false); - } else { - qobj = Qobj(input_qobj, enable_truncation_); - } + // Run qobj circuits + auto result = execute(qobj.circuits, qobj.noise_model, qobj.config); + + // Add QOBJ loading time + result.metadata.add(qobj_time_taken, "time_taken_load_qobj"); - auto result = execute(qobj.circuits, noise_model, config); // Get QOBJ id and pass through header to result result.qobj_id = qobj.id; if (!qobj.header.empty()) { result.header = qobj.header; } + // Stop the timer and add total timing data including qobj parsing - auto timer_stop = myclock_t::now(); auto time_taken = - std::chrono::duration(timer_stop - timer_start).count(); + std::chrono::duration(myclock_t::now() - timer_start).count(); result.metadata.add(time_taken, "time_taken"); + return result; } catch (std::exception &e) { // qobj was invalid, return valid output containing error message @@ -1007,7 +997,7 @@ Result Controller::execute(std::vector &circuits, auto timer_stop = myclock_t::now(); auto time_taken = std::chrono::duration(timer_stop - timer_start).count(); - result.metadata.add(time_taken, "time_taken"); + result.metadata.add(time_taken, "time_taken_execute"); } // If execution failed return valid output reporting error catch (std::exception &e) { diff --git a/src/framework/qobj.hpp b/src/framework/qobj.hpp index acec07f286..f470e0a776 100755 --- a/src/framework/qobj.hpp +++ b/src/framework/qobj.hpp @@ -21,6 +21,7 @@ #include #include "framework/circuit.hpp" +#include "noise/noise_model.hpp" namespace AER { @@ -40,7 +41,7 @@ class Qobj { // Deserialization constructor template - Qobj(const inputdata_t &input, bool truncation = false); + Qobj(const inputdata_t &input); //---------------------------------------------------------------- // Data @@ -50,6 +51,7 @@ class Qobj { std::vector circuits; // List of circuits json_t header; // (optional) passed through to result json_t config; // (optional) qobj level config data + Noise::NoiseModel noise_model; // (optional) noise model }; //============================================================================ @@ -60,7 +62,7 @@ class Qobj { inline void from_json(const json_t &js, Qobj &qobj) { qobj = Qobj(js); } template -Qobj::Qobj(const inputdata_t &input, bool truncation) { +Qobj::Qobj(const inputdata_t &input) { // Check required fields if (Parser::get_value(id, "qobj_id", input) == false) { throw std::invalid_argument(R"(Invalid qobj: no "qobj_id" field)"); @@ -73,9 +75,29 @@ Qobj::Qobj(const inputdata_t &input, bool truncation) { throw std::invalid_argument(R"(Invalid qobj: no "experiments" field.)"); } - // Get header and config; - Parser::get_value(config, "config", input); - Parser::get_value(header, "header", input); + // Apply qubit truncation + bool truncation = true; + + // Parse config + if (Parser::get_value(config, "config", input)) { + // Check for truncation option + Parser::get_value(truncation, "enable_truncation", config); + + // Load noise model + if (Parser::get_value(noise_model, "noise_model", config)) { + // If noise model has non-local errors disable trunction + if (noise_model.has_nonlocal_quantum_errors()) { + truncation = false; + } + } + } else { + config = json_t::object(); + } + + // Parse header + if (!Parser::get_value(header, "header", input)) { + header = json_t::object(); + } // Check for fixed simulator seed // If simulator seed is set, each experiment will be set to a fixed (but different) seed diff --git a/src/simulators/state_chunk.hpp b/src/simulators/state_chunk.hpp index 60fccccb9d..20ac3b8657 100644 --- a/src/simulators/state_chunk.hpp +++ b/src/simulators/state_chunk.hpp @@ -409,6 +409,15 @@ class StateChunk : public State { ExperimentResult &result, RngEngine &rng); + //apply ops for multi-shots to one group + template + void apply_ops_multi_shots_for_group(int_t i_group, + InputIterator first, InputIterator last, + const Noise::NoiseModel &noise, + ExperimentResult &result, + uint_t rng_seed, + bool final_ops); + //apply op to multiple shots , return flase if op is not supported to execute in a batch virtual bool apply_batched_op(const int_t iChunk, const Operations::Op &op, ExperimentResult &result, @@ -859,84 +868,57 @@ void StateChunk::apply_ops_multi_shots(InputIterator first, InputIterat //resize qregs allocate_qregs(n_shots); } - std::vector par_results(num_groups_); //initialization (equivalent to initialize_qreg + initialize_creg) -#pragma omp parallel for if(num_groups_ > 1) - for(i=0;i 1 && chunk_omp_parallel_){ +#pragma omp parallel for + for(i=0;i 1) - for(i=0;i rng(num_chunks_in_group_[i]); - - for(uint_t j=top_chunk_of_group_[i];jtype == Operations::OpType::qerror_loc){ - //sample error here - uint_t count = num_chunks_in_group_[i]; - uint_t max_ops = 0; - bool pauli_only = true; - std::vector> noise_ops(count); - for(uint_t j=0;j 1 && chunk_omp_parallel_){ + std::vector par_results(num_groups_); +#pragma omp parallel for + for(i=0;i::apply_ops_multi_shots(InputIterator first, InputIterat gather_creg_memory(); } +template +template +void StateChunk::apply_ops_multi_shots_for_group(int_t i_group, + InputIterator first, InputIterator last, + const Noise::NoiseModel &noise, + ExperimentResult &result, + uint_t rng_seed, + bool final_ops) +{ + uint_t istate = top_chunk_of_group_[i_group]; + std::vector rng(num_chunks_in_group_[i_group]); + + for(uint_t j=top_chunk_of_group_[i_group];jtype == Operations::OpType::qerror_loc){ + //sample error here + uint_t count = num_chunks_in_group_[i_group]; + uint_t max_ops = 0; + bool pauli_only = true; + std::vector> noise_ops(count); + for(uint_t j=0;j void StateChunk::apply_batched_noise_ops(const int_t i_group, const std::vector> &ops, ExperimentResult &result, diff --git a/src/simulators/statevector/qubitvector_thrust.hpp b/src/simulators/statevector/qubitvector_thrust.hpp index f5e9e22ebc..fdbbbbe2b5 100644 --- a/src/simulators/statevector/qubitvector_thrust.hpp +++ b/src/simulators/statevector/qubitvector_thrust.hpp @@ -5049,6 +5049,15 @@ void QubitVectorThrust::apply_batched_pauli_ops(const std::vector(x_max, (ops[i][j].qubits[0])); num_y++; } + else if(ops[i][j].name == "pauli"){ + uint_t pauli_x_mask = 0, pauli_z_mask = 0, pauli_num_y = 0, pauli_x_max = 0; + std::tie(pauli_x_mask, pauli_z_mask, pauli_num_y, pauli_x_max) = pauli_masks_and_phase(ops[i][j].qubits, ops[i][j].string_params[0]); + + x_mask ^= pauli_x_mask; + z_mask ^= pauli_z_mask; + x_max = std::max(x_max, pauli_x_max); + num_y += pauli_num_y; + } } params[i*4] = x_max; params[i*4+1] = num_y % 4; diff --git a/test/terra/backends/aer_simulator/test_noise.py b/test/terra/backends/aer_simulator/test_noise.py index b792faa204..676fc4189b 100644 --- a/test/terra/backends/aer_simulator/test_noise.py +++ b/test/terra/backends/aer_simulator/test_noise.py @@ -17,9 +17,11 @@ from qiskit.providers.aer import noise import qiskit.quantum_info as qi +from qiskit import transpile from qiskit.circuit import QuantumCircuit, Reset from qiskit.circuit.library import QFT from qiskit.circuit.library.standard_gates import IGate, HGate +from qiskit.quantum_info.states.densitymatrix import DensityMatrix from test.terra.backends.simulator_test_case import ( SimulatorTestCase, supported_methods) from test.terra.reference import ref_kraus_noise @@ -156,15 +158,35 @@ def test_kraus_gate_noise(self, method, device): def test_kraus_gate_noise_on_QFT(self, method, device): """Test Kraus noise on a QFT circuit""" shots = 10000 - noise_model = ref_kraus_noise.kraus_gate_error_noise_models_full() + + # Build noise model + error1 = noise.amplitude_damping_error(0.2) + error2 = error1.tensor(error1) + noise_model = noise.NoiseModel() + noise_model.add_all_qubit_quantum_error(error1, ['h']) + noise_model.add_all_qubit_quantum_error(error2, ['cp', 'swap']) + backend = self.backend( method=method, device=device, noise_model=noise_model) - circuit = QFT(3).decompose() - circuit.measure_all() - ref_target = ref_kraus_noise.kraus_gate_error_counts_on_QFT(shots) - result = backend.run(circuit, shots=shots).result() + ideal_circuit = transpile(QFT(3), backend) + + # manaully build noise circuit + noise_circuit = QuantumCircuit(3) + for inst, qargs, cargs in ideal_circuit.data: + noise_circuit.append(inst, qargs, cargs) + if inst.name == "h": + noise_circuit.append(error1, qargs) + elif inst.name in ["cp", "swap"]: + noise_circuit.append(error2, qargs) + # compute target counts + noise_state = DensityMatrix(noise_circuit) + ref_target = {i: shots * p for i, p in noise_state.probabilities_dict().items()} + + # Run sim + ideal_circuit.measure_all() + result = backend.run(ideal_circuit, shots=shots).result() self.assertSuccess(result) - self.compare_counts(result, [circuit], [ref_target], delta=0.1 * shots) + self.compare_counts(result, [ideal_circuit], [ref_target], hex_counts=False, delta=0.1 * shots) @supported_methods(ALL_METHODS) def test_clifford_circuit_noise(self, method, device): diff --git a/test/terra/backends/aer_simulator/test_truncate.py b/test/terra/backends/aer_simulator/test_truncate.py index ad82e14697..0119b058a1 100644 --- a/test/terra/backends/aer_simulator/test_truncate.py +++ b/test/terra/backends/aer_simulator/test_truncate.py @@ -40,7 +40,7 @@ def create_circuit_for_truncate(self): circuit.measure(1, 1) return circuit - def deveice_backend(self): + def device_backend(self): return mock.FakeQuito() def test_truncate_ideal_sparse_circuit(self): @@ -65,7 +65,7 @@ def test_truncate_default(self): [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 0] ] - noise_model = NoiseModel.from_backend(self.deveice_backend()) + noise_model = NoiseModel.from_backend(self.device_backend()) backend = self.backend(noise_model=noise_model) circuit = transpile( self.create_circuit_for_truncate(), @@ -78,7 +78,7 @@ def test_truncate_default(self): def test_truncate_non_measured_qubits(self): """Test truncation of non-measured uncoupled qubits.""" - noise_model = NoiseModel.from_backend(self.deveice_backend()) + noise_model = NoiseModel.from_backend(self.device_backend()) backend = self.backend(noise_model=noise_model) circuit = transpile( self.create_circuit_for_truncate(), @@ -95,7 +95,7 @@ def test_truncate_disable_noise(self): [0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 0] ] - noise_model = NoiseModel.from_backend(self.deveice_backend()) + noise_model = NoiseModel.from_backend(self.device_backend()) backend = self.backend(noise_model=noise_model, enable_truncation=False) circuit = transpile( self.create_circuit_for_truncate(), diff --git a/test/terra/noise/test_quantum_error.py b/test/terra/noise/test_quantum_error.py index 0cbd27b4a2..0bedf23b1b 100644 --- a/test/terra/noise/test_quantum_error.py +++ b/test/terra/noise/test_quantum_error.py @@ -283,8 +283,8 @@ def depol_error(self, param, **kwargs): ) @staticmethod - def aslist(qargs): - return [q.index for q in qargs] + def qubits_list(circ, qargs): + return [circ.find_bit(q).index for q in qargs] @ddt.data( ('id', np.eye(2)), @@ -384,7 +384,7 @@ def test_pauli_conversion_standard_gates(self): instr, _ = error.error_term(j) self.assertEqual(len(instr), 1) self.assertIn(instr[0][0].name, ['x', 'y', 'z', 'id']) - self.assertEqual(self.aslist(instr[0][1]), [0]) + self.assertEqual(self.qubits_list(instr, instr[0][1]), [0]) target = SuperOp(kraus) self.assertEqual(target, SuperOp(error)) @@ -398,7 +398,7 @@ def test_tensor_both_kraus(self): circ, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(circ[0][0].name, 'kraus') - self.assertEqual(self.aslist(circ.qubits), [0, 1]) + self.assertEqual(self.qubits_list(circ, circ.qubits), [0, 1]) self.assertEqual(target, SuperOp(error), msg="Incorrect tensor kraus") def test_tensor_both_unitary_standard_gates(self): @@ -417,7 +417,7 @@ def test_tensor_both_unitary_standard_gates(self): self.assertEqual(len(circ), 2) for instr, qargs, _ in circ: self.assertIn(instr.name, ['s', 'x', 'y', 'z']) - self.assertIn(self.aslist(qargs), [[0], [1]]) + self.assertIn(self.qubits_list(circ, qargs), [[0], [1]]) self.assertEqual(SuperOp(error), target) def test_tensor_kraus_and_unitary(self): @@ -428,7 +428,7 @@ def test_tensor_kraus_and_unitary(self): target = SuperOp(kraus).tensor(kraus_unitaries) circ, prob = error.error_term(0) - self.assertEqual(self.aslist(circ.qubits), [0, 1]) + self.assertEqual(self.qubits_list(circ, circ.qubits), [0, 1]) self.assertEqual(target, SuperOp(error)) def test_tensor_unitary_and_kraus(self): @@ -439,7 +439,7 @@ def test_tensor_unitary_and_kraus(self): target = SuperOp(kraus_unitaries).tensor(kraus) circ, prob = error.error_term(0) - self.assertEqual(self.aslist(circ.qubits), [0, 1]) + self.assertEqual(self.qubits_list(circ, circ.qubits), [0, 1]) self.assertEqual(target, SuperOp(error)) def test_expand_both_kraus(self): @@ -453,8 +453,8 @@ def test_expand_both_kraus(self): self.assertEqual(prob, 1) self.assertEqual(circ[0][0].name, 'kraus') self.assertEqual(circ[1][0].name, 'kraus') - self.assertEqual(self.aslist(circ[0][1]), [0]) - self.assertEqual(self.aslist(circ[1][1]), [1]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[1][1]), [1]) self.assertEqual(target, SuperOp(error)) def test_expand_both_unitary_standard_gates(self): @@ -473,7 +473,7 @@ def test_expand_both_unitary_standard_gates(self): self.assertEqual(len(circ), 2) for instr, qargs, _ in circ: self.assertIn(instr.name, ['s', 'x', 'y', 'z']) - self.assertIn(self.aslist(qargs), [[0], [1]]) + self.assertIn(self.qubits_list(circ, qargs), [[0], [1]]) self.assertEqual(SuperOp(error), target) def test_expand_kraus_and_unitary(self): @@ -486,7 +486,7 @@ def test_expand_kraus_and_unitary(self): circ, prob = error.error_term(0) # self.assertEqual(prob, 1) self.assertEqual(circ[0][0].name, 'kraus') - self.assertEqual(self.aslist(circ.qubits), [0, 1]) + self.assertEqual(self.qubits_list(circ, circ.qubits), [0, 1]) self.assertEqual(target, SuperOp(error)) def test_expand_unitary_and_kraus(self): @@ -497,7 +497,7 @@ def test_expand_unitary_and_kraus(self): target = SuperOp(kraus_unitaries).expand(kraus) circ, prob = error.error_term(0) - self.assertEqual(self.aslist(circ.qubits), [0, 1]) + self.assertEqual(self.qubits_list(circ, circ.qubits), [0, 1]) self.assertEqual(target, SuperOp(error)) def test_compose_both_kraus(self): @@ -510,7 +510,7 @@ def test_compose_both_kraus(self): kraus, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(kraus[0][0].name, 'kraus') - self.assertEqual(self.aslist(kraus[0][1]), [0]) + self.assertEqual(self.qubits_list(kraus, kraus[0][1]), [0]) self.assertEqual(target, SuperOp(error), msg="Incorrect tensor kraus") def test_compose_both_unitary_standard_gates(self): @@ -527,7 +527,7 @@ def test_compose_both_unitary_standard_gates(self): for j in range(4): circ, _ = error.error_term(j) self.assertIn(circ[0][0].name, ['s', 'x', 'y', 'z']) - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(SuperOp(error), target) def test_compose_kraus_and_unitary(self): @@ -540,7 +540,7 @@ def test_compose_kraus_and_unitary(self): circ, prob = error.error_term(0) # self.assertEqual(prob, 1) self.assertEqual(circ[0][0].name, 'kraus') - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(target, SuperOp(error)) def test_compose_unitary_and_kraus(self): @@ -551,7 +551,7 @@ def test_compose_unitary_and_kraus(self): target = SuperOp(kraus_unitaries).compose(kraus) circ, prob = error.error_term(0) - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(target, SuperOp(error)) def test_dot_both_kraus(self): @@ -564,7 +564,7 @@ def test_dot_both_kraus(self): kraus, prob = error.error_term(0) self.assertEqual(prob, 1) self.assertEqual(kraus[0][0].name, 'kraus') - self.assertEqual(self.aslist(kraus[0][1]), [0]) + self.assertEqual(self.qubits_list(kraus, kraus[0][1]), [0]) self.assertEqual(target, SuperOp(error), msg="Incorrect dot kraus") def test_dot_both_unitary_standard_gates(self): @@ -581,7 +581,7 @@ def test_dot_both_unitary_standard_gates(self): for j in range(4): circ, _ = error.error_term(j) self.assertIn(circ[0][0].name, ['s', 'x', 'y', 'z']) - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(SuperOp(error), target) def test_dot_kraus_and_unitary(self): @@ -592,7 +592,7 @@ def test_dot_kraus_and_unitary(self): target = SuperOp(kraus).dot(kraus_unitaries) circ, prob = error.error_term(0) - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(target, SuperOp(error)) def test_dot_unitary_and_kraus(self): @@ -605,7 +605,7 @@ def test_dot_unitary_and_kraus(self): circ, prob = error.error_term(0) # self.assertEqual(prob, 1) self.assertEqual(circ[0][0].name, 'kraus') - self.assertEqual(self.aslist(circ[0][1]), [0]) + self.assertEqual(self.qubits_list(circ, circ[0][1]), [0]) self.assertEqual(target, SuperOp(error)) def test_to_quantumchannel_kraus(self): diff --git a/test/terra/reference/ref_kraus_noise.py b/test/terra/reference/ref_kraus_noise.py index 9e239a1be8..53b2218527 100644 --- a/test/terra/reference/ref_kraus_noise.py +++ b/test/terra/reference/ref_kraus_noise.py @@ -60,15 +60,6 @@ def kraus_gate_error_noise_models(): return noise_models -def kraus_gate_error_noise_models_full(): - """Kraus gate error noise models on many gate types""" - - # Amplitude damping error on "u1", "u2", "u3", "cx" - error = amplitude_damping_error(0.2) - noise_model = NoiseModel() - noise_model.add_all_qubit_quantum_error(error, ['h']) - noise_model.add_all_qubit_quantum_error(error.tensor(error), ['cp', 'swap']) - return noise_model def kraus_gate_error_counts(shots, hex_counts=True): """Kraus gate error circuits reference counts""" @@ -80,13 +71,3 @@ def kraus_gate_error_counts(shots, hex_counts=True): # Convert to counts dict return [list2dict(i, hex_counts) for i in counts_lists] - -def kraus_gate_error_counts_on_QFT(shots, hex_counts=False): - """Kraus gate error QFT circuits reference counts""" - - # Kraus error on all kinds of gates - the results are highly dependent on the - # specific circuit and therefore are hardcoded - counts = {'0x0':370*shots/1000, '0x1':175*shots/1000, '0x2':170*shots/1000, - '0x3':75*shots/1000, '0x4':100*shots/1000, '0x5':45*shots/1000, - '0x6':45*shots/1000, '0x7':20*shots/1000} - return counts