From 1a8c51bc9f8f8c7e8103443f3e37a37dbef1ef49 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 23 Mar 2021 16:59:30 -0400 Subject: [PATCH 01/14] Add unified AerController * Includes unitary method and superop method * Adds GPU version of superop simulation method --- .../aer/backends/wrappers/bindings.cc | 8 + src/controllers/aer_controller.hpp | 2140 +++++++++++++++++ .../superoperator/superoperator_state.hpp | 13 +- .../superoperator/superoperator_thrust.hpp | 186 ++ src/simulators/unitary/unitary_state.hpp | 7 + 5 files changed, 2351 insertions(+), 3 deletions(-) create mode 100755 src/controllers/aer_controller.hpp create mode 100755 src/simulators/superoperator/superoperator_thrust.hpp diff --git a/qiskit/providers/aer/backends/wrappers/bindings.cc b/qiskit/providers/aer/backends/wrappers/bindings.cc index 72c3c1ab7d..884ef745be 100644 --- a/qiskit/providers/aer/backends/wrappers/bindings.cc +++ b/qiskit/providers/aer/backends/wrappers/bindings.cc @@ -18,6 +18,7 @@ DISABLE_WARNING_POP #include "framework/types.hpp" #include "framework/results/pybind_result.hpp" +#include "controllers/aer_controller.hpp" #include "controllers/qasm_controller.hpp" #include "controllers/statevector_controller.hpp" #include "controllers/unitary_controller.hpp" @@ -43,6 +44,13 @@ PYBIND11_MODULE(controller_wrappers, m) { MPI_Init_thread(nullptr,nullptr,MPI_THREAD_MULTIPLE,&prov); #endif + py::class_ > aer_ctrl (m, "aer_controller_execute"); + aer_ctrl.def(py::init<>()); + aer_ctrl.def("__call__", &ControllerExecutor::operator()); + aer_ctrl.def("__reduce__", [aer_ctrl](const ControllerExecutor &self) { + return py::make_tuple(aer_ctrl, py::tuple()); + }); + py::class_ > qasm_ctrl (m, "qasm_controller_execute"); qasm_ctrl.def(py::init<>()); qasm_ctrl.def("__call__", &ControllerExecutor::operator()); diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp new file mode 100755 index 0000000000..7d75054cf8 --- /dev/null +++ b/src/controllers/aer_controller.hpp @@ -0,0 +1,2140 @@ +/** + * This code is part of Qiskit. + * + * (C) Copyright IBM 2018, 2019. + * + * This code is licensed under the Apache License, Version 2.0. You may + * obtain a copy of this license in the LICENSE.txt file in the root directory + * of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. + * + * Any modifications or derivative works of this code must retain this + * copyright notice, and modified files need to carry a notice indicating + * that they have been altered from the originals. + */ + +#ifndef _aer_controller_hpp_ +#define _aer_controller_hpp_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) || defined(__APPLE__) +#include +#elif defined(_WIN64) || defined(_WIN32) +// This is needed because windows.h redefine min()/max() so interferes with +// std::min/max +#define NOMINMAX +#include +#endif + +#ifdef _OPENMP +#include +#endif + +#ifdef AER_MPI +#include +#endif + +#include "framework/creg.hpp" +#include "framework/qobj.hpp" +#include "framework/results/experiment_result.hpp" +#include "framework/results/result.hpp" +#include "framework/rng.hpp" +#include "noise/noise_model.hpp" + +#include "transpile/basic_opts.hpp" +#include "transpile/cacheblocking.hpp" +#include "transpile/delay_measure.hpp" +#include "transpile/fusion.hpp" +#include "transpile/truncate_qubits.hpp" + +#include "simulators/density_matrix/densitymatrix_state.hpp" +#include "simulators/density_matrix/densitymatrix_state_chunk.hpp" +#include "simulators/extended_stabilizer/extended_stabilizer_state.hpp" +#include "simulators/matrix_product_state/matrix_product_state.hpp" +#include "simulators/stabilizer/stabilizer_state.hpp" +#include "simulators/statevector/qubitvector.hpp" +#include "simulators/statevector/statevector_state.hpp" +#include "simulators/statevector/statevector_state_chunk.hpp" +#include "simulators/superoperator/superoperator_state.hpp" +#include "simulators/unitary/unitary_state.hpp" + +namespace AER { + +//========================================================================= +// AER::Controller class +//========================================================================= + +// This is the top level controller for the Qiskit-Aer simulator +// It manages execution of all the circuits in a QOBJ, parallelization, +// noise sampling from a noise model, and circuit optimizations. + +class Controller { +public: + Controller() { clear_parallelization(); } + + //----------------------------------------------------------------------- + // Execute qobj + //----------------------------------------------------------------------- + + // Load a QOBJ from a JSON file and execute on the State type + // class. + Result execute(const json_t &qobj); + + Result execute(std::vector &circuits, + const Noise::NoiseModel &noise_model, const json_t &config); + + //----------------------------------------------------------------------- + // Config settings + //----------------------------------------------------------------------- + + // Load Controller, State and Data config from a JSON + // config settings will be passed to the State and Data classes + void set_config(const json_t &config); + + // Clear the current config + void clear_config(); + +protected: + //----------------------------------------------------------------------- + // Simulation types + //----------------------------------------------------------------------- + + // Simulation methods for the Qasm Controller + enum class Method { + automatic, + statevector, + density_matrix, + matrix_product_state, + stabilizer, + extended_stabilizer, + unitary, + superop + }; + + enum class Device { CPU, GPU, ThrustCPU }; + + // Simulation precision + enum class Precision { Double, Single }; + + //----------------------------------------------------------------------- + // Config + //----------------------------------------------------------------------- + + // Timer type + using myclock_t = std::chrono::high_resolution_clock; + + // Transpile pass override flags + bool truncate_qubits_ = true; + + // Validation threshold for validating states and operators + double validation_threshold_ = 1e-8; + + // Save counts as memory list + bool save_creg_memory_ = false; + + // Simulation method + Method sim_method_ = Method::automatic; + + // Simulation device + Device sim_device_ = Device::CPU; + std::string sim_device_name_ = "CPU"; + + // Simulation precision + Precision sim_precision_ = Precision::Double; + + // Controller-level parameter for CH method + bool extended_stabilizer_measure_sampling_ = false; + + //----------------------------------------------------------------------- + // Circuit Execution + //----------------------------------------------------------------------- + + // Parallel execution of a circuit + // This function manages parallel shot configuration and internally calls + // the `run_circuit` method for each shot thread + void execute_circuit(Circuit &circ, Noise::NoiseModel &noise, + const json_t &config, ExperimentResult &result); + + // Abstract method for executing a circuit. + // This method must initialize a state and return output data for + // the required number of shots. + void run_circuit(const Circuit &circ, const Noise::NoiseModel &noise, + const json_t &config, uint_t shots, uint_t rng_seed, + ExperimentResult &result) const; + + //---------------------------------------------------------------- + // Run circuit helpers + //---------------------------------------------------------------- + + // Execute n-shots of a circuit on the input state + template + void run_circuit_helper(const Circuit &circ, const Noise::NoiseModel &noise, + const json_t &config, uint_t shots, uint_t rng_seed, + const Method method, bool cache_block, + ExperimentResult &result) const; + + // Execute a single shot a of circuit by initializing the state vector, + // running all ops in circ, and updating data with + // simulation output. + template + void run_single_shot(const Circuit &circ, State_t &state, + ExperimentResult &result, RngEngine &rng) const; + + // Execute multiple shots a of circuit by initializing the state vector, + // running all ops in circ, and updating data with + // simulation output. Will use measurement sampling if possible + template + void run_circuit_without_sampled_noise(Circuit &circ, + const json_t &config, + uint_t shots, + State_t &state, + const Method method, + bool cache_blocking, + ExperimentResult &result, + RngEngine &rng) const; + + template + void run_circuit_with_sampled_noise(const Circuit &circ, + const Noise::NoiseModel &noise, + const json_t &config, + uint_t shots, + State_t &state, + const Method method, + bool cache_blocking, + ExperimentResult &result, + RngEngine &rng) const; + + //---------------------------------------------------------------- + // Measurement + //---------------------------------------------------------------- + + // Sample measurement outcomes for the input measure ops from the + // current state of the input State_t + template + void measure_sampler(const std::vector &meas_ops, + uint_t shots, State_t &state, ExperimentResult &result, + RngEngine &rng) const; + + // Check if measure sampling optimization is valid for the input circuit + // for the given method. This checks if operation types before + // the first measurement in the circuit prevent sampling + bool check_measure_sampling_opt(const Circuit &circ, + const Method method) const; + + // Save count data + void save_count_data(ExperimentResult &result, + const ClassicalRegister &creg) const; + + //------------------------------------------------------------------------- + // State validation + //------------------------------------------------------------------------- + + // Return True if a given circuit (and internal noise model) are valid for + // execution on the given state. Otherwise return false. + // If throw_except is true an exception will be thrown on the return false + // case listing the invalid instructions in the circuit or noise model. + template + static bool validate_state(const state_t &state, const Circuit &circ, + const Noise::NoiseModel &noise, + bool throw_except = false); + + // Return True if a given circuit are valid for execution on the given state. + // Otherwise return false. + // If throw_except is true an exception will be thrown directly. + template + bool validate_memory_requirements(const state_t &state, const Circuit &circ, + bool throw_except = false) const; + + // Return an estimate of the required memory for a circuit. + size_t required_memory_mb(const Circuit &circuit, + const Noise::NoiseModel &noise) const; + + //---------------------------------------------------------------- + // Utility functions + //---------------------------------------------------------------- + + // Return the simulation method to use for the input circuit + // If a custom method is specified in the config this will be + // used. If the default automatic method is set this will choose + // the appropriate method based on the input circuit. + Method simulation_method(const Circuit &circ, const Noise::NoiseModel &noise, + bool validate = false) const; + + // Return a fusion transpilation pass configured for the current + // method, circuit and config + Transpile::Fusion transpile_fusion(Method method, + const Operations::OpSet &opset, + const json_t &config) const; + + // Return cache blocking transpiler pass + Transpile::CacheBlocking + transpile_cache_blocking(Controller::Method method, + const Circuit &circ, + const Noise::NoiseModel &noise, + const json_t &config) const; + + //----------------------------------------------------------------------- + // Parallelization Config + //----------------------------------------------------------------------- + + // Set OpenMP thread settings to default values + void clear_parallelization(); + + // Set parallelization for experiments + void + set_parallelization_experiments(const std::vector &circuits, + const std::vector &noise); + + // Set circuit parallelization + void set_parallelization_circuit(const Circuit &circ, + const Noise::NoiseModel &noise); + + void set_parallelization_circuit_method(const Circuit &circ, + const Noise::NoiseModel &noise); + + // Set distributed parallelization + void + set_distributed_parallelization(const std::vector &circuits, + const std::vector &noise); + + void set_distributed_parallelization_method( + const std::vector &circuits, + const std::vector &noise); + + bool multiple_chunk_required(const Circuit &circuit, + const Noise::NoiseModel &noise) const; + + void save_exception_to_results(Result &result, const std::exception &e); + + // Get system memory size + size_t get_system_memory_mb(); + size_t get_gpu_memory_mb(); + + uint_t get_distributed_num_processes(bool par_shots) const; + + size_t get_min_memory_mb() const { + if (num_gpus_ > 0) { + return max_gpu_memory_mb_ / num_gpus_; // return per GPU memory size + } + return max_memory_mb_; + } + + // The maximum number of threads to use for various levels of parallelization + int max_parallel_threads_; + + // Parameters for parallelization management in configuration + int max_parallel_experiments_; + int max_parallel_shots_; + size_t max_memory_mb_; + size_t max_gpu_memory_mb_; + int num_gpus_; // max number of GPU per process + + // use explicit parallelization + bool explicit_parallelization_; + + // Parameters for parallelization management for experiments + int parallel_experiments_; + int parallel_shots_; + int parallel_state_update_; + + bool parallel_nested_ = false; + + // max number of qubits in given circuits + int max_qubits_; + + // results are stored independently in each process if true + bool accept_distributed_results_ = true; + + // distributed experiments (MPI) + int distributed_experiments_rank_ = 0; + int distributed_experiments_group_id_ = 0; + uint_t distributed_experiments_num_processes_ = 1; + int distributed_experiments_ = 1; + uint_t num_process_per_experiment_; + uint_t distributed_experiments_begin_; + uint_t distributed_experiments_end_; + + // distributed shots (MPI) + int distributed_shots_rank_ = 0; + int distributed_shots_ = 1; + + // process information (MPI) + int myrank_ = 0; + int num_processes_ = 1; + + uint_t cache_block_qubit_ = 0; +}; + +//========================================================================= +// Implementations +//========================================================================= + +//------------------------------------------------------------------------- +// Config settings +//------------------------------------------------------------------------- + +void Controller::set_config(const json_t &config) { + + // Load validation threshold + JSON::get_value(validation_threshold_, "validation_threshold", config); + + // Load config for memory (creg list data) + JSON::get_value(save_creg_memory_, "memory", config); + +#ifdef _OPENMP + // Load OpenMP maximum thread settings + if (JSON::check_key("max_parallel_threads", config)) + JSON::get_value(max_parallel_threads_, "max_parallel_threads", config); + if (JSON::check_key("max_parallel_experiments", config)) + JSON::get_value(max_parallel_experiments_, "max_parallel_experiments", + config); + if (JSON::check_key("max_parallel_shots", config)) + JSON::get_value(max_parallel_shots_, "max_parallel_shots", config); + // Limit max threads based on number of available OpenMP threads + auto omp_threads = omp_get_max_threads(); + max_parallel_threads_ = (max_parallel_threads_ > 0) + ? std::min(max_parallel_threads_, omp_threads) + : std::max(1, omp_threads); +#else + // No OpenMP so we disable parallelization + max_parallel_threads_ = 1; + max_parallel_shots_ = 1; + max_parallel_experiments_ = 1; + parallel_nested_ = false; +#endif + + // Load configurations for parallelization + + if (JSON::check_key("max_memory_mb", config)) { + JSON::get_value(max_memory_mb_, "max_memory_mb", config); + } + + // for debugging + if (JSON::check_key("_parallel_experiments", config)) { + JSON::get_value(parallel_experiments_, "_parallel_experiments", config); + explicit_parallelization_ = true; + } + + // for debugging + if (JSON::check_key("_parallel_shots", config)) { + JSON::get_value(parallel_shots_, "_parallel_shots", config); + explicit_parallelization_ = true; + } + + // for debugging + if (JSON::check_key("_parallel_state_update", config)) { + JSON::get_value(parallel_state_update_, "_parallel_state_update", config); + explicit_parallelization_ = true; + } + + if (explicit_parallelization_) { + parallel_experiments_ = std::max({parallel_experiments_, 1}); + parallel_shots_ = std::max({parallel_shots_, 1}); + parallel_state_update_ = std::max({parallel_state_update_, 1}); + } + + if (JSON::check_key("accept_distributed_results", config)) { + JSON::get_value(accept_distributed_results_, "accept_distributed_results", + config); + } + + // enable multiple qregs if cache blocking is enabled + cache_block_qubit_ = 0; + if (JSON::check_key("blocking_qubits", config)) { + JSON::get_value(cache_block_qubit_, "blocking_qubits", config); + } + + // Override automatic simulation method with a fixed method + std::string method; + if (JSON::get_value(method, "method", config)) { + if (method == "statevector") { + sim_method_ = Method::statevector; + } else if (method == "density_matrix") { + sim_method_ = Method::density_matrix; + } else if (method == "stabilizer") { + sim_method_ = Method::stabilizer; + } else if (method == "extended_stabilizer") { + sim_method_ = Method::extended_stabilizer; + } else if (method == "matrix_product_state") { + sim_method_ = Method::matrix_product_state; + } else if (method == "unitary") { + sim_method_ = Method::unitary; + } else if (method == "superop") { + sim_method_ = Method::superop; + } else if (method != "automatic") { + throw std::runtime_error(std::string("Invalid simulation method (") + + method + std::string(").")); + } + } + + // Override automatic simulation method with a fixed method + if (JSON::get_value(sim_device_name_, "device", config)) { + if (sim_device_name_ == "CPU") { + sim_device_ = Device::CPU; + } else if (sim_device_name_ == "Thrust") { +#ifndef AER_THRUST_CPU + throw std::runtime_error( + "Simulation device \"Thrust\" is not supported on this system"); +#else + sim_device_ = Device::ThrustCPU; +#endif + } else if (sim_device_name_ == "GPU") { +#ifndef AER_THRUST_CUDA + throw std::runtime_error( + "Simulation device \"GPU\" is not supported on this system"); +#else + sim_device_ = Device::GPU; +#endif + } + else { + throw std::runtime_error(std::string("Invalid simulation device (\"") + + sim_device_name_ + std::string("\").")); + } + } + + std::string precision; + if (JSON::get_value(precision, "precision", config)) { + if (precision == "double") { + sim_precision_ = Precision::Double; + } else if (precision == "single") { + sim_precision_ = Precision::Single; + } else { + throw std::runtime_error(std::string("Invalid simulation precision (") + + precision + std::string(").")); + } + } +} + +void Controller::clear_config() { + clear_parallelization(); + validation_threshold_ = 1e-8; + sim_method_ = Method::automatic; + sim_device_ = Device::CPU; + sim_precision_ = Precision::Double; +} + +void Controller::clear_parallelization() { + max_parallel_threads_ = 0; + max_parallel_experiments_ = 1; + max_parallel_shots_ = 0; + + parallel_experiments_ = 1; + parallel_shots_ = 1; + parallel_state_update_ = 1; + parallel_nested_ = false; + + num_process_per_experiment_ = 1; + distributed_experiments_ = 1; + distributed_shots_ = 1; + + num_gpus_ = 0; + + explicit_parallelization_ = false; + max_memory_mb_ = get_system_memory_mb(); + max_gpu_memory_mb_ = get_gpu_memory_mb(); +} + +void Controller::set_parallelization_experiments( + const std::vector &circuits, + const std::vector &noise) { + // Use a local variable to not override stored maximum based + // on currently executed circuits + const auto max_experiments = + (max_parallel_experiments_ > 0) + ? std::min({max_parallel_experiments_, max_parallel_threads_}) + : max_parallel_threads_; + + if (max_experiments == 1 && num_processes_ == 1) { + // No parallel experiment execution + parallel_experiments_ = 1; + return; + } + + // If memory allows, execute experiments in parallel +#ifdef AER_MPI + std::vector required_memory_mb_list(distributed_experiments_end_ - + distributed_experiments_begin_); + for (size_t j = 0; + j < distributed_experiments_end_ - distributed_experiments_begin_; j++) { + required_memory_mb_list[j] = + required_memory_mb(circuits[j + distributed_experiments_begin_], + noise[j + distributed_experiments_begin_]) / + num_process_per_experiment_; + } +#else + std::vector required_memory_mb_list(circuits.size()); + for (size_t j = 0; j < circuits.size(); j++) { + required_memory_mb_list[j] = required_memory_mb(circuits[j], noise[j]); + } +#endif + std::sort(required_memory_mb_list.begin(), required_memory_mb_list.end(), + std::greater<>()); + size_t total_memory = 0; + parallel_experiments_ = 0; + for (size_t required_memory_mb : required_memory_mb_list) { + total_memory += required_memory_mb; + if (total_memory > max_memory_mb_ * num_process_per_experiment_) + break; + ++parallel_experiments_; + } + + if (parallel_experiments_ <= 0) + throw std::runtime_error( + "a circuit requires more memory than max_memory_mb."); +#ifdef AER_MPI + parallel_experiments_ = std::min( + {parallel_experiments_, max_experiments, max_parallel_threads_, + static_cast(distributed_experiments_end_ - + distributed_experiments_begin_)}); +#else + parallel_experiments_ = + std::min({parallel_experiments_, max_experiments, + max_parallel_threads_, static_cast(circuits.size())}); +#endif +} + +void Controller::set_parallelization_circuit(const Circuit &circ, + const Noise::NoiseModel &noise) { + + // Use a local variable to not override stored maximum based + // on currently executed circuits + const auto max_shots = + (max_parallel_shots_ > 0) + ? std::min({max_parallel_shots_, max_parallel_threads_}) + : max_parallel_threads_; + + // If we are executing circuits in parallel we disable + // parallel shots + if (max_shots == 1 || parallel_experiments_ > 1) { + parallel_shots_ = 1; + } else { + // Parallel shots is > 1 + // Limit parallel shots by available memory and number of shots + // And assign the remaining threads to state update + int circ_memory_mb = + required_memory_mb(circ, noise) / num_process_per_experiment_; + if (max_memory_mb_ + max_gpu_memory_mb_ < circ_memory_mb) + throw std::runtime_error( + "a circuit requires more memory than max_memory_mb."); + // If circ memory is 0, set it to 1 so that we don't divide by zero + circ_memory_mb = std::max({1, circ_memory_mb}); + +#ifdef AER_MPI + int shots = + (circ.shots * (distributed_shots_rank_ + 1) / distributed_shots_) - + (circ.shots * distributed_shots_rank_ / distributed_shots_); +#else + int shots = circ.shots; +#endif + parallel_shots_ = std::min( + {static_cast(max_memory_mb_ / circ_memory_mb), max_shots, shots}); + } + parallel_state_update_ = + (parallel_shots_ > 1) + ? std::max({1, max_parallel_threads_ / parallel_shots_}) + : std::max({1, max_parallel_threads_ / parallel_experiments_}); +} + +void Controller::set_distributed_parallelization( + const std::vector &circuits, + const std::vector &noise) { + std::vector required_memory_mb_list(circuits.size()); + num_process_per_experiment_ = 1; + for (size_t j = 0; j < circuits.size(); j++) { + size_t size = required_memory_mb(circuits[j], noise[j]); + if (size > max_memory_mb_ + max_gpu_memory_mb_) { + num_process_per_experiment_ = + std::max(num_process_per_experiment_, + (size + (max_memory_mb_ + max_gpu_memory_mb_) - 1) / + (max_memory_mb_ + max_gpu_memory_mb_)); + } + } + while ((num_processes_ % num_process_per_experiment_) != 0) { + num_process_per_experiment_++; + } + + distributed_experiments_ = num_processes_ / num_process_per_experiment_; + + if (circuits.size() < distributed_experiments_) { + // e.g. np = 8, circuits = 3, npe = 2, de = 4 -> 3 , then np_in_group = + // [3,3,2] + // np = 4, circuits = 1, npe = 2, de = 2 -> 1 , then np_in_group = [4] + distributed_experiments_ = circuits.size(); + + distributed_experiments_num_processes_ = + (num_processes_ + distributed_experiments_ - 1) / + distributed_experiments_; + distributed_experiments_group_id_ = + myrank_ / distributed_experiments_num_processes_; + if ((distributed_experiments_group_id_ + 1) * + distributed_experiments_num_processes_ > + num_processes_) { + distributed_experiments_num_processes_ = + num_processes_ - distributed_experiments_group_id_ * + distributed_experiments_num_processes_; + } + + if (distributed_experiments_num_processes_ > num_process_per_experiment_ && + (distributed_experiments_num_processes_ % + num_process_per_experiment_) == 0) { + distributed_shots_ = + distributed_experiments_num_processes_ / num_process_per_experiment_; + distributed_shots_rank_ = 0; + } else { + // shots are not distributed + distributed_shots_ = 1; + distributed_shots_rank_ = 0; + } + distributed_experiments_rank_ = myrank_ % distributed_experiments_; + + distributed_experiments_begin_ = distributed_experiments_group_id_; + distributed_experiments_end_ = distributed_experiments_begin_ + 1; + } else { + distributed_experiments_group_id_ = myrank_ / num_process_per_experiment_; + distributed_experiments_rank_ = myrank_ % num_process_per_experiment_; + distributed_experiments_num_processes_ = num_process_per_experiment_; + + distributed_experiments_begin_ = circuits.size() * + distributed_experiments_group_id_ / + distributed_experiments_; + distributed_experiments_end_ = circuits.size() * + (distributed_experiments_group_id_ + 1) / + distributed_experiments_; + + // shots are not distributed + distributed_shots_ = 1; + distributed_shots_rank_ = 0; + } +} + +uint_t Controller::get_distributed_num_processes(bool par_shots) const { + if (par_shots) { + return num_process_per_experiment_; + } else { + return distributed_experiments_num_processes_; // no shot distribution, + // parallelize this + // experiment by processes in + // group + } +} + +bool Controller::multiple_chunk_required(const Circuit &circ, + const Noise::NoiseModel &noise) const { + if (circ.num_qubits < 3) + return false; + + if (num_process_per_experiment_ > 1 || + Controller::get_min_memory_mb() < required_memory_mb(circ, noise)) + return true; + + if (cache_block_qubit_ >= 2 && cache_block_qubit_ < circ.num_qubits) + return true; + + return false; +} + +size_t Controller::get_system_memory_mb() { + size_t total_physical_memory = 0; +#if defined(__linux__) || defined(__APPLE__) + auto pages = sysconf(_SC_PHYS_PAGES); + auto page_size = sysconf(_SC_PAGE_SIZE); + total_physical_memory = pages * page_size; +#elif defined(_WIN64) || defined(_WIN32) + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + total_physical_memory = status.ullTotalPhys; +#endif +#ifdef AER_MPI + // get minimum memory size per process + uint64_t locMem, minMem; + locMem = total_physical_memory; + MPI_Allreduce(&locMem, &minMem, 1, MPI_UINT64_T, MPI_MIN, MPI_COMM_WORLD); + total_physical_memory = minMem; +#endif + + return total_physical_memory >> 20; +} + +size_t Controller::get_gpu_memory_mb() { + size_t total_physical_memory = 0; +#ifdef AER_THRUST_CUDA + int iDev, nDev, j; + if (cudaGetDeviceCount(&nDev) != cudaSuccess) { + cudaGetLastError(); + nDev = 0; + } + for (iDev = 0; iDev < nDev; iDev++) { + size_t freeMem, totalMem; + cudaSetDevice(iDev); + cudaMemGetInfo(&freeMem, &totalMem); + total_physical_memory += totalMem; + } + num_gpus_ = nDev; +#endif +#ifdef AER_MPI + // get minimum memory size per process + uint64_t locMem, minMem; + locMem = total_physical_memory; + MPI_Allreduce(&locMem, &minMem, 1, MPI_UINT64_T, MPI_MIN, MPI_COMM_WORLD); + total_physical_memory = minMem; + + int t = num_gpus_; + MPI_Allreduce(&t, &num_gpus_, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD); +#endif + + return total_physical_memory >> 20; +} + +//------------------------------------------------------------------------- +// State validation +//------------------------------------------------------------------------- + +template +bool Controller::validate_state(const state_t &state, const Circuit &circ, + const Noise::NoiseModel &noise, + bool throw_except) { + // First check if a noise model is valid for a given state + bool noise_valid = noise.is_ideal() || state.opset().contains(noise.opset()); + bool circ_valid = state.opset().contains(circ.opset()); + if (noise_valid && circ_valid) { + return true; + } + + // If we didn't return true then either noise model or circ has + // invalid instructions. + if (throw_except == false) + return false; + + // If we are throwing an exception we include information + // about the invalid operations + std::stringstream msg; + if (!noise_valid) { + msg << "Noise model contains invalid instructions "; + msg << state.opset().difference(noise.opset()); + msg << " for \"" << state.name() << "\" method"; + } + if (!circ_valid) { + msg << "Circuit contains invalid instructions "; + msg << state.opset().difference(circ.opset()); + msg << " for \"" << state.name() << "\" method"; + } + throw std::runtime_error(msg.str()); +} + +template +bool Controller::validate_memory_requirements(const state_t &state, + const Circuit &circ, + bool throw_except) const { + if (max_memory_mb_ == 0) + return true; + + size_t required_mb = state.required_memory_mb(circ.num_qubits, circ.ops) / + num_process_per_experiment_; + if (max_memory_mb_ + max_gpu_memory_mb_ < required_mb) { + if (throw_except) { + std::string name = ""; + JSON::get_value(name, "name", circ.header); + throw std::runtime_error("Insufficient memory to run circuit \"" + name + + "\" using the " + state.name() + " simulator."); + } + return false; + } + return true; +} + +void Controller::save_exception_to_results(Result &result, + const std::exception &e) { + result.status = Result::Status::error; + result.message = e.what(); + for (auto &res : result.results) { + res.status = ExperimentResult::Status::error; + res.message = e.what(); + } +} + +Transpile::CacheBlocking +Controller::transpile_cache_blocking(Controller::Method method, const Circuit &circ, + const Noise::NoiseModel &noise, + const json_t &config) const { + Transpile::CacheBlocking cache_block_pass; + + const bool is_matrix = (method == Method::density_matrix + || method == Method::unitary); + const auto complex_size = (sim_precision_ == Precision::Single) + ? sizeof(std::complex) + : sizeof(std::complex); + + cache_block_pass.set_config(config); + if (!cache_block_pass.enabled()) { + // if blocking is not set by config, automatically set if required + if (multiple_chunk_required(circ, noise)) { + int nplace = num_process_per_experiment_; + if (num_gpus_ > 0) + nplace *= num_gpus_; + cache_block_pass.set_blocking(circ.num_qubits, get_min_memory_mb() << 20, + nplace, complex_size, is_matrix); + } + } + return cache_block_pass; +} + +//------------------------------------------------------------------------- +// Qobj execution +//------------------------------------------------------------------------- +Result Controller::execute(const json_t &qobj_js) { +#ifdef AER_MPI + MPI_Comm_size(MPI_COMM_WORLD, &num_processes_); + MPI_Comm_rank(MPI_COMM_WORLD, &myrank_); +#endif + + // Load QOBJ in a try block so we can catch parsing errors and still return + // a valid JSON output containing the error message. + try { + // Start QOBJ timer + auto timer_start = myclock_t::now(); + + Qobj qobj(qobj_js); + Noise::NoiseModel noise_model; + json_t config; + // Check for config + if (JSON::get_value(config, "config", qobj_js)) { + // Set config + set_config(config); + // Load noise model + JSON::get_value(noise_model, "noise_model", config); + } + 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(); + result.metadata.add(time_taken, "time_taken"); + return result; + } catch (std::exception &e) { + // qobj was invalid, return valid output containing error message + Result result; + + result.status = Result::Status::error; + result.message = std::string("Failed to load qobj: ") + e.what(); + return result; + } +} + +//------------------------------------------------------------------------- +// Experiment execution +//------------------------------------------------------------------------- + +Result Controller::execute(std::vector &circuits, + const Noise::NoiseModel &noise_model, + const json_t &config) { + // Start QOBJ timer + auto timer_start = myclock_t::now(); + + // Initialize Result object for the given number of experiments + Result result(circuits.size()); + // Make a copy of the noise model for each circuit execution + // so that it can be modified if required + std::vector circ_noise_models(circuits.size(), + noise_model); + + // Execute each circuit in a try block + try { + // truncate circuits before experiment settings (to get correct + // required_memory_mb value) + if (truncate_qubits_) { + for (size_t j = 0; j < circuits.size(); j++) { + // Truncate unused qubits from circuit and noise model + Transpile::TruncateQubits truncate_pass; + truncate_pass.set_config(config); + truncate_pass.optimize_circuit(circuits[j], circ_noise_models[j], + circuits[j].opset(), result.results[j]); + } + } + +#ifdef AER_MPI + try { + // catch exception raised by required_memory_mb because of invalid + // simulation method + set_distributed_parallelization_method(circuits, circ_noise_models); + } catch (std::exception &e) { + save_exception_to_results(result, e); + } + + const auto num_circuits = + distributed_experiments_end_ - distributed_experiments_begin_; + result.resize(num_circuits); +#endif + + // get max qubits for this process (to allocate qubit register at once) + max_qubits_ = 0; +#ifdef AER_MPI + for (size_t j = distributed_experiments_begin_; + j < distributed_experiments_end_; j++) { +#else + for (size_t j = 0; j < circuits.size(); j++) { +#endif + if (circuits[j].num_qubits > max_qubits_) { + max_qubits_ = circuits[j].num_qubits; + } + } + + if (!explicit_parallelization_) { + // set parallelization for experiments + try { + // catch exception raised by required_memory_mb because of invalid + // simulation method + set_parallelization_experiments(circuits, circ_noise_models); + } catch (std::exception &e) { + save_exception_to_results(result, e); + } + } + +#ifdef _OPENMP + result.metadata.add(true, "omp_enabled"); +#else + result.metadata.add(false, "omp_enabled"); +#endif + result.metadata.add(parallel_experiments_, "parallel_experiments"); + result.metadata.add(max_memory_mb_, "max_memory_mb"); + result.metadata.add(max_gpu_memory_mb_, "max_gpu_memory_mb"); + + // store rank and number of processes, if no distribution rank=0 procs=1 is + // set + result.metadata.add(num_processes_, "num_mpi_processes"); + result.metadata.add(myrank_, "mpi_rank"); +#ifdef AER_MPI + result.metadata.add(distributed_experiments_, "distributed_experiments"); + result.metadata.add(distributed_experiments_group_id_, + "distributed_experiments_group_id"); + result.metadata.add(distributed_experiments_rank_, + "distributed_experiments_rank_in_group"); +#endif + +#ifdef _OPENMP + // Check if circuit parallelism is nested with one of the others + if (parallel_experiments_ > 1 && + parallel_experiments_ < max_parallel_threads_) { + // Nested parallel experiments + parallel_nested_ = true; +#ifdef _WIN32 + omp_set_nested(1); +#else + omp_set_max_active_levels(3); +#endif + result.metadata.add(parallel_nested_, "omp_nested"); + } else { + parallel_nested_ = false; +#ifdef _WIN32 + omp_set_nested(0); +#else + omp_set_max_active_levels(1); +#endif + } +#endif + uint_t offset = 0; +#ifdef AER_MPI + offset = distributed_experiments_begin_; +#endif + // then- and else-blocks have intentionally duplication. + // Nested omp has significant overheads even though a guard condition + // exists. + const int NUM_RESULTS = result.results.size(); + if (parallel_experiments_ > 1) { +#pragma omp parallel for num_threads(parallel_experiments_) + for (int j = 0; j < result.results.size(); ++j) { + execute_circuit(circuits[j + offset], circ_noise_models[j + offset], + config, result.results[j]); + } + } else { + for (int j = 0; j < result.results.size(); ++j) { + execute_circuit(circuits[j + offset], circ_noise_models[j + offset], + config, result.results[j]); + } + } + + // Check each experiment result for completed status. + // If only some experiments completed return partial completed status. + + bool all_failed = true; + result.status = Result::Status::completed; + for (int i = 0; i < NUM_RESULTS; ++i) { + auto &experiment = result.results[i]; + if (experiment.status == ExperimentResult::Status::completed) { + all_failed = false; + } else { + result.status = Result::Status::partial_completed; + result.message += std::string(" [Experiment ") + std::to_string(i) + + std::string("] ") + experiment.message; + } + } + if (all_failed) { + result.status = Result::Status::error; + } + + // Stop the timer and add total timing data + auto timer_stop = myclock_t::now(); + auto time_taken = + std::chrono::duration(timer_stop - timer_start).count(); + result.metadata.add(time_taken, "time_taken"); + } + // If execution failed return valid output reporting error + catch (std::exception &e) { + result.status = Result::Status::error; + result.message = e.what(); + } + return result; +} + +void Controller::execute_circuit(Circuit &circ, Noise::NoiseModel &noise, + const json_t &config, + ExperimentResult &result) { + // Start individual circuit timer + auto timer_start = myclock_t::now(); // state circuit timer + + // Initialize circuit json return + result.legacy_data.set_config(config); + + // Execute in try block so we can catch errors and return the error message + // for individual circuit failures. + try { + // Remove barriers from circuit + Transpile::ReduceBarrier barrier_pass; + barrier_pass.optimize_circuit(circ, noise, circ.opset(), result); + + // Truncate unused qubits from circuit and noise model + if (truncate_qubits_) { + Transpile::TruncateQubits truncate_pass; + truncate_pass.set_config(config); + truncate_pass.optimize_circuit(circ, noise, circ.opset(), result); + } + + // set parallelization for this circuit + if (!explicit_parallelization_) { + set_parallelization_circuit_method(circ, noise); + } + + int shots = circ.shots; +#ifdef AER_MPI + if (parallel_shots_ > 1 && + distributed_shots_ > 1) { // if shots can be distributed + shots = + (circ.shots * (distributed_shots_rank_ + 1) / distributed_shots_) - + (circ.shots * distributed_shots_rank_ / distributed_shots_); + } +#endif + + // Single shot thread execution + if (parallel_shots_ <= 1) { + run_circuit(circ, noise, config, shots, circ.seed, result); + // Parallel shot thread execution + } else { + // Calculate shots per thread + std::vector subshots; + for (int j = 0; j < parallel_shots_; ++j) { + subshots.push_back(shots / parallel_shots_); + } + // If shots is not perfectly divisible by threads, assign the remainder + for (int j = 0; j < int(shots % parallel_shots_); ++j) { + subshots[j] += 1; + } + + // Vector to store parallel thread output data + std::vector par_results(parallel_shots_); + std::vector error_msgs(parallel_shots_); + +#ifdef _OPENMP + if (!parallel_nested_) { + if (parallel_shots_ > 1 && parallel_state_update_ > 1) { +// Nested parallel shots + state update +#ifdef _WIN32 + omp_set_nested(1); +#else + omp_set_max_active_levels(2); +#endif + result.metadata.add(true, "omp_nested"); + } else { +#ifdef _WIN32 + omp_set_nested(0); +#else + omp_set_max_active_levels(1); +#endif + } + } +#endif + +#pragma omp parallel for if (parallel_shots_ > 1) num_threads(parallel_shots_) + for (int i = 0; i < parallel_shots_; i++) { + try { + run_circuit(circ, noise, config, subshots[i], circ.seed + i, + par_results[i]); + } catch (std::runtime_error &error) { + error_msgs[i] = error.what(); + } + } + + for (std::string error_msg : error_msgs) + if (error_msg != "") + throw std::runtime_error(error_msg); + + // Accumulate results across shots + // Use move semantics to avoid copying data + for (auto &res : par_results) { + result.combine(std::move(res)); + } + } + // Report success + result.status = ExperimentResult::Status::completed; + + // Pass through circuit header and add metadata + result.header = circ.header; + result.shots = shots; + result.seed = circ.seed; + result.metadata.add(parallel_shots_, "parallel_shots"); + result.metadata.add(parallel_state_update_, "parallel_state_update"); +#ifdef AER_MPI + if (parallel_shots_ > 1 && distributed_shots_ > 1) { + result.metadata.add(distributed_shots_, "distributed_shots"); + } +#endif + // Add timer data + auto timer_stop = myclock_t::now(); // stop timer + double time_taken = + std::chrono::duration(timer_stop - timer_start).count(); + result.time_taken = time_taken; + } + // If an exception occurs during execution, catch it and pass it to the output + catch (std::exception &e) { + result.status = ExperimentResult::Status::error; + result.message = e.what(); + } +} + +void Controller::save_count_data(ExperimentResult &result, + const ClassicalRegister &creg) const { + if (creg.memory_size() > 0) { + std::string memory_hex = creg.memory_hex(); + result.data.add_accum(static_cast(1ULL), "counts", memory_hex); + if (save_creg_memory_) { + result.data.add_list(std::move(memory_hex), "memory"); + } + } +} + +//------------------------------------------------------------------------- +// Base class override +//------------------------------------------------------------------------- + +void Controller::run_circuit(const Circuit &circ, + const Noise::NoiseModel &noise, + const json_t &config, uint_t shots, + uint_t rng_seed, ExperimentResult &result) const { + // Validate circuit for simulation method + switch (simulation_method(circ, noise, true)) { + case Method::statevector: { + if (sim_device_ == Device::CPU) { + if (multiple_chunk_required(circ, noise)) { + // Chunk based simualtion + if (sim_precision_ == Precision::Double) { + // Double-precision Statevector simulation + return run_circuit_helper< + StatevectorChunk::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + true, result); + } else { + // Single-precision Statevector simulation + return run_circuit_helper< + StatevectorChunk::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + true, result); + } + } else { + // Non-chunk based simulation + if (sim_precision_ == Precision::Double) { + // Double-precision Statevector simulation + return run_circuit_helper< + Statevector::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + false, result); + } else { + // Single-precision Statevector simulation + return run_circuit_helper>>( + circ, noise, config, shots, rng_seed, Method::statevector, + false, result); + } + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (multiple_chunk_required(circ, noise) || + (parallel_shots_ > 1 || parallel_experiments_ > 1)) { + // Chunk based simulation + if (sim_precision_ == Precision::Double) { + // Double-precision Statevector simulation + return run_circuit_helper< + StatevectorChunk::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + true, result); + } else { + // Single-precision Statevector simulation + return run_circuit_helper< + StatevectorChunk::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + true, result); + } + } else { + // Non-chunk based simulation + if (sim_precision_ == Precision::Double) { + // Double-precision Statevector simulation + return run_circuit_helper< + Statevector::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + false, result); + } else { + // Single-precision Statevector simulation + return run_circuit_helper< + Statevector::State>>( + circ, noise, config, shots, rng_seed, Method::statevector, + false, result); + } + } +#endif + } + } + case Method::density_matrix: { + if (sim_device_ == Device::CPU) { + if (multiple_chunk_required(circ, noise)) { + if (sim_precision_ == Precision::Double) { + // Double-precision density matrix simulation + return run_circuit_helper< + DensityMatrixChunk::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + true, result); + } else { + // Single-precision density matrix simulation + return run_circuit_helper< + DensityMatrixChunk::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + true, result); + } + } else { + if (sim_precision_ == Precision::Double) { + // Double-precision density matrix simulation + return run_circuit_helper< + DensityMatrix::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + false, result); + } else { + // Single-precision density matrix simulation + return run_circuit_helper< + DensityMatrix::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + false, result); + } + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (multiple_chunk_required(circ, noise) || + (parallel_shots_ > 1 || parallel_experiments_ > 1)) { + if (sim_precision_ == Precision::Double) { + // Double-precision density matrix simulation + return run_circuit_helper< + DensityMatrixChunk::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + true, result); + } else { + // Single-precision density matrix simulation + return run_circuit_helper< + DensityMatrixChunk::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + true, result); + } + } else { + if (sim_precision_ == Precision::Double) { + // Double-precision density matrix simulation + return run_circuit_helper< + DensityMatrix::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + false, result); + } else { + // Single-precision density matrix simulation + return run_circuit_helper< + DensityMatrix::State>>( + circ, noise, config, shots, rng_seed, Method::density_matrix, + false, result); + } + } +#endif + } + } + case Method::unitary: { + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Double) { + // Double-precision unitary simulation + return run_circuit_helper< + QubitUnitary::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } else { + // Single-precision unitary simulation + return run_circuit_helper< + QubitUnitary::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Double) { + // Double-precision unitary simulation + return run_circuit_helper< + QubitUnitary::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } else { + // Single-precision unitary simulation + return run_circuit_helper< + QubitUnitary::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } +#endif + } + } + case Method::superop: { + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Double) { + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::superop, + false, result); + } else { + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::superop, + false, result); + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Double) { + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } else { + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::unitary, + false, result); + } +#endif + } + } + case Method::stabilizer: + // Stabilizer simulation + // TODO: Stabilizer doesn't yet support custom state initialization + return run_circuit_helper( + circ, noise, config, shots, rng_seed, Method::stabilizer, + false, result); + case Method::extended_stabilizer: + return run_circuit_helper( + circ, noise, config, shots, rng_seed, Method::extended_stabilizer, + false, result); + case Method::matrix_product_state: + return run_circuit_helper( + circ, noise, config, shots, rng_seed, Method::matrix_product_state, + false, result); + default: + throw std::runtime_error("Controller:Invalid simulation method"); + } +} + +//------------------------------------------------------------------------- +// Utility methods +//------------------------------------------------------------------------- +Controller::Method +Controller::simulation_method(const Circuit &circ, + const Noise::NoiseModel &noise_model, + bool validate) const { + // Check simulation method and validate state + switch (sim_method_) { + case Method::statevector: { + if (validate) { + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Single) { + Statevector::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + Statevector::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Single) { + Statevector::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + Statevector::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } +#endif + } + } + return Method::statevector; + } + case Method::density_matrix: { + if (validate) { + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Single) { + DensityMatrix::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + DensityMatrix::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Single) { + DensityMatrix::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + DensityMatrix::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } +#endif + } + } + return Method::density_matrix; + } + case Method::unitary: { + if (validate) { + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Single) { + QubitUnitary::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + QubitUnitary::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + } else { +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Single) { + QubitUnitary::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + QubitUnitary::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } +#endif + } + } + return Method::unitary; + } + case Method::superop: { + if (validate) { + if (sim_precision_ == Precision::Single) { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + } + return Method::superop; + } + case Method::stabilizer: { + if (validate) { + Stabilizer::State state; + validate_state(Stabilizer::State(), circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + return Method::stabilizer; + } + case Method::extended_stabilizer: { + if (validate) { + ExtendedStabilizer::State state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(ExtendedStabilizer::State(), circ, true); + } + return Method::extended_stabilizer; + } + case Method::matrix_product_state: { + if (validate) { + MatrixProductState::State state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } + return Method::matrix_product_state; + } + case Method::automatic: { + // If circuit and noise model are Clifford run on Stabilizer simulator + if (validate_state(Stabilizer::State(), circ, noise_model, false)) { + return Method::stabilizer; + } + // For noisy simulations we enable the density matrix method if + // shots > 2 ** num_qubits. This is based on a rough estimate that + // a single shot of the density matrix simulator is approx 2 ** nq + // times slower than a single shot of statevector due the increased + // dimension + if (noise_model.has_quantum_errors() && + circ.shots > (1ULL << circ.num_qubits) && + validate_memory_requirements(DensityMatrix::State<>(), circ, false) && + validate_state(DensityMatrix::State<>(), circ, noise_model, false) && + check_measure_sampling_opt(circ, Method::density_matrix)) { + return Method::density_matrix; + } + // Finally we check the statevector memory requirement for the + // current number of qubits. If it fits in available memory we + // default to the Statevector method. Otherwise we raise an exception + // and suggest using one of the other simulation methods. + bool enough_memory = true; + if (sim_precision_ == Precision::Single) { + Statevector::State> sv_state; + enough_memory = validate_memory_requirements(sv_state, circ, false); + } else { + Statevector::State<> sv_state; + enough_memory = validate_memory_requirements(sv_state, circ, false); + } + if (!enough_memory) { + throw std::runtime_error( + "QasmSimulator: Insufficient memory for " + + std::to_string(circ.num_qubits) + + "-qubit" + R"( circuit using "statevector" method. You could try using the)" + R"( "matrix_product_state" or "extended_stabilizer" method instead.)"); + } + } + default: { + // For default we use statevector followed by density matrix (for the case + // when the circuit contains invalid instructions for statevector) + if (validate_state(Statevector::State<>(), circ, noise_model, false)) { + return Method::statevector; + } + // If circuit contains invalid instructions for statevector throw a hail + // mary and try for density matrix. + if (validate) + validate_state(DensityMatrix::State<>(), circ, noise_model, true); + return Method::density_matrix; + } + } +} + +size_t Controller::required_memory_mb(const Circuit &circ, + const Noise::NoiseModel &noise) const { + switch (simulation_method(circ, noise, false)) { + case Method::statevector: { + if (sim_precision_ == Precision::Single) { + Statevector::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } else { + Statevector::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + } + case Method::density_matrix: { + if (sim_precision_ == Precision::Single) { + DensityMatrix::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } else { + DensityMatrix::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + } + case Method::unitary: { + if (sim_precision_ == Precision::Single) { + QubitUnitary::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } else { + QubitUnitary::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + } + case Method::superop: { + if (sim_precision_ == Precision::Single) { + QubitSuperoperator::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } else { + QubitSuperoperator::State> state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + } + case Method::stabilizer: { + Stabilizer::State state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + case Method::extended_stabilizer: { + ExtendedStabilizer::State state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + case Method::matrix_product_state: { + MatrixProductState::State state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } + default: + // We shouldn't get here, so throw an exception if we do + throw std::runtime_error("Controller: Invalid simulation method"); + } +} + +Transpile::Fusion Controller::transpile_fusion(Method method, + const Operations::OpSet &opset, + const json_t &config) const { + Transpile::Fusion fusion_pass; + fusion_pass.set_parallelization(parallel_state_update_); + + if (opset.contains(Operations::OpType::superop)) { + fusion_pass.allow_superop = true; + } + if (opset.contains(Operations::OpType::kraus)) { + fusion_pass.allow_kraus = true; + } + switch (method) { + case Method::density_matrix: + case Method::unitary: + case Method::superop: { + // Halve the default threshold and max fused qubits for density matrix + fusion_pass.threshold /= 2; + fusion_pass.max_qubit /= 2; + break; + } + case Method::matrix_product_state: { + // Disable fusion by default, but allow it to be enabled by config settings + fusion_pass.active = false; + } + case Method::statevector: { + if (fusion_pass.allow_kraus) { + // Halve default max fused qubits for Kraus noise fusion + fusion_pass.max_qubit /= 2; + } + break; + } + default: { + fusion_pass.active = false; + return fusion_pass; + } + } + // Override default fusion settings with custom config + fusion_pass.set_config(config); + return fusion_pass; +} + +void Controller::set_parallelization_circuit_method( + const Circuit &circ, const Noise::NoiseModel &noise_model) { + const auto method = simulation_method(circ, noise_model, false); + switch (method) { + case Method::statevector: + case Method::stabilizer: + case Method::unitary: + case Method::matrix_product_state: { + if (circ.shots == 1 || + (!noise_model.has_quantum_errors() && + check_measure_sampling_opt(circ, method))) { + parallel_shots_ = 1; + parallel_state_update_ = + std::max({1, max_parallel_threads_ / parallel_experiments_}); + return; + } + set_parallelization_circuit(circ, noise_model); + break; + } + case Method::density_matrix: + case Method::superop: { + if (circ.shots == 1 || + check_measure_sampling_opt(circ, method)) { + parallel_shots_ = 1; + parallel_state_update_ = + std::max({1, max_parallel_threads_ / parallel_experiments_}); + return; + } + set_parallelization_circuit(circ, noise_model); + break; + } + default: { + set_parallelization_circuit(circ, noise_model); + } + } +} + +void Controller::set_distributed_parallelization_method( + const std::vector &circuits, + const std::vector &noise) { +#ifdef AER_MPI + uint_t i, ncircuits; + bool sample_opt = true; + + ncircuits = circuits.size(); + for (i = 0; i < ncircuits; i++) { + const auto method = simulation_method(circuits[i], noise[i], false); + switch (method) { + case Method::statevector: + case Method::stabilizer: + case Method::unitary: + case Method::matrix_product_state: { + if (circuits[i].shots > 1 && + (noise[i].has_quantum_errors() || + !check_measure_sampling_opt(circuits[i], method))) { + sample_opt = false; + } + break; + } + case Method::density_matrix: + case Method::superop: { + if (circuits[i].shots > 1 && + !check_measure_sampling_opt(circuits[i], method)) { + sample_opt = false; + } + break; + } + default: { + sample_opt = false; + } + } + if (!sample_opt) { + break; + } + } + + if (sample_opt) { + set_distributed_parallelization(circuits, noise); + + // shots are not distributed + distributed_shots_ = 1; + distributed_shots_rank_ = 0; + } +#endif +} + +//------------------------------------------------------------------------- +// Run circuit helpers +//------------------------------------------------------------------------- + +template +void Controller::run_circuit_helper(const Circuit &circ, + const Noise::NoiseModel &noise, + const json_t &config, uint_t shots, + uint_t rng_seed, const Method method, + bool cache_blocking, + ExperimentResult &result) const { + // Initialize new state object + State_t state; + + // Check memory requirements, raise exception if they're exceeded + validate_memory_requirements(state, circ, true); + + // Set state config + state.set_config(config); + state.set_parallalization(parallel_state_update_); + state.set_distribution(get_distributed_num_processes(shots == circ.shots)); + state.set_global_phase(circ.global_phase_angle); + + // Rng engine + RngEngine rng; + rng.set_seed(rng_seed); + + // Output data container + result.set_config(config); + result.metadata.add(state.name(), "method"); + if (method == Method::statevector || method == Method::density_matrix || + method == Method::unitary) { + result.metadata.add(sim_device_name_, "device"); + } else { + result.metadata.add("CPU", "device"); + } + state.add_metadata(result); + + // Add measure sampling to metadata + // Note: this will set to `true` if sampling is enabled for the circuit + result.metadata.add(false, "measure_sampling"); + // Choose execution method based on noise and method + Circuit opt_circ; + + // Ideal circuit + if (noise.is_ideal()) { + opt_circ = circ; + } + // Readout error only + else if (noise.has_quantum_errors() == false) { + opt_circ = noise.sample_noise(circ, rng); + } + // Superop noise sampling + else if (method == Method::density_matrix || method == Method::superop) { + // Sample noise using SuperOp method + auto noise_superop = noise; + noise_superop.activate_superop_method(); + opt_circ = noise_superop.sample_noise(circ, rng); + } + // Kraus noise sampling + else if (noise.opset().contains(Operations::OpType::kraus) || + noise.opset().contains(Operations::OpType::superop)) { + auto noise_kraus = noise; + noise_kraus.activate_kraus_method(); + opt_circ = noise_kraus.sample_noise(circ, rng); + } + // General circuit noise sampling + else { + run_circuit_with_sampled_noise(circ, noise, config, shots, state, method, + cache_blocking, result, rng); + return; + } + + // Run multishot simulation without noise sampling + run_circuit_without_sampled_noise(opt_circ, config, shots, state, + method, cache_blocking, result, rng); +} + +template +void Controller::run_single_shot(const Circuit &circ, State_t &state, + ExperimentResult &result, + RngEngine &rng) const { + state.initialize_qreg(circ.num_qubits); + state.initialize_creg(circ.num_memory, circ.num_registers); + state.apply_ops(circ.ops, result, rng, true); + save_count_data(result, state.creg()); +} + +template +void Controller::run_circuit_without_sampled_noise(Circuit &circ, + const json_t &config, + uint_t shots, + State_t &state, + const Method method, + bool cache_blocking, + ExperimentResult &result, + RngEngine &rng) const { + // Optimize circuit + Noise::NoiseModel dummy_noise; + Transpile::DelayMeasure measure_pass; + measure_pass.set_config(config); + measure_pass.optimize_circuit(circ, dummy_noise, state.opset(), result); + + auto fusion_pass = transpile_fusion(method, circ.opset(), config); + fusion_pass.optimize_circuit(circ, dummy_noise, state.opset(), result); + + // Check if measure sampling supported + const bool can_sample = check_measure_sampling_opt(circ, method); + + // Cache blocking pass + if (cache_blocking) { + auto cache_block_pass = transpile_cache_blocking(method, circ, dummy_noise, config); + cache_block_pass.set_sample_measure(can_sample); + cache_block_pass.optimize_circuit(circ, dummy_noise, state.opset(), result); + uint_t block_bits = 0; + if (cache_block_pass.enabled()) { + block_bits = cache_block_pass.block_bits(); + } + // allocate qubit register + state.allocate(max_qubits_, block_bits); + } + + // Check if measure sampler and optimization are valid + if (can_sample) { + // Implement measure sampler + auto& ops = circ.ops; + auto pos =circ.first_measure_pos; // Position of first measurement op + auto it_pos = std::next(ops.begin(), pos); + bool final_ops = (pos == ops.size()); + + // Get measurement opts + std::vector meas_ops; + std::move(it_pos, ops.end(), std::back_inserter(meas_ops)); + ops.resize(pos); + + // Run circuit instructions before first measure + state.initialize_qreg(circ.num_qubits); + state.initialize_creg(circ.num_memory, circ.num_registers); + state.apply_ops(ops, result, rng, final_ops); + + // Get measurement operations and set of measured qubits + measure_sampler(meas_ops, shots, state, result, rng); + + // Add measure sampling metadata + result.metadata.add(true, "measure_sampling"); + } else { + // Perform standard execution if we cannot apply the + // measurement sampling optimization + while (shots-- > 0) { + run_single_shot(circ, state, result, rng); + } + } +} + +template +void Controller::run_circuit_with_sampled_noise( + const Circuit &circ, const Noise::NoiseModel &noise, const json_t &config, + uint_t shots, State_t &state, const Method method, bool cache_blocking, + ExperimentResult &result, RngEngine &rng) const { + // Transpilation for circuit noise method + auto fusion_pass = transpile_fusion(method, circ.opset(), config); + auto cache_block_pass = transpile_cache_blocking(method, circ, noise, config); + Transpile::DelayMeasure measure_pass; + measure_pass.set_config(config); + Noise::NoiseModel dummy_noise; + + // Sample noise using circuit method + while (shots-- > 0) { + Circuit noise_circ = noise.sample_noise(circ, rng); + noise_circ.shots = 1; + measure_pass.optimize_circuit(noise_circ, dummy_noise, state.opset(), + result); + fusion_pass.optimize_circuit(noise_circ, dummy_noise, state.opset(), + result); + if (cache_blocking) { + cache_block_pass.optimize_circuit(noise_circ, dummy_noise, state.opset(), + result); + uint_t block_bits = 0; + if (cache_block_pass.enabled()) { + block_bits = cache_block_pass.block_bits(); + } + // allocate qubit register + state.allocate(max_qubits_, block_bits); + } + run_single_shot(noise_circ, state, result, rng); + } +} + +//------------------------------------------------------------------------- +// Measure sampling optimization +//------------------------------------------------------------------------- + +bool Controller::check_measure_sampling_opt(const Circuit &circ, + const Method method) const { + // Check if circuit has sampling flag disabled + if (circ.can_sample == false) { + return false; + } + + // If density matrix or superop method all noise instructions allow + // Sampling + if (method == Method::density_matrix || method == Method::unitary) { + return true; + } + + // Check if non-density matrix simulation and circuit contains + // a stochastic instruction before measurement + // ie. initialize, reset, kraus, superop, conditional + // TODO: + // * If initialize should be allowed if applied to product states (ie start of + // circuit) + // * Resets should be allowed if applied to |0> state (no gates before). + if (circ.opset().contains(Operations::OpType::reset) || + circ.opset().contains(Operations::OpType::initialize) || + circ.opset().contains(Operations::OpType::kraus) || + circ.opset().contains(Operations::OpType::superop)) { + return false; + } + // Otherwise true + return true; +} + +template +void Controller::measure_sampler( + const std::vector &meas_roerror_ops, uint_t shots, + State_t &state, ExperimentResult &result, RngEngine &rng) const { + // Check if meas_circ is empty, and if so return initial creg + if (meas_roerror_ops.empty()) { + while (shots-- > 0) { + save_count_data(result, state.creg()); + } + return; + } + + std::vector meas_ops; + std::vector roerror_ops; + for (const Operations::Op &op : meas_roerror_ops) + if (op.type == Operations::OpType::roerror) + roerror_ops.push_back(op); + else /*(op.type == Operations::OpType::measure) */ + meas_ops.push_back(op); + + // Get measured qubits from circuit sort and delete duplicates + std::vector meas_qubits; // measured qubits + for (const auto &op : meas_ops) { + for (size_t j = 0; j < op.qubits.size(); ++j) + meas_qubits.push_back(op.qubits[j]); + } + sort(meas_qubits.begin(), meas_qubits.end()); + meas_qubits.erase(unique(meas_qubits.begin(), meas_qubits.end()), + meas_qubits.end()); + // Generate the samples + auto all_samples = state.sample_measure(meas_qubits, shots, rng); + + // Make qubit map of position in vector of measured qubits + std::unordered_map qubit_map; + for (uint_t j = 0; j < meas_qubits.size(); ++j) { + qubit_map[meas_qubits[j]] = j; + } + + // Maps of memory and register to qubit position + std::unordered_map memory_map; + std::unordered_map register_map; + for (const auto &op : meas_ops) { + for (size_t j = 0; j < op.qubits.size(); ++j) { + auto pos = qubit_map[op.qubits[j]]; + if (!op.memory.empty()) + memory_map[op.memory[j]] = pos; + if (!op.registers.empty()) + register_map[op.registers[j]] = pos; + } + } + + // Process samples + // Convert opts to circuit so we can get the needed creg sizes + // NB: this function could probably be moved somewhere else like Utils or Ops + Circuit meas_circ(meas_roerror_ops); + ClassicalRegister creg; + while (!all_samples.empty()) { + auto sample = all_samples.back(); + creg.initialize(meas_circ.num_memory, meas_circ.num_registers); + + // process memory bit measurements + for (const auto &pair : memory_map) { + creg.store_measure(reg_t({sample[pair.second]}), reg_t({pair.first}), + reg_t()); + } + // process register bit measurements + for (const auto &pair : register_map) { + creg.store_measure(reg_t({sample[pair.second]}), reg_t(), + reg_t({pair.first})); + } + + // process read out errors for memory and registers + for (const Operations::Op &roerror : roerror_ops) { + creg.apply_roerror(roerror, rng); + } + + // Save count data + save_count_data(result, creg); + + // pop off processed sample + all_samples.pop_back(); + } +} + +//------------------------------------------------------------------------- +} // end namespace AER +//------------------------------------------------------------------------- +#endif diff --git a/src/simulators/superoperator/superoperator_state.hpp b/src/simulators/superoperator/superoperator_state.hpp index 338d9982ac..aa5d825846 100755 --- a/src/simulators/superoperator/superoperator_state.hpp +++ b/src/simulators/superoperator/superoperator_state.hpp @@ -32,6 +32,7 @@ const Operations::OpSet StateOpSet( // Op types {Operations::OpType::gate, Operations::OpType::reset, Operations::OpType::snapshot, Operations::OpType::barrier, + Operations::OpType::bfunc, Operations::OpType::roerror, Operations::OpType::matrix, Operations::OpType::diagonal_matrix, Operations::OpType::kraus, Operations::OpType::superop, Operations::OpType::save_state, Operations::OpType::set_unitary, @@ -42,7 +43,7 @@ const Operations::OpSet StateOpSet( "tdg", "ccx", "r", "rx", "ry", "rz", "rxx", "ryy", "rzz", "rzx", "p", "cp", "cu1", "sx", "x90", "delay"}, // Snapshots - {"superoperator"}); + {"superop"}); // Allowed gates enum class enum class Gates { @@ -70,7 +71,7 @@ class State : public Base::State { //----------------------------------------------------------------------- // Return the string name of the State class - virtual std::string name() const override { return "superoperator"; } + virtual std::string name() const override { return "superop"; } // Apply a sequence of operations by looping over list // If the input is not in allowed_ops an exeption will be raised. @@ -243,6 +244,12 @@ void State::apply_ops(const std::vector &ops, if (BaseState::creg_.check_conditional(op)) apply_gate(op); break; + case Operations::OpType::bfunc: + BaseState::creg_.apply_bfunc(op); + break; + case Operations::OpType::roerror: + BaseState::creg_.apply_roerror(op, rng); + break; case Operations::OpType::reset: apply_reset(op.qubits); break; @@ -496,7 +503,7 @@ void State::apply_snapshot(const Operations::Op &op, ExperimentResult &result) { // Look for snapshot type in snapshotset if (op.name == "superopertor" || op.name == "state") { - BaseState::snapshot_state(op, result, "superoperator"); + BaseState::snapshot_state(op, result, "superop"); } else { throw std::invalid_argument( "QubitSuperoperator::State::invalid snapshot instruction \'" + op.name + diff --git a/src/simulators/superoperator/superoperator_thrust.hpp b/src/simulators/superoperator/superoperator_thrust.hpp new file mode 100755 index 0000000000..205ab56223 --- /dev/null +++ b/src/simulators/superoperator/superoperator_thrust.hpp @@ -0,0 +1,186 @@ +/** + * This code is part of Qiskit. + * + * (C) Copyright IBM 2018, 2019. + * + * This code is licensed under the Apache License, Version 2.0. You may + * obtain a copy of this license in the LICENSE.txt file in the root directory + * of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. + * + * Any modifications or derivative works of this code must retain this + * copyright notice, and modified files need to carry a notice indicating + * that they have been altered from the originals. + */ + +#ifndef _qv_superoperator_thrust_hpp_ +#define _qv_superoperator_thrust_hpp_ + + +#include "framework/utils.hpp" +#include "simulators/density_matrix/densitymatrix_thrust.hpp" + +namespace AER { +namespace QV { + +//============================================================================ +// Superoperator class +//============================================================================ + +// This class is derived from the DensityMatrix class and stores an N-qubit +// superoperator as a 2 * N-qubit vector. +// The vector is formed using column-stacking vectorization of the +// superoperator (itself with respect to column-stacking vectorization). + +template +class SuperoperatorThrust : public DensityMatrixThrust { + +public: + // Parent class aliases + using BaseVector = QubitVectorThrust; + using BaseDensity = DensityMatrixThrust; + using BaseUnitary = UnitaryMatrixThrust; + + //----------------------------------------------------------------------- + // Constructors and Destructor + //----------------------------------------------------------------------- + + SuperoperatorThrust() : SuperoperatorThrust(0) {}; + explicit SuperoperatorThrust(size_t num_qubits); + SuperoperatorThrust(const Superoperator& obj) = delete; + Superoperator &operator=(const Superoperator& obj) = delete; + + //----------------------------------------------------------------------- + // Utility functions + //----------------------------------------------------------------------- + + // Set the size of the vector in terms of qubit number + void set_num_qubits(size_t num_qubits); + + // Returns the number of qubits for the superoperator + virtual uint_t num_qubits() const override {return num_qubits_;} + + // Initialize to the identity superoperator + void initialize(); + + // Initializes the vector to a custom initial state. + // The matrix can either be superoperator matrix or unitary matrix. + // The type is inferred by the dimensions of the input matrix. + template + void initialize_from_matrix(const matrix> &data); + void initialize_from_matrix(matrix> &&data); + +protected: + // Number of qubits for the superoperator + size_t num_qubits_; +}; + +/******************************************************************************* + * + * Implementations + * + ******************************************************************************/ + + +//------------------------------------------------------------------------------ +// Constructors & Destructor +//------------------------------------------------------------------------------ + +template +SuperOperatorThrust::SuperoperatorThrust(size_t num_qubits) { + set_num_qubits(num_qubits); +} + +//------------------------------------------------------------------------------ +// Utility +//------------------------------------------------------------------------------ + +template +void SuperOperatorThrust::set_num_qubits(size_t num_qubits) { + num_qubits_ = num_qubits; + // Superoperator is same size matrix as a unitary matrix + // of twice as many qubits + BaseDensity::set_num_qubits(2 * num_qubits); +} + + +template +void SuperOperatorThrust::initialize() { + // Set underlying unitary matrix to identity + BaseUnitary::initialize(); +} + + +template +template +void SuperOperatorThrust::initialize_from_matrix(const matrix> &mat) { + if (AER::Utils::is_square(mat)) { + const size_t nrows = mat.GetRows(); + if (nrows == BaseUnitary::rows_) { + // The matrix is the same size as the superoperator matrix so we + // initialze as the matrix. + BaseUnitary::initialize_from_matrix(mat); + return; + } else if (nrows * nrows == BaseUnitary::rows_) { + // If the input matrix has half the number of rows we assume it is + // A unitary matrix input so we convert to a superoperator + BaseUnitary::initialize_from_matrix( + AER::Utils::tensor_product(AER::Utils::conjugate(mat), mat) + ); + return; + } + } + // Throw an exception if the input matrix is the wrong size for + // unitary or superoperator input + throw std::runtime_error( + "Superoperator::initial matrix is wrong size (" + + std::to_string(BaseUnitary::rows_) + "," + + std::to_string(BaseUnitary::rows_) + ")!=(" + + std::to_string(mat.GetRows()) + "," + std::to_string(mat.GetColumns()) + ")." + ); +} + +template +void SuperOperatorThrust::initialize_from_matrix(matrix> &&mat) { + if (AER::Utils::is_square(mat)) { + const size_t nrows = mat.GetRows(); + if (nrows == BaseUnitary::rows_) { + // The matrix is the same size as the superoperator matrix so we + // initialze as the matrix. + BaseUnitary::initialize_from_matrix(std::move(mat)); + return; + } else if (nrows * nrows == BaseUnitary::rows_) { + // If the input matrix has half the number of rows we assume it is + // A unitary matrix input so we convert to a superoperator + BaseUnitary::initialize_from_matrix( + AER::Utils::tensor_product(AER::Utils::conjugate(mat), mat) + ); + return; + } + } + // Throw an exception if the input matrix is the wrong size for + // unitary or superoperator input + throw std::runtime_error( + "Superoperator::initial matrix is wrong size (" + + std::to_string(BaseUnitary::rows_) + "," + + std::to_string(BaseUnitary::rows_) + ")!=(" + + std::to_string(mat.GetRows()) + "," + std::to_string(mat.GetColumns()) + ")." + ); +} + + + +//------------------------------------------------------------------------------ +} // end namespace QV +} // end namespace AER +//------------------------------------------------------------------------------ + +// ostream overload for templated qubitvector +template +inline std::ostream &operator<<(std::ostream &out, const AER::QV::SuperOperatorThrust&m) { + out << m.copy_to_matrix(); + return out; +} + +//------------------------------------------------------------------------------ +#endif // end module + diff --git a/src/simulators/unitary/unitary_state.hpp b/src/simulators/unitary/unitary_state.hpp index 448e29917c..8f64e37ab9 100755 --- a/src/simulators/unitary/unitary_state.hpp +++ b/src/simulators/unitary/unitary_state.hpp @@ -35,6 +35,7 @@ namespace QubitUnitary { const Operations::OpSet StateOpSet( // Op types {Operations::OpType::gate, Operations::OpType::barrier, + Operations::OpType::bfunc, Operations::OpType::roerror, Operations::OpType::matrix, Operations::OpType::diagonal_matrix, Operations::OpType::snapshot, Operations::OpType::save_unitary, Operations::OpType::save_state, Operations::OpType::set_unitary}, @@ -270,6 +271,12 @@ void State::apply_ops( switch (op.type) { case Operations::OpType::barrier: break; + case Operations::OpType::bfunc: + BaseState::creg_.apply_bfunc(op); + break; + case Operations::OpType::roerror: + BaseState::creg_.apply_roerror(op, rng); + break; case Operations::OpType::gate: // Note conditionals will always fail since no classical registers if (BaseState::creg_.check_conditional(op)) From 4aab5985db08ea0737e497550999b22e211c3473 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 24 Mar 2021 12:27:40 -0400 Subject: [PATCH 02/14] Add AerSimulator backend --- README.md | 16 +- qiskit/providers/aer/__init__.py | 1 + qiskit/providers/aer/aerprovider.py | 47 +- qiskit/providers/aer/backends/__init__.py | 1 + .../providers/aer/backends/aer_simulator.py | 707 ++++++++++++++++++ qiskit/providers/aer/backends/aerbackend.py | 17 +- .../providers/aer/backends/backend_utils.py | 19 + qiskit/providers/aer/noise/noise_model.py | 4 + 8 files changed, 789 insertions(+), 23 deletions(-) create mode 100644 qiskit/providers/aer/backends/aer_simulator.py diff --git a/README.md b/README.md index dce7fad753..751dcaccd8 100755 --- a/README.md +++ b/README.md @@ -48,20 +48,20 @@ $ python ```python import qiskit from qiskit import IBMQ -from qiskit.providers.aer import QasmSimulator +from qiskit.providers.aer import AerSimulator # Generate 3-qubit GHZ state -circ = qiskit.QuantumCircuit(3, 3) +circ = qiskit.QuantumCircuit(3) circ.h(0) circ.cx(0, 1) circ.cx(1, 2) -circ.measure([0, 1, 2], [0, 1 ,2]) +circ.measure_all() # Construct an ideal simulator -sim = QasmSimulator() +aersim = AerSimulator() # Perform an ideal simulation -result_ideal = qiskit.execute(circ, sim).result() +result_ideal = qiskit.execute(circ, aersim).result() counts_ideal = result_ideal.get_counts(0) print('Counts(ideal):', counts_ideal) # Counts(ideal): {'000': 493, '111': 531} @@ -70,11 +70,11 @@ print('Counts(ideal):', counts_ideal) # This simulator backend will be automatically configured # using the device configuration and noise model provider = IBMQ.load_account() -vigo_backend = provider.get_backend('ibmq_vigo') -vigo_sim = QasmSimulator.from_backend(vigo_backend) +backend = provider.get_backend('ibmq_athens') +aersim_backend = AerSimulator.from_backend(backend) # Perform noisy simulation -result_noise = qiskit.execute(circ, vigo_sim).result() +result_noise = qiskit.execute(circ, aersim_backend).result() counts_noise = result_noise.get_counts(0) print('Counts(noise):', counts_noise) diff --git a/qiskit/providers/aer/__init__.py b/qiskit/providers/aer/__init__.py index ee562147ee..20f57960a2 100644 --- a/qiskit/providers/aer/__init__.py +++ b/qiskit/providers/aer/__init__.py @@ -31,6 +31,7 @@ .. autosummary:: :toctree: ../stubs/ + AerSimulator QasmSimulator StatevectorSimulator UnitarySimulator diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index 8d26926843..ec144b7ed3 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -16,6 +16,7 @@ from qiskit.providers import ProviderV1 as Provider from qiskit.providers.providerutils import filter_backends +from .backends.aer_simulator import AerSimulator from .backends.qasm_simulator import QasmSimulator from .backends.statevector_simulator import StatevectorSimulator from .backends.unitary_simulator import UnitarySimulator @@ -25,14 +26,37 @@ class AerProvider(Provider): """Provider for Qiskit Aer backends.""" + _BACKENDS = None + def __init__(self): - # Populate the list of Aer simulator backends. - self._backends = [ - ('qasm_simulator', QasmSimulator), - ('statevector_simulator', StatevectorSimulator), - ('unitary_simulator', UnitarySimulator), - ('pulse_simulator', PulseSimulator) - ] + if AerProvider._BACKENDS is None: + # Populate the list of Aer simulator backends. + methods = AerSimulator().available_methods() + devices = AerSimulator().available_devices() + backends = [] + for method in methods: + name = 'aer_simulator' + if name != 'automatic': + name += f'_{method}' + device_name = 'CPU' + backends.append((name, AerSimulator, method, device_name)) + + # Add GPU device backends + if method in ['statevector', 'density_matrix', 'unitary']: + for device in devices: + if device != 'CPU': + new_name = f'{name}_{device}'.lower() + device_name = device + backends.append((new_name, AerSimulator, method, device_name)) + + # Add legacy backend names + backends += [ + ('qasm_simulator', QasmSimulator, None, None), + ('statevector_simulator', StatevectorSimulator, None, None), + ('unitary_simulator', UnitarySimulator, None, None), + ('pulse_simulator', PulseSimulator, None, None) + ] + AerProvider._BACKENDS = backends def get_backend(self, name=None, **kwargs): return super().get_backend(name=name, **kwargs) @@ -42,9 +66,14 @@ def backends(self, name=None, filters=None, **kwargs): # Instantiate a new backend instance so if config options # are set they will only last as long as that backend object exists backends = [] - for backend_name, backend_cls in self._backends: + for backend_name, backend_cls, method, device in self._BACKENDS: + opts = {'provider': self} + if method is not None: + opts['method'] = method + if device is not None: + opts['device'] = device if name is None or backend_name == name: - backends.append(backend_cls(provider=self)) + backends.append(backend_cls(**opts)) return filter_backends(backends, filters=filters) def __str__(self): diff --git a/qiskit/providers/aer/backends/__init__.py b/qiskit/providers/aer/backends/__init__.py index 669afa8a78..70d7977044 100644 --- a/qiskit/providers/aer/backends/__init__.py +++ b/qiskit/providers/aer/backends/__init__.py @@ -14,6 +14,7 @@ Aer Provider Simulator Backends """ +from .aer_simulator import AerSimulator from .qasm_simulator import QasmSimulator from .statevector_simulator import StatevectorSimulator from .unitary_simulator import UnitarySimulator diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py new file mode 100644 index 0000000000..a404fb1f32 --- /dev/null +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -0,0 +1,707 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019, 2021 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Qiskit Aer qasm simulator backend. +""" + +import copy +import logging +from qiskit.providers.options import Options +from qiskit.providers.models import QasmBackendConfiguration + +from ..version import __version__ +from .aerbackend import AerBackend, AerError +from .backend_utils import (cpp_execute, available_methods, + available_devices, + MAX_QUBITS_STATEVECTOR) +# pylint: disable=import-error, no-name-in-module +from .controller_wrappers import aer_controller_execute + +logger = logging.getLogger(__name__) + + +class AerSimulator(AerBackend): + """ + Noisy quantum circuit simulator backend. + + **Configurable Options** + + The `AerSimulator` supports multiple simulation methods and + configurable options for each simulation method. These may be set using the + appropriate kwargs during initialization. They can also be set of updated + using the :meth:`set_options` method. + + Run-time options may also be specified as kwargs using the :meth:`run` method. + These will not be stored in the backend and will only apply to that execution. + They will also override any previously set options. + + For example, to configure a density matrix simulator with a custom noise + model to use for every execution + + .. code-block:: python + + noise_model = NoiseModel.from_backend(backend) + backend = AerSimulator(method='density_matrix', + noise_model=noise_model) + + **Simulating an IBMQ Backend** + + The simulator can be automatically configured to mimic an IBMQ backend using + the :meth:`from_backend` method. This will configure the simulator to use the + basic device :class:`NoiseModel` for that backend, and the same basis gates + and coupling map. + + .. code-block:: python + + backend = AerSimulator.from_backend(backend) + + **Returning the Final State** + + The final state of the simulator can be saved to the returned + ``Result`` object by appending the + :func:`~qiskit.providers.aer.library.save_state` instruction to a + quantum circuit. The format of the final state will depend on the + simulation method used. Additional simulation data may also be saved + using the other save instructions in :mod:`qiskit.provider.aer.library`. + + **Simulation Method Option** + + The simulation method is set using the ``method`` kwarg. A list supported + simulation methods can be returned using :meth:`available_methods`, these + are + + * ``"automatic"``: Default simulation method. Either the "statevector", + "density_matrix", or "stabilizer" simulation method is selected + automatically at runtime for each circuit based on the circuit + instructions, number of qubits, and noise model. + + * ``"statevector"``: A dense statevector simulation that can sample + measurement outcomes from *ideal* circuits with all measurements at + end of the circuit. For noisy simulations each shot samples a + randomly sampled noisy circuit from the noise model. Supports CPU and + GPU devices. + + * ``"density_matrix"``: A dense density matrix simulation that may + sample measurement outcomes from *noisy* circuits with all + measurements at end of the circuit. Supports CPU and GPU devices. + + * ``"stabilizer"``: An efficient Clifford stabilizer state simulator + that can simulate noisy Clifford circuits if all errors in the noise + model are also Clifford errors. Supports CPU device only. + + * ``"extended_stabilizer"``: An approximate simulated for Clifford + T + circuits based on a state decomposition into ranked-stabilizer state. + The number of terms grows with the number of non-Clifford (T) gates. + Supports CPU device only. + + * ``"matrix_product_state"``: A tensor-network statevector simulator that + uses a Matrix Product State (MPS) representation for the state. This + can be done either with or without truncation of the MPS bond dimensions + depending on the simulator options. The default behaviour is no + truncation. Supports CPU device only. + + * ``"unitary"``: A dense unitary matrix simulation of an ideal circuit. + This simulates the unitary matrix of the circuit itself rather than + the evolution of an initial quantum state. This method can only + simulate gates, it does not support measurement, reset, or noise. + Supports CPU and GPU devices. + + * ``"superop"``: A dense superoperator matrix simulation of an ideal or + noisy circuit. This simulates the superoperator matrix of the circuit + itself rather than the evolution of an initial quantum state. This method + can simulate ideal and noisy gates, and reset, but does not support + measurement. Supports CPU device only. + + **GPU Simulation** + + By default all simulation methods run on the CPU, however select methods + also support running on a GPU if qiskit-aer was installed with GPU support + on a compatible NVidia GPU and CUDA version. + + +--------------------------+---------------+ + | Method | GPU Supported | + +==========================+===============+ + | ``automatic`` | Sometimes | + +--------------------------+---------------+ + | ``statevector`` | Yes | + +--------------------------+---------------+ + | ``density_matrix`` | Yes | + +--------------------------+---------------+ + | ``unitary`` | Yes | + +--------------------------+---------------+ + | ``stabilizer`` | No | + +--------------------------+---------------+ + | `"matrix_product_state`` | No | + +--------------------------+---------------+ + | ``extended_stabilizer`` | No | + +--------------------------+---------------+ + | ``superop`` | No | + +--------------------------+---------------+ + + Running a GPU simulation is done using ``device="GPU"`` kwarg during + initialization or with :meth:`set_options`. The list of supported devices + for the current system can be returned using :meth:`available_devices`. + + **Additional Backend Options** + + The following simulator specific backend options are supported + + * ``method`` (str): Set the simulation method (Default: ``"automatic"``). + + * ``device`` (str): Set the simulation device (Default: ``"CPU"``). + + * ``precision`` (str): Set the floating point precision for + certain simulation methods to either ``"single"`` or ``"double"`` + precision (default: ``"double"``). + + * ``zero_threshold`` (double): Sets the threshold for truncating + small values to zero in the result data (Default: 1e-10). + + * ``validation_threshold`` (double): Sets the threshold for checking + if initial states are valid (Default: 1e-8). + + * ``max_parallel_threads`` (int): Sets the maximum number of CPU + cores used by OpenMP for parallelization. If set to 0 the + maximum will be set to the number of CPU cores (Default: 0). + + * ``max_parallel_experiments`` (int): Sets the maximum number of + qobj experiments that may be executed in parallel up to the + max_parallel_threads value. If set to 1 parallel circuit + execution will be disabled. If set to 0 the maximum will be + automatically set to max_parallel_threads (Default: 1). + + * ``max_parallel_shots`` (int): Sets the maximum number of + shots that may be executed in parallel during each experiment + execution, up to the max_parallel_threads value. If set to 1 + parallel shot execution will be disabled. If set to 0 the + maximum will be automatically set to max_parallel_threads. + Note that this cannot be enabled at the same time as parallel + experiment execution (Default: 0). + + * ``max_memory_mb`` (int): Sets the maximum size of memory + to store a state vector. If a state vector needs more, an error + is thrown. In general, a state vector of n-qubits uses 2^n complex + values (16 Bytes). If set to 0, the maximum will be automatically + set to the system memory size (Default: 0). + + * ``optimize_ideal_threshold`` (int): Sets the qubit threshold for + applying circuit optimization passes on ideal circuits. + Passes include gate fusion and truncation of unused qubits + (Default: 5). + + * ``optimize_noise_threshold`` (int): Sets the qubit threshold for + applying circuit optimization passes on ideal circuits. + Passes include gate fusion and truncation of unused qubits + (Default: 12). + + These backend options only apply when using the ``"statevector"`` + simulation method: + + * ``statevector_parallel_threshold`` (int): Sets the threshold that + the number of qubits must be greater than to enable OpenMP + parallelization for matrix multiplication during execution of + an experiment. If parallel circuit or shot execution is enabled + this will only use unallocated CPU cores up to + max_parallel_threads. Note that setting this too low can reduce + performance (Default: 14). + + * ``statevector_sample_measure_opt`` (int): Sets the threshold that + the number of qubits must be greater than to enable a large + qubit optimized implementation of measurement sampling. Note + that setting this two low can reduce performance (Default: 10) + + These backend options only apply when using the ``"stabilizer"`` + simulation method: + + * ``stabilizer_max_snapshot_probabilities`` (int): set the maximum + qubit number for the + `~qiskit.providers.aer.extensions.SnapshotProbabilities` + instruction (Default: 32). + + These backend options only apply when using the ``"extended_stabilizer"`` + simulation method: + + * ``extended_stabilizer_sampling_methid`` (string): Choose how to simulate + measurements on qubits. The performance of the simulator depends + significantly on this choice. In the following, let n be the number of + qubits in the circuit, m the number of qubits measured, and S be the + number of shots. (Default: resampled_metropolis) + + * ``"metropolis"``: Use a Monte-Carlo method to sample many output + strings from the simulator at once. To be accurate, this method + requires that all the possible output strings have a non-zero + probability. It will give inaccurate results on cases where + the circuit has many zero-probability outcomes. + This method has an overall runtime that scales as n^{2} + (S-1)n. + + * ``"resampled_metropolis"``: A variant of the metropolis method, + where the Monte-Carlo method is reinitialised for every shot. This + gives better results for circuits where some outcomes have zero + probability, but will still fail if the output distribution + is sparse. The overall runtime scales as Sn^{2}. + + * ``"norm_estimation"``: An alternative sampling method using + random state inner products to estimate outcome probabilites. This + method requires twice as much memory, and significantly longer + runtimes, but gives accurate results on circuits with sparse + output distributions. The overall runtime scales as Sn^{3}m^{3}. + + * ``extended_stabilizer_metropolis_mixing_time`` (int): Set how long the + monte-carlo method runs before performing measurements. If the + output distribution is strongly peaked, this can be decreased + alongside setting extended_stabilizer_disable_measurement_opt + to True (Default: 5000). + + * ``"extended_stabilizer_approximation_error"`` (double): Set the error + in the approximation for the extended_stabilizer method. A + smaller error needs more memory and computational time + (Default: 0.05). + + * ``extended_stabilizer_norm_estimation_samples`` (int): The default number + of samples for the norm estimation sampler. The method will use the + default, or 4m^{2} samples where m is the number of qubits to be + measured, whichever is larger (Default: 100). + + * ``extended_stabilizer_norm_estimation_repetitions`` (int): The number + of times to repeat the norm estimation. The median of these reptitions + is used to estimate and sample output strings (Default: 3). + + * ``extended_stabilizer_parallel_threshold`` (int): Set the minimum + size of the extended stabilizer decomposition before we enable + OpenMP parallelization. If parallel circuit or shot execution + is enabled this will only use unallocated CPU cores up to + max_parallel_threads (Default: 100). + + * ``extended_stabilizer_probabilities_snapshot_samples`` (int): If using + the metropolis or resampled_metropolis sampling method, set the number of + samples used to estimate probabilities in a probabilities snapshot + (Default: 3000). + + These backend options only apply when using the ``"matrix_product_state"`` + simulation method: + + * ``matrix_product_state_max_bond_dimension`` (int): Sets a limit + on the number of Schmidt coefficients retained at the end of + the svd algorithm. Coefficients beyond this limit will be discarded. + (Default: None, i.e., no limit on the bond dimension). + + * ``matrix_product_state_truncation_threshold`` (double): + Discard the smallest coefficients for which the sum of + their squares is smaller than this threshold. + (Default: 1e-16). + + * ``mps_sample_measure_algorithm`` (str): + Choose which algorithm to use for ``"sample_measure"``. ``"mps_probabilities"`` + means all state probabilities are computed and measurements are based on them. + It is more efficient for a large number of shots, small number of qubits and low + entanglement. ``"mps_apply_measure"`` creates a copy of the mps structure and + makes a measurement on it. It is more effients for a small number of shots, high + number of qubits, and low entanglement. If the user does not specify the algorithm, + a heuristic algorithm is used to select between the two algorithms. + (Default: "mps_heuristic"). + + These backend options apply in circuit optimization passes: + + * ``fusion_enable`` (bool): Enable fusion optimization in circuit + optimization passes [Default: True] + * ``fusion_verbose`` (bool): Output gates generated in fusion optimization + into metadata [Default: False] + * ``fusion_max_qubit`` (int): Maximum number of qubits for a operation generated + in a fusion optimization [Default: 5] + * ``fusion_threshold`` (int): Threshold that number of qubits must be greater + than or equal to enable fusion optimization [Default: 14] + """ + # Supported basis gates for each simulation method + _BASIS_GATES = { + 'statevector': sorted([ + 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', + 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', + 'cy', 'cz', 'csx', 'cp', 'cu1', 'cu2', 'cu3', 'rxx', 'ryy', + 'rzz', 'rzx', 'ccx', 'cswap', 'mcx', 'mcy', 'mcz', 'mcsx', + 'mcphase', 'mcu1', 'mcu2', 'mcu3', 'mcrx', 'mcry', 'mcrz', + 'mcr', 'mcswap', 'unitary', 'diagonal', 'multiplexer', + 'initialize', 'delay', 'pauli', 'mcx_gray' + ]), + 'density_matrix': sorted([ + 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', + 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', + 'cy', 'cz', 'cp', 'cu1', 'rxx', 'ryy', 'rzz', 'rzx', 'ccx', + 'unitary', 'diagonal', 'delay', 'pauli', + ]), + 'matrix_product_state': sorted([ + 'u1', 'u2', 'u3', 'u', 'p', 'cp', 'cx', 'cy', 'cz', 'id', 'x', 'y', 'z', 'h', 's', + 'sdg', 'sx', 't', 'tdg', 'swap', 'ccx', 'unitary', 'roerror', 'delay', + 'r', 'rx', 'ry', 'rz', 'rxx', 'ryy', 'rzz', 'rzx', 'csx', 'cswap', 'diagonal', + 'initialize' + ]), + 'stabilizer': sorted([ + 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'sx', 'cx', 'cy', 'cz', + 'swap', 'delay', + ]), + 'extended_stabilizer': sorted([ + 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'sx', + 'swap', 'u0', 't', 'tdg', 'u1', 'p', 'ccx', 'ccz', 'delay' + ]), + 'unitary': sorted([ + 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', + 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', + 'cy', 'cz', 'csx', 'cp', 'cu1', 'cu2', 'cu3', 'rxx', 'ryy', + 'rzz', 'rzx', 'ccx', 'cswap', 'mcx', 'mcy', 'mcz', 'mcsx', + 'mcp', 'mcu1', 'mcu2', 'mcu3', 'mcrx', 'mcry', 'mcrz', + 'mcr', 'mcswap', 'unitary', 'diagonal', 'multiplexer', 'delay', 'pauli', + ]), + 'superop': sorted([ + 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', + 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', + 'cy', 'cz', 'cp', 'cu1', 'rxx', 'ryy', + 'rzz', 'rzx', 'ccx', 'unitary', 'diagonal', 'delay', + ]) + } + # Automatic method basis gates are the union of statevector, + # density matrix, and stabilizer methods + _BASIS_GATES[None] = _BASIS_GATES['automatic'] = sorted( + set(_BASIS_GATES['statevector']).union( + _BASIS_GATES['stabilizer']).union( + _BASIS_GATES['density_matrix'])) + + _CUSTOM_INSTR = { + 'statevector': sorted([ + 'roerror', 'kraus', 'snapshot', 'save_expval', 'save_expval_var', + 'save_probabilities', 'save_probabilities_dict', + 'save_amplitudes', 'save_amplitudes_sq', + 'save_density_matrix', 'save_state', 'save_statevector', + 'save_statevector_dict', 'set_statevector' + ]), + 'density_matrix': sorted([ + 'roerror', 'kraus', 'superop', 'snapshot', + 'save_state', 'save_expval', 'save_expval_var', + 'save_probabilities', 'save_probabilities_dict', + 'save_density_matrix', 'save_amplitudes_sq', + 'set_density_matrix' + ]), + 'matrix_product_state': sorted([ + 'roerror', 'snapshot', 'kraus', 'save_expval', 'save_expval_var', + 'save_probabilities', 'save_probabilities_dict', + 'save_state', 'save_matrix_product_state', 'save_statevector', + 'save_density_matrix', 'save_amplitudes', 'save_amplitudes_sq' + ]), + 'stabilizer': sorted([ + 'roerror', 'snapshot', 'save_expval', 'save_expval_var', + 'save_probabilities', 'save_probabilities_dict', + 'save_amplitudes_sq', 'save_state', 'save_stabilizer', + 'set_stabilizer' + ]), + 'extended_stabilizer': sorted([ + 'roerror', 'snapshot', 'save_statevector', + 'save_expval', 'save_expval_var' + ]), + 'unitary': sorted([ + 'snapshot', 'save_state', 'save_unitary', 'set_unitary' + ]), + 'superop': sorted([ + 'kraus', 'superop', 'save_state', 'save_superop', 'set_superop' + ]) + } + # Automatic method custom instructions are the union of statevector, + # density matrix, and stabilizer methods + _CUSTOM_INSTR[None] = _CUSTOM_INSTR['automatic'] = sorted( + set(_CUSTOM_INSTR['statevector']).union( + _CUSTOM_INSTR['stabilizer']).union( + _CUSTOM_INSTR['density_matrix'])) + + _DEFAULT_CONFIGURATION = { + 'backend_name': 'aer_simulator', + 'backend_version': __version__, + 'n_qubits': MAX_QUBITS_STATEVECTOR, + 'url': 'https://github.com/Qiskit/qiskit-aer', + 'simulator': True, + 'local': True, + 'conditional': True, + 'open_pulse': False, + 'memory': True, + 'max_shots': int(1e6), + 'description': 'A C++ QasmQobj simulator with noise', + 'coupling_map': None, + 'basis_gates': _BASIS_GATES['automatic'], + 'custom_instructions': _CUSTOM_INSTR['automatic'], + 'gates': [] + } + + _SIMULATION_METHODS = [ + 'automatic', 'statevector', 'density_matrix', + 'stabilizer', 'matrix_product_state', 'extended_stabilizer', + 'unitary', 'superop' + ] + + _AVAILABLE_METHODS = None + + _SIMULATION_DEVICES = ['CPU', 'GPU', 'Thrust'] + + _AVAILABLE_DEVICES = None + + def __init__(self, + configuration=None, + properties=None, + provider=None, + **backend_options): + + self._controller = aer_controller_execute() + + # Update available methods and devices for class + if AerSimulator._AVAILABLE_METHODS is None: + AerSimulator._AVAILABLE_METHODS = available_methods( + self._controller, AerSimulator._SIMULATION_METHODS) + if AerSimulator._AVAILABLE_DEVICES is None: + AerSimulator._AVAILABLE_DEVICES = available_devices( + self._controller, AerSimulator._SIMULATION_DEVICES) + + # Default configuration + if configuration is None: + configuration = QasmBackendConfiguration.from_dict( + AerSimulator._DEFAULT_CONFIGURATION) + + super().__init__(configuration, + properties=properties, + available_methods=AerSimulator._AVAILABLE_METHODS, + provider=provider, + backend_options=backend_options) + + @classmethod + def _default_options(cls): + return Options( + # Global options + shots=1024, + method=None, + device=None, + precision="double", + zero_threshold=1e-10, + validation_threshold=None, + max_parallel_threads=None, + max_parallel_experiments=None, + max_parallel_shots=None, + max_memory_mb=None, + optimize_ideal_threshold=5, + optimize_noise_threshold=12, + fusion_enable=True, + fusion_verbose=False, + fusion_max_qubit=5, + fusion_threshold=14, + accept_distributed_results=None, + blocking_qubits=None, + memory=None, + noise_model=None, + # statevector options + statevector_parallel_threshold=14, + statevector_sample_measure_opt=10, + # stabilizer options + stabilizer_max_snapshot_probabilities=32, + # extended stabilizer options + extended_stabilizer_sampling_method='resampled_metropolis', + extended_stabilizer_metropolis_mixing_time=5000, + extended_stabilizer_approximation_error=0.05, + extended_stabilizer_norm_estimation_samples=100, + extended_stabilizer_norm_estimation_repitions=3, + extended_stabilizer_parallel_threshold=100, + extended_stabilizer_probabilities_snapshot_samples=3000, + # MPS options + matrix_product_state_truncation_threshold=1e-16, + matrix_product_state_max_bond_dimension=None, + mps_sample_measure_algorithm='mps_heuristic', + chop_threshold=1e-8, + mps_parallel_threshold=14, + mps_omp_threads=1) + + def __repr__(self): + """String representation of an AerSimulator.""" + display = super().__repr__() + noise_model = getattr(self.options, 'noise_model', None) + 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)) + + def name(self): + """Format backend name string for simulator""" + name = self._configuration.backend_name + method = getattr(self.options, 'method', None) + if method not in [None, 'automatic']: + name += f'_{method}' + device = getattr(self.options, 'device', None) + if device not in [None, 'CPU']: + name += f'_{device}'.lower() + return name + + @classmethod + def from_backend(cls, backend, **options): + """Initialize simulator from backend.""" + # pylint: disable=import-outside-toplevel + # Avoid cyclic import + from ..noise.noise_model import NoiseModel + + # Get configuration and properties from backend + configuration = copy.copy(backend.configuration()) + properties = copy.copy(backend.properties()) + + # Customize configuration name + name = configuration.backend_name + configuration.backend_name = 'aer_simulator({})'.format(name) + + # Use automatic noise model if none is provided + if 'noise_model' not in options: + noise_model = NoiseModel.from_backend(backend) + if not noise_model.is_ideal(): + options['noise_model'] = noise_model + + # Initialize simulator + sim = cls(configuration=configuration, + properties=properties, + **options) + return sim + + def available_devices(self): + """Return the available simulation methods.""" + return self._AVAILABLE_DEVICES + + def configuration(self): + """Return the simulator backend configuration. + + Returns: + BackendConfiguration: the configuration for the backend. + """ + # Update basis gates based on custom options, config, method, + # and noise model + basis_gates = self._basis_gates() + method = getattr(self.options, 'method', 'automatic') + custom_inst = self._CUSTOM_INSTR[method] + config = super().configuration() + config.custom_instructions = custom_inst + config.basis_gates = basis_gates + custom_inst + # Update simulator name + config.backend_name = self.name() + return config + + def _execute(self, qobj): + """Execute a qobj on the backend. + + Args: + qobj (QasmQobj): simulator input. + + Returns: + dict: return a dictionary of results. + """ + return cpp_execute(self._controller, qobj) + + def set_options(self, **fields): + out_options = {} + for key, value in fields.items(): + if key == 'method': + self._set_method_config(value) + out_options[key] = value + elif key == 'device': + if value is not None and value not in self._AVAILABLE_DEVICES: + raise AerError( + "Invalid simulation device {}. Available devices" + " are: {}".format(value, self._AVAILABLE_DEVICES)) + out_options[key] = value + elif key == 'custom_instructions': + self._set_configuration_option(key, value) + else: + out_options[key] = value + super().set_options(**out_options) + + def _validate(self, qobj): + """Semantic validations of the qobj which cannot be done via schemas. + + Warn if no measure or save instructions in run circuits. + """ + for experiment in qobj.experiments: + # If circuit does not contain measurement or save + # instructions raise a warning + no_data = True + for op in experiment.instructions: + if op.name == "measure" or op.name[:5] == "save_": + no_data = False + break + if no_data: + logger.warning( + 'No measure or save instruction in circuit "%s": ' + 'results will be empty.', + experiment.header.name) + + def _basis_gates(self): + """Return simualtor basis gates. + + This will be the option value of basis gates if it was set, + otherwise it will be the intersection of the configuration, noise model + and method supported basis gates. + """ + # Use option value for basis gates if set + if 'basis_gates' in self._options_configuration: + return self._options_configuration['basis_gates'] + + # Set basis gates to be the intersection of config, method, and noise model + # basis gates + config_gates = self._configuration.basis_gates + basis_gates = set(config_gates) + + # Compute intersection with method basis gates + method = getattr(self._options, 'method', 'automatic') + method_gates = self._BASIS_GATES[method] + basis_gates = basis_gates.intersection(method_gates) + + # Compute intersection with noise model basis gates + noise_model = getattr(self.options, 'noise_model', None) + if noise_model: + noise_gates = noise_model.basis_gates + basis_gates = basis_gates.intersection(noise_gates) + else: + noise_gates = None + + if not basis_gates: + logger.warning( + "The intersection of configuration basis gates (%s), " + "simulation method basis gates (%s), and " + "noise model basis gates (%s) is empty", + config_gates, method_gates, noise_gates) + return sorted(basis_gates) + + def _set_method_config(self, method=None): + """Set non-basis gate options when setting method""" + super().set_options(method=method) + # Update configuration description and number of qubits + if method == 'statevector': + description = 'A C++ statevector simulator with noise' + n_qubits = MAX_QUBITS_STATEVECTOR + elif method == 'density_matrix': + description = 'A C++ density matrix simulator with noise' + n_qubits = MAX_QUBITS_STATEVECTOR // 2 + elif method == 'untiary': + description = 'A C++ unitary matrix simulator' + n_qubits = MAX_QUBITS_STATEVECTOR // 2 + elif method == 'superop': + description = 'A C++ superop matrix simulator with noise' + n_qubits = MAX_QUBITS_STATEVECTOR // 4 + elif method == 'matrix_product_state': + description = 'A C++ matrix product state simulator with noise' + n_qubits = 63 # TODO: not sure what to put here? + elif method == 'stabilizer': + description = 'A C++ Clifford stabilizer simulator with noise' + n_qubits = 10000 # TODO: estimate from memory + elif method == 'extended_stabilizer': + description = 'A C++ Clifford+T extended stabilizer simulator with noise' + n_qubits = 63 # TODO: estimate from memory + else: + # Clear options to default + description = None + n_qubits = None + self._set_configuration_option('description', description) + self._set_configuration_option('n_qubits', n_qubits) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 2399351610..38ea67df4c 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -104,8 +104,7 @@ def __init__(self, # Set options from backend_options dictionary if backend_options is not None: - for key, val in backend_options.items(): - self.set_option(key, val) + self.set_options(**backend_options) # pylint: disable=arguments-differ @deprecate_arguments({'qobj': 'circuits'}) @@ -282,6 +281,14 @@ def _run(self, qobj, job_id=''): # Add execution time output["time_taken"] = time.time() - start + + # Display warning if simulation failed + if not output.get("success", False): + msg = "Simulation failed" + if "status" in output: + msg += f" and returned the following error message:\n{output['status']}" + logger.warning(msg) + return Result.from_dict(output) @abstractmethod @@ -314,7 +321,7 @@ def set_option(self, key, value): AerError: if key is 'method' and val isn't in available methods. """ # If key is method, we validate it is one of the available methods - if key == 'method' and value not in self._available_methods: + if (key == 'method' and value is not None and value not in self._available_methods): raise AerError("Invalid simulation method {}. Available methods" " are: {}".format(value, self._available_methods)) @@ -409,7 +416,5 @@ def _run_config( def __repr__(self): """String representation of an AerBackend.""" name = self.__class__.__name__ - display = f"backend_name='{self.name()}'" - if self.provider(): - display += f', provider={self.provider()}()' + display = f"'{self.name()}'" return f'{name}({display})' diff --git a/qiskit/providers/aer/backends/backend_utils.py b/qiskit/providers/aer/backends/backend_utils.py index 5db843f850..ed0e0ecc21 100644 --- a/qiskit/providers/aer/backends/backend_utils.py +++ b/qiskit/providers/aer/backends/backend_utils.py @@ -58,3 +58,22 @@ def available_methods(controller, methods): if result.get('success', False): valid_methods.append(method) return valid_methods + + +def available_devices(controller, devices): + """Check available simulation devices by running a dummy circuit.""" + # Test methods are available using the controller + dummy_circ = QuantumCircuit(1) + dummy_circ.i(0) + + valid_devices = [] + for device in devices: + qobj = assemble(dummy_circ, + optimization_level=0, + shots=1, + method="statevector", + device=device) + result = cpp_execute(controller, qobj) + if result.get('success', False): + valid_devices.append(device) + return valid_devices diff --git a/qiskit/providers/aer/noise/noise_model.py b/qiskit/providers/aer/noise/noise_model.py index defc7f3f39..1f7885ad08 100644 --- a/qiskit/providers/aer/noise/noise_model.py +++ b/qiskit/providers/aer/noise/noise_model.py @@ -318,6 +318,10 @@ def is_ideal(self): return False return True + def __repr__(self): + """Noise model repr""" + return "".format(list(self._noise_instructions)) + def __str__(self): """Noise model string representation""" From 0ced3fe3a3445c24493882dffbf39e6a90ebfb9a Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Fri, 26 Mar 2021 13:21:37 -0400 Subject: [PATCH 03/14] Add SaveSuperOp and SetSuperOp instructions --- qiskit/providers/aer/library/__init__.py | 28 +++++-- .../aer/library/instructions_table.csv | 37 ++++----- .../aer/library/save_instructions/__init__.py | 1 + .../library/save_instructions/save_superop.py | 61 ++++++++++++++ .../library/save_instructions/save_unitary.py | 7 +- .../aer/library/set_instructions/__init__.py | 1 + .../library/set_instructions/set_superop.py | 80 +++++++++++++++++++ .../library/set_instructions/set_unitary.py | 1 + src/framework/operations.hpp | 9 ++- .../superoperator/superoperator_state.hpp | 25 ++---- 10 files changed, 199 insertions(+), 51 deletions(-) create mode 100644 qiskit/providers/aer/library/save_instructions/save_superop.py create mode 100644 qiskit/providers/aer/library/set_instructions/set_superop.py diff --git a/qiskit/providers/aer/library/__init__.py b/qiskit/providers/aer/library/__init__.py index 176534a930..a6c965c3d5 100644 --- a/qiskit/providers/aer/library/__init__.py +++ b/qiskit/providers/aer/library/__init__.py @@ -37,6 +37,7 @@ SetStatevector SetDensityMatrix SetStabilizer + SetSuperOp SetUnitary QuantumCircuit Methods @@ -52,6 +53,7 @@ set_density_matrix set_stabilizer set_unitary + set_superop Saving Simulator Data @@ -82,6 +84,7 @@ SaveDensityMatrix SaveMatrixProductState SaveStabilizer + SaveSuperOp SaveUnitary .. note:: @@ -97,6 +100,9 @@ simulator state to the returned result object. One some are compatible with certain simulation methods. +For convenience the save instructions can be accessed using +custom ``QuantumCircuit`` methods + .. autosummary:: :toctree: ../stubs/ @@ -158,19 +164,25 @@ """ __all__ = [ - 'SaveState', - 'SaveStatevector', - 'SaveStatevectorDict', + 'SaveAmplitudes', + 'SaveAmplitudesSquared', 'SaveDensityMatrix', - 'SaveUnitary', - 'SaveStabilizer', - 'SaveMatrixProductState', 'SaveExpectationValue', 'SaveExpectationValueVariance', + 'SaveMatrixProductState', 'SaveProbabilities', 'SaveProbabilitiesDict', - 'SaveAmplitudes', - 'SaveAmplitudesSquared', + 'SaveStabilizer', + 'SaveState', + 'SaveStatevector', + 'SaveStatevectorDict', + 'SaveSuperOp', + 'SaveUnitary', + 'SetDensityMatrix', + 'SetStabilizer', + 'SetStatevector', + 'SetSuperOp', + 'SetUnitary', ] from .save_instructions import * diff --git a/qiskit/providers/aer/library/instructions_table.csv b/qiskit/providers/aer/library/instructions_table.csv index b54baeea33..dda33ccf0d 100644 --- a/qiskit/providers/aer/library/instructions_table.csv +++ b/qiskit/providers/aer/library/instructions_table.csv @@ -1,19 +1,20 @@ -Instruction,Automatic,Statevector,Density Matrix,MPS,Stabilizer,Ext. Stabilizer,Unitary -:class:`SaveAmplitudes`,✔,✔,✘,✔,✘,✘,✘ -:class:`SaveAmplitudesSquared`,✔,✔,✔,✔,✔,✘,✘ -:class:`SaveDensityMatrix`,✔,✔,✔,✔,✘,✘,✘ -:class:`SaveExpectationValue`,✔,✔,✔,✔,✔,✔,✘ -:class:`SaveExpectationValueVariance`,✔,✔,✔,✔,✔,✔,✘ -:class:`SaveMatrixProductState`,✘,✘,✘,✔,✘,✘,✘ -:class:`SaveProbabilities`,✔,✔,✔,✔,✔,✘,✘ -:class:`SaveProbabilitiesDict`,✔,✔,✔,✔,✔,✘,✘ -:class:`SaveStabilizer`,✔,✘,✘,✘,✔,✘,✘ -:class:`SaveState`,✔,✔,✔,✔,✔,✔,✔ -:class:`SaveStatevector`,✔,✔,✘,✔,✘,✔,✘ -:class:`SaveStatevectorDict`,✔,✔,✘,✘,✘,✘,✘ -:class:`SaveUnitary`,✘,✘,✘,✘,✘,✘,✔ -:class:`SetDensityMatrix`,✔,✘,✔,✘,✘,✘,✘ -:class:`SetStabilizer`,✔,✘,✘,✘,✔,✘,✘ -:class:`SetStatevector`,✔,✔,✘,✘,✘,✘,✘ -:class:`SetUnitary`,✘,✘,✘,✘,✘,✘,✔ +Instruction,Automatic,Statevector,Density Matrix,MPS,Stabilizer,Ext. Stabilizer,Unitary,SuperOp +:class:`SaveAmplitudes`,✔,✔,✘,✔,✘,✘,✘,✘ +:class:`SaveAmplitudesSquared`,✔,✔,✔,✔,✔,✘,✘,✘ +:class:`SaveDensityMatrix`,✔,✔,✔,✔,✘,✘,✘,✘ +:class:`SaveExpectationValue`,✔,✔,✔,✔,✔,✔,✘,✘ +:class:`SaveExpectationValueVariance`,✔,✔,✔,✔,✔,✔,✘,✘ +:class:`SaveMatrixProductState`,✘,✘,✘,✔,✘,✘,✘,✘ +:class:`SaveProbabilities`,✔,✔,✔,✔,✔,✘,✘,✘ +:class:`SaveProbabilitiesDict`,✔,✔,✔,✔,✔,✘,✘,✘ +:class:`SaveStabilizer`,✔,✘,✘,✘,✔,✘,✘,✘ +:class:`SaveState`,✔,✔,✔,✔,✔,✔,✔,✔ +:class:`SaveStatevector`,✔,✔,✘,✔,✘,✔,✘,✘ +:class:`SaveStatevectorDict`,✔,✔,✘,✘,✘,✘,✘,✘ +:class:`SaveSuperOp`,✘,✘,✘,✘,✘,✘,✘,✔ +:class:`SaveUnitary`,✘,✘,✘,✘,✘,✘,✔,✘ +:class:`SetDensityMatrix`,✔,✘,✔,✘,✘,✘,✘,✘ +:class:`SetStabilizer`,✔,✘,✘,✘,✔,✘,✘,✘ +:class:`SetStatevector`,✔,✔,✘,✘,✘,✘,✘,✘ +:class:`SetUnitary`,✘,✘,✘,✘,✘,✘,✔,✘ ,,,,,,,, \ No newline at end of file diff --git a/qiskit/providers/aer/library/save_instructions/__init__.py b/qiskit/providers/aer/library/save_instructions/__init__.py index cfe043c6e6..8e3a636fd9 100644 --- a/qiskit/providers/aer/library/save_instructions/__init__.py +++ b/qiskit/providers/aer/library/save_instructions/__init__.py @@ -28,3 +28,4 @@ from .save_unitary import (SaveUnitary, save_unitary) from .save_matrix_product_state import ( SaveMatrixProductState, save_matrix_product_state) +from .save_superop import (SaveSuperOp, save_superop) diff --git a/qiskit/providers/aer/library/save_instructions/save_superop.py b/qiskit/providers/aer/library/save_instructions/save_superop.py new file mode 100644 index 0000000000..d8ebca6103 --- /dev/null +++ b/qiskit/providers/aer/library/save_instructions/save_superop.py @@ -0,0 +1,61 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Simulator instruction to save a SuperOp matrix. +""" + +from qiskit.circuit import QuantumCircuit +from .save_data import SaveSingleData +from ..default_qubits import default_qubits + + +class SaveSuperOp(SaveSingleData): + """Save a SuperOp matrix.""" + def __init__(self, num_qubits, label="superop", pershot=False): + """Create new instruction to save the superop simulator state. + + Args: + num_qubits (int): the number of qubits for the save instruction. + label (str): the key for retrieving saved data from results. + pershot (bool): if True save a list of SuperOp matrices for each shot + of the simulation [Default: False]. + + .. note:: + + This save instruction must always be performed on the full width of + qubits in a circuit, otherwise an exception will be raised during + simulation. + """ + super().__init__("save_superop", num_qubits, label, pershot=pershot) + + +def save_superop(self, label="superop", pershot=False): + """Save the current state of the superop simulator. + + Args: + label (str): the key for retrieving saved data from results. + pershot (bool): if True save a list of SuperOp matrices for each shot + of the simulation [Default: False]. + + Returns: + QuantumCircuit: with attached instruction. + + .. note:: + + This instruction is always defined across all qubits in a circuit. + """ + qubits = default_qubits(self) + instr = SaveSuperOp(len(qubits), label=label, pershot=pershot) + return self.append(instr, qubits) + + +QuantumCircuit.save_superop = save_superop diff --git a/qiskit/providers/aer/library/save_instructions/save_unitary.py b/qiskit/providers/aer/library/save_instructions/save_unitary.py index e5c0c434c4..8766852cbc 100644 --- a/qiskit/providers/aer/library/save_instructions/save_unitary.py +++ b/qiskit/providers/aer/library/save_instructions/save_unitary.py @@ -36,8 +36,7 @@ def __init__(self, num_qubits, label="unitary", pershot=False): qubits in a circuit, otherwise an exception will be raised during simulation. """ - super().__init__('save_unitary', num_qubits, label, - pershot=pershot) + super().__init__('save_unitary', num_qubits, label, pershot=pershot) def save_unitary(self, label="unitary", pershot=False): @@ -56,9 +55,7 @@ def save_unitary(self, label="unitary", pershot=False): This instruction is always defined across all qubits in a circuit. """ qubits = default_qubits(self) - instr = SaveUnitary(len(qubits), - label=label, - pershot=pershot) + instr = SaveUnitary(len(qubits), label=label, pershot=pershot) return self.append(instr, qubits) diff --git a/qiskit/providers/aer/library/set_instructions/__init__.py b/qiskit/providers/aer/library/set_instructions/__init__.py index 76cbd6dc49..34fafcc61f 100644 --- a/qiskit/providers/aer/library/set_instructions/__init__.py +++ b/qiskit/providers/aer/library/set_instructions/__init__.py @@ -15,3 +15,4 @@ from .set_density_matrix import SetDensityMatrix, set_density_matrix from .set_unitary import SetUnitary, set_unitary from .set_stabilizer import SetStabilizer, set_stabilizer +from .set_superop import SetSuperOp, set_superop diff --git a/qiskit/providers/aer/library/set_instructions/set_superop.py b/qiskit/providers/aer/library/set_instructions/set_superop.py new file mode 100644 index 0000000000..e176834c99 --- /dev/null +++ b/qiskit/providers/aer/library/set_instructions/set_superop.py @@ -0,0 +1,80 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Instruction to set the state simulator state to a superop matrix. +""" + +from qiskit.circuit import QuantumCircuit, Instruction +from qiskit.extensions.exceptions import ExtensionError +from qiskit.quantum_info import SuperOp +from ..default_qubits import default_qubits + + +class SetSuperOp(Instruction): + """Set superop state of the simulator""" + + _directive = True + + def __init__(self, state): + """Create new instruction to set the superop simulator state. + + Args: + state (QuantumChannel): A CPTP quantum channel. + + Raises: + ExtensionError: if the input QuantumChannel is not CPTP. + + .. note:: + + This set instruction must always be performed on the full width of + qubits in a circuit, otherwise an exception will be raised during + simulation. + """ + if not isinstance(state, SuperOp): + state = SuperOp(state) + if not state.num_qubits or not state.is_cptp(): + raise ExtensionError("The input quantum channel is not CPTP") + super().__init__('set_superop', state.num_qubits, 0, + [state.data]) + + +def set_superop(self, state): + """Set the superop state of the simulator. + + Args: + state (QuantumChannel): A CPTP quantum channel. + + Returns: + QuantumCircuit: with attached instruction. + + Raises: + ExtensionError: If the state is the incorrect size for the + current circuit. + ExtensionError: if the input QuantumChannel is not CPTP. + + .. note: + + This instruction is always defined across all qubits in a circuit. + """ + qubits = default_qubits(self) + if not isinstance(state, SuperOp): + state = SuperOp(state) + if not state.num_qubits or state.num_qubits != len(qubits): + raise ExtensionError( + "The size of the quantum channel for the set_superop" + " instruction must be equal to the number of qubits" + f" in the circuit (state.num_qubits ({state.num_qubits})" + f" != QuantumCircuit.num_qubits ({self.num_qubits})).") + return self.append(SetSuperOp(state), qubits) + + +QuantumCircuit.set_superop = set_superop diff --git a/qiskit/providers/aer/library/set_instructions/set_unitary.py b/qiskit/providers/aer/library/set_instructions/set_unitary.py index 610de4c139..744923d5ac 100644 --- a/qiskit/providers/aer/library/set_instructions/set_unitary.py +++ b/qiskit/providers/aer/library/set_instructions/set_unitary.py @@ -59,6 +59,7 @@ def set_unitary(self, state): Raises: ExtensionError: If the state is the incorrect size for the current circuit. + ExtensionError: if the input matrix is not unitary. .. note: diff --git a/src/framework/operations.hpp b/src/framework/operations.hpp index f27d03d90e..66fb2b963d 100755 --- a/src/framework/operations.hpp +++ b/src/framework/operations.hpp @@ -44,7 +44,7 @@ enum class OpType { // Save instructions save_state, save_expval, save_expval_var, save_statevec, save_statevec_dict, save_densmat, save_probs, save_probs_ket, save_amps, save_amps_sq, - save_stabilizer, save_unitary, save_mps, + save_stabilizer, save_unitary, save_mps, save_superop, // Set instructions set_statevec, set_densmat, set_unitary, set_superop, set_stabilizer @@ -59,7 +59,7 @@ static const std::unordered_set SAVE_TYPES = { OpType::save_statevec, OpType::save_statevec_dict, OpType::save_densmat, OpType::save_probs, OpType::save_probs_ket, OpType::save_amps, OpType::save_amps_sq, OpType::save_stabilizer, - OpType::save_unitary, OpType::save_mps + OpType::save_unitary, OpType::save_mps, OpType::save_superop }; inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { @@ -117,6 +117,9 @@ inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { case OpType::save_unitary: stream << "save_unitary"; break; + case OpType::save_superop: + stream << "save_superop"; + break; case OpType::set_statevec: stream << "set_statevector"; break; @@ -594,6 +597,8 @@ Op input_to_op(const inputdata_t& input) { return input_to_op_save_default(input, OpType::save_stabilizer); if (name == "save_unitary") return input_to_op_save_default(input, OpType::save_unitary); + if (name == "save_superop") + return input_to_op_save_default(input, OpType::save_superop); if (name == "save_density_matrix") return input_to_op_save_default(input, OpType::save_densmat); if (name == "save_probabilities") diff --git a/src/simulators/superoperator/superoperator_state.hpp b/src/simulators/superoperator/superoperator_state.hpp index aa5d825846..8d34b7c93d 100755 --- a/src/simulators/superoperator/superoperator_state.hpp +++ b/src/simulators/superoperator/superoperator_state.hpp @@ -35,7 +35,8 @@ const Operations::OpSet StateOpSet( Operations::OpType::bfunc, Operations::OpType::roerror, Operations::OpType::matrix, Operations::OpType::diagonal_matrix, Operations::OpType::kraus, Operations::OpType::superop, - Operations::OpType::save_state, Operations::OpType::set_unitary, + Operations::OpType::save_state, Operations::OpType::save_superop, + Operations::OpType::set_unitary, Operations::OpType::set_superop}, // Gates {"U", "CX", "u1", "u2", "u3", "u", "cx", "cy", "cz", @@ -274,6 +275,7 @@ void State::apply_ops(const std::vector &ops, apply_snapshot(op, result); break; case Operations::OpType::save_state: + case Operations::OpType::save_superop: apply_save_state(op, result, final_ops && ops.size() == i + 1); break; default: @@ -520,31 +522,18 @@ void State::apply_save_state(const Operations::Op &op, op.name + " was not applied to all qubits." " Only the full state can be saved."); } - // Renamp single data type to average - Operations::DataSubType save_type; - switch (op.save_type) { - case Operations::DataSubType::single: - save_type = Operations::DataSubType::average; - break; - case Operations::DataSubType::c_single: - save_type = Operations::DataSubType::c_average; - break; - default: - save_type = op.save_type; - } - // Default key std::string key = (op.string_params[0] == "_method_") ? "superop" : op.string_params[0]; if (last_op) { - BaseState::save_data_average(result, key, + BaseState::save_data_pershot(result, key, BaseState::qreg_.move_to_matrix(), - save_type); + op.save_type); } else { - BaseState::save_data_average(result, key, + BaseState::save_data_pershot(result, key, BaseState::qreg_.copy_to_matrix(), - save_type); + op.save_type); } } From 050c079ceac952b50141630a943e14398a3e3ec3 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 24 Mar 2021 12:36:10 -0400 Subject: [PATCH 04/14] Add pending deprecation warnings to legacy simulators --- qiskit/providers/aer/__init__.py | 9 ++++++++- qiskit/providers/aer/backends/qasm_simulator.py | 5 +++++ qiskit/providers/aer/backends/statevector_simulator.py | 7 +++++++ qiskit/providers/aer/backends/unitary_simulator.py | 7 +++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/qiskit/providers/aer/__init__.py b/qiskit/providers/aer/__init__.py index 20f57960a2..3b7cbe3bc3 100644 --- a/qiskit/providers/aer/__init__.py +++ b/qiskit/providers/aer/__init__.py @@ -32,10 +32,17 @@ :toctree: ../stubs/ AerSimulator + PulseSimulator + +Legacy Simulator Backends +========================= + +.. autosummary:: + :toctree: ../stubs/ + QasmSimulator StatevectorSimulator UnitarySimulator - PulseSimulator Job Class ========= diff --git a/qiskit/providers/aer/backends/qasm_simulator.py b/qiskit/providers/aer/backends/qasm_simulator.py index a3aef26892..30f42257a2 100644 --- a/qiskit/providers/aer/backends/qasm_simulator.py +++ b/qiskit/providers/aer/backends/qasm_simulator.py @@ -15,6 +15,7 @@ import copy import logging +from warnings import warn from qiskit.providers.options import Options from qiskit.providers.models import QasmBackendConfiguration @@ -325,6 +326,10 @@ def __init__(self, provider=None, **backend_options): + warn('The `QasmSimulator` backend will be deprecated in the' + ' future. It has been superseded by the `AerSimulator`' + ' backend.', PendingDeprecationWarning) + self._controller = qasm_controller_execute() # Update available methods for class diff --git a/qiskit/providers/aer/backends/statevector_simulator.py b/qiskit/providers/aer/backends/statevector_simulator.py index 524f96db80..3dc8ddac05 100644 --- a/qiskit/providers/aer/backends/statevector_simulator.py +++ b/qiskit/providers/aer/backends/statevector_simulator.py @@ -14,6 +14,7 @@ """ import logging +from warnings import warn from qiskit.util import local_hardware_info from qiskit.providers.options import Options from qiskit.providers.models import QasmBackendConfiguration @@ -148,6 +149,12 @@ def __init__(self, provider=None, **backend_options): + warn('The `StatevectorSimulator` backend will be deprecated in the' + ' future. It has been superseded by the `AerSimulator`' + ' backend. To obtain legacy functionality initalize with' + ' `AerSimulator(method="statevector")` and append run circuits' + ' with the `save_state` instruction.', PendingDeprecationWarning) + self._controller = statevector_controller_execute() if StatevectorSimulator._AVAILABLE_METHODS is None: diff --git a/qiskit/providers/aer/backends/unitary_simulator.py b/qiskit/providers/aer/backends/unitary_simulator.py index ea74c57486..a51571894f 100644 --- a/qiskit/providers/aer/backends/unitary_simulator.py +++ b/qiskit/providers/aer/backends/unitary_simulator.py @@ -16,6 +16,7 @@ """ import logging +from warnings import warn from qiskit.util import local_hardware_info from qiskit.providers.options import Options from qiskit.providers.models import QasmBackendConfiguration @@ -147,6 +148,12 @@ def __init__(self, provider=None, **backend_options): + warn('The `UnitarySimulator` backend will be deprecated in the' + ' future. It has been superseded by the `AerSimulator`' + ' backend. To obtain legacy functionality initalize with' + ' `AerSimulator(method="unitary")` and append run circuits' + ' with the `save_state` instruction.', PendingDeprecationWarning) + self._controller = unitary_controller_execute() if UnitarySimulator._AVAILABLE_METHODS is None: From c13aa35e564b3a64bcd2562024b4a31815904841 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 25 Mar 2021 14:19:15 -0400 Subject: [PATCH 05/14] Start adding refactored tests for AerSimulator * Algorithm tests * Standard gate library tests * Save instruction tests --- test/terra/backends/aer_simulator/__init__.py | 32 +++ .../aer_simulator/aer_simulator_test_case.py | 69 +++++ .../aer_simulator/instructions/__init__.py | 32 +++ .../instructions/test_save_amplitudes.py | 107 +++++++ .../instructions/test_save_density_matrix.py | 172 +++++++++++ .../instructions/test_save_expval.py | 266 ++++++++++++++++++ .../test_save_matrix_product_state.py | 61 ++++ .../instructions/test_save_probabilities.py | 113 ++++++++ .../instructions/test_save_state.py | 110 ++++++++ .../instructions/test_save_statevector.py | 178 ++++++++++++ .../test_save_statevector_dict.py | 155 ++++++++++ .../instructions/test_save_superop.py | 50 ++++ .../instructions/test_save_unitary.py | 50 ++++ .../instructions/test_set_state.py | 140 +++++++++ .../instructions/test_standard_gates.py | 200 +++++++++++++ .../backends/aer_simulator/test_algorithms.py | 80 ++++++ test/terra/common.py | 3 +- test/terra/decorators.py | 6 +- test/terra/noise/test_noise_model.py | 6 +- 19 files changed, 1822 insertions(+), 8 deletions(-) create mode 100644 test/terra/backends/aer_simulator/__init__.py create mode 100644 test/terra/backends/aer_simulator/aer_simulator_test_case.py create mode 100644 test/terra/backends/aer_simulator/instructions/__init__.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_amplitudes.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_density_matrix.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_expval.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_probabilities.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_state.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_statevector.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_statevector_dict.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_superop.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_save_unitary.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_set_state.py create mode 100644 test/terra/backends/aer_simulator/instructions/test_standard_gates.py create mode 100644 test/terra/backends/aer_simulator/test_algorithms.py diff --git a/test/terra/backends/aer_simulator/__init__.py b/test/terra/backends/aer_simulator/__init__.py new file mode 100644 index 0000000000..0e1a4c3082 --- /dev/null +++ b/test/terra/backends/aer_simulator/__init__.py @@ -0,0 +1,32 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +''' +Terra tests +''' + +import os + + +def load_tests(loader, standard_tests, pattern): + """ + test suite for unittest discovery + """ + this_dir = os.path.dirname(__file__) + if pattern in ['test*.py', '*_test.py']: + package_tests = loader.discover(start_dir=this_dir, pattern=pattern) + standard_tests.addTests(package_tests) + elif pattern in ['profile*.py', '*_profile.py']: + loader.testMethodPrefix = 'profile' + package_tests = loader.discover(start_dir=this_dir, pattern='test*.py') + standard_tests.addTests(package_tests) + return standard_tests diff --git a/test/terra/backends/aer_simulator/aer_simulator_test_case.py b/test/terra/backends/aer_simulator/aer_simulator_test_case.py new file mode 100644 index 0000000000..97a4c9766b --- /dev/null +++ b/test/terra/backends/aer_simulator/aer_simulator_test_case.py @@ -0,0 +1,69 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +AerSimulator test case class +""" + +import ddt +import itertools as it +from qiskit.providers.aer import AerSimulator +from test.terra.common import QiskitAerTestCase + + +class AerSimulatorTestCase(QiskitAerTestCase): + """AerSimulator test class""" + + OPTIONS = {} + + def backend(self, **options): + """Return AerSimulator backend using current class options""" + sim_options = self.OPTIONS.copy() + for key, val in options.items(): + sim_options[key] = val + return AerSimulator(**sim_options) + + +def supported_methods(methods, *other_args, product=True): + """ddt decorator for iterating over supported methods and args""" + method_args = _method_device(methods) + if other_args: + data_args = [] + if product: + items = list(it.product(*other_args)) + else: + items = list(zip(*other_args)) + for method, device in method_args: + for args in items: + data_args.append((method, device, *args)) + else: + data_args = method_args + + def decorator(func): + return ddt.data(*data_args)(ddt.unpack(func)) + + return decorator + + +def _method_device(methods): + """Return list of (methods, device) supported on current system""" + if not methods: + methods = AerSimulator().available_methods() + available_devices = AerSimulator().available_devices() + gpu_methods = ['statevector', 'density_matrix', 'unitary', 'superop'] + data_args = [] + for method in methods: + if method in gpu_methods: + for device in available_devices: + data_args.append((method, device)) + else: + data_args.append((method, 'CPU')) + return data_args diff --git a/test/terra/backends/aer_simulator/instructions/__init__.py b/test/terra/backends/aer_simulator/instructions/__init__.py new file mode 100644 index 0000000000..0e1a4c3082 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/__init__.py @@ -0,0 +1,32 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +''' +Terra tests +''' + +import os + + +def load_tests(loader, standard_tests, pattern): + """ + test suite for unittest discovery + """ + this_dir = os.path.dirname(__file__) + if pattern in ['test*.py', '*_test.py']: + package_tests = loader.discover(start_dir=this_dir, pattern=pattern) + standard_tests.addTests(package_tests) + elif pattern in ['profile*.py', '*_profile.py']: + loader.testMethodPrefix = 'profile' + package_tests = loader.discover(start_dir=this_dir, pattern='test*.py') + standard_tests.addTests(package_tests) + return standard_tests diff --git a/test/terra/backends/aer_simulator/instructions/test_save_amplitudes.py b/test/terra/backends/aer_simulator/instructions/test_save_amplitudes.py new file mode 100644 index 0000000000..ccebe631ec --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_amplitudes.py @@ -0,0 +1,107 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveAmplitudes instruction +""" + +from ddt import ddt +import numpy as np +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit, transpile +from qiskit.circuit.library import QFT + + +@ddt +class TestSaveAmplitudes(AerSimulatorTestCase): + """SaveAmplitudes instruction tests.""" + + AMPLITUDES = [ + [0, 1, 2, 3, 4, 5, 6, 7], + [7, 6, 5, 4, 3, 2, 1, 0], + [5, 3, 0, 2], + [0], + [5, 2], + [7, 0] + ] + + def _test_save_amplitudes(self, circuit, params, amp_squared, **options): + """Test save_amplitudes instruction""" + backend = self.backend(**options) + + # Stabilizer test circuit + circ = circuit.copy() + + # Target statevector + target = qi.Statevector(circ).data[params] + if amp_squared: + target = np.abs(target) ** 2 + + # Add save to circuit + label = 'amps' + if amp_squared: + circ.save_amplitudes_squared(params, label=label) + else: + circ.save_amplitudes(params, label=label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertTrue(np.allclose(value, target)) + + @supported_methods( + ['automatic', 'statevector', 'matrix_product_state'], AMPLITUDES) + def test_save_amplitudes(self, method, device, params): + """Test save_amplitudes instruction""" + self._test_save_amplitudes( + QFT(3), params, False, method=method, device=device) + + @supported_methods( + ['automatic', 'statevector', 'matrix_product_state', 'density_matrix'], + AMPLITUDES) + def test_save_amplitudes_squared(self, method, device, params): + """Test save_amplitudes_squared instruction""" + self._test_save_amplitudes( + QFT(3), params, True, method=method, device=device) + + @supported_methods( + ['automatic', 'stabilizer', 'statevector', 'matrix_product_state', + 'density_matrix'], AMPLITUDES) + def test_save_amplitudes_squared_clifford(self, method, device, params): + """Test save_amplitudes_squared instruction for Clifford circuit""" + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.cx(0, 1) + circ.x(2) + circ.sdg(1) + self._test_save_amplitudes( + circ, params, True, method=method, device=device) + + @supported_methods(['statevector'], AMPLITUDES) + def test_save_amplitudes_cache_blocking(self, method, device, params): + """Test save_amplitudes instruction""" + self._test_save_amplitudes( + QFT(3), params, False, method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + @supported_methods(['statevector', 'density_matrix'], AMPLITUDES) + def test_save_amplitudes_squared_cache_blocking(self, method, device, params): + """Test save_amplitudes_squared instruction""" + self._test_save_amplitudes( + QFT(3), params, True, method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_density_matrix.py b/test/terra/backends/aer_simulator/instructions/test_save_density_matrix.py new file mode 100644 index 0000000000..45f6ab46ef --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_density_matrix.py @@ -0,0 +1,172 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveDensityMatrix instruction +""" + +from ddt import ddt +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +import numpy as np +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit, transpile + + +@ddt +class QasmSaveDensityMatrixTests(AerSimulatorTestCase): + """Test SaveDensityMatrix instruction.""" + + @supported_methods(['automatic', 'statevector', 'density_matrix', + 'matrix_product_state']) + def test_save_density_matrix(self, method, device): + """Test save density matrix for instruction""" + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.cx(0, 2) + + # Target statevector + target = qi.DensityMatrix(circ) + + # Add save to circuit + label = 'state' + circ.save_density_matrix(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.DensityMatrix(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['automatic', 'statevector', 'density_matrix', + 'matrix_product_state']) + def test_save_density_matrix_conditional(self, method, device): + """Test conditional save density matrix instruction""" + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + label = 'state' + circ = QuantumCircuit(2) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.measure_all() + circ.save_density_matrix(label=label, conditional=True) + + # Target statevector + target = {'0x0': qi.DensityMatrix(np.diag([1, 0, 0, 0])), + '0x3': qi.DensityMatrix(np.diag([0, 0, 0, 1]))} + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + for key, state in simdata[label].items(): + self.assertIn(key, target) + self.assertEqual(qi.DensityMatrix(state), target[key]) + + @supported_methods(['automatic', 'statevector', 'density_matrix', + 'matrix_product_state']) + def test_save_density_matrix_pershot(self, method, device): + """Test pershot save density matrix instruction""" + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = qi.DensityMatrix(circ) + + # Add save + label = 'state' + circ.save_density_matrix(label=label, pershot=True) + + # Run + shots = 10 + result = backend.run(transpile(circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + for state in value: + self.assertEqual(qi.DensityMatrix(state), target) + + @supported_methods(['automatic', 'statevector', 'density_matrix', + 'matrix_product_state']) + def test_save_density_matrix_pershot_conditional(self, method, device): + """Test pershot conditional save density matrix instruction""" + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = qi.DensityMatrix(circ) + + # Add save + label = 'state' + circ.save_density_matrix(label=label, pershot=True, conditional=True) + circ.measure_all() + + # Run + shots = 10 + result = backend.run(transpile(circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertIn('0x0', value) + for state in value['0x0']: + self.assertEqual(qi.DensityMatrix(state), target) + + @supported_methods(['statevector', 'density_matrix']) + def test_save_density_matrix_cache_blocking(self, method, device): + """Test save density matrix for instruction""" + backend = self.backend(method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.cx(0, 2) + + # Target statevector + target = qi.DensityMatrix(circ) + + # Add save to circuit + label = 'state' + circ.save_density_matrix(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.DensityMatrix(simdata[label]) + self.assertEqual(value, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_expval.py b/test/terra/backends/aer_simulator/instructions/test_save_expval.py new file mode 100644 index 0000000000..1685c96972 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_expval.py @@ -0,0 +1,266 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveExpval instruction +""" + +from ddt import ddt +from numpy import allclose +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit +from qiskit.circuit.library import QuantumVolume +from qiskit.compiler import transpile + +PAULI2 = [ + 'II', 'IX', 'IY', 'IZ', 'XI', 'XX', 'XY', 'XZ', 'YI', 'YX', 'YY', 'YZ', + 'ZI', 'ZX', 'ZY', 'ZZ' +] + + +@ddt +class TestSaveExpectationValueTests(AerSimulatorTestCase): + """Test SaveExpectationValue instruction.""" + def _test_save_expval(self, circuit, oper, qubits, variance, **options): + """Test Pauli expval for stabilizer circuit""" + backend = self.backend(**options) + label = 'expval' + + # Format operator and target value + circ = circuit.copy() + oper = qi.Operator(oper) + state = qi.DensityMatrix(circ) + expval = state.expectation_value(oper, qubits).real + + if variance: + var = state.expectation_value(oper**2, qubits).real - expval**2 + target = [expval, var] + circ.save_expectation_value_variance(oper, qubits, label=label) + else: + target = expval + circ.save_expectation_value(oper, qubits, label=label) + + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + if variance: + self.assertTrue(allclose(value, target)) + else: + self.assertAlmostEqual(value, target) + + @supported_methods( + [ + 'automatic', 'stabilizer', 'statevector', 'density_matrix', + 'matrix_product_state' + ], # , 'extended_stabilizer'], + PAULI2) + def test_save_expval_stabilizer_pauli(self, method, device, pauli): + """Test Pauli expval for stabilizer circuit""" + SEED = 5832 + circ = qi.random_clifford(2, seed=SEED).to_circuit() + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device) + + @supported_methods( + [ + 'automatic', 'stabilizer', 'statevector', 'density_matrix', + 'matrix_product_state' + ], # , 'extended_stabilizer'], + PAULI2) + def test_save_expval_var_stabilizer_pauli(self, method, device, pauli): + """Test Pauli expval_var for stabilizer circuit""" + SEED = 5832 + circ = qi.random_clifford(2, seed=SEED).to_circuit() + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device) + + @supported_methods( + [ + 'automatic', 'stabilizer', 'statevector', 'density_matrix', + 'matrix_product_state' + ], # , 'extended_stabilizer'], + [[0, 1], [1, 0], [0, 2], [2, 0], [1, 2], [2, 1]]) + def test_save_expval_stabilizer_hermitian(self, method, device, qubits): + """Test expval for stabilizer circuit and Hermitian operator""" + SEED = 7123 + circ = qi.random_clifford(3, seed=SEED).to_circuit() + oper = qi.random_hermitian(4, traceless=True, seed=SEED) + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device) + + @supported_methods( + [ + 'automatic', 'stabilizer', 'statevector', 'density_matrix', + 'matrix_product_state' + ], # , 'extended_stabilizer'], + [[0, 1], [1, 0], [0, 2], [2, 0], [1, 2], [2, 1]]) + def test_save_expval_var_stabilizer_hermitian(self, method, device, + qubits): + """Test expval_var for stabilizer circuit and Hermitian operator""" + SEED = 7123 + circ = qi.random_clifford(3, seed=SEED).to_circuit() + oper = qi.random_hermitian(4, traceless=True, seed=SEED) + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state'], + PAULI2) + def test_save_expval_nonstabilizer_pauli(self, method, device, pauli): + """Test Pauli expval for non-stabilizer circuit""" + SEED = 7382 + circ = QuantumVolume(2, 1, seed=SEED) + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state'], + PAULI2) + def test_save_expval_var_nonstabilizer_pauli(self, method, device, pauli): + """Test Pauli expval_var for non-stabilizer circuit""" + SEED = 7382 + circ = QuantumVolume(2, 1, seed=SEED) + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state'], + [[0, 1], [1, 0], [0, 2], [2, 0], [1, 2], [2, 1]]) + def test_save_expval_nonstabilizer_hermitian(self, method, device, qubits): + """Test expval for non-stabilizer circuit and Hermitian operator""" + SEED = 8124 + circ = QuantumVolume(3, 1, seed=SEED) + oper = qi.random_hermitian(4, traceless=True, seed=SEED) + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state'], + [[0, 1], [1, 0], [0, 2], [2, 0], [1, 2], [2, 1]]) + def test_save_expval_var_nonstabilizer_hermitian(self, method, device, + qubits): + """Test expval_var for non-stabilizer circuit and Hermitian operator""" + SEED = 8124 + circ = QuantumVolume(3, 1, seed=SEED) + oper = qi.random_hermitian(4, traceless=True, seed=SEED) + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device) + + @supported_methods(['density_matrix'], PAULI2) + def test_save_expval_cptp_pauli(self, method, device, pauli): + """Test Pauli expval for stabilizer circuit""" + SEED = 5832 + oper = qi.Operator(qi.Pauli(pauli)) + channel = qi.random_quantum_channel(4, seed=SEED) + circ = QuantumCircuit(2) + circ.append(channel, range(2)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device) + + @supported_methods(['density_matrix'], PAULI2) + def test_save_expval_var_cptp_pauli(self, method, device, pauli): + """Test Pauli expval_var for stabilizer circuit""" + SEED = 5832 + oper = qi.Operator(qi.Operator(qi.Pauli(pauli))) + channel = qi.random_quantum_channel(4, seed=SEED) + circ = QuantumCircuit(2) + circ.append(channel, range(2)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device) + + @supported_methods(['statevector', 'density_matrix'], PAULI2) + def test_save_expval_stabilizer_pauli_cache_blocking( + self, method, device, pauli): + """Test Pauli expval for stabilizer circuit""" + SEED = 5832 + circ = qi.random_clifford(2, seed=SEED).to_circuit() + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + False, + method=method, + device=device, + blocking_qubits=2, + max_parallel_threads=1) + + @supported_methods(['statevector', 'density_matrix'], PAULI2) + def test_save_expval_var_stabilizer_pauli_cache_blocking( + self, method, device, pauli): + """Test Pauli expval_var for stabilizer circuit""" + SEED = 5832 + circ = qi.random_clifford(2, seed=SEED).to_circuit() + oper = qi.Operator(qi.Pauli(pauli)) + qubits = [0, 1] + self._test_save_expval(circ, + oper, + qubits, + True, + method=method, + device=device, + blocking_qubits=2, + max_parallel_threads=1) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py b/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py new file mode 100644 index 0000000000..736d721218 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py @@ -0,0 +1,61 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests for SaveMatrixProductState instruction +""" +from ddt import ddt +import numpy as np +from qiskit import QuantumCircuit, transpile +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestSaveMatrixProductStateTests(AerSimulatorTestCase): + """SaveMatrixProductState instruction tests.""" + + @supported_methods(["matrix_product_state"]) + def test_save_matrix_product_state(self, method, device): + """Test save matrix_product_state instruction""" + backend = self.backend(method=method, device=device) + + # Target mps structure + target_qreg = [] + target_qreg.append((np.array([[1, 0]], dtype=complex), np.array([[0, 1]], dtype=complex))) + target_qreg.append((np.array([[1], [0]], dtype=complex), np.array([[0], [1]], dtype=complex))) + target_qreg.append((np.array([[1]], dtype=complex), np.array([[0]], dtype=complex))) + + target_lambda_reg = [] + target_lambda_reg.append(np.array([1 / np.math.sqrt(2)], dtype=float)) + target_lambda_reg.append(np.array([1], dtype=float)) + + # Matrix product state test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.cx(0, 1) + + # Add save to circuit + label = 'mps' + circ.save_matrix_product_state(label=label) + + # Run + shots = 10 + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + for val, target in zip(value[0], target_qreg): + self.assertTrue(np.allclose(val, target)) + for val, target in zip(value[1], target_lambda_reg): + self.assertTrue(np.allclose(val, target)) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_probabilities.py b/test/terra/backends/aer_simulator/instructions/test_save_probabilities.py new file mode 100644 index 0000000000..6ec3222ae1 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_probabilities.py @@ -0,0 +1,113 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveExpval instruction +""" + +from ddt import ddt +import numpy as np +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit +from qiskit.compiler import transpile +from qiskit.result import Counts + + +@ddt +class TestSaveProbabilities(AerSimulatorTestCase): + """Test SaveProbabilities instruction.""" + def _test_save_probabilities(self, qubits, **options): + """Test save probabilities instruction""" + backend = self.backend(**options) + + circ = QuantumCircuit(3) + circ.x(0) + circ.h(1) + circ.cx(1, 2) + + # Target probabilities + state = qi.Statevector(circ) + target = state.probabilities(qubits) + + label = 'probs' + circ.save_probabilities(qubits, label=label) + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertTrue(np.allclose(value, target)) + + def _test_save_probabilities_dict(self, qubits, **options): + """Test save probabilities dict instruction""" + backend = self.backend(**options) + + circ = QuantumCircuit(3) + circ.x(0) + circ.h(1) + circ.cx(1, 2) + + # Target probabilities + state = qi.Statevector(circ) + target = state.probabilities_dict(qubits) + + # Snapshot circuit + label = 'probs' + circ.save_probabilities_dict(qubits, label=label) + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = Counts(result.data(0)[label], memory_slots=len(qubits)) + self.assertDictAlmostEqual(value, target) + + @supported_methods([ + 'automatic', 'statevector', 'density_matrix', 'matrix_product_state', + 'stabilizer' + ], [[0, 1], [1, 0], [0], [1]]) + def test_save_probabilities(self, method, device, qubits): + """Test save probabilities instruction""" + self._test_save_probabilities(qubits, method=method, device=device) + + @supported_methods([ + 'automatic', 'statevector', 'density_matrix', 'matrix_product_state', + 'stabilizer' + ], [[0, 1], [1, 0], [0], [1]]) + def test_save_probabilities_dict(self, method, device, qubits): + """Test save probabilities dict instruction""" + self._test_save_probabilities_dict(qubits, + method=method, + device=device) + + @supported_methods(['statevector', 'density_matrix'], + [[0, 1], [1, 0], [0], [1]]) + def test_save_probabilities_cache_blocking(self, method, device, qubits): + """Test save probabilities instruction""" + self._test_save_probabilities(qubits, + method=method, + device=device, + blocking_qubits=2, + max_parallel_threads=1) + + @supported_methods(['statevector', 'density_matrix'], + [[0, 1], [1, 0], [0], [1]]) + def test_save_probabilities_dict_cache_blocking(self, method, device, + qubits): + """Test save probabilities dict instruction""" + self._test_save_probabilities_dict(qubits, + method=method, + device=device, + blocking_qubits=2, + max_parallel_threads=1) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_state.py b/test/terra/backends/aer_simulator/instructions/test_save_state.py new file mode 100644 index 0000000000..0351815d81 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_state.py @@ -0,0 +1,110 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveState instruction +""" + +import numpy as np +from ddt import ddt +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +from qiskit import QuantumCircuit, transpile +from qiskit.providers.aer.library import ( + SaveStatevector, SaveDensityMatrix, SaveStabilizer, + SaveMatrixProductState, SaveUnitary, SaveSuperOp) + + +@ddt +class TestSaveState(AerSimulatorTestCase): + """Test instructions for saving simulator state.""" + + @supported_methods(['automatic', 'statevector', 'density_matrix', + 'stabilizer', 'matrix_product_state', + 'unitary', 'superop']) + def test_save_state(self, method, device): + """Test save_amplitudes instruction""" + + REFERENCE_SAVE = { + 'automatic': SaveStabilizer, + 'stabilizer': SaveStabilizer, + 'statevector': SaveStatevector, + 'density_matrix': SaveDensityMatrix, + 'matrix_product_state': SaveMatrixProductState, + 'unitary': SaveUnitary, + 'superop': SaveSuperOp + } + + backend = self.backend(method=method, device=device) + if method == 'automatic': + label = 'stabilizer' + else: + label = method + + # Stabilizer test circuit + num_qubits = 2 + target_instr = REFERENCE_SAVE[method](num_qubits, label='target') + circ = QuantumCircuit(num_qubits) + circ.h(0) + for i in range(1, num_qubits): + circ.cx(i - 1, i) + circ.save_state() + circ.append(target_instr, range(num_qubits)) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + self.assertIn('target', simdata) + value = simdata[label] + target = simdata['target'] + if method == 'matrix_product_state': + for val, targ in zip(value[0], target[0]): + self.assertTrue(np.allclose(val, targ)) + for val, targ in zip(value[1], target[1]): + self.assertTrue(np.allclose(val, targ)) + else: + self.assertTrue(np.all(value == target)) + + @supported_methods(['statevector', 'density_matrix']) + def test_save_state_cache_blocking(self, method, device): + """Test save_amplitudes instruction""" + + REFERENCE_SAVE = { + 'statevector': SaveStatevector, + 'density_matrix': SaveDensityMatrix, + } + + backend = self.backend(method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + # Stabilizer test circuit + num_qubits = 4 + target_instr = REFERENCE_SAVE[method](num_qubits, label='target') + circ = QuantumCircuit(num_qubits) + circ.h(0) + for i in range(1, num_qubits): + circ.cx(i - 1, i) + circ.save_state() + circ.append(target_instr, range(num_qubits)) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(method, simdata) + self.assertIn('target', simdata) + value = simdata[method] + target = simdata['target'] + self.assertTrue(np.all(value == target)) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_statevector.py b/test/terra/backends/aer_simulator/instructions/test_save_statevector.py new file mode 100644 index 0000000000..8f9602c1aa --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_statevector.py @@ -0,0 +1,178 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveStatevector instruction +""" + +from ddt import ddt +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit, transpile +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestSaveStatevector(AerSimulatorTestCase): + """SaveStatevector instruction tests.""" + + @supported_methods(['automatic', 'statevector', 'matrix_product_state']) + def test_save_statevector(self, method, device): + """Test save statevector instruction""" + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.cx(0, 2) + + # Target statevector + target = qi.Statevector(circ) + + # Add save to circuit + label = 'state' + circ.save_statevector(label=label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Statevector(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['automatic', 'statevector', 'matrix_product_state']) + def test_save_statevector_conditional(self, method, device): + """Test conditional save statevector instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + # Add save to circuit + label = 'state' + circ = QuantumCircuit(2) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.measure_all() + circ.save_statevector(label=label, conditional=True) + + # Target statevector + target = {'0x0': qi.Statevector([1, 0, 0, 0]), + '0x3': qi.Statevector([0, 0, 0, -1j])} + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + for key, vec in simdata[label].items(): + self.assertIn(key, target) + self.assertEqual(qi.Statevector(vec), target[key]) + + @supported_methods(['automatic', 'statevector', 'matrix_product_state']) + def test_save_statevector_pershot(self, method, device): + """Test pershot save statevector instruction""" + print(method) + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = qi.Statevector(circ) + + # Add save + label = 'state' + circ.save_statevector(label=label, pershot=True) + + # Run + shots = 10 + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertEqual(len(value), shots) + for vec in value: + self.assertEqual(qi.Statevector(vec), target) + + @supported_methods(['automatic', 'statevector', 'matrix_product_state']) + def test_save_statevector_pershot2_conditional(self, method, device): + """Test pershot conditional save statevector instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = qi.Statevector(circ) + + # Add save + label = 'state' + circ.save_statevector(label=label, pershot=True, conditional=True) + circ.measure_all() + + # Run + shots = 10 + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertIn('0x0', value) + self.assertEqual(len(value['0x0']), shots) + for vec in value['0x0']: + self.assertEqual(qi.Statevector(vec), target) + + @supported_methods(['statevector']) + def test_save_statevector_cache_blocking(self, method, device): + """Test save statevector for instruction""" + backend = self.backend(method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.cx(0, 2) + + # Target statevector + target = qi.Statevector(circ) + + # Add save to circuit + label = 'state' + circ.save_statevector(label=label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Statevector(simdata[label]) + self.assertEqual(value, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_statevector_dict.py b/test/terra/backends/aer_simulator/instructions/test_save_statevector_dict.py new file mode 100644 index 0000000000..805bc8870a --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_statevector_dict.py @@ -0,0 +1,155 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Integration Tests for SaveStatevector instruction +""" + +from ddt import ddt +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit, transpile + + +def statevec_as_dict(data): + return {hex(int(key, 2)): val + for key, val in qi.Statevector(data).to_dict().items()} + + +@ddt +class TestSaveStatevectorDict(AerSimulatorTestCase): + """Test SaveStatevectorDict instruction.""" + + @supported_methods(['automatic', 'statevector']) + def test_save_statevector_dict(self, method, device): + """Test save statevector for instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(3) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.cx(0, 2) + + # Target statevector + target = statevec_as_dict(circ) + + # Add save to circuit + label = 'sv' + circ.save_statevector_dict(label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertDictAlmostEqual(value, target) + + @supported_methods(['automatic', 'statevector']) + def test_save_statevector_conditional(self, method, device): + """Test conditional save statevector instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + label = 'sv' + circ = QuantumCircuit(2) + circ.h(0) + circ.sdg(0) + circ.cx(0, 1) + circ.measure_all() + circ.save_statevector_dict(label, conditional=True) + + # Target statevector + target = {'0x0': statevec_as_dict([1, 0, 0, 0]), + '0x3': statevec_as_dict([0, 0, 0, -1j])} + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + for key, vec in simdata[label].items(): + self.assertIn(key, target) + self.assertDictAlmostEqual(vec, target[key]) + + @supported_methods(['automatic', 'statevector']) + def test_save_statevector_dict_pershot(self, method, device): + """Test pershot save statevector instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = statevec_as_dict(circ) + + # Add save + label = 'sv' + circ.save_statevector_dict(label, pershot=True) + + # Run + shots = 10 + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertEqual(len(value), shots) + for vec in value: + self.assertDictAlmostEqual(vec, target) + + @supported_methods(['automatic', 'statevector']) + def test_save_statevector_dict_pershot_conditional( + self, method, device): + """Test pershot conditional save statevector instruction""" + + backend = self.backend(method=method, device=device) + + # Stabilizer test circuit + circ = QuantumCircuit(1) + circ.x(0) + circ.reset(0) + circ.h(0) + circ.sdg(0) + + # Target statevector + target = statevec_as_dict(circ) + + # Add save + label = 'sv' + circ.save_statevector_dict(label, pershot=True, conditional=True) + circ.measure_all() + + # Run + shots = 10 + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=shots).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = simdata[label] + self.assertIn('0x0', value) + self.assertEqual(len(value['0x0']), shots) + for vec in value['0x0']: + self.assertDictAlmostEqual(vec, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_superop.py b/test/terra/backends/aer_simulator/instructions/test_save_superop.py new file mode 100644 index 0000000000..b1f83285c5 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_superop.py @@ -0,0 +1,50 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests for SaveStatevector instruction +""" +from ddt import ddt +import qiskit.quantum_info as qi +from qiskit import transpile +from qiskit.circuit.library import QuantumVolume +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestSaveSuperOp(AerSimulatorTestCase): + """SaveSuperOp instruction tests.""" + + @supported_methods(["superop"]) + def test_save_superop(self, method, device): + """Test save superop instruction""" + backend = self.backend(method=method, device=device) + + # Test circuit + SEED = 712 + circ = QuantumVolume(2, seed=SEED) + + # Target unitary + target = qi.SuperOp(circ) + + # Add save to circuit + label = 'state' + circ.save_superop(label=label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.SuperOp(simdata[label]) + self.assertEqual(value, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_unitary.py b/test/terra/backends/aer_simulator/instructions/test_save_unitary.py new file mode 100644 index 0000000000..402ffd2b76 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_save_unitary.py @@ -0,0 +1,50 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests for SaveStatevector instruction +""" +from ddt import ddt +import qiskit.quantum_info as qi +from qiskit import transpile +from qiskit.circuit.library import QuantumVolume +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestSaveUnitary(AerSimulatorTestCase): + """SaveUnitary instruction tests.""" + + @supported_methods(["unitary"]) + def test_save_unitary(self, method, device): + """Test save unitary instruction""" + backend = self.backend(method=method, device=device) + + # Test circuit + SEED = 5426 + circ = QuantumVolume(3, seed=SEED) + + # Target unitary + target = qi.Operator(circ) + + # Add save to circuit + label = 'state' + circ.save_unitary(label=label) + + # Run + result = backend.run(transpile( + circ, backend, optimization_level=0), shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Operator(simdata[label]) + self.assertEqual(value, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_set_state.py b/test/terra/backends/aer_simulator/instructions/test_set_state.py new file mode 100644 index 0000000000..23b873a063 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_set_state.py @@ -0,0 +1,140 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests for set state instructions +""" + +from ddt import ddt +import qiskit.quantum_info as qi +from qiskit import QuantumCircuit, transpile +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestSetState(AerSimulatorTestCase): + """Test for set state instructions""" + @supported_methods(['automatic', 'stabilizer'], [1, 2, 3, 4]) + def test_set_stabilizer(self, method, device, num_qubits): + """Test SetStabilizer instruction""" + backend = self.backend(method=method, device=device) + + seed = 100 + label = 'state' + + target = qi.random_clifford(num_qubits, seed=seed) + + circ = QuantumCircuit(num_qubits) + circ.set_stabilizer(target) + circ.save_stabilizer(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Clifford.from_dict(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['automatic', 'statevector'], [1, 2, 3, 4, 5]) + def test_set_statevector(self, method, device, num_qubits): + """Test SetStatevector for instruction""" + backend = self.backend(method=method, device=device) + + seed = 100 + label = 'state' + + target = qi.random_statevector(2**num_qubits, seed=seed) + + circ = QuantumCircuit(num_qubits) + circ.set_statevector(target) + circ.save_statevector(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Statevector(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['automatic', 'density_matrix'], [1, 2, 3]) + def test_set_density_matrix(self, method, device, num_qubits): + """Test SetDensityMatrix instruction""" + backend = self.backend(method=method, device=device) + + seed = 100 + label = 'state' + + target = qi.random_density_matrix(2**num_qubits, seed=seed) + + circ = QuantumCircuit(num_qubits) + circ.set_density_matrix(target) + circ.save_density_matrix(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.DensityMatrix(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['unitary'], [1, 2, 3]) + def test_set_unitary(self, method, device, num_qubits): + """Test SetUnitary instruction""" + backend = self.backend(method=method, device=device) + + seed = 100 + label = 'state' + + target = qi.random_unitary(2**num_qubits, seed=seed) + + circ = QuantumCircuit(num_qubits) + circ.set_unitary(target) + circ.save_unitary(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.Operator(simdata[label]) + self.assertEqual(value, target) + + @supported_methods(['superop'], [1, 2]) + def test_set_superop(self, method, device, num_qubits): + """Test SetSuperOp instruction""" + backend = self.backend(method=method, device=device) + + seed = 100 + label = 'state' + + target = qi.SuperOp(qi.random_quantum_channel(2**num_qubits, + seed=seed)) + + circ = QuantumCircuit(num_qubits) + circ.set_superop(target) + circ.save_superop(label=label) + + # Run + result = backend.run(transpile(circ, backend, optimization_level=0), + shots=1).result() + self.assertTrue(result.success) + simdata = result.data(0) + self.assertIn(label, simdata) + value = qi.SuperOp(simdata[label]) + self.assertEqual(value, target) diff --git a/test/terra/backends/aer_simulator/instructions/test_standard_gates.py b/test/terra/backends/aer_simulator/instructions/test_standard_gates.py new file mode 100644 index 0000000000..eb70872126 --- /dev/null +++ b/test/terra/backends/aer_simulator/instructions/test_standard_gates.py @@ -0,0 +1,200 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests for circuit library standard gates +""" + +from ddt import ddt +from numpy.random import default_rng +from test.terra.backends.aer_simulator.aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + +from qiskit import transpile +import qiskit.quantum_info as qi + +from qiskit.circuit.library.standard_gates import ( + CXGate, CYGate, CZGate, DCXGate, HGate, IGate, SGate, SXGate, SXdgGate, + SdgGate, SwapGate, XGate, YGate, ZGate, TGate, TdgGate, iSwapGate, C3XGate, + C4XGate, CCXGate, CHGate, CSXGate, CSwapGate, CPhaseGate, CRXGate, CRYGate, + CRZGate, CU1Gate, CU3Gate, CUGate, PhaseGate, RC3XGate, RCCXGate, RGate, + RXGate, RXXGate, RYGate, RYYGate, RZGate, RZXGate, RZZGate, U1Gate, U2Gate, + U3Gate, UGate, MCXGate, MCPhaseGate, MCXGrayCode) + + +CLIFFORD_GATES = [ + # Clifford Gates + (CXGate, 0, False), + (CYGate, 0, False), + (CZGate, 0, False), + (DCXGate, 0, False), + (HGate, 0, False), + (IGate, 0, False), + (SGate, 0, False), + (SXGate, 0, False), + (SXdgGate, 0, False), + (SdgGate, 0, False), + (SwapGate, 0, False), + (XGate, 0, False), + (YGate, 0, False), + (ZGate, 0, False), +] + +NONCLIFFORD_GATES = [ + # Non-Clifford Gates + (TGate, 0, False), + (TdgGate, 0, False), + (iSwapGate, 0, False), + (CCXGate, 0, False), + (CHGate, 0, False), + (CSXGate, 0, False), + (CSwapGate, 0, False), + # Parameterized Gates + (CPhaseGate, 1, False), + (CRXGate, 1, False), + (CRYGate, 1, False), + (CRZGate, 1, False), + (CU1Gate, 1, False), + (CU3Gate, 3, False), + (CUGate, 4, False), + (PhaseGate, 1, False), + (RGate, 2, False), + (RXGate, 1, False), + (RXXGate, 1, False), + (RYGate, 1, False), + (RYYGate, 1, False), + (RZGate, 1, False), + (RZXGate, 1, False), + (RZZGate, 1, False), + (U1Gate, 1, False), + (U2Gate, 2, False), + (U3Gate, 3, False), + (UGate, 3, False), +] + +MC_GATES = [ + (C3XGate, 0, False), + (C4XGate, 0, False), + # Parameterized Gates + (RC3XGate, 1, False), + (RCCXGate, 1, False), + (MCXGate, 0, True), + (MCPhaseGate, 1, True), + (MCXGrayCode, 0, True), +] + +# Convert to label keys for nicer display format with ddt +CLIFFORD_GATES_DICT = {i[0].__name__: i for i in CLIFFORD_GATES} +NONCLIFFORD_GATES_DICT = {i[0].__name__: i for i in NONCLIFFORD_GATES} +MC_GATES_DICT = {i[0].__name__: i for i in MC_GATES} + + +@ddt +class TestGates(AerSimulatorTestCase): + """Test standard gate library.""" + + SEED = 8181 + RNG = default_rng(seed=SEED) + + def _test_gate(self, gate, gates_dict, **options): + """Test standard gates.""" + + backend = self.backend(**options) + + gate_cls, num_angles, has_ctrl_qubits = gates_dict[gate] + circuits = self.gate_circuits(gate_cls, + num_angles=num_angles, + has_ctrl_qubits=has_ctrl_qubits, + rng=self.RNG) + + label = 'final' + method = backend.options.method + for circuit in circuits: + if method == 'density_matrix': + target = qi.DensityMatrix(circuit) + circuit.save_density_matrix(label=label) + state_fn = qi.DensityMatrix + fidelity_fn = qi.state_fidelity + elif method == 'stabilizer': + target = qi.Clifford(circuit) + circuit.save_stabilizer(label=label) + state_fn = qi.Clifford.from_dict + fidelity_fn = qi.process_fidelity + elif method == 'unitary': + target = qi.Operator(circuit) + circuit.save_unitary(label=label) + state_fn = qi.Operator + fidelity_fn = qi.process_fidelity + elif method == 'superop': + target = qi.SuperOp(circuit) + circuit.save_superop(label=label) + state_fn = qi.SuperOp + fidelity_fn = qi.process_fidelity + else: + target = qi.Statevector(circuit) + circuit.save_statevector(label=label) + state_fn = qi.Statevector + fidelity_fn = qi.state_fidelity + + result = backend.run(transpile( + circuit, backend, optimization_level=0), shots=1).result() + + # Check results + success = getattr(result, 'success', False) + self.assertTrue(success) + data = result.data(0) + self.assertIn(label, data) + value = state_fn(data[label]) + fidelity = fidelity_fn(target, value) + self.assertGreater(fidelity, 0.9999) + + @supported_methods( + ["automatic", "stabilizer", "statevector", "density_matrix", "matrix_product_state", + "unitary", "superop"], + CLIFFORD_GATES_DICT) + def test_clifford_gate(self, method, device, gate): + """Test Clifford standard gates.""" + self._test_gate(gate, CLIFFORD_GATES_DICT, method=method, device=device) + + @supported_methods( + ["statevector", "density_matrix"], + CLIFFORD_GATES_DICT) + def test_clifford_gate_cache_blocking(self, method, device, gate): + """Test Clifford standard gates.""" + self._test_gate(gate, CLIFFORD_GATES_DICT, method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + @supported_methods( + ["automatic", "statevector", "density_matrix", "matrix_product_state", + "unitary", "superop"], + NONCLIFFORD_GATES_DICT) + def test_nonclifford_gate(self, method, device, gate): + """Test non-Clifford standard gates.""" + self._test_gate(gate, NONCLIFFORD_GATES_DICT, method=method, device=device) + + @supported_methods( + ["statevector", "density_matrix"], + NONCLIFFORD_GATES_DICT) + def test_nonclifford_gate_cache_blocking(self, method, device, gate): + """Test non-Clifford standard gates.""" + self._test_gate(gate, NONCLIFFORD_GATES_DICT, method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + @supported_methods(["automatic", "statevector", "unitary"], MC_GATES_DICT) + def test_multictrl_gate(self, method, device, gate): + """Test multi-controlled standard gates.""" + self._test_gate(gate, MC_GATES_DICT, method=method, device=device) + + @supported_methods(["statevector"], MC_GATES_DICT) + def test_multictrl_gate_cache_blocking(self, method, device, gate): + """Test multi-controlled standard gates.""" + self._test_gate(gate, MC_GATES_DICT, method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) diff --git a/test/terra/backends/aer_simulator/test_algorithms.py b/test/terra/backends/aer_simulator/test_algorithms.py new file mode 100644 index 0000000000..5d5503b071 --- /dev/null +++ b/test/terra/backends/aer_simulator/test_algorithms.py @@ -0,0 +1,80 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +QasmSimulator Integration Tests +""" + +from ddt import ddt +from test.terra.reference import ref_algorithms +from qiskit import transpile +from .aer_simulator_test_case import ( + AerSimulatorTestCase, supported_methods) + + +@ddt +class TestAlgorithms(AerSimulatorTestCase): + """AerSimulator algorithm tests in the default basis""" + + def _test_grovers(self, **options): + shots = 4000 + backend = self.backend(**options) + + circuits = ref_algorithms.grovers_circuit( + final_measure=True, allow_sampling=True) + + targets = ref_algorithms.grovers_counts(shots) + circuits = transpile(circuits, backend) + job = backend.run(circuits, shots=shots) + result = job.result() + + self.assertSuccess(result) + self.compare_counts(result, circuits, targets, delta=0.05 * shots) + + def _test_teleport(self, **options): + """Test teleport circuits.""" + shots = 4000 + backend = self.backend(**options) + + circuits = ref_algorithms.teleport_circuit() + targets = ref_algorithms.teleport_counts(shots) + circuits = transpile(circuits, backend) + job = backend.run(circuits, shots=shots) + result = job.result() + + self.assertSuccess(result) + self.compare_counts(result, circuits, targets, delta=0.05 * shots) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state']) + def test_grovers(self, method, device): + """Test grovers circuits execute.""" + self._test_grovers(method=method, device=device) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state']) + def test_teleport(self, method, device): + """Test teleport circuits.""" + self._test_teleport(method=method, device=device) + + @supported_methods(['statevector', 'density_matrix']) + def test_grovers_cache_blocking(self, method, device): + """Test grovers circuits execute.""" + self._test_grovers( + method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) + + @supported_methods(['statevector', 'density_matrix']) + def test_teleport_cache_blocking(self, method, device): + """Test teleport circuits.""" + self._test_teleport( + method=method, device=device, + blocking_qubits=2, max_parallel_threads=1) diff --git a/test/terra/common.py b/test/terra/common.py index 647a833293..5282a42e75 100644 --- a/test/terra/common.py +++ b/test/terra/common.py @@ -19,7 +19,6 @@ import inspect import logging import os -import unittest from unittest.util import safe_repr from itertools import repeat from random import choice, sample @@ -117,7 +116,7 @@ def gate_circuits(gate_cls, num_angles=0, has_ctrl_qubits=False, params.append(5) gate = gate_cls(*params) - + if basis_states is None: basis_states = [bin(i)[2:].zfill(gate.num_qubits) \ for i in range(1< Date: Tue, 30 Mar 2021 14:00:25 -0400 Subject: [PATCH 06/14] fix typos --- qiskit/providers/aer/backends/aer_simulator.py | 2 +- qiskit/providers/aer/backends/unitary_simulator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index a404fb1f32..bca4233cf0 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -684,7 +684,7 @@ def _set_method_config(self, method=None): elif method == 'density_matrix': description = 'A C++ density matrix simulator with noise' n_qubits = MAX_QUBITS_STATEVECTOR // 2 - elif method == 'untiary': + elif method == 'unitary': description = 'A C++ unitary matrix simulator' n_qubits = MAX_QUBITS_STATEVECTOR // 2 elif method == 'superop': diff --git a/qiskit/providers/aer/backends/unitary_simulator.py b/qiskit/providers/aer/backends/unitary_simulator.py index a51571894f..9e91c9bb31 100644 --- a/qiskit/providers/aer/backends/unitary_simulator.py +++ b/qiskit/providers/aer/backends/unitary_simulator.py @@ -58,7 +58,7 @@ class UnitarySimulator(AerBackend): The following configurable backend options are supported * ``method`` (str): Set the simulation method supported methods are - ``"unitary"`` for CPU simulation, and ``"untiary_gpu"`` + ``"unitary"`` for CPU simulation, and ``"unitary_gpu"`` for GPU simulation (Default: ``"unitary"``). * ``precision`` (str): Set the floating point precision for From 2babf304080f3ba659b8ab007fbcfadfc613f3be Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 30 Mar 2021 14:10:11 -0400 Subject: [PATCH 07/14] Add that superop supports gpu to doc --- qiskit/providers/aer/backends/aer_simulator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index bca4233cf0..839afb8411 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -136,15 +136,15 @@ class AerSimulator(AerBackend): +--------------------------+---------------+ | ``density_matrix`` | Yes | +--------------------------+---------------+ - | ``unitary`` | Yes | - +--------------------------+---------------+ | ``stabilizer`` | No | +--------------------------+---------------+ | `"matrix_product_state`` | No | +--------------------------+---------------+ | ``extended_stabilizer`` | No | +--------------------------+---------------+ - | ``superop`` | No | + | ``unitary`` | Yes | + +--------------------------+---------------+ + | ``superop`` | Yes | +--------------------------+---------------+ Running a GPU simulation is done using ``device="GPU"`` kwarg during From 91c11aadf60e95da4b6cfb4b6ec0a29bce11fd72 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 30 Mar 2021 22:12:09 -0400 Subject: [PATCH 08/14] Add Parser to AerController --- src/controllers/aer_controller.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 7d75054cf8..71a0fb68bf 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -85,7 +85,8 @@ class Controller { // Load a QOBJ from a JSON file and execute on the State type // class. - Result execute(const json_t &qobj); + template + Result execute(const inputdata_t &qobj); Result execute(std::vector &circuits, const Noise::NoiseModel &noise_model, const json_t &config); @@ -889,7 +890,8 @@ Controller::transpile_cache_blocking(Controller::Method method, const Circuit &c //------------------------------------------------------------------------- // Qobj execution //------------------------------------------------------------------------- -Result Controller::execute(const json_t &qobj_js) { +template +Result Controller::execute(const inputdata_t &input_qobj) { #ifdef AER_MPI MPI_Comm_size(MPI_COMM_WORLD, &num_processes_); MPI_Comm_rank(MPI_COMM_WORLD, &myrank_); @@ -901,15 +903,15 @@ Result Controller::execute(const json_t &qobj_js) { // Start QOBJ timer auto timer_start = myclock_t::now(); - Qobj qobj(qobj_js); + Qobj qobj(input_qobj); Noise::NoiseModel noise_model; json_t config; // Check for config - if (JSON::get_value(config, "config", qobj_js)) { + if (Parser::get_value(config, "config", input_qobj)) { // Set config set_config(config); // Load noise model - JSON::get_value(noise_model, "noise_model", config); + Parser::get_value(noise_model, "noise_model", config); } auto result = execute(qobj.circuits, noise_model, config); // Get QOBJ id and pass through header to result From 372f93adcff9a76aea6af17c950575dd5921c625 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 30 Mar 2021 22:59:25 -0400 Subject: [PATCH 09/14] Add unitary, superop and mps to AerSimulator automatic method After trying the heuristic for stabilizer or density matrix simulation the remaining simulation methods are selected between based on supported instructions with ordering statevector > density matrix > matrix product state > unitary > superop. --- .../providers/aer/backends/aer_simulator.py | 16 +++-- src/controllers/aer_controller.hpp | 63 ++++++++++--------- .../test_save_matrix_product_state.py | 2 +- .../instructions/test_save_superop.py | 2 +- .../instructions/test_save_unitary.py | 2 +- .../instructions/test_set_state.py | 4 +- 6 files changed, 47 insertions(+), 42 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index 839afb8411..0436fa9ad9 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -79,10 +79,8 @@ class AerSimulator(AerBackend): simulation methods can be returned using :meth:`available_methods`, these are - * ``"automatic"``: Default simulation method. Either the "statevector", - "density_matrix", or "stabilizer" simulation method is selected - automatically at runtime for each circuit based on the circuit - instructions, number of qubits, and noise model. + * ``"automatic"``: Default simulation method. Select the simulation + method automatically based on the circuit and noise model. * ``"statevector"``: A dense statevector simulation that can sample measurement outcomes from *ideal* circuits with all measurements at @@ -371,7 +369,10 @@ class AerSimulator(AerBackend): _BASIS_GATES[None] = _BASIS_GATES['automatic'] = sorted( set(_BASIS_GATES['statevector']).union( _BASIS_GATES['stabilizer']).union( - _BASIS_GATES['density_matrix'])) + _BASIS_GATES['density_matrix']).union( + _BASIS_GATES['matrix_product_state']).union( + _BASIS_GATES['unitary']).union( + _BASIS_GATES['superop'])) _CUSTOM_INSTR = { 'statevector': sorted([ @@ -416,7 +417,10 @@ class AerSimulator(AerBackend): _CUSTOM_INSTR[None] = _CUSTOM_INSTR['automatic'] = sorted( set(_CUSTOM_INSTR['statevector']).union( _CUSTOM_INSTR['stabilizer']).union( - _CUSTOM_INSTR['density_matrix'])) + _CUSTOM_INSTR['density_matrix']).union( + _CUSTOM_INSTR['matrix_product_state']).union( + _CUSTOM_INSTR['unitary']).union( + _CUSTOM_INSTR['superop'])) _DEFAULT_CONFIGURATION = { 'backend_name': 'aer_simulator', diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 71a0fb68bf..77f013e064 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1609,40 +1609,41 @@ Controller::simulation_method(const Circuit &circ, check_measure_sampling_opt(circ, Method::density_matrix)) { return Method::density_matrix; } - // Finally we check the statevector memory requirement for the - // current number of qubits. If it fits in available memory we - // default to the Statevector method. Otherwise we raise an exception - // and suggest using one of the other simulation methods. - bool enough_memory = true; - if (sim_precision_ == Precision::Single) { - Statevector::State> sv_state; - enough_memory = validate_memory_requirements(sv_state, circ, false); - } else { - Statevector::State<> sv_state; - enough_memory = validate_memory_requirements(sv_state, circ, false); - } - if (!enough_memory) { - throw std::runtime_error( - "QasmSimulator: Insufficient memory for " + - std::to_string(circ.num_qubits) + - "-qubit" - R"( circuit using "statevector" method. You could try using the)" - R"( "matrix_product_state" or "extended_stabilizer" method instead.)"); - } - } - default: { - // For default we use statevector followed by density matrix (for the case - // when the circuit contains invalid instructions for statevector) + + // If the special conditions for stabilizer or density matrix are + // not satisfied we choose simulation method based on supported + // operations only with preference given by memory requirements + // statevector > density matrix > matrix product state > unitary > superop + // typically any save state instructions will decide the method. if (validate_state(Statevector::State<>(), circ, noise_model, false)) { return Method::statevector; } - // If circuit contains invalid instructions for statevector throw a hail - // mary and try for density matrix. - if (validate) - validate_state(DensityMatrix::State<>(), circ, noise_model, true); - return Method::density_matrix; - } - } + if (validate_state(DensityMatrix::State<>(), circ, noise_model, false)) { + return Method::density_matrix; + } + if (validate_state(MatrixProductState::State(), circ, noise_model, false)) { + return Method::matrix_product_state; + } + if (validate_state(QubitUnitary::State<>(), circ, noise_model, false)) { + return Method::unitary; + } + if (validate_state(QubitSuperoperator::State<>(), circ, noise_model, false)) { + return Method::superop; + } + // If we got here, circuit isn't compatible with any of the simulation + // methods + std::stringstream msg; + msg << "AerSimulator: "; + if (noise_model.is_ideal()) { + msg << "circuit with instructions " << circ.opset(); + } else { + auto opset = circ.opset(); + opset.insert(noise_model.opset()); + msg << "circuit and noise model with instructions" << opset; + } + msg << " is not compatible with any of the automatic simulation methods"; + throw std::runtime_error(msg.str()); + }} } size_t Controller::required_memory_mb(const Circuit &circ, diff --git a/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py b/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py index 736d721218..994977ea4b 100644 --- a/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py +++ b/test/terra/backends/aer_simulator/instructions/test_save_matrix_product_state.py @@ -23,7 +23,7 @@ class TestSaveMatrixProductStateTests(AerSimulatorTestCase): """SaveMatrixProductState instruction tests.""" - @supported_methods(["matrix_product_state"]) + @supported_methods(["automatic", "matrix_product_state"]) def test_save_matrix_product_state(self, method, device): """Test save matrix_product_state instruction""" backend = self.backend(method=method, device=device) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_superop.py b/test/terra/backends/aer_simulator/instructions/test_save_superop.py index b1f83285c5..8a447de892 100644 --- a/test/terra/backends/aer_simulator/instructions/test_save_superop.py +++ b/test/terra/backends/aer_simulator/instructions/test_save_superop.py @@ -24,7 +24,7 @@ class TestSaveSuperOp(AerSimulatorTestCase): """SaveSuperOp instruction tests.""" - @supported_methods(["superop"]) + @supported_methods(["automatic", "superop"]) def test_save_superop(self, method, device): """Test save superop instruction""" backend = self.backend(method=method, device=device) diff --git a/test/terra/backends/aer_simulator/instructions/test_save_unitary.py b/test/terra/backends/aer_simulator/instructions/test_save_unitary.py index 402ffd2b76..076d217184 100644 --- a/test/terra/backends/aer_simulator/instructions/test_save_unitary.py +++ b/test/terra/backends/aer_simulator/instructions/test_save_unitary.py @@ -24,7 +24,7 @@ class TestSaveUnitary(AerSimulatorTestCase): """SaveUnitary instruction tests.""" - @supported_methods(["unitary"]) + @supported_methods(["automatic", "unitary"]) def test_save_unitary(self, method, device): """Test save unitary instruction""" backend = self.backend(method=method, device=device) diff --git a/test/terra/backends/aer_simulator/instructions/test_set_state.py b/test/terra/backends/aer_simulator/instructions/test_set_state.py index 23b873a063..b1b61f63a6 100644 --- a/test/terra/backends/aer_simulator/instructions/test_set_state.py +++ b/test/terra/backends/aer_simulator/instructions/test_set_state.py @@ -92,7 +92,7 @@ def test_set_density_matrix(self, method, device, num_qubits): value = qi.DensityMatrix(simdata[label]) self.assertEqual(value, target) - @supported_methods(['unitary'], [1, 2, 3]) + @supported_methods(['automatic', 'unitary'], [1, 2, 3]) def test_set_unitary(self, method, device, num_qubits): """Test SetUnitary instruction""" backend = self.backend(method=method, device=device) @@ -115,7 +115,7 @@ def test_set_unitary(self, method, device, num_qubits): value = qi.Operator(simdata[label]) self.assertEqual(value, target) - @supported_methods(['superop'], [1, 2]) + @supported_methods(['automatic', 'superop'], [1, 2]) def test_set_superop(self, method, device, num_qubits): """Test SetSuperOp instruction""" backend = self.backend(method=method, device=device) From f20053c02d2caf077aa7e59cb105e802b174b026 Mon Sep 17 00:00:00 2001 From: Victor Villar Date: Wed, 31 Mar 2021 15:40:19 +0200 Subject: [PATCH 10/14] Fix superoperator thrust compilation (#15) --- src/controllers/aer_controller.hpp | 17 ++++++++++------- .../superoperator/superoperator_state.hpp | 10 ++++++++++ .../superoperator/superoperator_thrust.hpp | 16 ++++++++-------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 77f013e064..fc69c974cc 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1941,19 +1941,20 @@ void Controller::run_circuit_without_sampled_noise(Circuit &circ, const bool can_sample = check_measure_sampling_opt(circ, method); // Cache blocking pass + uint_t block_bits = 0; if (cache_blocking) { auto cache_block_pass = transpile_cache_blocking(method, circ, dummy_noise, config); cache_block_pass.set_sample_measure(can_sample); cache_block_pass.optimize_circuit(circ, dummy_noise, state.opset(), result); - uint_t block_bits = 0; if (cache_block_pass.enabled()) { block_bits = cache_block_pass.block_bits(); } - // allocate qubit register - state.allocate(max_qubits_, block_bits); } - // Check if measure sampler and optimization are valid + // allocate qubit register + state.allocate(max_qubits_, block_bits); + + // Check if measure sampler and optimization are valid if (can_sample) { // Implement measure sampler auto& ops = circ.ops; @@ -2005,16 +2006,18 @@ void Controller::run_circuit_with_sampled_noise( result); fusion_pass.optimize_circuit(noise_circ, dummy_noise, state.opset(), result); + uint_t block_bits = 0; if (cache_blocking) { cache_block_pass.optimize_circuit(noise_circ, dummy_noise, state.opset(), result); - uint_t block_bits = 0; if (cache_block_pass.enabled()) { block_bits = cache_block_pass.block_bits(); } - // allocate qubit register - state.allocate(max_qubits_, block_bits); } + + // allocate qubit register + state.allocate(max_qubits_, block_bits); + run_single_shot(noise_circ, state, result, rng); } } diff --git a/src/simulators/superoperator/superoperator_state.hpp b/src/simulators/superoperator/superoperator_state.hpp index 8d34b7c93d..598fc187d6 100755 --- a/src/simulators/superoperator/superoperator_state.hpp +++ b/src/simulators/superoperator/superoperator_state.hpp @@ -23,6 +23,9 @@ #include "framework/utils.hpp" #include "simulators/state.hpp" #include "superoperator.hpp" +#ifdef AER_THRUST_SUPPORTED +#include "superoperator_thrust.hpp" +#endif namespace AER { namespace QubitSuperoperator { @@ -100,6 +103,8 @@ class State : public Base::State { // Config: {"omp_qubit_threshold": 3} virtual void set_config(const json_t &config) override; + virtual void allocate(uint_t num_qubits,uint_t block_bits) override; + //----------------------------------------------------------------------- // Additional methods //----------------------------------------------------------------------- @@ -348,6 +353,11 @@ template void State::initialize_omp() { BaseState::threads_); // set allowed OMP threads in qubitvector } +template +void State::allocate(uint_t num_qubits, uint_t block_bits){ + BaseState::qreg_.chunk_setup(num_qubits * 2, num_qubits * 2, 0, 1); +} + //========================================================================= // Implementation: Reset //========================================================================= diff --git a/src/simulators/superoperator/superoperator_thrust.hpp b/src/simulators/superoperator/superoperator_thrust.hpp index 205ab56223..9e97bd3b36 100755 --- a/src/simulators/superoperator/superoperator_thrust.hpp +++ b/src/simulators/superoperator/superoperator_thrust.hpp @@ -46,8 +46,8 @@ class SuperoperatorThrust : public DensityMatrixThrust { SuperoperatorThrust() : SuperoperatorThrust(0) {}; explicit SuperoperatorThrust(size_t num_qubits); - SuperoperatorThrust(const Superoperator& obj) = delete; - Superoperator &operator=(const Superoperator& obj) = delete; + SuperoperatorThrust(const SuperoperatorThrust& obj) = delete; + SuperoperatorThrust &operator=(const SuperoperatorThrust& obj) = delete; //----------------------------------------------------------------------- // Utility functions @@ -86,7 +86,7 @@ class SuperoperatorThrust : public DensityMatrixThrust { //------------------------------------------------------------------------------ template -SuperOperatorThrust::SuperoperatorThrust(size_t num_qubits) { +SuperoperatorThrust::SuperoperatorThrust(size_t num_qubits) { set_num_qubits(num_qubits); } @@ -95,7 +95,7 @@ SuperOperatorThrust::SuperoperatorThrust(size_t num_qubits) { //------------------------------------------------------------------------------ template -void SuperOperatorThrust::set_num_qubits(size_t num_qubits) { +void SuperoperatorThrust::set_num_qubits(size_t num_qubits) { num_qubits_ = num_qubits; // Superoperator is same size matrix as a unitary matrix // of twice as many qubits @@ -104,7 +104,7 @@ void SuperOperatorThrust::set_num_qubits(size_t num_qubits) { template -void SuperOperatorThrust::initialize() { +void SuperoperatorThrust::initialize() { // Set underlying unitary matrix to identity BaseUnitary::initialize(); } @@ -112,7 +112,7 @@ void SuperOperatorThrust::initialize() { template template -void SuperOperatorThrust::initialize_from_matrix(const matrix> &mat) { +void SuperoperatorThrust::initialize_from_matrix(const matrix> &mat) { if (AER::Utils::is_square(mat)) { const size_t nrows = mat.GetRows(); if (nrows == BaseUnitary::rows_) { @@ -140,7 +140,7 @@ void SuperOperatorThrust::initialize_from_matrix(const matrix -void SuperOperatorThrust::initialize_from_matrix(matrix> &&mat) { +void SuperoperatorThrust::initialize_from_matrix(matrix> &&mat) { if (AER::Utils::is_square(mat)) { const size_t nrows = mat.GetRows(); if (nrows == BaseUnitary::rows_) { @@ -176,7 +176,7 @@ void SuperOperatorThrust::initialize_from_matrix(matrix -inline std::ostream &operator<<(std::ostream &out, const AER::QV::SuperOperatorThrust&m) { +inline std::ostream &operator<<(std::ostream &out, const AER::QV::SuperoperatorThrust&m) { out << m.copy_to_matrix(); return out; } From 35cea0b3086e48d204f50215c325d51075a0a7f3 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 31 Mar 2021 11:16:50 -0400 Subject: [PATCH 11/14] Fixup superop method --- src/controllers/aer_controller.hpp | 32 +++++++++++++------ .../superoperator/superoperator_state.hpp | 3 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index fc69c974cc..ba81744d43 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1431,12 +1431,12 @@ void Controller::run_circuit(const Circuit &circ, if (sim_precision_ == Precision::Double) { return run_circuit_helper< QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::unitary, + circ, noise, config, shots, rng_seed, Method::superop, false, result); } else { return run_circuit_helper< QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::unitary, + circ, noise, config, shots, rng_seed, Method::superop, false, result); } #endif @@ -1556,14 +1556,28 @@ Controller::simulation_method(const Circuit &circ, } case Method::superop: { if (validate) { - if (sim_precision_ == Precision::Single) { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); + if (sim_device_ == Device::CPU) { + if (sim_precision_ == Precision::Single) { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } } else { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); +#ifdef AER_THRUST_SUPPORTED + if (sim_precision_ == Precision::Single) { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } else { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); + } +#endif } } return Method::superop; diff --git a/src/simulators/superoperator/superoperator_state.hpp b/src/simulators/superoperator/superoperator_state.hpp index 598fc187d6..03b9cac1f0 100755 --- a/src/simulators/superoperator/superoperator_state.hpp +++ b/src/simulators/superoperator/superoperator_state.hpp @@ -333,7 +333,6 @@ void State::initialize_qreg(uint_t num_qubits, const data_t &supermat) { template void State::initialize_qreg(uint_t num_qubits, const cmatrix_t &mat) { - // Check dimension of unitary const auto sz_uni = 1ULL << (2 * num_qubits); const auto sz_super = 1ULL << (4 * num_qubits); @@ -355,7 +354,7 @@ template void State::initialize_omp() { template void State::allocate(uint_t num_qubits, uint_t block_bits){ - BaseState::qreg_.chunk_setup(num_qubits * 2, num_qubits * 2, 0, 1); + BaseState::qreg_.chunk_setup(num_qubits * 4, num_qubits * 4, 0, 1); } //========================================================================= From 4faeccc8184140f3be4d75322c06c2283e969816 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 31 Mar 2021 11:26:01 -0400 Subject: [PATCH 12/14] Disable SuperoperatorThrust method There is a segfault happening somewhere so this disables the method until we can investigate further --- .../providers/aer/backends/aer_simulator.py | 15 ++--- src/controllers/aer_controller.hpp | 62 +++++-------------- .../aer_simulator/aer_simulator_test_case.py | 2 +- 3 files changed, 23 insertions(+), 56 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index 0436fa9ad9..322bd55696 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -85,39 +85,36 @@ class AerSimulator(AerBackend): * ``"statevector"``: A dense statevector simulation that can sample measurement outcomes from *ideal* circuits with all measurements at end of the circuit. For noisy simulations each shot samples a - randomly sampled noisy circuit from the noise model. Supports CPU and - GPU devices. + randomly sampled noisy circuit from the noise model. * ``"density_matrix"``: A dense density matrix simulation that may sample measurement outcomes from *noisy* circuits with all - measurements at end of the circuit. Supports CPU and GPU devices. + measurements at end of the circuit. * ``"stabilizer"``: An efficient Clifford stabilizer state simulator that can simulate noisy Clifford circuits if all errors in the noise - model are also Clifford errors. Supports CPU device only. + model are also Clifford errors. * ``"extended_stabilizer"``: An approximate simulated for Clifford + T circuits based on a state decomposition into ranked-stabilizer state. The number of terms grows with the number of non-Clifford (T) gates. - Supports CPU device only. * ``"matrix_product_state"``: A tensor-network statevector simulator that uses a Matrix Product State (MPS) representation for the state. This can be done either with or without truncation of the MPS bond dimensions depending on the simulator options. The default behaviour is no - truncation. Supports CPU device only. + truncation. * ``"unitary"``: A dense unitary matrix simulation of an ideal circuit. This simulates the unitary matrix of the circuit itself rather than the evolution of an initial quantum state. This method can only simulate gates, it does not support measurement, reset, or noise. - Supports CPU and GPU devices. * ``"superop"``: A dense superoperator matrix simulation of an ideal or noisy circuit. This simulates the superoperator matrix of the circuit itself rather than the evolution of an initial quantum state. This method can simulate ideal and noisy gates, and reset, but does not support - measurement. Supports CPU device only. + measurement. **GPU Simulation** @@ -142,7 +139,7 @@ class AerSimulator(AerBackend): +--------------------------+---------------+ | ``unitary`` | Yes | +--------------------------+---------------+ - | ``superop`` | Yes | + | ``superop`` | No | +--------------------------+---------------+ Running a GPU simulation is done using ``device="GPU"`` kwarg during diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index ba81744d43..26c938b64d 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1414,32 +1414,16 @@ void Controller::run_circuit(const Circuit &circ, } } case Method::superop: { - if (sim_device_ == Device::CPU) { - if (sim_precision_ == Precision::Double) { - return run_circuit_helper< - QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::superop, - false, result); - } else { - return run_circuit_helper< - QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::superop, - false, result); - } + if (sim_precision_ == Precision::Double) { + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::superop, + false, result); } else { -#ifdef AER_THRUST_SUPPORTED - if (sim_precision_ == Precision::Double) { - return run_circuit_helper< - QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::superop, - false, result); - } else { - return run_circuit_helper< - QubitSuperoperator::State>>( - circ, noise, config, shots, rng_seed, Method::superop, - false, result); - } -#endif + return run_circuit_helper< + QubitSuperoperator::State>>( + circ, noise, config, shots, rng_seed, Method::superop, + false, result); } } case Method::stabilizer: @@ -1556,28 +1540,14 @@ Controller::simulation_method(const Circuit &circ, } case Method::superop: { if (validate) { - if (sim_device_ == Device::CPU) { - if (sim_precision_ == Precision::Single) { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } + if (sim_precision_ == Precision::Single) { + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); } else { -#ifdef AER_THRUST_SUPPORTED - if (sim_precision_ == Precision::Single) { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } -#endif + QubitSuperoperator::State> state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(state, circ, true); } } return Method::superop; diff --git a/test/terra/backends/aer_simulator/aer_simulator_test_case.py b/test/terra/backends/aer_simulator/aer_simulator_test_case.py index 97a4c9766b..9a9e6f4cc3 100644 --- a/test/terra/backends/aer_simulator/aer_simulator_test_case.py +++ b/test/terra/backends/aer_simulator/aer_simulator_test_case.py @@ -58,7 +58,7 @@ def _method_device(methods): if not methods: methods = AerSimulator().available_methods() available_devices = AerSimulator().available_devices() - gpu_methods = ['statevector', 'density_matrix', 'unitary', 'superop'] + gpu_methods = ['statevector', 'density_matrix', 'unitary'] data_args = [] for method in methods: if method in gpu_methods: From b6d8d25ea9f5c1703d35c10851b46f2f2d2e553b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 31 Mar 2021 11:28:09 -0400 Subject: [PATCH 13/14] Fixup matrix type caster --- src/framework/pybind_casts.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/framework/pybind_casts.hpp b/src/framework/pybind_casts.hpp index 291b817a01..de59f6de37 100644 --- a/src/framework/pybind_casts.hpp +++ b/src/framework/pybind_casts.hpp @@ -33,8 +33,8 @@ template struct type_caster>{ if(py_matrix.ndim() != 2){ throw std::invalid_argument(std::string("Python: invalid matrix (empty array).")); } - size_t ncols = py_matrix.shape(0); - size_t nrows = py_matrix.shape(1); + size_t nrows = py_matrix.shape(0); + size_t ncols = py_matrix.shape(1); // Matrix looks ok, now we parse it auto raw_mat = py_matrix.template unchecked<2>(); if(c_order){ From 5a7e0f8c85bd3077cde07ea66ecac01d680e1180 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 31 Mar 2021 12:46:58 -0400 Subject: [PATCH 14/14] fix typo in AerProvider --- qiskit/providers/aer/aerprovider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index ec144b7ed3..5d8ecea460 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -36,7 +36,7 @@ def __init__(self): backends = [] for method in methods: name = 'aer_simulator' - if name != 'automatic': + if method not in [None, 'automatic']: name += f'_{method}' device_name = 'CPU' backends.append((name, AerSimulator, method, device_name))