From 98fe72803111fe8ce80e80b99429c25649631715 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Wed, 5 Apr 2023 12:14:56 +0900 Subject: [PATCH 1/3] Stop using circuit metadata to internaly manage simulation results This fixes `AerSimulator` to use circuit metadata to maintain mapping from input and output of an executor call. This fixes an issue https://github.com/Qiskit/qiskit-aer/issues/1723. --- qiskit_aer/backends/aer_compiler.py | 15 ++++++++------ qiskit_aer/backends/aerbackend.py | 20 ++++--------------- ..._not_modify_metadata-60bb4b88707bd021.yaml | 8 ++++++++ .../backends/aer_simulator/test_circuit.py | 18 +++++++++++++++++ 4 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/do_not_modify_metadata-60bb4b88707bd021.yaml diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index fdb4fd88bd..500ee6e8da 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -15,7 +15,7 @@ import itertools from copy import copy -from typing import List +from typing import List, Dict from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression from qiskit.extensions import Initialize @@ -364,7 +364,7 @@ def generate_aer_config( return config -def assemble_circuit(circuit: QuantumCircuit): +def assemble_circuit(circuit: QuantumCircuit, metadata: Dict): """assemble circuit object mapped to AER::Circuit""" num_qubits = circuit.num_qubits @@ -391,8 +391,8 @@ def assemble_circuit(circuit: QuantumCircuit): global_phase=global_phase, ) - if circuit.metadata is not None: - header.metadata = circuit.metadata + if metadata is not None: + header.metadata = metadata qubit_indices = {qubit: idx for idx, qubit in enumerate(circuit.qubits)} clbit_indices = {clbit: idx for idx, clbit in enumerate(circuit.clbits)} @@ -588,11 +588,12 @@ def _assemble_op(aer_circ, inst, qubit_indices, clbit_indices, is_conditional, c raise AerError(f"unknown instruction: {name}") -def assemble_circuits(circuits: List[QuantumCircuit]) -> List[AerCircuit]: +def assemble_circuits(circuits: List[QuantumCircuit], metadata_list: Dict) -> List[AerCircuit]: """converts a list of Qiskit circuits into circuits mapped AER::Circuit Args: circuits: circuit(s) to be converted + metadata_list: list of metadata (dict) of circuits Returns: circuits to be run on the Aer backends @@ -611,4 +612,6 @@ def assemble_circuits(circuits: List[QuantumCircuit]) -> List[AerCircuit]: # Generate AerCircuit from the input circuit aer_qc_list = assemble_circuits(circuits=[qc]) """ - return [assemble_circuit(circuit) for circuit in circuits] + return [ + assemble_circuit(circuit, metadata) for circuit, metadata in zip(circuits, metadata_list) + ] diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index 06481ae144..90fc0630b0 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -423,19 +423,10 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format # Start timer start = time.time() - # Take metadata from headers of experiments to work around JSON serialization error - metadata_list = [] - for idx, circ in enumerate(circuits): - metadata_list.append(circ.metadata) - # TODO: we test for True-like on purpose here to condition against both None and {}, - # which allows us to support versions of Terra before and after QuantumCircuit.metadata - # accepts None as a valid value. This logic should be revisited after terra>=0.24.0 is - # required. - if circ.metadata: - circ.metadata = {"metadata_index": idx} - # Run simulation - aer_circuits = assemble_circuits(circuits) + aer_circuits = assemble_circuits( + circuits, [{"metadata_index": idx} for idx in range(len(circuits))] + ) output = self._execute_circuits(aer_circuits, noise_model, config) # Validate output @@ -460,10 +451,7 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format and "metadata_index" in result["header"]["metadata"] ): metadata_index = result["header"]["metadata"]["metadata_index"] - result["header"]["metadata"] = metadata_list[metadata_index] - - for circ, metadata in zip(circuits, metadata_list): - circ.metadata = metadata + result["header"]["metadata"] = circuits[metadata_index].metadata # Add execution time output["time_taken"] = time.time() - start diff --git a/releasenotes/notes/do_not_modify_metadata-60bb4b88707bd021.yaml b/releasenotes/notes/do_not_modify_metadata-60bb4b88707bd021.yaml new file mode 100644 index 0000000000..c0886bef97 --- /dev/null +++ b/releasenotes/notes/do_not_modify_metadata-60bb4b88707bd021.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Previously :class:`~.AerSimulator` modifies circuit metadata to maintain + consistency between input and output of simulation with side effect of + unexpected view of metadata from applicatiln in simiulation. This fix + avoids using circuit metadata to maintain consistency internaly and then + always provides consistent view of metadata to application. diff --git a/test/terra/backends/aer_simulator/test_circuit.py b/test/terra/backends/aer_simulator/test_circuit.py index f387e27a16..a8cc4c3762 100644 --- a/test/terra/backends/aer_simulator/test_circuit.py +++ b/test/terra/backends/aer_simulator/test_circuit.py @@ -171,3 +171,21 @@ def test_partial_result_a_single_invalid_circuit(self): self.assertEqual(result.status, "PARTIAL COMPLETED") self.assertTrue(hasattr(result.results[1].data, "counts")) self.assertFalse(hasattr(result.results[0].data, "counts")) + + def test_metadata_protected(self): + """Test metadata is consitently viewed from users""" + + qc = QuantumCircuit(2) + qc.metadata = {"foo": "bar", "object": object} + + circuits = [qc.copy() for _ in range(5)] + + backend = self.backend() + job = backend.run(circuits) + + for circuit in circuits: + self.assertTrue("foo" in circuit.metadata) + self.assertEqual(circuit.metadata["foo"], "bar") + self.assertEqual(circuit.metadata["object"], object) + + job.result() From 1e9e83d080d1abeb66f93cfa326b6dfde34e42ed Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Thu, 6 Apr 2023 02:02:53 +0900 Subject: [PATCH 2/3] add index of AER::Circuit and ExperimentResult --- qiskit_aer/backends/aer_compiler.py | 14 ++++---------- qiskit_aer/backends/aerbackend.py | 17 ++++++----------- src/controllers/aer_controller.hpp | 1 + src/controllers/controller_execute.hpp | 1 + src/framework/circuit.hpp | 1 + src/framework/qobj.hpp | 2 ++ src/framework/results/experiment_result.hpp | 1 + src/framework/results/pybind_result.hpp | 1 + 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/qiskit_aer/backends/aer_compiler.py b/qiskit_aer/backends/aer_compiler.py index 500ee6e8da..93b067a84e 100644 --- a/qiskit_aer/backends/aer_compiler.py +++ b/qiskit_aer/backends/aer_compiler.py @@ -15,7 +15,7 @@ import itertools from copy import copy -from typing import List, Dict +from typing import List from qiskit.circuit import QuantumCircuit, Clbit, ParameterExpression from qiskit.extensions import Initialize @@ -364,7 +364,7 @@ def generate_aer_config( return config -def assemble_circuit(circuit: QuantumCircuit, metadata: Dict): +def assemble_circuit(circuit: QuantumCircuit): """assemble circuit object mapped to AER::Circuit""" num_qubits = circuit.num_qubits @@ -391,9 +391,6 @@ def assemble_circuit(circuit: QuantumCircuit, metadata: Dict): global_phase=global_phase, ) - if metadata is not None: - header.metadata = metadata - qubit_indices = {qubit: idx for idx, qubit in enumerate(circuit.qubits)} clbit_indices = {clbit: idx for idx, clbit in enumerate(circuit.clbits)} @@ -588,12 +585,11 @@ def _assemble_op(aer_circ, inst, qubit_indices, clbit_indices, is_conditional, c raise AerError(f"unknown instruction: {name}") -def assemble_circuits(circuits: List[QuantumCircuit], metadata_list: Dict) -> List[AerCircuit]: +def assemble_circuits(circuits: List[QuantumCircuit]) -> List[AerCircuit]: """converts a list of Qiskit circuits into circuits mapped AER::Circuit Args: circuits: circuit(s) to be converted - metadata_list: list of metadata (dict) of circuits Returns: circuits to be run on the Aer backends @@ -612,6 +608,4 @@ def assemble_circuits(circuits: List[QuantumCircuit], metadata_list: Dict) -> Li # Generate AerCircuit from the input circuit aer_qc_list = assemble_circuits(circuits=[qc]) """ - return [ - assemble_circuit(circuit, metadata) for circuit, metadata in zip(circuits, metadata_list) - ] + return [assemble_circuit(circuit) for circuit in circuits] diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index 90fc0630b0..99f98dbb9b 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -424,9 +424,7 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format start = time.time() # Run simulation - aer_circuits = assemble_circuits( - circuits, [{"metadata_index": idx} for idx in range(len(circuits))] - ) + aer_circuits = assemble_circuits(circuits) output = self._execute_circuits(aer_circuits, noise_model, config) # Validate output @@ -444,14 +442,11 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format # Push metadata to experiment headers for result in output["results"]: - if ( - "header" in result - and "metadata" in result["header"] - and result["header"]["metadata"] - and "metadata_index" in result["header"]["metadata"] - ): - metadata_index = result["header"]["metadata"]["metadata_index"] - result["header"]["metadata"] = circuits[metadata_index].metadata + if "header" not in result: + continue + if "circuit_index" not in result: + continue + result["header"]["metadata"] = circuits[result["circuit_index"]].metadata # Add execution time output["time_taken"] = time.time() - start diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 704de9c501..e448a60e3d 100644 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1407,6 +1407,7 @@ void Controller::run_circuit_helper(const Circuit &circ, result.header = circ.header; result.shots = circ.shots; result.seed = circ.seed; + result.circuit_index = circ.index; result.metadata.add(parallel_shots_, "parallel_shots"); result.metadata.add(parallel_state_update_, "parallel_state_update"); diff --git a/src/controllers/controller_execute.hpp b/src/controllers/controller_execute.hpp index 46ffcdc81a..e79ce24a00 100644 --- a/src/controllers/controller_execute.hpp +++ b/src/controllers/controller_execute.hpp @@ -81,6 +81,7 @@ Result controller_execute(std::vector &input_circs, // Load circuits for (size_t i = 0; i < num_circs; i++) { auto &circ = input_circs[i]; + circ.index = i; if (param_table.empty() || param_table[i].empty()) { // Non parameterized circuit circ.set_params(truncate); diff --git a/src/framework/circuit.hpp b/src/framework/circuit.hpp index 58154a81e9..150a188ee4 100644 --- a/src/framework/circuit.hpp +++ b/src/framework/circuit.hpp @@ -59,6 +59,7 @@ class Circuit { json_t header; double global_phase_angle = 0; bool remapped_qubits = false; // True if qubits have been remapped + uint_t index = 0; // Constructor // The constructor automatically calculates the num_qubits, num_memory, diff --git a/src/framework/qobj.hpp b/src/framework/qobj.hpp index 0e502b978c..cf949935e9 100644 --- a/src/framework/qobj.hpp +++ b/src/framework/qobj.hpp @@ -133,11 +133,13 @@ Qobj::Qobj(const inputdata_t &input) { if (param_table.empty() || param_table[i].empty()) { // Get base circuit from qobj Circuit circuit(static_cast(circs[i]), config, truncation); + circuit.index = i; // Non parameterized circuit circuits.push_back(std::move(circuit)); } else { // Get base circuit from qobj without truncation Circuit circuit(static_cast(circs[i]), config, false); + circuit.index = i; // Load different parameterizations of the initial circuit const auto circ_params = param_table[i]; const size_t num_params = circ_params[0].second.size(); diff --git a/src/framework/results/experiment_result.hpp b/src/framework/results/experiment_result.hpp index 00604f1ad6..7cae7ceda1 100644 --- a/src/framework/results/experiment_result.hpp +++ b/src/framework/results/experiment_result.hpp @@ -40,6 +40,7 @@ struct ExperimentResult { uint_t shots; uint_t seed; double time_taken; + uint_t circuit_index = 0; // Success and status Status status = Status::empty; diff --git a/src/framework/results/pybind_result.hpp b/src/framework/results/pybind_result.hpp index 3a30477f7c..bf157500e0 100644 --- a/src/framework/results/pybind_result.hpp +++ b/src/framework/results/pybind_result.hpp @@ -44,6 +44,7 @@ py::object AerToPy::to_python(AER::ExperimentResult &&result) { py::dict pyexperiment; pyexperiment["shots"] = result.shots; + pyexperiment["circuit_index"] = result.circuit_index; pyexperiment["seed_simulator"] = result.seed; pyexperiment["data"] = AerToPy::to_python(std::move(result.data)); From 00dd1d1d68c0c00afd2b4248249c0defb8a608e9 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Thu, 20 Apr 2023 15:24:47 +0900 Subject: [PATCH 3/3] add a link to an input circuit in each experiment result --- qiskit_aer/backends/aerbackend.py | 7 +-- .../backends/wrappers/aer_circuit_binding.hpp | 2 +- .../wrappers/aer_controller_binding.hpp | 4 +- src/controllers/aer_controller.hpp | 48 +++++++++---------- src/controllers/controller_execute.hpp | 39 ++++++++------- src/framework/circuit.hpp | 1 - src/framework/qobj.hpp | 30 ++++++------ src/framework/results/experiment_result.hpp | 3 +- src/framework/results/pybind_result.hpp | 2 +- 9 files changed, 72 insertions(+), 64 deletions(-) diff --git a/qiskit_aer/backends/aerbackend.py b/qiskit_aer/backends/aerbackend.py index 99f98dbb9b..1fc5fcac1c 100644 --- a/qiskit_aer/backends/aerbackend.py +++ b/qiskit_aer/backends/aerbackend.py @@ -425,6 +425,9 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format # Run simulation aer_circuits = assemble_circuits(circuits) + metadata_map = { + aer_circuit: circuit.metadata for aer_circuit, circuit in zip(aer_circuits, circuits) + } output = self._execute_circuits(aer_circuits, noise_model, config) # Validate output @@ -444,9 +447,7 @@ def _execute_circuits_job(self, circuits, noise_model, config, job_id="", format for result in output["results"]: if "header" not in result: continue - if "circuit_index" not in result: - continue - result["header"]["metadata"] = circuits[result["circuit_index"]].metadata + result["header"]["metadata"] = metadata_map[result["circuit"]] # Add execution time output["time_taken"] = time.time() - start diff --git a/qiskit_aer/backends/wrappers/aer_circuit_binding.hpp b/qiskit_aer/backends/wrappers/aer_circuit_binding.hpp index 6aaf9a8cba..550f9b54ca 100644 --- a/qiskit_aer/backends/wrappers/aer_circuit_binding.hpp +++ b/qiskit_aer/backends/wrappers/aer_circuit_binding.hpp @@ -40,7 +40,7 @@ using namespace AER; template void bind_aer_circuit(MODULE m) { - py::class_ aer_circuit(m, "AerCircuit"); + py::class_> aer_circuit(m, "AerCircuit"); aer_circuit.def(py::init()); aer_circuit.def("__repr__", [](const Circuit &circ) { std::stringstream ss; diff --git a/qiskit_aer/backends/wrappers/aer_controller_binding.hpp b/qiskit_aer/backends/wrappers/aer_controller_binding.hpp index d862c8690f..3c0086710d 100644 --- a/qiskit_aer/backends/wrappers/aer_controller_binding.hpp +++ b/qiskit_aer/backends/wrappers/aer_controller_binding.hpp @@ -50,7 +50,7 @@ class ControllerExecutor { #endif } - py::object execute(std::vector &circuits, + py::object execute(std::vector> &circuits, Noise::NoiseModel &noise_model, AER::Config &config) const { return AerToPy::to_python( @@ -91,7 +91,7 @@ void bind_aer_controller(MODULE m) { }); aer_ctrl.def("execute", [aer_ctrl](ControllerExecutor &self, - std::vector &circuits, + std::vector> &circuits, py::object noise_model, AER::Config &config) { Noise::NoiseModel noise_model_native; if (noise_model) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index e448a60e3d..012013761d 100644 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -85,8 +85,8 @@ class Controller { template Result execute(const inputdata_t &qobj); - Result execute(std::vector &circuits, Noise::NoiseModel &noise_model, - const Config &config); + Result execute(std::vector> &circuits, + Noise::NoiseModel &noise_model, const Config &config); //----------------------------------------------------------------------- // Config settings @@ -262,7 +262,7 @@ class Controller { // The noise model will be modified to enable superop or kraus sampling // methods if required by the chosen methods. std::vector - simulation_methods(std::vector &circuits, + simulation_methods(std::vector> &circuits, Noise::NoiseModel &noise_model) const; // Return the simulation method to use based on the input circuit @@ -297,9 +297,9 @@ class Controller { void clear_parallelization(); // Set parallelization for experiments - void set_parallelization_experiments(const std::vector &circuits, - const Noise::NoiseModel &noise, - const std::vector &methods); + void set_parallelization_experiments( + const std::vector> &circuits, + const Noise::NoiseModel &noise, const std::vector &methods); // Set circuit parallelization void set_parallelization_circuit(const Circuit &circ, @@ -573,15 +573,15 @@ void Controller::clear_parallelization() { } void Controller::set_parallelization_experiments( - const std::vector &circuits, const Noise::NoiseModel &noise, - const std::vector &methods) { + const std::vector> &circuits, + const Noise::NoiseModel &noise, const std::vector &methods) { std::vector required_memory_mb_list(circuits.size()); max_qubits_ = 0; for (size_t j = 0; j < circuits.size(); j++) { - if (circuits[j].num_qubits > max_qubits_) - max_qubits_ = circuits[j].num_qubits; + if (circuits[j]->num_qubits > max_qubits_) + max_qubits_ = circuits[j]->num_qubits; required_memory_mb_list[j] = - required_memory_mb(circuits[j], noise, methods[j]); + required_memory_mb(*circuits[j], noise, methods[j]); } std::sort(required_memory_mb_list.begin(), required_memory_mb_list.end(), std::greater<>()); @@ -902,7 +902,7 @@ Result Controller::execute(const inputdata_t &input_qobj) { // Experiment execution //------------------------------------------------------------------------- -Result Controller::execute(std::vector &circuits, +Result Controller::execute(std::vector> &circuits, Noise::NoiseModel &noise_model, const Config &config) { // Start QOBJ timer @@ -920,8 +920,8 @@ Result Controller::execute(std::vector &circuits, // check if multi-chunk distribution is required bool multi_chunk_required_ = false; for (size_t j = 0; j < circuits.size(); j++) { - if (circuits[j].num_qubits > 0) { - if (multiple_chunk_required(circuits[j], noise_model, methods[j])) + if (circuits[j]->num_qubits > 0) { + if (multiple_chunk_required(*circuits[j], noise_model, methods[j])) multi_chunk_required_ = true; } } @@ -982,11 +982,11 @@ Result Controller::execute(std::vector &circuits, reg_t seeds(circuits.size()); reg_t avg_seeds(circuits.size()); for (int_t i = 0; i < circuits.size(); i++) - seeds[i] = circuits[i].seed; - MPI_Allreduce(seeds.data(), avg_seeds.data(), circuits.size(), + seeds[i] = circuits[i]->seed; + MPI_Allreduce(seeds.data(), avg_seeds.data(), circuits->size(), MPI_UINT64_T, MPI_SUM, MPI_COMM_WORLD); for (int_t i = 0; i < circuits.size(); i++) - circuits[i].seed = avg_seeds[i] / num_processes_; + circuits[i]->seed = avg_seeds[i] / num_processes_; } #endif @@ -996,14 +996,14 @@ Result Controller::execute(std::vector &circuits, // in #pragma omp) if (parallel_experiments_ == 1) { for (int j = 0; j < NUM_RESULTS; ++j) { - set_parallelization_circuit(circuits[j], noise_model, methods[j]); - run_circuit(circuits[j], noise_model, methods[j], config, + set_parallelization_circuit(*circuits[j], noise_model, methods[j]); + run_circuit(*circuits[j], noise_model, methods[j], config, result.results[j]); } } else { #pragma omp parallel for num_threads(parallel_experiments_) for (int j = 0; j < NUM_RESULTS; ++j) { - run_circuit(circuits[j], noise_model, methods[j], config, + run_circuit(*circuits[j], noise_model, methods[j], config, result.results[j]); } } @@ -1407,7 +1407,6 @@ void Controller::run_circuit_helper(const Circuit &circ, result.header = circ.header; result.shots = circ.shots; result.seed = circ.seed; - result.circuit_index = circ.index; result.metadata.add(parallel_shots_, "parallel_shots"); result.metadata.add(parallel_state_update_, "parallel_state_update"); @@ -1846,7 +1845,7 @@ void Controller::measure_sampler(InputIterator first_meas, //------------------------------------------------------------------------- std::vector -Controller::simulation_methods(std::vector &circuits, +Controller::simulation_methods(std::vector> &circuits, Noise::NoiseModel &noise_model) const { // Does noise model contain kraus noise bool kraus_noise = @@ -1858,7 +1857,8 @@ Controller::simulation_methods(std::vector &circuits, std::vector sim_methods; bool superop_enabled = false; bool kraus_enabled = false; - for (const auto &circ : circuits) { + for (const auto &_circ : circuits) { + const auto circ = *_circ; auto method = automatic_simulation_method(circ, noise_model); sim_methods.push_back(method); if (!superop_enabled && @@ -1888,7 +1888,7 @@ Controller::simulation_methods(std::vector &circuits, } else if (method_ == Method::tensor_network) { bool has_save_statevec = false; for (const auto &circ : circuits) { - has_save_statevec |= has_statevector_ops(circ); + has_save_statevec |= has_statevector_ops(*circ); if (has_save_statevec) break; } diff --git a/src/controllers/controller_execute.hpp b/src/controllers/controller_execute.hpp index e79ce24a00..11e53ba0a4 100644 --- a/src/controllers/controller_execute.hpp +++ b/src/controllers/controller_execute.hpp @@ -45,7 +45,7 @@ Result controller_execute(const inputdata_t &qobj) { } template -Result controller_execute(std::vector &input_circs, +Result controller_execute(std::vector> &input_circs, AER::Noise::NoiseModel &noise_model, AER::Config &config) { controller_t controller; @@ -75,29 +75,30 @@ Result controller_execute(std::vector &input_circs, R"(Invalid parameterized circuits: "parameterizations" length does not match number of circuits.)"); } - std::vector circs; + std::vector> circs; + std::vector> template_circs; try { // Load circuits for (size_t i = 0; i < num_circs; i++) { auto &circ = input_circs[i]; - circ.index = i; if (param_table.empty() || param_table[i].empty()) { // Non parameterized circuit - circ.set_params(truncate); - circ.set_metadata(config, truncate); + circ->set_params(truncate); + circ->set_metadata(config, truncate); circs.push_back(circ); + template_circs.push_back(circ); } else { // Get base circuit without truncation - circ.set_params(false); - circ.set_metadata(config, truncate); + circ->set_params(false); + circ->set_metadata(config, truncate); // Load different parameterizations of the initial circuit const auto circ_params = param_table[i]; const size_t num_params = circ_params[0].second.size(); - const size_t num_instr = circ.ops.size(); + const size_t num_instr = circ->ops.size(); for (size_t j = 0; j < num_params; j++) { // Make a copy of the initial circuit - Circuit param_circ = circ; + auto param_circ = std::make_shared(*circ); for (const auto ¶ms : circ_params) { const auto instr_pos = params.first.first; const auto param_pos = params.first.second; @@ -106,7 +107,7 @@ Result controller_execute(std::vector &input_circs, throw std::invalid_argument( R"(Invalid parameterized qobj: instruction position out of range)"); } - auto &op = param_circ.ops[instr_pos]; + auto &op = param_circ->ops[instr_pos]; if (param_pos >= op.params.size()) { throw std::invalid_argument( R"(Invalid parameterized qobj: instruction param position out of range)"); @@ -124,10 +125,11 @@ Result controller_execute(std::vector &input_circs, // of instructions, which can be changed in truncation. Therefore, // current implementation performs truncation for each parameter set. if (truncate) { - param_circ.set_params(true); - param_circ.set_metadata(config, true); + param_circ->set_params(true); + param_circ->set_metadata(config, true); } - circs.push_back(std::move(param_circ)); + circs.push_back(param_circ); + template_circs.push_back(circ); } } } @@ -145,10 +147,10 @@ Result controller_execute(std::vector &input_circs, if (config.seed_simulator.has_value()) seed = config.seed_simulator.value(); else - seed = circs[0].seed; + seed = circs[0]->seed; for (auto &circ : circs) { - circ.seed = seed + seed_shift; + circ->seed = seed + seed_shift; seed_shift += 2113; } @@ -156,7 +158,12 @@ Result controller_execute(std::vector &input_circs, // Issue: https://github.com/Qiskit/qiskit-aer/issues/1 Hacks::maybe_load_openmp(config.library_dir); controller.set_config(config); - return controller.execute(circs, noise_model, config); + auto ret = controller.execute(circs, noise_model, config); + + for (size_t i = 0; i < ret.results.size(); ++i) + ret.results[i].circuit = template_circs[i]; + + return ret; } } // end namespace AER diff --git a/src/framework/circuit.hpp b/src/framework/circuit.hpp index 150a188ee4..58154a81e9 100644 --- a/src/framework/circuit.hpp +++ b/src/framework/circuit.hpp @@ -59,7 +59,6 @@ class Circuit { json_t header; double global_phase_angle = 0; bool remapped_qubits = false; // True if qubits have been remapped - uint_t index = 0; // Constructor // The constructor automatically calculates the num_qubits, num_memory, diff --git a/src/framework/qobj.hpp b/src/framework/qobj.hpp index cf949935e9..c88b79afb7 100644 --- a/src/framework/qobj.hpp +++ b/src/framework/qobj.hpp @@ -46,9 +46,9 @@ class Qobj { //---------------------------------------------------------------- // Data //---------------------------------------------------------------- - std::string id; // qobj identifier passed to result - std::string type = "QASM"; // currently we only support QASM - std::vector circuits; // List of circuits + std::string id; // qobj identifier passed to result + std::string type = "QASM"; // currently we only support QASM + std::vector> circuits; // List of circuits json_t header; // (optional) passed through to result json_t config; // (optional) qobj level config data Noise::NoiseModel noise_model; // (optional) noise model @@ -132,21 +132,21 @@ Qobj::Qobj(const inputdata_t &input) { for (size_t i = 0; i < num_circs; i++) { if (param_table.empty() || param_table[i].empty()) { // Get base circuit from qobj - Circuit circuit(static_cast(circs[i]), config, truncation); - circuit.index = i; + auto circuit = std::make_shared( + static_cast(circs[i]), config, truncation); // Non parameterized circuit - circuits.push_back(std::move(circuit)); + circuits.push_back(circuit); } else { // Get base circuit from qobj without truncation - Circuit circuit(static_cast(circs[i]), config, false); - circuit.index = i; + auto circuit = std::make_shared( + static_cast(circs[i]), config, false); // Load different parameterizations of the initial circuit const auto circ_params = param_table[i]; const size_t num_params = circ_params[0].second.size(); - const size_t num_instr = circuit.ops.size(); + const size_t num_instr = circuit->ops.size(); for (size_t j = 0; j < num_params; j++) { // Make a copy of the initial circuit - Circuit param_circuit = circuit; + auto param_circuit = std::make_shared(*circuit); for (const auto ¶ms : circ_params) { const auto instr_pos = params.first.first; const auto param_pos = params.first.second; @@ -155,7 +155,7 @@ Qobj::Qobj(const inputdata_t &input) { throw std::invalid_argument( R"(Invalid parameterized qobj: instruction position out of range)"); } - auto &op = param_circuit.ops[instr_pos]; + auto &op = param_circuit->ops[instr_pos]; if (param_pos >= op.params.size()) { throw std::invalid_argument( R"(Invalid parameterized qobj: instruction param position out of range)"); @@ -173,8 +173,8 @@ Qobj::Qobj(const inputdata_t &input) { // instructions, which can be changed in truncation. Therefore, current // implementation performs truncation for each parameter set. if (truncation) - param_circuit.set_params(true); - circuits.push_back(std::move(param_circuit)); + param_circuit->set_params(true); + circuits.push_back(param_circuit); } } } @@ -182,10 +182,10 @@ Qobj::Qobj(const inputdata_t &input) { // We shift the seed for each successive experiment // So that results aren't correlated between experiments if (!has_simulator_seed) { - seed = circuits[0].seed; + seed = circuits[0]->seed; } for (auto &circuit : circuits) { - circuit.seed = seed + seed_shift; + circuit->seed = seed + seed_shift; seed_shift += 2113; // Shift the seed } } diff --git a/src/framework/results/experiment_result.hpp b/src/framework/results/experiment_result.hpp index 7cae7ceda1..e69a0af264 100644 --- a/src/framework/results/experiment_result.hpp +++ b/src/framework/results/experiment_result.hpp @@ -15,6 +15,7 @@ #ifndef _aer_framework_results_experiment_result_hpp_ #define _aer_framework_results_experiment_result_hpp_ +#include "framework/circuit.hpp" #include "framework/config.hpp" #include "framework/creg.hpp" #include "framework/opset.hpp" @@ -40,7 +41,7 @@ struct ExperimentResult { uint_t shots; uint_t seed; double time_taken; - uint_t circuit_index = 0; + std::shared_ptr circuit; // Success and status Status status = Status::empty; diff --git a/src/framework/results/pybind_result.hpp b/src/framework/results/pybind_result.hpp index bf157500e0..b9cd5fddbe 100644 --- a/src/framework/results/pybind_result.hpp +++ b/src/framework/results/pybind_result.hpp @@ -44,7 +44,7 @@ py::object AerToPy::to_python(AER::ExperimentResult &&result) { py::dict pyexperiment; pyexperiment["shots"] = result.shots; - pyexperiment["circuit_index"] = result.circuit_index; + pyexperiment["circuit"] = result.circuit; pyexperiment["seed_simulator"] = result.seed; pyexperiment["data"] = AerToPy::to_python(std::move(result.data));