diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index cc219e9fcb..2e802c66d1 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -288,9 +288,30 @@ def _run(self, qobj, job_id='', format_result=True): # Start timer start = time.time() + # Take metadata from headers of experiments to work around JSON serialization error + metadata_list = [] + metadata_index = 0 + for expr in qobj.experiments: + if hasattr(expr.header, "metadata"): + metadata_copy = expr.header.metadata.copy() + metadata_list.append(metadata_copy) + expr.header.metadata.clear() + if "id" in metadata_copy: + expr.header.metadata["id"] = metadata_copy["id"] + expr.header.metadata["metadata_index"] = metadata_index + metadata_index += 1 + # Run simulation output = self._execute(qobj) + # Recover metadata + metadata_index = 0 + for expr in qobj.experiments: + if hasattr(expr.header, "metadata"): + expr.header.metadata.clear() + expr.header.metadata.update(metadata_list[metadata_index]) + metadata_index += 1 + # Validate output if not isinstance(output, dict): logger.error("%s: simulation failed.", self.name()) @@ -305,6 +326,14 @@ def _run(self, qobj, job_id='', format_result=True): output["backend_name"] = self.name() output["backend_version"] = self.configuration().backend_version + # Push metadata to experiment headers + for result in output["results"]: + if ("header" in result and + "metadata" in result["header"] and + "metadata_index" in result["header"]["metadata"]): + metadata_index = result["header"]["metadata"]["metadata_index"] + result["header"]["metadata"] = metadata_list[metadata_index] + # Add execution time output["time_taken"] = time.time() - start diff --git a/releasenotes/notes/remove_circuit_metadata_from_qobj-324e7ea9b369ee67.yaml b/releasenotes/notes/remove_circuit_metadata_from_qobj-324e7ea9b369ee67.yaml new file mode 100644 index 0000000000..b5c0266890 --- /dev/null +++ b/releasenotes/notes/remove_circuit_metadata_from_qobj-324e7ea9b369ee67.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Fixed a potential issue with running simulations on circuits that have the + :attr:`.QuantumCircuit.metadata` attribute set. The :attr:`~.QuantumCircuit.metadata` + attribute can be any python dictionary and previously qiskit-aer would attempt to + JSON serialize the contents of the attribute to process it with the rest of the rest + of the circuit input, even if the contents were not JSON serializable. This no longer + occurs as the :attr:`.QuantumCircuit.metadata` attribute is not used to run the + simulation so now the contents are no serialized and instead are directly attached + to the :class:`qiskit.result.Result` object without attempting to JSON serialize + the contents. + Fixed `#1435 `__ diff --git a/test/terra/backends/aer_simulator/test_metadata.py b/test/terra/backends/aer_simulator/test_metadata.py new file mode 100644 index 0000000000..96902d322a --- /dev/null +++ b/test/terra/backends/aer_simulator/test_metadata.py @@ -0,0 +1,104 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019, 2020, 2021, 2022. +# +# 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 Integration Tests +""" +from math import sqrt +from ddt import ddt +from qiskit import transpile, QuantumCircuit +from test.terra.reference import ref_algorithms + +from test.terra.backends.simulator_test_case import ( + SimulatorTestCase, supported_methods) + + +@ddt +class TestMetadata(SimulatorTestCase): + """AerSimulator algorithm tests in the default basis""" + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', + 'matrix_product_state', 'extended_stabilizer']) + def test_single_circuit_metadata(self, method, device): + """Test circuits with object metadata.""" + backend = self.backend(method=method, device=device) + metadata = {1: object} + circuit = QuantumCircuit(1, name='circ0', metadata=metadata.copy()) + result = backend.run(circuit).result() + self.assertSuccess(result) + self.assertEqual(result.results[0].header.metadata, metadata) + self.assertEqual(circuit.metadata, metadata) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', + 'matrix_product_state', 'extended_stabilizer']) + def test_three_circuit_metadata(self, method, device): + """Test circuits with object metadata.""" + backend = self.backend(method=method, device=device) + + metadata0 = {0: object} + circuit0 = QuantumCircuit(1, name='circ0', metadata=metadata0.copy()) + + metadata1 = {1: object} + circuit1 = QuantumCircuit(1, name='circ1', metadata=metadata1.copy()) + + metadata2 = {2: object} + circuit2 = QuantumCircuit(1, name='circ2', metadata=metadata2.copy()) + + result = backend.run([circuit0, circuit1, circuit2]).result() + self.assertSuccess(result) + self.assertEqual(len(result.results), 3) + self.assertEqual(result.results[0].header.metadata, metadata0) + self.assertEqual(result.results[1].header.metadata, metadata1) + self.assertEqual(result.results[2].header.metadata, metadata2) + self.assertEqual(circuit0.metadata, metadata0) + self.assertEqual(circuit1.metadata, metadata1) + self.assertEqual(circuit2.metadata, metadata2) + + @supported_methods( + ['automatic', 'statevector', 'density_matrix', 'matrix_product_state']) + def test_three_parameterized_circuit_metadata(self, method, device): + """Test circuits with object metadata.""" + backend = self.backend(method=method, device=device) + + metadata0 = {0: object} + circuit0 = QuantumCircuit(1, name='circ0', metadata=metadata0.copy()) + circuit0.ry(0.1, 0) + circuit0.measure_all() + + metadata1 = {1: object} + circuit1 = QuantumCircuit(1, name='circ1', metadata=metadata1.copy()) + circuit1.ry(0.1, 0) + circuit1.measure_all() + + metadata2 = {2: object} + circuit2 = QuantumCircuit(1, name='circ2', metadata=metadata2.copy()) + circuit2.ry(0.1, 0) + circuit2.measure_all() + + parameterizations=[[[[0, 0], [0, 1]]], + [[[0, 0], [0, 1, 2]]], + []] + + result = backend.run([circuit0, circuit1, circuit2], + parameterizations=parameterizations).result() + self.assertSuccess(result) + self.assertEqual(len(result.results), 6) + self.assertEqual(result.results[0].header.metadata, metadata0) + self.assertEqual(result.results[1].header.metadata, metadata0) + self.assertEqual(result.results[2].header.metadata, metadata1) + self.assertEqual(result.results[3].header.metadata, metadata1) + self.assertEqual(result.results[4].header.metadata, metadata1) + self.assertEqual(result.results[5].header.metadata, metadata2) + self.assertEqual(circuit0.metadata, metadata0) + self.assertEqual(circuit1.metadata, metadata1) + self.assertEqual(circuit2.metadata, metadata2)