From 0d0c1ef31a8ff59626d7f5477a78c4869e3d8771 Mon Sep 17 00:00:00 2001 From: Hiroshi Horii Date: Wed, 5 Apr 2023 12:14:56 +0900 Subject: [PATCH] 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 | 14 +++++++------ qiskit_aer/backends/aerbackend.py | 20 ++++--------------- ..._not_modify_metadata-60bb4b88707bd021.yaml | 8 ++++++++ .../backends/aer_simulator/test_circuit.py | 18 +++++++++++++++++ 4 files changed, 38 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..32cf80a8d2 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,7 +588,7 @@ 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: @@ -611,4 +611,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()