From 34c7bff4b7e4eee73ffc899d58b22e0d09b9ab1f Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 8 Nov 2023 03:38:34 +0900 Subject: [PATCH 1/6] Remove unnecessary circuit metadata from builtin experiments and add tips to tutorial. --- docs/tutorials/custom_experiment.rst | 13 ++++++++ .../library/characterization/drag.py | 2 -- .../characterization/fine_amplitude.py | 6 ---- .../library/characterization/fine_drag.py | 7 +--- .../characterization/fine_frequency.py | 7 +--- .../library/characterization/half_angle.py | 7 +--- .../characterization/qubit_spectroscopy.py | 2 +- .../library/characterization/rabi.py | 8 +---- .../library/characterization/ramsey_xy.py | 33 ++++++++----------- .../library/characterization/readout_angle.py | 9 ++--- .../resonator_spectroscopy.py | 2 +- .../library/characterization/spectroscopy.py | 11 ++----- .../library/characterization/t1.py | 7 +--- .../library/characterization/t2hahn.py | 10 ++---- .../library/characterization/t2ramsey.py | 9 ++--- .../library/characterization/zz_ramsey.py | 8 ++--- .../library/quantum_volume/qv_experiment.py | 2 -- .../interleaved_rb_experiment.py | 2 -- .../randomized_benchmarking/standard_rb.py | 1 - 19 files changed, 43 insertions(+), 103 deletions(-) diff --git a/docs/tutorials/custom_experiment.rst b/docs/tutorials/custom_experiment.rst index c1ccf36546..239df97e2f 100644 --- a/docs/tutorials/custom_experiment.rst +++ b/docs/tutorials/custom_experiment.rst @@ -365,6 +365,19 @@ signature to restore the output to what it should be without the random Pauli fr at the end. We make a new :class:`.AnalysisResultData` object since we're rewriting the counts from the original experiment. +.. note:: + + As you may find here, circuit metadata is mainly used to generate a structured data + in the analysis class for convenience of result handling. + A metadata supplied to a particular circuit should appear in the corresponding + experiment result data dictionary stored in the experiment data. + If you attach large amount of metadata which is not expected to be used in the analysis, + the metadata just unnecessarily increases the job payload memory footprint, + and it prevents your experiment class from scaling in qubit size through + the composite experiment tooling. + If you still want to store some experiment setting, which is common to all circuits + or irrelevant to the analysis, maybe the experiment metadata is right place. + .. jupyter-input:: from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData diff --git a/qiskit_experiments/library/characterization/drag.py b/qiskit_experiments/library/characterization/drag.py index fb48add906..6a338212e8 100644 --- a/qiskit_experiments/library/characterization/drag.py +++ b/qiskit_experiments/library/characterization/drag.py @@ -171,8 +171,6 @@ def circuits(self) -> List[QuantumCircuit]: assigned_circuit = circuit.assign_parameters({beta: beta_val}, inplace=False) assigned_circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, "xval": beta_val, "nrep": rep, } diff --git a/qiskit_experiments/library/characterization/fine_amplitude.py b/qiskit_experiments/library/characterization/fine_amplitude.py index 7cf32a6138..69232e2e48 100644 --- a/qiskit_experiments/library/characterization/fine_amplitude.py +++ b/qiskit_experiments/library/characterization/fine_amplitude.py @@ -162,10 +162,7 @@ def _spam_cal_circuits(self, meas_circuit: QuantumCircuit) -> List[QuantumCircui circ.compose(meas_circuit, inplace=True) circ.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, "xval": add_x, - "unit": "gate number", "series": "spam-cal", } @@ -229,10 +226,7 @@ def circuits(self) -> List[QuantumCircuit]: circuit.compose(meas_circ, qubits, range(meas_circ.num_clbits), inplace=True) circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, "xval": repetition, - "unit": "gate number", "series": 1, } diff --git a/qiskit_experiments/library/characterization/fine_drag.py b/qiskit_experiments/library/characterization/fine_drag.py index 668fdec8d1..24dafb060c 100644 --- a/qiskit_experiments/library/characterization/fine_drag.py +++ b/qiskit_experiments/library/characterization/fine_drag.py @@ -218,12 +218,7 @@ def circuits(self) -> List[QuantumCircuit]: params=[], ) - circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "xval": repetition, - "unit": "gate number", - } + circuit.metadata = {"xval": repetition} circuits.append(circuit) diff --git a/qiskit_experiments/library/characterization/fine_frequency.py b/qiskit_experiments/library/characterization/fine_frequency.py index 5505b320a9..a1a913e7f8 100644 --- a/qiskit_experiments/library/characterization/fine_frequency.py +++ b/qiskit_experiments/library/characterization/fine_frequency.py @@ -127,12 +127,7 @@ def circuits(self) -> List[QuantumCircuit]: circuit.sx(0) circuit.measure_all() - circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "xval": repetition, - "unit": "Number of delays", - } + circuit.metadata = {"xval": repetition} circuits.append(circuit) diff --git a/qiskit_experiments/library/characterization/half_angle.py b/qiskit_experiments/library/characterization/half_angle.py index 4743149512..c41adb677d 100644 --- a/qiskit_experiments/library/characterization/half_angle.py +++ b/qiskit_experiments/library/characterization/half_angle.py @@ -160,12 +160,7 @@ def circuits(self) -> List[QuantumCircuit]: circuit.sx(0) circuit.measure_all() - circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "xval": repetition, - "unit": "repetition number", - } + circuit.metadata = {"xval": repetition} circuits.append(circuit) diff --git a/qiskit_experiments/library/characterization/qubit_spectroscopy.py b/qiskit_experiments/library/characterization/qubit_spectroscopy.py index aacc2da5a7..064e10ac6c 100644 --- a/qiskit_experiments/library/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/library/characterization/qubit_spectroscopy.py @@ -123,7 +123,7 @@ def circuits(self): freq_shift = np.round(freq_shift, decimals=3) assigned_circ = circuit.assign_parameters({freq_param: freq_shift}, inplace=False) - self._add_metadata(assigned_circ, freq, sched) + self._add_metadata(assigned_circ, freq) circs.append(assigned_circ) diff --git a/qiskit_experiments/library/characterization/rabi.py b/qiskit_experiments/library/characterization/rabi.py index 4f102f84d3..bedc41427e 100644 --- a/qiskit_experiments/library/characterization/rabi.py +++ b/qiskit_experiments/library/characterization/rabi.py @@ -163,13 +163,7 @@ def circuits(self) -> List[QuantumCircuit]: # which isn't serializable in the metadata. amp = float(np.round(amp, decimals=6)) assigned_circ = circuit.assign_parameters({param: amp}, inplace=False) - assigned_circ.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "xval": amp, - "unit": "arb. unit", - "amplitude": amp, - } + assigned_circ.metadata = {"xval": amp} circs.append(assigned_circ) diff --git a/qiskit_experiments/library/characterization/ramsey_xy.py b/qiskit_experiments/library/characterization/ramsey_xy.py index 3a4046253e..1d0238b652 100644 --- a/qiskit_experiments/library/characterization/ramsey_xy.py +++ b/qiskit_experiments/library/characterization/ramsey_xy.py @@ -155,20 +155,12 @@ def circuits(self) -> List[QuantumCircuit]: rotation_angle = rotation_angle * timing.dt # Create the X and Y circuits. - metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "osc_freq": self.experiment_options.osc_freq, - "unit": "s", - } - ram_x = self._pre_circuit() ram_x.sx(0) ram_x.delay(p_delay, 0, timing.delay_unit) ram_x.rz(rotation_angle, 0) ram_x.sx(0) ram_x.measure_active() - ram_x.metadata = metadata.copy() ram_y = self._pre_circuit() ram_y.sx(0) @@ -176,21 +168,22 @@ def circuits(self) -> List[QuantumCircuit]: ram_y.rz(rotation_angle - np.pi / 2, 0) ram_y.sx(0) ram_y.measure_active() - ram_y.metadata = metadata.copy() circs = [] for delay in self.experiment_options.delays: - assigned_x = ram_x.assign_parameters( - {p_delay: timing.round_delay(time=delay)}, inplace=False - ) - assigned_x.metadata["series"] = "X" - assigned_x.metadata["xval"] = timing.delay_time(time=delay) - - assigned_y = ram_y.assign_parameters( - {p_delay: timing.round_delay(time=delay)}, inplace=False - ) - assigned_y.metadata["series"] = "Y" - assigned_y.metadata["xval"] = timing.delay_time(time=delay) + delay_dt = timing.round_delay(time=delay) + delay_sec = timing.delay_time(time=delay) + + assigned_x = ram_x.assign_parameters({p_delay: delay_dt}, inplace=False) + assigned_x.metadata = { + "series": "X", + "xval": delay_sec, + } + assigned_y = ram_y.assign_parameters({p_delay: delay_dt}, inplace=False) + assigned_y.metadata = { + "series": "Y", + "xval": delay_sec, + } circs.extend([assigned_x, assigned_y]) diff --git a/qiskit_experiments/library/characterization/readout_angle.py b/qiskit_experiments/library/characterization/readout_angle.py index 72df20c40b..b1c0c88a4f 100644 --- a/qiskit_experiments/library/characterization/readout_angle.py +++ b/qiskit_experiments/library/characterization/readout_angle.py @@ -85,16 +85,11 @@ def circuits(self) -> List[QuantumCircuit]: """ circ0 = QuantumCircuit(1, 1) circ0.measure(0, 0) + circ0.metadata = {"xval": 0} circ1 = QuantumCircuit(1, 1) circ1.x(0) circ1.measure(0, 0) - - for i, circ in enumerate([circ0, circ1]): - circ.metadata = { - "experiment_type": self._type, - "qubit": self.physical_qubits[0], - "xval": i, - } + circ1.metadata = {"xval": 1} return [circ0, circ1] diff --git a/qiskit_experiments/library/characterization/resonator_spectroscopy.py b/qiskit_experiments/library/characterization/resonator_spectroscopy.py index 1d7f6eabd3..373bf18e6f 100644 --- a/qiskit_experiments/library/characterization/resonator_spectroscopy.py +++ b/qiskit_experiments/library/characterization/resonator_spectroscopy.py @@ -283,7 +283,7 @@ def circuits(self): circuit = self._template_circuit() circuit.add_calibration("measure", self.physical_qubits, sched_) - self._add_metadata(circuit, freq, sched) + self._add_metadata(circuit, freq) circs.append(circuit) diff --git a/qiskit_experiments/library/characterization/spectroscopy.py b/qiskit_experiments/library/characterization/spectroscopy.py index 3ca3e3687f..90ec4b9281 100644 --- a/qiskit_experiments/library/characterization/spectroscopy.py +++ b/qiskit_experiments/library/characterization/spectroscopy.py @@ -17,7 +17,6 @@ import numpy as np from qiskit import QuantumCircuit -from qiskit import pulse from qiskit.exceptions import QiskitError from qiskit.providers import Backend from qiskit.qobj.utils import MeasLevel @@ -109,19 +108,13 @@ def _backend_center_frequency(self) -> float: which depends on the nature of the spectroscopy experiment. """ - def _add_metadata(self, circuit: QuantumCircuit, freq: float, sched: pulse.ScheduleBlock): + def _add_metadata(self, circuit: QuantumCircuit, freq: float): """Helper method to add the metadata to avoid code duplication with subclasses.""" if not self._absolute: freq += self._backend_center_frequency - circuit.metadata = { - "experiment_type": self._type, - "qubits": self.physical_qubits, - "xval": np.round(freq, decimals=3), - "unit": "Hz", - "schedule": str(sched), - } + circuit.metadata = {"xval": np.round(freq, decimals=3)} def _metadata(self): metadata = super()._metadata() diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index 43275d836c..751f9a569b 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -105,12 +105,7 @@ def circuits(self) -> List[QuantumCircuit]: circ.barrier(0) circ.measure(0, 0) - circ.metadata = { - "experiment_type": self._type, - "qubit": self.physical_qubits[0], - "unit": "s", - } - circ.metadata["xval"] = timing.delay_time(time=delay) + circ.metadata = {"xval": timing.delay_time(time=delay)} circuits.append(circ) diff --git a/qiskit_experiments/library/characterization/t2hahn.py b/qiskit_experiments/library/characterization/t2hahn.py index d15a9e8d70..57ce07d363 100644 --- a/qiskit_experiments/library/characterization/t2hahn.py +++ b/qiskit_experiments/library/characterization/t2hahn.py @@ -131,18 +131,12 @@ def circuits(self) -> List[QuantumCircuit]: """ timing = BackendTiming(self.backend) - template = QuantumCircuit(1, 1) - template.metadata = { - "experiment_type": self._type, - "qubit": self.physical_qubits[0], - "unit": "s", - } - delay_param = Parameter("delay") num_echoes = self.experiment_options.num_echoes # First X rotation in 90 degrees + template = QuantumCircuit(1, 1) template.rx(np.pi / 2, 0) # Brings the qubit to the X Axis if num_echoes == 0: # if number of echoes is 0 then just apply the delay gate @@ -174,7 +168,7 @@ def circuits(self) -> List[QuantumCircuit]: assigned = template.assign_parameters( {delay_param: timing.round_delay(time=single_delay)}, inplace=False ) - assigned.metadata["xval"] = total_delay + assigned.metadata = {"xval": total_delay} circuits.append(assigned) return circuits diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index 920c6f25f0..489839f0f1 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -125,13 +125,7 @@ def circuits(self) -> List[QuantumCircuit]: circ.barrier(0) circ.measure(0, 0) - circ.metadata = { - "experiment_type": self._type, - "qubit": self.physical_qubits[0], - "xval": timing.delay_time(time=delay), - "osc_freq": self.experiment_options.osc_freq, - "unit": "s", - } + circ.metadata = {"xval": timing.delay_time(time=delay)} circuits.append(circ) @@ -144,4 +138,5 @@ def _metadata(self): for run_opt in ["meas_level", "meas_return"]: if hasattr(self.run_options, run_opt): metadata[run_opt] = getattr(self.run_options, run_opt) + metadata["osc_freq"] = self.experiment_options.osc_freq return metadata diff --git a/qiskit_experiments/library/characterization/zz_ramsey.py b/qiskit_experiments/library/characterization/zz_ramsey.py index 79b2b05a3d..daf546302b 100644 --- a/qiskit_experiments/library/characterization/zz_ramsey.py +++ b/qiskit_experiments/library/characterization/zz_ramsey.py @@ -221,10 +221,6 @@ def _template_circuits( Returns: Circuits for series 0 and 1 """ - metadata = { - "unit": "s", - } - delay = Parameter("delay") timing = BackendTiming(self.backend) @@ -244,7 +240,7 @@ def _template_circuits( # Template circuit for series 0 # Control qubit starting in |0> state, flipping to |1> in middle - circ0 = QuantumCircuit(2, 1, metadata=metadata.copy()) + circ0 = QuantumCircuit(2, 1) circ0.metadata["series"] = "0" circ0.sx(0) @@ -271,7 +267,7 @@ def _template_circuits( # Template circuit for series 1 # Control qubit starting in |1> state, flipping to |0> in middle - circ1 = QuantumCircuit(2, 1, metadata=metadata.copy()) + circ1 = QuantumCircuit(2, 1) circ1.metadata["series"] = "1" circ1.x(1) diff --git a/qiskit_experiments/library/quantum_volume/qv_experiment.py b/qiskit_experiments/library/quantum_volume/qv_experiment.py index 8f1528f157..984d1ec7e2 100644 --- a/qiskit_experiments/library/quantum_volume/qv_experiment.py +++ b/qiskit_experiments/library/quantum_volume/qv_experiment.py @@ -167,10 +167,8 @@ def circuits(self) -> List[QuantumCircuit]: qv_circ = QuantumVolumeCircuit(depth, depth, seed=rng) qv_circ.measure_active() qv_circ.metadata = { - "experiment_type": self._type, "depth": depth, "trial": trial, - "qubits": self.physical_qubits, "ideal_probabilities": self._get_ideal_data(qv_circ), } circuits.append(qv_circ) diff --git a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py index 54746737ef..0d105ece0f 100644 --- a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py @@ -199,7 +199,6 @@ def circuits(self) -> List[QuantumCircuit]: circ.metadata = { "xval": len(seq), "group": "Clifford", - "physical_qubits": self.physical_qubits, "interleaved": False, } # Build circuits of interleaved sequences @@ -215,7 +214,6 @@ def circuits(self) -> List[QuantumCircuit]: circ.metadata = { "xval": len(seq), # set length of the reference sequence "group": "Clifford", - "physical_qubits": self.physical_qubits, "interleaved": True, } diff --git a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py index 97e8e71ea2..549f9aa207 100644 --- a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py +++ b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py @@ -187,7 +187,6 @@ def circuits(self) -> List[QuantumCircuit]: circ.metadata = { "xval": len(seq), "group": "Clifford", - "physical_qubits": self.physical_qubits, } return circuits From 7cf91f15136ef167b4b2b810d2cd6f4e2c991a69 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 8 Nov 2023 03:38:58 +0900 Subject: [PATCH 2/6] Better handling of union of metadata in curve analysis. --- qiskit_experiments/curve_analysis/curve_analysis.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/curve_analysis/curve_analysis.py b/qiskit_experiments/curve_analysis/curve_analysis.py index 7fec75b0b4..78120a0f22 100644 --- a/qiskit_experiments/curve_analysis/curve_analysis.py +++ b/qiskit_experiments/curve_analysis/curve_analysis.py @@ -15,6 +15,7 @@ """ # pylint: disable=invalid-name +import numbers from typing import Dict, List, Tuple, Union, Optional from functools import partial from itertools import groupby @@ -295,10 +296,15 @@ def _format_data( continue if len(g_values) == 1: averaged[k] = v[0] + elif all(isinstance(v, numbers.Number) for v in g_values): + averaged[k] = np.average(g_values) else: - unique = set(v) + try: + unique = set(v) + except TypeError: + unique = set(map(repr, v)) if len(unique) == 1: - averaged[k] = next(iter(unique)) + averaged[k] = v[0] else: averaged[k] = list(unique) formatted.append(list(averaged.values())) From 64e81861b9813aa45c2fb55bdd6718dc4f9fdfd3 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 8 Nov 2023 03:44:29 +0900 Subject: [PATCH 3/6] reno --- .../upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml diff --git a/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml b/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml new file mode 100644 index 0000000000..af32e2b255 --- /dev/null +++ b/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + Removed unnecessary circuit metadata from the builtin experiment classes. + Circuit metadata such as the associated qubit indices and experiment type + are separately stored in the experiment metadata, and never used in the analysis. + Removal of unnecessary circuit metadata compresses the job payload and + thus expected to benefit scalability. From 8d8c4ba0cdb812adee5504f31bd70d28bc9673b0 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 9 Nov 2023 11:50:06 +0900 Subject: [PATCH 4/6] Remove metadata dependency from experiment helper --- qiskit_experiments/test/mock_iq_helpers.py | 85 +++++-------------- test/library/characterization/test_t1.py | 97 ++++++++-------------- test/library/characterization/test_tphi.py | 22 ++--- 3 files changed, 64 insertions(+), 140 deletions(-) diff --git a/qiskit_experiments/test/mock_iq_helpers.py b/qiskit_experiments/test/mock_iq_helpers.py index f583080940..41d20178c0 100644 --- a/qiskit_experiments/test/mock_iq_helpers.py +++ b/qiskit_experiments/test/mock_iq_helpers.py @@ -389,57 +389,34 @@ def _parallel_exp_circ_splitter(self, qc_list: List[QuantumCircuit]): experiment. TypeError: The data type provided doesn't match the expected type (`tuple` or `int`). """ - # exp_idx_map connects an experiment to its circuit in the output. - exp_idx_map = {exp: exp_idx for exp_idx, exp in enumerate(self.exp_list)} - qubit_exp_map = self._create_qubit_exp_map() - exp_circuits_list = [[] for _ in self.exp_list] + qubits_expid_map = {exp.physical_qubits: i for i, exp in enumerate(self.exp_list)} for qc in qc_list: - # Quantum Register to qubit mapping - qubit_indices = {bit: idx for idx, bit in enumerate(qc.qubits)} - # initialize quantum circuit for each experiment for this instance of circuit to fill # with instructions. - for exp_circuit in exp_circuits_list: + for i in range(len(self.exp_list)): # we copy the circuit to ensure that the circuit properties (e.g. calibrations and qubit # frequencies) are the same in the new circuit. - qcirc = qc.copy() - qcirc.data.clear() - qcirc.metadata.clear() - exp_circuit.append(qcirc) + empty_qc = qc.copy_empty_like() + empty_qc.metadata.clear() + exp_circuits_list[i].append(empty_qc) # fixing metadata - for exp_metadata in qc.metadata["composite_metadata"]: - # getting a qubit of one of the experiments that we ran in parallel. The key in the - # metadata is different for different experiments. - qubit_metadata = ( - exp_metadata.get("qubit") - if exp_metadata.get("qubit") is not None - else exp_metadata.get("qubits") - ) - if isinstance(qubit_metadata, tuple): - exp = qubit_exp_map[qubit_metadata[0]] - elif isinstance(qubit_metadata, int): - exp = qubit_exp_map[qubit_metadata] - else: - raise TypeError( - f"The qubit information in the metadata is of type {type(qubit_metadata)}." - f" Supported formats are `tuple` and `int`" - ) - # using the qubit to access the experiment. Then, we go to the last circuit in - # `exp_circuit` of the corresponding experiment, and we overwrite the metadata. - exp_circuits_list[exp_idx_map[exp]][-1].metadata = exp_metadata.copy() + for exp_idx, sub_metadata in zip( + qc.metadata["composite_index"], + qc.metadata["composite_metadata"], + ): + exp_circuits_list[exp_idx][-1].metadata = sub_metadata.copy() + # sorting instructions by qubits indexes and inserting them into a circuit of the relevant # experiment for inst, qarg, carg in qc.data: - exp = qubit_exp_map[qubit_indices[qarg[0]]] - # making a list from the qubits the instruction affects - qubit_indexes = [qubit_indices[qr] for qr in qarg] - # check that the instruction is part of the experiment - if set(qubit_indexes).issubset(set(exp.physical_qubits)): - # appending exp_circuits_list[experiment_index][last_circuit] - exp_circuits_list[exp_idx_map[exp]][-1].append(inst, qarg, carg) + qubit_indices = set(qc.find_bit(qr).index for qr in qarg) + for qubits, exp_idx in qubits_expid_map.items(): + if qubit_indices.issubset(qubits): + exp_circuits_list[exp_idx][-1].append(inst, qarg, carg) + break else: raise QiskitError( "A gate operates on two qubits that don't belong to the same experiment." @@ -453,27 +430,6 @@ def _parallel_exp_circ_splitter(self, qc_list: List[QuantumCircuit]): return exp_circuits_list - def _create_qubit_exp_map(self) -> Dict[int, BaseExperiment]: - """ - Creating a dictionary that connect qubits to their respective experiments. - Returns: - Dict: A dictionary in the form {num: experiment} where num in experiment.physical_qubits - - Raises: - QiskitError: If a qubit belong to two experiments. - """ - qubit_experiment_mapping = {} - for exp in self.exp_list: - for qubit in exp.physical_qubits: - if qubit not in qubit_experiment_mapping: - qubit_experiment_mapping[qubit] = exp - else: - raise QiskitError( - "There are duplications of qubits between parallel experiments" - ) - - return qubit_experiment_mapping - class MockIQDragHelper(MockIQExperimentHelper): """Functions needed for test_drag""" @@ -861,12 +817,12 @@ class MockIQT1Helper(MockIQExperimentHelper): def __init__( self, - t1: List[float] = None, + t1: float = None, iq_cluster_centers: Optional[List[Tuple[IQPoint, IQPoint]]] = None, iq_cluster_width: Optional[List[float]] = None, ): super().__init__(iq_cluster_centers, iq_cluster_width) - self._t1 = t1 or [90e-6] + self._t1 = t1 or 90e-6 def compute_probabilities(self, circuits: List[QuantumCircuit]) -> List[Dict[str, float]]: """Return the probability of being in the excited state.""" @@ -875,13 +831,10 @@ def compute_probabilities(self, circuits: List[QuantumCircuit]) -> List[Dict[str probability_output_dict = {} # extracting information from the circuit. - qubit_idx = circuit.metadata["qubit"] delay = circuit.metadata["xval"] # creating a probability dict. - if qubit_idx >= len(self._t1): - raise QiskitError(f"There is no 'T1' value for qubit index {qubit_idx}.") - probability_output_dict["1"] = np.exp(-delay / self._t1[qubit_idx]) + probability_output_dict["1"] = np.exp(-delay / self._t1) probability_output_dict["0"] = 1 - probability_output_dict["1"] output_dict_list.append(probability_output_dict) diff --git a/test/library/characterization/test_t1.py b/test/library/characterization/test_t1.py index 7c60e96ddd..6c49a9130d 100644 --- a/test/library/characterization/test_t1.py +++ b/test/library/characterization/test_t1.py @@ -57,13 +57,13 @@ def test_t1_measurement_level_1(self): ns = 1e-9 mu = 1e-6 - t1 = [45 * mu, 45 * mu] + t1 = 45 * mu # delays delays = np.logspace(1, 11, num=23, base=np.exp(1)) delays *= ns delays = np.insert(delays, 0, 0) - delays = np.append(delays, [t1[0] * 3]) + delays = np.append(delays, [t1 * 3]) num_shots = 4096 backend = MockIQBackend( @@ -78,7 +78,7 @@ def test_t1_measurement_level_1(self): exp0 = T1([0], delays) exp0.analysis = T1KerneledAnalysis() - exp0.analysis.set_options(p0={"amp": 1, "tau": t1[0], "base": 0}) + exp0.analysis.set_options(p0={"amp": 1, "tau": t1, "base": 0}) expdata0 = exp0.run( backend=backend, meas_return="avg", @@ -92,7 +92,7 @@ def test_t1_measurement_level_1(self): res = expdata0.analysis_results("T1") self.assertEqual(res.quality, "good") - self.assertAlmostEqual(res.value.n, t1[0], delta=3) + self.assertAlmostEqual(res.value.n, t1, delta=3) self.assertEqual(res.extra["unit"], "s") def test_t1_parallel(self): @@ -129,34 +129,26 @@ def test_t1_parallel_measurement_level_1(self): ns = 1e-9 mu = 1e-6 - t1 = [25 * mu, 20 * mu, 15 * mu] + t1s = [25 * mu, 20 * mu] + qubits = [0, 1] num_shots = 4096 - # qubits - qubit0 = 0 - qubit1 = 1 - - quantum_bit = [qubit0, qubit1] - # Delays delays = np.logspace(1, 11, num=23, base=np.exp(1)) delays *= ns delays = np.insert(delays, 0, 0) - delays = np.append(delays, [t1[0] * 3]) - - # Experiments - exp0 = T1(physical_qubits=[qubit0], delays=delays) - exp0.analysis = T1KerneledAnalysis() - - exp2 = T1(physical_qubits=[qubit1], delays=delays) - exp2.analysis = T1KerneledAnalysis() - - par_exp_list = [exp0, exp2] - par_exp = ParallelExperiment([exp0, exp2], flatten_results=False) - - # Helpers - exp_helper = [ - MockIQT1Helper( + delays = np.append(delays, [t1s[0] * 3]) + + par_exp_list = [] + exp_helpers = [] + for qidx, t1 in zip(qubits, t1s): + # Experiment + exp = T1(physical_qubits=[qidx], delays=delays) + exp.analysis = T1KerneledAnalysis() + par_exp_list.append(exp) + + # Helper + helper = MockIQT1Helper( t1=t1, iq_cluster_centers=[ ((-5.0, -4.0), (-5.0, 4.0)), @@ -165,10 +157,15 @@ def test_t1_parallel_measurement_level_1(self): ], iq_cluster_width=[1.0, 2.0, 1.0], ) - for _ in par_exp_list - ] + exp_helpers.append(helper) + + par_exp = ParallelExperiment( + par_exp_list, + flatten_results=False, + ) par_helper = MockIQParallelExperimentHelper( - exp_list=par_exp_list, exp_helper_list=exp_helper + exp_list=par_exp_list, + exp_helper_list=exp_helpers, ) # Backend @@ -185,10 +182,10 @@ def test_t1_parallel_measurement_level_1(self): self.assertExperimentDone(res) # Checking analysis - for i, qb in enumerate(quantum_bit): + for i, t1 in enumerate(t1s): sub_res = res.child_data(i).analysis_results("T1") self.assertEqual(sub_res.quality, "good") - self.assertAlmostEqual(sub_res.value.n, t1[qb], delta=3) + self.assertAlmostEqual(sub_res.value.n, t1, delta=3) def test_t1_analysis(self): """ @@ -204,12 +201,7 @@ def test_t1_analysis(self): data.add_data( { "counts": {"0": count0, "1": 10000 - count0}, - "metadata": { - "xval": (3 * i + 1) * 1e-9, - "experiment_type": "T1", - "qubit": 0, - "unit": "s", - }, + "metadata": {"xval": (3 * i + 1) * 1e-9}, } ) @@ -230,16 +222,8 @@ def test_t1_metadata(self): self.assertEqual(len(circs), len(delays)) for delay, circ in zip(delays, circs): - xval = circ.metadata.pop("xval") - self.assertAlmostEqual(xval, delay) - self.assertEqual( - circ.metadata, - { - "experiment_type": "T1", - "qubit": 0, - "unit": "s", - }, - ) + # xval is rounded to nealest granularity value. + self.assertAlmostEqual(circ.metadata["xval"], delay) def test_t1_low_quality(self): """ @@ -253,12 +237,7 @@ def test_t1_low_quality(self): data.add_data( { "counts": {"0": 10, "1": 10}, - "metadata": { - "xval": i * 1e-9, - "experiment_type": "T1", - "qubit": 0, - "unit": "s", - }, + "metadata": {"xval": i * 1e-9}, } ) @@ -338,13 +317,5 @@ def test_circuits_with_backend(self): self.assertEqual(len(circs), len(delays)) for delay, circ in zip(delays, circs): - xval = circ.metadata.pop("xval") - self.assertAlmostEqual(xval, delay) - self.assertEqual( - circ.metadata, - { - "experiment_type": "T1", - "qubit": 0, - "unit": "s", - }, - ) + # xval is rounded to nealest granularity value. + self.assertAlmostEqual(circ.metadata["xval"], delay) diff --git a/test/library/characterization/test_tphi.py b/test/library/characterization/test_tphi.py index 619289a088..3581c747c0 100644 --- a/test/library/characterization/test_tphi.py +++ b/test/library/characterization/test_tphi.py @@ -83,11 +83,12 @@ def test_tphi_with_changing_params(self): x_values_t1 = [] x_values_t2 = [] for datum in expdata.data(): - comp_meta = datum["metadata"]["composite_metadata"][0] - if comp_meta["experiment_type"] == "T1": - x_values_t1.append(comp_meta["xval"]) + metadata = datum["metadata"] + xval = metadata["composite_metadata"][0]["xval"] + if metadata["composite_index"][0] == 0: + x_values_t1.append(xval) else: - x_values_t2.append(comp_meta["xval"]) + x_values_t2.append(xval) self.assertListEqual(x_values_t1, delays_t1, "Incorrect delays_t1") self.assertListEqual(x_values_t2, delays_t2, "Incorrect delays_t2") @@ -104,15 +105,14 @@ def test_tphi_with_changing_params(self): # Extract x values from metadata x_values_t1 = [] x_values_t2 = [] - new_freq_t2 = None + new_freq_t2 = expdata.metadata["component_metadata"][1]["osc_freq"] for datum in expdata.data(): - comp_meta = datum["metadata"]["composite_metadata"][0] - if comp_meta["experiment_type"] == "T1": - x_values_t1.append(comp_meta["xval"]) + metadata = datum["metadata"] + xval = metadata["composite_metadata"][0]["xval"] + if metadata["composite_index"][0] == 0: + x_values_t1.append(xval) else: - x_values_t2.append(comp_meta["xval"]) - if new_freq_t2 is None: - new_freq_t2 = comp_meta["osc_freq"] + x_values_t2.append(xval) self.assertListEqual(x_values_t1, new_delays_t1, "Incorrect delays_t1") self.assertListEqual(x_values_t2, new_delays_t2, "Incorrect delays_t2") self.assertEqual(new_freq_t2, new_osc_freq, "Option osc_freq not set correctly") From e0129d2df2c57198f3ae4423a2cfe075e54f5261 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 10 Jan 2024 11:36:18 +0900 Subject: [PATCH 5/6] Wording suggestion Co-authored-by: Will Shanks Co-authored-by: Helena Zhang --- docs/tutorials/custom_experiment.rst | 2 +- .../notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/custom_experiment.rst b/docs/tutorials/custom_experiment.rst index 239df97e2f..c97d07b9ce 100644 --- a/docs/tutorials/custom_experiment.rst +++ b/docs/tutorials/custom_experiment.rst @@ -376,7 +376,7 @@ counts from the original experiment. and it prevents your experiment class from scaling in qubit size through the composite experiment tooling. If you still want to store some experiment setting, which is common to all circuits - or irrelevant to the analysis, maybe the experiment metadata is right place. + or irrelevant to the analysis, use the experiment metadata instead. .. jupyter-input:: diff --git a/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml b/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml index af32e2b255..b54e867d17 100644 --- a/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml +++ b/releasenotes/notes/upgrade-remove-circuit-metadata-ec7d3c6b08781184.yaml @@ -5,4 +5,4 @@ upgrade: Circuit metadata such as the associated qubit indices and experiment type are separately stored in the experiment metadata, and never used in the analysis. Removal of unnecessary circuit metadata compresses the job payload and - thus expected to benefit scalability. + thus is expected to benefit scalability. From dcedd4f213329a56cf7c787bf10b1599a73af1c4 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 10 Jan 2024 13:05:15 +0900 Subject: [PATCH 6/6] Fix tutorial code --- docs/manuals/characterization/t1.rst | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/manuals/characterization/t1.rst b/docs/manuals/characterization/t1.rst index afe0bb1130..8a926e9254 100644 --- a/docs/manuals/characterization/t1.rst +++ b/docs/manuals/characterization/t1.rst @@ -98,11 +98,10 @@ that is close to a logical value '0'. mu = 1e-6 # qubit properties - t1 = [45 * mu, 45 * mu] - t2 = [value/2 for value in t1] + t1 = 45 * mu # we will guess that our guess is 10% off the exact value of t1 for qubit 0. - t1_estimated_shift = t1[0]/10 + t1_estimated_shift = t1/10 # We use log space for the delays because of the noise properties delays = np.logspace(1, 11, num=23, base=np.exp(1)) @@ -111,25 +110,28 @@ that is close to a logical value '0'. # Adding circuits with delay=0 and long delays so the centers in the IQ plane won't be misplaced. # Without this, the fitting can provide wrong results. delays = np.insert(delays, 0, 0) - delays = np.append(delays, [t1[0]*3]) + delays = np.append(delays, [t1*3]) num_qubits = 2 num_shots = 2048 backend = MockIQBackend( - MockIQT1Helper(t1=t1, iq_cluster_centers=[((-5.0, -4.0), (-5.0, 4.0)), ((3.0, 1.0), (5.0, -3.0))] - , iq_cluster_width=[1.0, 2.0]) + MockIQT1Helper( + t1=t1, + iq_cluster_centers=[((-5.0, -4.0), (-5.0, 4.0)), ((3.0, 1.0), (5.0, -3.0))], + iq_cluster_width=[1.0, 2.0], + ) ) # Creating a T1 experiment expT1_kerneled = T1((0,), delays) expT1_kerneled.analysis = T1KerneledAnalysis() - expT1_kerneled.analysis.set_options(p0={"amp": 1, "tau": t1[0] + t1_estimated_shift, "base": 0}) + expT1_kerneled.analysis.set_options(p0={"amp": 1, "tau": t1 + t1_estimated_shift, "base": 0}) # Running the experiment - expdataT1_kerneled = expT1_kerneled.run(backend=backend, meas_return="avg", - meas_level=MeasLevel.KERNELED, - shots=num_shots).block_for_results() + expdataT1_kerneled = expT1_kerneled.run( + backend=backend, meas_return="avg", meas_level=MeasLevel.KERNELED, shots=num_shots + ).block_for_results() # Displaying results display(expdataT1_kerneled.figure(0))