From 8d8c4ba0cdb812adee5504f31bd70d28bc9673b0 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 9 Nov 2023 11:50:06 +0900 Subject: [PATCH] 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")