From d1307ef1641508081146380d5006411c69f22528 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 14:46:34 +0200 Subject: [PATCH 01/59] added T1 experiment --- qiskit_experiments/__init__.py | 2 + qiskit_experiments/base_experiment.py | 10 +- .../characterization/t1_experiment.py | 129 ++++++++++++++++++ test/test_t1.py | 68 +++++++++ 4 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 qiskit_experiments/characterization/t1_experiment.py create mode 100644 test/test_t1.py diff --git a/qiskit_experiments/__init__.py b/qiskit_experiments/__init__.py index eb4560cf67..2ac660043c 100644 --- a/qiskit_experiments/__init__.py +++ b/qiskit_experiments/__init__.py @@ -19,3 +19,5 @@ # Experiment modules from . import composite +from . import characterization + diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index aad92eeaad..5ec835599b 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -60,14 +60,14 @@ class BaseExperiment(ABC): __experiment_data__ = ExperimentData def __init__(self, qubits, experiment_type=None, circuit_options=None): - """Initialize the analysis object. + """Initialize the experiment object. Args: qubits (int or Iterable[int]): the number of qubits or list of physical qubits for the experiment. experiment_type (str): Optional, the experiment type string. - circuit_options (str): Optional, dictionary of allowed kwargs and - default values for the `circuit` method. + circuit_options (dict): Optional, dictionary of allowed kwargs and + default values for the `circuit` method. Raises: QiskitError: if qubits is a list and contains duplicates. @@ -111,7 +111,7 @@ def run(self, backend, experiment_data=None, **kwargs): # Generate and run circuits circuits = self.transpiled_circuits(backend, **kwargs) - qobj = assemble(circuits) + qobj = assemble(circuits, backend) job = backend.run(qobj, **kwargs) # Add Job to ExperimentData @@ -190,7 +190,7 @@ def transpiled_circuits(self, backend=None, **kwargs): circuit_options = {} transpile_options = {} for key in kwargs: - if key in self._circuit_options: + if self._circuit_options is not None and key in self._circuit_options: circuit_options[key] = kwargs[key] elif key in _TRANSPILE_OPTIONS: transpile_options[key] = kwargs[key] diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py new file mode 100644 index 0000000000..7876313c4c --- /dev/null +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. +""" +T1 Experiment class. +""" + +from typing import List, Optional, Union, Tuple +import numpy as np +from scipy.optimize import curve_fit + +from qiskit.circuit import QuantumCircuit + +from qiskit_experiments.base_experiment import BaseExperiment +from qiskit_experiments.base_analysis import BaseAnalysis +from qiskit_experiments import AnalysisResult + + +class T1Analysis(BaseAnalysis): + """T1 Experiment result analysis class.""" + + def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None]: + """ + Calculate T1 + + Args: + experiment_data: the experiment data to analyze + params: expected parameters therein are: + `fit_p0` and `fit_bounds` - to be passed to scipy.optimize.curve_fit + as the `p0` and `bounds` parameters + + Returns: + The analysis result with the estimated T1 + """ + + prob1 = {} + for circ in experiment_data._data: + delay = circ['metadata']['delay'] + count0 = circ['counts'].get('0', 0) + count1 = circ['counts'].get('1', 0) + shots = count0 + count1 + mean = count1 / shots + std = np.sqrt(mean * (1-mean) / shots) + # problem for the fitter if one of the std points is + # exactly zero + if std == 0: + std = 1e-4 + prob1[delay] = (mean, std) + + delays = [] + means = [] + stds = [] + for delay in sorted(prob1): + delays.append(delay) + means.append(prob1[delay][0]) + stds.append(prob1[delay][1]) + + def exp_fit_fun(x, a, tau, c): + return a * np.exp(-x / tau) + c + + fit_out, _ = \ + curve_fit(exp_fit_fun, delays, means, sigma=stds, + p0=params['fit_p0'], bounds=params['fit_bounds']) + + analysis_result = AnalysisResult({'value': fit_out[1]}) + return analysis_result, None + + +class T1Experiment(BaseExperiment): + """T1 experiment class""" + + __analysis_class__ = T1Analysis + + def __init__(self, + qubit: int, + delays: Union[List[float], np.array], + unit: str = 'dt'): + """ + Initialize the T1 experiment class + + Args: + qubit: the qubit whose T1 is to be estimated + delays: delay times of the experiments + unit: time unit of `delays` + """ + self._delays = delays + self._unit = unit + super().__init__([qubit], type(self).__name__) + + def circuits(self, backend: Optional["Backend"] = None, + **circuit_options) -> List[QuantumCircuit]: + """ + Return a list of experiment circuits + + Args: + backend: a backend object + circuit_options: kwarg options for the function + + Returns: + The experiment circuits + """ + + circuits = [] + + for circ_index, delay in enumerate(self._delays): + circ = QuantumCircuit(1, 1) + circ.x(0) + circ.delay(delay, 0, self._unit) + circ.measure(0, 0) + + # pylint: disable = eval-used + circ.metadata = { + 'experiment_type': self._type, + 'qubit': self.physical_qubits[0], + 'delay': delay} + + circuits.append(circ) + + return circuits diff --git a/test/test_t1.py b/test/test_t1.py new file mode 100644 index 0000000000..2792dc8e33 --- /dev/null +++ b/test/test_t1.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. +""" +Test T1 experiment +""" + +import unittest + +import numpy as np + +from qiskit.providers.aer import QasmSimulator +from qiskit.providers.aer.noise.errors.standard_errors import thermal_relaxation_error +from qiskit.providers.aer.noise import NoiseModel + +from qiskit_experiments.composite import BatchExperiment, ParallelExperiment +from qiskit_experiments.characterization import T1Experiment + +backend = QasmSimulator() +instruction_durations = \ + [('measure', [0], 3, 'dt'), + ('x', [0], 3, 'dt')] +gate_time = 0.1 + +# Fix seed for simulations +SEED = 9000 + +class TestT1(unittest.TestCase): + """ + Test measurement of T1 + """ + + def test_t1(self): + """ + Test T1 experiment using a simulator. + Currently only verifies that there is no exception, + but does not verify accuracy of the estimate. + """ + + t1 = 25 + + noise_model = NoiseModel() + noise_model.add_quantum_error( + thermal_relaxation_error(t1, 2*t1, gate_time), + 'delay', [0]) + + delays = list(range(1, 33, 6)) + p0 = [1, t1, 0] + bounds = ([0, 0, -1], [2, 40, 1]) + + exp = T1Experiment(0, delays) + data = exp.run(backend, noise_model=noise_model, + fit_p0=p0, fit_bounds=bounds, + instruction_durations=instruction_durations) + + +if __name__ == '__main__': + unittest.main() From b980a194cef47054d6ec06c1092b13b5c19265ad Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 14:50:28 +0200 Subject: [PATCH 02/59] --amend --- qiskit_experiments/characterization/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 qiskit_experiments/characterization/__init__.py diff --git a/qiskit_experiments/characterization/__init__.py b/qiskit_experiments/characterization/__init__.py new file mode 100644 index 0000000000..280b38dfb8 --- /dev/null +++ b/qiskit_experiments/characterization/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. + +"""Qiskit Experiments Characterziation.""" + +from .t1_experiment import T1Experiment From 0ec5d647747545bebcfc46e544cd9cd876f403d0 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 16:00:29 +0200 Subject: [PATCH 03/59] black --- qiskit_experiments/__init__.py | 1 - .../characterization/t1_experiment.py | 33 +++++++++---------- test/test_t1.py | 21 ++++++------ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/qiskit_experiments/__init__.py b/qiskit_experiments/__init__.py index 2ac660043c..42e800cb3c 100644 --- a/qiskit_experiments/__init__.py +++ b/qiskit_experiments/__init__.py @@ -20,4 +20,3 @@ # Experiment modules from . import composite from . import characterization - diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 7876313c4c..40df88418c 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -45,12 +45,12 @@ def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None prob1 = {} for circ in experiment_data._data: - delay = circ['metadata']['delay'] - count0 = circ['counts'].get('0', 0) - count1 = circ['counts'].get('1', 0) + delay = circ["metadata"]["delay"] + count0 = circ["counts"].get("0", 0) + count1 = circ["counts"].get("1", 0) shots = count0 + count1 mean = count1 / shots - std = np.sqrt(mean * (1-mean) / shots) + std = np.sqrt(mean * (1 - mean) / shots) # problem for the fitter if one of the std points is # exactly zero if std == 0: @@ -68,11 +68,11 @@ def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c - fit_out, _ = \ - curve_fit(exp_fit_fun, delays, means, sigma=stds, - p0=params['fit_p0'], bounds=params['fit_bounds']) + fit_out, _ = curve_fit( + exp_fit_fun, delays, means, sigma=stds, p0=params["fit_p0"], bounds=params["fit_bounds"] + ) - analysis_result = AnalysisResult({'value': fit_out[1]}) + analysis_result = AnalysisResult({"value": fit_out[1]}) return analysis_result, None @@ -81,10 +81,7 @@ class T1Experiment(BaseExperiment): __analysis_class__ = T1Analysis - def __init__(self, - qubit: int, - delays: Union[List[float], np.array], - unit: str = 'dt'): + def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = "dt"): """ Initialize the T1 experiment class @@ -97,8 +94,9 @@ def __init__(self, self._unit = unit super().__init__([qubit], type(self).__name__) - def circuits(self, backend: Optional["Backend"] = None, - **circuit_options) -> List[QuantumCircuit]: + def circuits( + self, backend: Optional["Backend"] = None, **circuit_options + ) -> List[QuantumCircuit]: """ Return a list of experiment circuits @@ -120,9 +118,10 @@ def circuits(self, backend: Optional["Backend"] = None, # pylint: disable = eval-used circ.metadata = { - 'experiment_type': self._type, - 'qubit': self.physical_qubits[0], - 'delay': delay} + "experiment_type": self._type, + "qubit": self.physical_qubits[0], + "delay": delay, + } circuits.append(circ) diff --git a/test/test_t1.py b/test/test_t1.py index 2792dc8e33..9ca3f973fb 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -27,14 +27,13 @@ from qiskit_experiments.characterization import T1Experiment backend = QasmSimulator() -instruction_durations = \ - [('measure', [0], 3, 'dt'), - ('x', [0], 3, 'dt')] +instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] gate_time = 0.1 # Fix seed for simulations SEED = 9000 + class TestT1(unittest.TestCase): """ Test measurement of T1 @@ -50,19 +49,21 @@ def test_t1(self): t1 = 25 noise_model = NoiseModel() - noise_model.add_quantum_error( - thermal_relaxation_error(t1, 2*t1, gate_time), - 'delay', [0]) + noise_model.add_quantum_error(thermal_relaxation_error(t1, 2 * t1, gate_time), "delay", [0]) delays = list(range(1, 33, 6)) p0 = [1, t1, 0] bounds = ([0, 0, -1], [2, 40, 1]) exp = T1Experiment(0, delays) - data = exp.run(backend, noise_model=noise_model, - fit_p0=p0, fit_bounds=bounds, - instruction_durations=instruction_durations) + data = exp.run( + backend, + noise_model=noise_model, + fit_p0=p0, + fit_bounds=bounds, + instruction_durations=instruction_durations, + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() From 82ac1ebb2abbfedbe76bcf74163656f8fd71a8f3 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 16:09:21 +0200 Subject: [PATCH 04/59] lint --- .../characterization/t1_experiment.py | 4 ++-- test/test_t1.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 40df88418c..9fc636ac41 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -34,7 +34,7 @@ def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None Calculate T1 Args: - experiment_data: the experiment data to analyze + experiment_data (ExperimentData): the experiment data to analyze params: expected parameters therein are: `fit_p0` and `fit_bounds` - to be passed to scipy.optimize.curve_fit as the `p0` and `bounds` parameters @@ -110,7 +110,7 @@ def circuits( circuits = [] - for circ_index, delay in enumerate(self._delays): + for delay in self._delays: circ = QuantumCircuit(1, 1) circ.x(0) circ.delay(delay, 0, self._unit) diff --git a/test/test_t1.py b/test/test_t1.py index 9ca3f973fb..ca43085896 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -17,18 +17,15 @@ import unittest -import numpy as np - from qiskit.providers.aer import QasmSimulator from qiskit.providers.aer.noise.errors.standard_errors import thermal_relaxation_error from qiskit.providers.aer.noise import NoiseModel -from qiskit_experiments.composite import BatchExperiment, ParallelExperiment from qiskit_experiments.characterization import T1Experiment -backend = QasmSimulator() -instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] -gate_time = 0.1 +BACKEND = QasmSimulator() +INSTRUCTION_DURATIONS = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] +GATE_TIME = 0.1 # Fix seed for simulations SEED = 9000 @@ -49,19 +46,19 @@ def test_t1(self): t1 = 25 noise_model = NoiseModel() - noise_model.add_quantum_error(thermal_relaxation_error(t1, 2 * t1, gate_time), "delay", [0]) + noise_model.add_quantum_error(thermal_relaxation_error(t1, 2 * t1, GATE_TIME), "delay", [0]) delays = list(range(1, 33, 6)) p0 = [1, t1, 0] bounds = ([0, 0, -1], [2, 40, 1]) exp = T1Experiment(0, delays) - data = exp.run( - backend, + exp.run( + BACKEND, noise_model=noise_model, fit_p0=p0, fit_bounds=bounds, - instruction_durations=instruction_durations, + instruction_durations=INSTRUCTION_DURATIONS, ) From 8b6f5697f5467df4b100020e1bf46fe2a5c1e293 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 16:15:10 +0200 Subject: [PATCH 05/59] fixed header --- qiskit_experiments/characterization/t1_experiment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 9fc636ac41..9c3dc36f51 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This code is part of Qiskit. # # (C) Copyright IBM 2021. From 87dc98fcb37d9661983f4a6b5c3f07353182bc70 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 4 Mar 2021 17:03:31 +0200 Subject: [PATCH 06/59] changing variable names --- qiskit_experiments/characterization/t1_experiment.py | 6 +++--- test/test_t1.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 9c3dc36f51..65b4bc72b6 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -34,8 +34,8 @@ def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None Args: experiment_data (ExperimentData): the experiment data to analyze params: expected parameters therein are: - `fit_p0` and `fit_bounds` - to be passed to scipy.optimize.curve_fit - as the `p0` and `bounds` parameters + `p0` and `bounds` - to be passed to scipy.optimize.curve_fit + as the `p0` and `bounds` parameters Returns: The analysis result with the estimated T1 @@ -67,7 +67,7 @@ def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c fit_out, _ = curve_fit( - exp_fit_fun, delays, means, sigma=stds, p0=params["fit_p0"], bounds=params["fit_bounds"] + exp_fit_fun, delays, means, sigma=stds, p0=params["p0"], bounds=params["bounds"] ) analysis_result = AnalysisResult({"value": fit_out[1]}) diff --git a/test/test_t1.py b/test/test_t1.py index ca43085896..0a1726228e 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -56,8 +56,7 @@ def test_t1(self): exp.run( BACKEND, noise_model=noise_model, - fit_p0=p0, - fit_bounds=bounds, + p0=p0, bounds=bounds, instruction_durations=INSTRUCTION_DURATIONS, ) From 4dadd6cca9f57a37d288c3077c1afbc89ad65a4a Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 7 Mar 2021 16:33:52 +0200 Subject: [PATCH 07/59] changed backend in t1 test --- .../characterization/t1_experiment.py | 2 + test/test_t1.py | 95 +++++++++++++++---- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 65b4bc72b6..80754c976c 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -111,7 +111,9 @@ def circuits( for delay in self._delays: circ = QuantumCircuit(1, 1) circ.x(0) + circ.barrier(0) circ.delay(delay, 0, self._unit) + circ.barrier(0) circ.measure(0, 0) # pylint: disable = eval-used diff --git a/test/test_t1.py b/test/test_t1.py index 0a1726228e..c0b9d528cc 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -16,20 +16,82 @@ """ import unittest +import numpy as np +from qiskit.providers import BaseBackend +from qiskit.providers.models import BackendConfiguration +from qiskit.result import Result +from qiskit_experiments.characterization import T1Experiment -from qiskit.providers.aer import QasmSimulator -from qiskit.providers.aer.noise.errors.standard_errors import thermal_relaxation_error -from qiskit.providers.aer.noise import NoiseModel -from qiskit_experiments.characterization import T1Experiment +class T1Backend(BaseBackend): + """ + A simple and primitive backend, to be run by the T1 tests + """ + + def __init__(self, t1): + """ + Initialize the T1 backend + """ + + configuration = BackendConfiguration( + 't1_simulator', '0', int(1e6), + ['barrier', 'x', 'delay', 'measure'], + [], True, True, False, False, False, + int(1e6), None) -BACKEND = QasmSimulator() -INSTRUCTION_DURATIONS = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] -GATE_TIME = 0.1 + self._t1 = t1 + super().__init__(configuration) -# Fix seed for simulations -SEED = 9000 + def run(self, qobj, **kwargs): + """ + Run the T1 backend + """ + + shots = qobj.config.shots + + result = { + 'backend_name': 'T1 backend', + 'backend_version': '0', + 'qobj_id': 0, + 'job_id': 0, + 'success': True, + 'results': [] + } + + for circ in qobj.experiments: + counts = dict() + for _ in range(shots): + prob1 = np.zeros(circ.config.n_qubits) + clbits = np.zeros(circ.config.memory_slots, dtype=int) + + for op in circ.instructions: + qubit = op.qubits[0] + if op.name == "x": + prob1[qubit] = 1 - prob1[qubit] + if op.name == "delay": + prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1) + if op.name == "measure": + meas_res = np.random.binomial(1, prob1[qubit]) + clbits[op.memory[0]] = meas_res + prob1[qubit] = meas_res + + clstr = '' + for clbit in clbits[::-1]: + clstr = clstr + str(clbit) + + if clstr in counts: + counts[clstr] += 1 + else: + counts[clstr] = 1 + + result['results'].append({'shots': shots, + 'success': True, + 'header': {'metadata': circ.header.metadata}, + 'data': {'counts': counts}}) + + return Result.from_dict(result) + class TestT1(unittest.TestCase): """ @@ -39,27 +101,24 @@ class TestT1(unittest.TestCase): def test_t1(self): """ Test T1 experiment using a simulator. - Currently only verifies that there is no exception, - but does not verify accuracy of the estimate. """ t1 = 25 - noise_model = NoiseModel() - noise_model.add_quantum_error(thermal_relaxation_error(t1, 2 * t1, GATE_TIME), "delay", [0]) - delays = list(range(1, 33, 6)) p0 = [1, t1, 0] bounds = ([0, 0, -1], [2, 40, 1]) + instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] exp = T1Experiment(0, delays) - exp.run( - BACKEND, - noise_model=noise_model, + res = exp.run( + T1Backend(t1), p0=p0, bounds=bounds, - instruction_durations=INSTRUCTION_DURATIONS, + instruction_durations=instruction_durations ) + self.assertAlmostEqual(res.analysis_result(0)['value'], t1, delta=2) + if __name__ == "__main__": unittest.main() From 1a43b2e1c6998d818f2fa85ce1f3e0b630a1e003 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 7 Mar 2021 17:03:43 +0200 Subject: [PATCH 08/59] added a test of a parallel experiment with T1 --- qiskit_experiments/base_experiment.py | 2 +- test/test_t1.py | 43 ++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index 5ec835599b..2dc428d947 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -111,7 +111,7 @@ def run(self, backend, experiment_data=None, **kwargs): # Generate and run circuits circuits = self.transpiled_circuits(backend, **kwargs) - qobj = assemble(circuits, backend) + qobj = assemble(circuits, backend, **kwargs) job = backend.run(qobj, **kwargs) # Add Job to ExperimentData diff --git a/test/test_t1.py b/test/test_t1.py index c0b9d528cc..129ec0e222 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -20,6 +20,7 @@ from qiskit.providers import BaseBackend from qiskit.providers.models import BackendConfiguration from qiskit.result import Result +from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T1Experiment @@ -70,7 +71,8 @@ def run(self, qobj, **kwargs): if op.name == "x": prob1[qubit] = 1 - prob1[qubit] if op.name == "delay": - prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1) + if self._t1[qubit] is not None: + prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1[qubit]) if op.name == "measure": meas_res = np.random.binomial(1, prob1[qubit]) clbits[op.memory[0]] = meas_res @@ -108,17 +110,50 @@ def test_t1(self): delays = list(range(1, 33, 6)) p0 = [1, t1, 0] bounds = ([0, 0, -1], [2, 40, 1]) + + # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] exp = T1Experiment(0, delays) res = exp.run( - T1Backend(t1), + T1Backend([t1]), p0=p0, bounds=bounds, - instruction_durations=instruction_durations - ) + instruction_durations=instruction_durations, + shots=10000 + ) self.assertAlmostEqual(res.analysis_result(0)['value'], t1, delta=2) + def test_t1_parallel(self): + """ + Test parallel experiments of T1 using a simulator. + """ + + t1 = [25, 15] + + delays = list(range(1, 33, 6)) + p0 = [1, t1[0], 0] + bounds = ([0, 0, -1], [2, 40, 1]) + + # dummy numbers to avoid exception triggerring + instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] + + exp0 = T1Experiment(0, delays) + exp2 = T1Experiment(2, delays) + par_exp = ParallelExperiment([exp0, exp2]) + res = par_exp.run( + T1Backend([t1[0], None, t1[1]]), + p0=p0, bounds=bounds, + instruction_durations=instruction_durations, + shots=10000 + ) + + for i in range(2): + self.assertAlmostEqual( + res.component_experiment_data(i).analysis_result(0)['value'], + t1[i], delta=2 + ) + if __name__ == "__main__": unittest.main() From e36046de28e1cb3b99f5990bbc7d26b703348e0a Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 8 Mar 2021 17:54:07 +0200 Subject: [PATCH 09/59] preparation and readout error in t1 tests --- test/test_t1.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/test/test_t1.py b/test/test_t1.py index 129ec0e222..c372dd0e6f 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -29,7 +29,10 @@ class T1Backend(BaseBackend): A simple and primitive backend, to be run by the T1 tests """ - def __init__(self, t1): + def __init__(self, t1, + initial_prob1=None, + readout0to1=None, + readout1to0=None): """ Initialize the T1 backend """ @@ -41,6 +44,9 @@ def __init__(self, t1): int(1e6), None) self._t1 = t1 + self._initial_prob1 = initial_prob1 + self._readout0to1 = readout0to1 + self._readout1to0 = readout1to0 super().__init__(configuration) def run(self, qobj, **kwargs): @@ -60,10 +66,25 @@ def run(self, qobj, **kwargs): } for circ in qobj.experiments: + nqubits = circ.config.n_qubits counts = dict() + if self._readout0to1 is None: + ro01 = np.zeros(nqubits) + else: + ro01 = self._readout0to1 + + if self._readout1to0 is None: + ro10 = np.zeros(nqubits) + else: + ro10 = self._readout1to0 + for _ in range(shots): - prob1 = np.zeros(circ.config.n_qubits) + if self._initial_prob1 is None: + prob1 = np.zeros(nqubits) + else: + prob1 = self._initial_prob1.copy() + clbits = np.zeros(circ.config.memory_slots, dtype=int) for op in circ.instructions: @@ -74,7 +95,7 @@ def run(self, qobj, **kwargs): if self._t1[qubit] is not None: prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1[qubit]) if op.name == "measure": - meas_res = np.random.binomial(1, prob1[qubit]) + meas_res = np.random.binomial(1, prob1[qubit] * (1 - ro10[qubit]) + (1 - prob1[qubit]) * ro01[qubit]) clbits[op.memory[0]] = meas_res prob1[qubit] = meas_res @@ -86,7 +107,7 @@ def run(self, qobj, **kwargs): counts[clstr] += 1 else: counts[clstr] = 1 - + result['results'].append({'shots': shots, 'success': True, 'header': {'metadata': circ.header.metadata}, @@ -116,13 +137,15 @@ def test_t1(self): exp = T1Experiment(0, delays) res = exp.run( - T1Backend([t1]), + T1Backend([t1], initial_prob1=[0.1], + readout0to1=[0.1], + readout1to0=[0.1]), p0=p0, bounds=bounds, instruction_durations=instruction_durations, shots=10000 ) - self.assertAlmostEqual(res.analysis_result(0)['value'], t1, delta=2) + self.assertAlmostEqual(res.analysis_result(0)['value'], t1, delta=3) def test_t1_parallel(self): """ @@ -151,7 +174,7 @@ def test_t1_parallel(self): for i in range(2): self.assertAlmostEqual( res.component_experiment_data(i).analysis_result(0)['value'], - t1[i], delta=2 + t1[i], delta=3 ) From 8e7d313b8e5e27db1b579c319eec2a0a778792f4 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 8 Mar 2021 21:29:30 +0200 Subject: [PATCH 10/59] p0 and bounds as explicit function parameters --- qiskit_experiments/characterization/t1_experiment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 80754c976c..66786c181e 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -27,15 +27,14 @@ class T1Analysis(BaseAnalysis): """T1 Experiment result analysis class.""" - def _run_analysis(self, experiment_data, **params) -> Tuple[AnalysisResult, None]: + def _run_analysis(self, experiment_data, p0, bounds, **kwargs) -> Tuple[AnalysisResult, None]: """ Calculate T1 Args: experiment_data (ExperimentData): the experiment data to analyze - params: expected parameters therein are: - `p0` and `bounds` - to be passed to scipy.optimize.curve_fit - as the `p0` and `bounds` parameters + p0 (list): to be passed to scipy.optimize.curve_fit, see documentation there + bounds (tuple): to be passed to scipy.optimize.curve_fit, see documentation there Returns: The analysis result with the estimated T1 @@ -67,7 +66,7 @@ def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c fit_out, _ = curve_fit( - exp_fit_fun, delays, means, sigma=stds, p0=params["p0"], bounds=params["bounds"] + exp_fit_fun, delays, means, sigma=stds, p0=p0, bounds=bounds ) analysis_result = AnalysisResult({"value": fit_out[1]}) From f51c76a78d7622b9adb7f449d396a1097bf1bc8b Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 8 Mar 2021 21:31:36 +0200 Subject: [PATCH 11/59] black --- .../characterization/t1_experiment.py | 4 +- test/test_t1.py | 91 +++++++++++-------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 66786c181e..9115cd87b0 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -65,9 +65,7 @@ def _run_analysis(self, experiment_data, p0, bounds, **kwargs) -> Tuple[Analysis def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c - fit_out, _ = curve_fit( - exp_fit_fun, delays, means, sigma=stds, p0=p0, bounds=bounds - ) + fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stds, p0=p0, bounds=bounds) analysis_result = AnalysisResult({"value": fit_out[1]}) return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index c372dd0e6f..474ee1ede8 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -29,19 +29,25 @@ class T1Backend(BaseBackend): A simple and primitive backend, to be run by the T1 tests """ - def __init__(self, t1, - initial_prob1=None, - readout0to1=None, - readout1to0=None): + def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None): """ Initialize the T1 backend """ configuration = BackendConfiguration( - 't1_simulator', '0', int(1e6), - ['barrier', 'x', 'delay', 'measure'], - [], True, True, False, False, False, - int(1e6), None) + "t1_simulator", + "0", + int(1e6), + ["barrier", "x", "delay", "measure"], + [], + True, + True, + False, + False, + False, + int(1e6), + None, + ) self._t1 = t1 self._initial_prob1 = initial_prob1 @@ -57,14 +63,14 @@ def run(self, qobj, **kwargs): shots = qobj.config.shots result = { - 'backend_name': 'T1 backend', - 'backend_version': '0', - 'qobj_id': 0, - 'job_id': 0, - 'success': True, - 'results': [] - } - + "backend_name": "T1 backend", + "backend_version": "0", + "qobj_id": 0, + "job_id": 0, + "success": True, + "results": [], + } + for circ in qobj.experiments: nqubits = circ.config.n_qubits counts = dict() @@ -73,7 +79,7 @@ def run(self, qobj, **kwargs): ro01 = np.zeros(nqubits) else: ro01 = self._readout0to1 - + if self._readout1to0 is None: ro10 = np.zeros(nqubits) else: @@ -86,7 +92,7 @@ def run(self, qobj, **kwargs): prob1 = self._initial_prob1.copy() clbits = np.zeros(circ.config.memory_slots, dtype=int) - + for op in circ.instructions: qubit = op.qubits[0] if op.name == "x": @@ -95,11 +101,13 @@ def run(self, qobj, **kwargs): if self._t1[qubit] is not None: prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1[qubit]) if op.name == "measure": - meas_res = np.random.binomial(1, prob1[qubit] * (1 - ro10[qubit]) + (1 - prob1[qubit]) * ro01[qubit]) + meas_res = np.random.binomial( + 1, prob1[qubit] * (1 - ro10[qubit]) + (1 - prob1[qubit]) * ro01[qubit] + ) clbits[op.memory[0]] = meas_res prob1[qubit] = meas_res - clstr = '' + clstr = "" for clbit in clbits[::-1]: clstr = clstr + str(clbit) @@ -108,13 +116,17 @@ def run(self, qobj, **kwargs): else: counts[clstr] = 1 - result['results'].append({'shots': shots, - 'success': True, - 'header': {'metadata': circ.header.metadata}, - 'data': {'counts': counts}}) - - return Result.from_dict(result) - + result["results"].append( + { + "shots": shots, + "success": True, + "header": {"metadata": circ.header.metadata}, + "data": {"counts": counts}, + } + ) + + return Result.from_dict(result) + class TestT1(unittest.TestCase): """ @@ -137,15 +149,14 @@ def test_t1(self): exp = T1Experiment(0, delays) res = exp.run( - T1Backend([t1], initial_prob1=[0.1], - readout0to1=[0.1], - readout1to0=[0.1]), - p0=p0, bounds=bounds, + T1Backend([t1], initial_prob1=[0.1], readout0to1=[0.1], readout1to0=[0.1]), + p0=p0, + bounds=bounds, instruction_durations=instruction_durations, - shots=10000 - ) + shots=10000, + ) - self.assertAlmostEqual(res.analysis_result(0)['value'], t1, delta=3) + self.assertAlmostEqual(res.analysis_result(0)["value"], t1, delta=3) def test_t1_parallel(self): """ @@ -166,16 +177,16 @@ def test_t1_parallel(self): par_exp = ParallelExperiment([exp0, exp2]) res = par_exp.run( T1Backend([t1[0], None, t1[1]]), - p0=p0, bounds=bounds, + p0=p0, + bounds=bounds, instruction_durations=instruction_durations, - shots=10000 - ) + shots=10000, + ) for i in range(2): self.assertAlmostEqual( - res.component_experiment_data(i).analysis_result(0)['value'], - t1[i], delta=3 - ) + res.component_experiment_data(i).analysis_result(0)["value"], t1[i], delta=3 + ) if __name__ == "__main__": From 23bd8395013737f754e7afe63ae8f7671706fe24 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 9 Mar 2021 10:19:46 +0200 Subject: [PATCH 12/59] a small implementation change with t1 --- .../characterization/t1_experiment.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 9115cd87b0..26cb71abe3 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -40,32 +40,27 @@ def _run_analysis(self, experiment_data, p0, bounds, **kwargs) -> Tuple[Analysis The analysis result with the estimated T1 """ - prob1 = {} - for circ in experiment_data._data: - delay = circ["metadata"]["delay"] + size = len(experiment_data._data) + delays = np.zeros(size, dtype=float) + means = np.zeros(size, dtype=float) + stddevs = np.zeros(size, dtype=float) + + for i, circ in enumerate(experiment_data._data): + delays[i] = circ["metadata"]["delay"] count0 = circ["counts"].get("0", 0) count1 = circ["counts"].get("1", 0) shots = count0 + count1 - mean = count1 / shots - std = np.sqrt(mean * (1 - mean) / shots) + means[i] = count1 / shots + stddevs[i] = np.sqrt(means[i] * (1 - means[i]) / shots) # problem for the fitter if one of the std points is # exactly zero - if std == 0: - std = 1e-4 - prob1[delay] = (mean, std) - - delays = [] - means = [] - stds = [] - for delay in sorted(prob1): - delays.append(delay) - means.append(prob1[delay][0]) - stds.append(prob1[delay][1]) + if stddevs[i] == 0: + stddevs[i] = 1e-4 def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c - fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stds, p0=p0, bounds=bounds) + fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stddevs, p0=p0, bounds=bounds) analysis_result = AnalysisResult({"value": fit_out[1]}) return analysis_result, None From 3ca68ea3797b0f22a27efd8ab118870fad16f885 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 9 Mar 2021 11:00:42 +0200 Subject: [PATCH 13/59] added documentation to unit in t1 --- qiskit_experiments/characterization/t1_experiment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 26cb71abe3..ebe276945c 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -78,7 +78,8 @@ def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = Args: qubit: the qubit whose T1 is to be estimated delays: delay times of the experiments - unit: time unit of `delays` + unit: unit of the duration. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. + Default is ``dt``, i.e. integer time unit depending on the target backend. """ self._delays = delays self._unit = unit From 3544e1cd96235dc00cf9162deee553190e960dc0 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 9 Mar 2021 11:16:13 +0200 Subject: [PATCH 14/59] removed circuit_options from parameter list of circuits in t1 --- qiskit_experiments/characterization/t1_experiment.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index ebe276945c..5821af07af 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -85,15 +85,12 @@ def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = self._unit = unit super().__init__([qubit], type(self).__name__) - def circuits( - self, backend: Optional["Backend"] = None, **circuit_options - ) -> List[QuantumCircuit]: + def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: """ Return a list of experiment circuits Args: backend: a backend object - circuit_options: kwarg options for the function Returns: The experiment circuits From 6f6736242d7dfbff617efab01f729d28487d3c8c Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 9 Mar 2021 11:23:19 +0200 Subject: [PATCH 15/59] a minor doc change --- qiskit_experiments/characterization/t1_experiment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 5821af07af..fa33901ace 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -90,7 +90,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: Return a list of experiment circuits Args: - backend: a backend object + backend: Optional, a backend object Returns: The experiment circuits From c934818f0519f63d427669a36f2a518b0765158c Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 9 Mar 2021 13:38:57 +0200 Subject: [PATCH 16/59] default values to p0 and bounds --- qiskit_experiments/characterization/t1_experiment.py | 12 +++++++++--- test/test_t1.py | 6 +----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index fa33901ace..2a55a84642 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -27,14 +27,14 @@ class T1Analysis(BaseAnalysis): """T1 Experiment result analysis class.""" - def _run_analysis(self, experiment_data, p0, bounds, **kwargs) -> Tuple[AnalysisResult, None]: + def _run_analysis(self, experiment_data, p0=None, bounds=None, **kwargs) -> Tuple[AnalysisResult, None]: """ Calculate T1 Args: experiment_data (ExperimentData): the experiment data to analyze - p0 (list): to be passed to scipy.optimize.curve_fit, see documentation there - bounds (tuple): to be passed to scipy.optimize.curve_fit, see documentation there + p0 (list): Optional, to be passed to scipy.optimize.curve_fit, see documentation there + bounds (tuple): Optional, to be passed to scipy.optimize.curve_fit, see documentation there Returns: The analysis result with the estimated T1 @@ -60,6 +60,12 @@ def _run_analysis(self, experiment_data, p0, bounds, **kwargs) -> Tuple[Analysis def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c + if p0 is None: + p0 = [1, np.mean(delays), 0] + + if bounds is None: + bounds = ([0, 0, 0], [1, 500, 1]) + fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stddevs, p0=p0, bounds=bounds) analysis_result = AnalysisResult({"value": fit_out[1]}) diff --git a/test/test_t1.py b/test/test_t1.py index 474ee1ede8..c7556b0c13 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -142,7 +142,7 @@ def test_t1(self): delays = list(range(1, 33, 6)) p0 = [1, t1, 0] - bounds = ([0, 0, -1], [2, 40, 1]) + bounds = ([0, 0, 0], [1, 40, 1]) # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] @@ -166,8 +166,6 @@ def test_t1_parallel(self): t1 = [25, 15] delays = list(range(1, 33, 6)) - p0 = [1, t1[0], 0] - bounds = ([0, 0, -1], [2, 40, 1]) # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] @@ -177,8 +175,6 @@ def test_t1_parallel(self): par_exp = ParallelExperiment([exp0, exp2]) res = par_exp.run( T1Backend([t1[0], None, t1[1]]), - p0=p0, - bounds=bounds, instruction_durations=instruction_durations, shots=10000, ) From aea763203c99511b25478195e47055fbd8d50e2b Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 10 Mar 2021 13:22:00 +0200 Subject: [PATCH 17/59] updated p0 and removed bounds --- .../characterization/t1_experiment.py | 23 +++++++++++-------- test/test_t1.py | 5 +--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 2a55a84642..e5c73b7e78 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -27,14 +27,17 @@ class T1Analysis(BaseAnalysis): """T1 Experiment result analysis class.""" - def _run_analysis(self, experiment_data, p0=None, bounds=None, **kwargs) -> Tuple[AnalysisResult, None]: + def _run_analysis(self, experiment_data, + t1_guess=None, amplitude_guess=None, offset_guess=None, + **kwargs) -> Tuple[AnalysisResult, None]: """ Calculate T1 Args: experiment_data (ExperimentData): the experiment data to analyze - p0 (list): Optional, to be passed to scipy.optimize.curve_fit, see documentation there - bounds (tuple): Optional, to be passed to scipy.optimize.curve_fit, see documentation there + t1_guess: Optional, an initial guess of T1 + amplitude_guess: Optional, an initial guess of the coefficient of the exponent + offset_guess: Optional, an initial guess of the offset Returns: The analysis result with the estimated T1 @@ -60,13 +63,15 @@ def _run_analysis(self, experiment_data, p0=None, bounds=None, **kwargs) -> Tupl def exp_fit_fun(x, a, tau, c): return a * np.exp(-x / tau) + c - if p0 is None: - p0 = [1, np.mean(delays), 0] + if t1_guess is None: + t1_guess = np.mean(delays) + if offset_guess is None: + offset_guess = means[-1] + if amplitude_guess is None: + amplitude_guess = means[0] - offset_guess - if bounds is None: - bounds = ([0, 0, 0], [1, 500, 1]) - - fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stddevs, p0=p0, bounds=bounds) + fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stddevs, + p0=[amplitude_guess, t1_guess, offset_guess]) analysis_result = AnalysisResult({"value": fit_out[1]}) return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index c7556b0c13..6db23e259b 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -141,8 +141,6 @@ def test_t1(self): t1 = 25 delays = list(range(1, 33, 6)) - p0 = [1, t1, 0] - bounds = ([0, 0, 0], [1, 40, 1]) # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] @@ -150,8 +148,7 @@ def test_t1(self): exp = T1Experiment(0, delays) res = exp.run( T1Backend([t1], initial_prob1=[0.1], readout0to1=[0.1], readout1to0=[0.1]), - p0=p0, - bounds=bounds, + amplitude_guess=1, t1_guess=t1, offset_guess=0, instruction_durations=instruction_durations, shots=10000, ) From 4d180b1fdd072d83e296fdd87ec4611d43f875b8 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 10 Mar 2021 13:22:40 +0200 Subject: [PATCH 18/59] black --- qiskit_experiments/characterization/t1_experiment.py | 11 ++++++----- test/test_t1.py | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index e5c73b7e78..8d1591703f 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -27,9 +27,9 @@ class T1Analysis(BaseAnalysis): """T1 Experiment result analysis class.""" - def _run_analysis(self, experiment_data, - t1_guess=None, amplitude_guess=None, offset_guess=None, - **kwargs) -> Tuple[AnalysisResult, None]: + def _run_analysis( + self, experiment_data, t1_guess=None, amplitude_guess=None, offset_guess=None, **kwargs + ) -> Tuple[AnalysisResult, None]: """ Calculate T1 @@ -70,8 +70,9 @@ def exp_fit_fun(x, a, tau, c): if amplitude_guess is None: amplitude_guess = means[0] - offset_guess - fit_out, _ = curve_fit(exp_fit_fun, delays, means, sigma=stddevs, - p0=[amplitude_guess, t1_guess, offset_guess]) + fit_out, _ = curve_fit( + exp_fit_fun, delays, means, sigma=stddevs, p0=[amplitude_guess, t1_guess, offset_guess] + ) analysis_result = AnalysisResult({"value": fit_out[1]}) return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index 6db23e259b..c8c921ee1b 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -148,7 +148,9 @@ def test_t1(self): exp = T1Experiment(0, delays) res = exp.run( T1Backend([t1], initial_prob1=[0.1], readout0to1=[0.1], readout1to0=[0.1]), - amplitude_guess=1, t1_guess=t1, offset_guess=0, + amplitude_guess=1, + t1_guess=t1, + offset_guess=0, instruction_durations=instruction_durations, shots=10000, ) From 580f572d56faf3c18f6db1cc404b2a05cd54f47c Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 10 Mar 2021 17:41:24 +0200 Subject: [PATCH 19/59] return more info about t1 fit --- .../characterization/t1_experiment.py | 33 +++++++++++++++++-- test/test_t1.py | 12 +++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 8d1591703f..6ffc066238 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -70,11 +70,40 @@ def exp_fit_fun(x, a, tau, c): if amplitude_guess is None: amplitude_guess = means[0] - offset_guess - fit_out, _ = curve_fit( + fit_out, fit_cov = curve_fit( exp_fit_fun, delays, means, sigma=stddevs, p0=[amplitude_guess, t1_guess, offset_guess] ) - analysis_result = AnalysisResult({"value": fit_out[1]}) + chisq = 0 + for i in range(len(delays)): + chisq += ( + exp_fit_fun(delays[i], fit_out[0], fit_out[1], fit_out[2]) - means[i] + ) ** 2 / stddevs[i] ** 2 + chisq /= len(delays) + + fit_err = np.sqrt(np.diag(fit_cov)) + + analysis_result = AnalysisResult( + { + "amplitude": fit_out[0], + "t1": fit_out[1], + "offset": fit_out[2], + "amplitude_err": fit_err[0], + "t1_err": fit_err[1], + "offset_err": fit_err[2], + "chisq": chisq, + } + ) + + analysis_result["is_good_fit"] = ( + abs(analysis_result["amplitude"] - 1.0) < 0.1 + and abs(analysis_result["offset"]) < 0.1 + and analysis_result["chisq"] < 3 + and analysis_result["amplitude_err"] < 0.1 + and analysis_result["offset_err"] < 0.1 + and analysis_result["t1_err"] < analysis_result["t1"] + ) + return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index c8c921ee1b..b80ede4198 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -140,14 +140,14 @@ def test_t1(self): t1 = 25 - delays = list(range(1, 33, 6)) + delays = list(range(1, 40, 3)) # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] exp = T1Experiment(0, delays) res = exp.run( - T1Backend([t1], initial_prob1=[0.1], readout0to1=[0.1], readout1to0=[0.1]), + T1Backend([t1], initial_prob1=[0.02], readout0to1=[0.02], readout1to0=[0.02]), amplitude_guess=1, t1_guess=t1, offset_guess=0, @@ -155,7 +155,7 @@ def test_t1(self): shots=10000, ) - self.assertAlmostEqual(res.analysis_result(0)["value"], t1, delta=3) + self.assertTrue(res.analysis_result(0)["is_good_fit"]) def test_t1_parallel(self): """ @@ -164,7 +164,7 @@ def test_t1_parallel(self): t1 = [25, 15] - delays = list(range(1, 33, 6)) + delays = list(range(1, 40, 3)) # dummy numbers to avoid exception triggerring instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] @@ -179,9 +179,7 @@ def test_t1_parallel(self): ) for i in range(2): - self.assertAlmostEqual( - res.component_experiment_data(i).analysis_result(0)["value"], t1[i], delta=3 - ) + self.assertTrue(res.component_experiment_data(i).analysis_result(0)["is_good_fit"]) if __name__ == "__main__": From 0ede9efe2548c8db6972f351a68163c19649dbef Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 11 Mar 2021 10:15:13 +0200 Subject: [PATCH 20/59] a small change in t1 tests --- test/test_t1.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test_t1.py b/test/test_t1.py index b80ede4198..cb1e9b5ab0 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -153,9 +153,10 @@ def test_t1(self): offset_guess=0, instruction_durations=instruction_durations, shots=10000, - ) + ).analysis_result(0) - self.assertTrue(res.analysis_result(0)["is_good_fit"]) + self.assertTrue(res["is_good_fit"]) + self.assertAlmostEqual(res["t1"], t1, delta=3) def test_t1_parallel(self): """ @@ -179,7 +180,9 @@ def test_t1_parallel(self): ) for i in range(2): - self.assertTrue(res.component_experiment_data(i).analysis_result(0)["is_good_fit"]) + sub_res = res.component_experiment_data(i).analysis_result(0) + self.assertTrue(sub_res["is_good_fit"]) + self.assertAlmostEqual(sub_res["t1"], t1[i], delta=3) if __name__ == "__main__": From 9d34c99802fe21f0eb9741ab1fa123f2c607d13d Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 11 Mar 2021 11:30:23 +0200 Subject: [PATCH 21/59] small improvements to t1 --- .../characterization/t1_experiment.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 6ffc066238..6a2e4cdbf2 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -75,10 +75,10 @@ def exp_fit_fun(x, a, tau, c): ) chisq = 0 - for i in range(len(delays)): + for delay, mean, stddev in zip(delays, means, stddevs): chisq += ( - exp_fit_fun(delays[i], fit_out[0], fit_out[1], fit_out[2]) - means[i] - ) ** 2 / stddevs[i] ** 2 + exp_fit_fun(delay, fit_out[0], fit_out[1], fit_out[2]) - mean + ) ** 2 / stddev ** 2 chisq /= len(delays) fit_err = np.sqrt(np.diag(fit_cov)) @@ -99,9 +99,12 @@ def exp_fit_fun(x, a, tau, c): abs(analysis_result["amplitude"] - 1.0) < 0.1 and abs(analysis_result["offset"]) < 0.1 and analysis_result["chisq"] < 3 - and analysis_result["amplitude_err"] < 0.1 - and analysis_result["offset_err"] < 0.1 - and analysis_result["t1_err"] < analysis_result["t1"] + and (analysis_result["amplitude_err"] is None or analysis_result["amplitude_err"] < 0.1) + and (analysis_result["offset_err"] is None or analysis_result["offset_err"] < 0.1) + and ( + analysis_result["t1_err"] is None + or analysis_result["t1_err"] < analysis_result["t1"] + ) ) return analysis_result, None @@ -121,7 +124,13 @@ def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = delays: delay times of the experiments unit: unit of the duration. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. Default is ``dt``, i.e. integer time unit depending on the target backend. + + Raises: + ValueError: if the number of delays is smaller than 3 """ + if len(delays) < 3: + raise ValueError("T1 experiment: number of delays must be at least 3") + self._delays = delays self._unit = unit super().__init__([qubit], type(self).__name__) From 2067572b731d1208bf2288a5d432cc5ffebf8880 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 11 Mar 2021 14:40:41 +0200 Subject: [PATCH 22/59] exp_fit_fun and curve_fit wrapper in a shared space --- qiskit_experiments/base_analysis.py | 33 +++++++++++++++++++ .../characterization/t1_experiment.py | 21 ++++-------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index f6329fe203..b0321a1bf5 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -14,6 +14,8 @@ """ from abc import ABC, abstractmethod +import numpy as np +from scipy.optimize import curve_fit from qiskit.exceptions import QiskitError from .experiment_data import ExperimentData, AnalysisResult @@ -90,3 +92,34 @@ def _run_analysis(self, experiment_data, **options): None, a single figure, or a list of figures. """ pass + + @staticmethod + def exp_fit_fun(x, a, tau, c): + """ + Exponential fit function + """ + return a * np.exp(-x / tau) + c + + # pylint: disable = invalid-name + @staticmethod + def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): + """ + A wrapper to curve_fit that calculates and returns fit_err + (square root of the diagonal of the covariance matrix) and chi square. + + Arguments are identical to curve_fit arguments, with the following exceptions: + - The `sigma` parameter is mandatory. + - The order of arguments is slightly different. + + Returns fit_out, fit_err, chisq + """ + fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) + + chisq = 0 + for x, y, sig in zip(xdata, ydata, sigma): + chisq += (f(x, *fit_out) - y) ** 2 / sig ** 2 + chisq /= len(xdata) + + fit_err = np.sqrt(np.diag(fit_cov)) + + return fit_out, fit_err, chisq diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 6a2e4cdbf2..7a1f9204d3 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -15,7 +15,6 @@ from typing import List, Optional, Union, Tuple import numpy as np -from scipy.optimize import curve_fit from qiskit.circuit import QuantumCircuit @@ -60,9 +59,6 @@ def _run_analysis( if stddevs[i] == 0: stddevs[i] = 1e-4 - def exp_fit_fun(x, a, tau, c): - return a * np.exp(-x / tau) + c - if t1_guess is None: t1_guess = np.mean(delays) if offset_guess is None: @@ -70,19 +66,14 @@ def exp_fit_fun(x, a, tau, c): if amplitude_guess is None: amplitude_guess = means[0] - offset_guess - fit_out, fit_cov = curve_fit( - exp_fit_fun, delays, means, sigma=stddevs, p0=[amplitude_guess, t1_guess, offset_guess] + fit_out, fit_err, chisq = BaseAnalysis.curve_fit_wrapper( + BaseAnalysis.exp_fit_fun, + delays, + means, + stddevs, + p0=[amplitude_guess, t1_guess, offset_guess], ) - chisq = 0 - for delay, mean, stddev in zip(delays, means, stddevs): - chisq += ( - exp_fit_fun(delay, fit_out[0], fit_out[1], fit_out[2]) - mean - ) ** 2 / stddev ** 2 - chisq /= len(delays) - - fit_err = np.sqrt(np.diag(fit_cov)) - analysis_result = AnalysisResult( { "amplitude": fit_out[0], From 2ff0e3a922758046bef47f3e636409e33a471feb Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 11 Mar 2021 14:51:23 +0200 Subject: [PATCH 23/59] unit in circuit metadata and analysis result --- qiskit_experiments/characterization/t1_experiment.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 7a1f9204d3..bc1a88d529 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -83,6 +83,7 @@ def _run_analysis( "t1_err": fit_err[1], "offset_err": fit_err[2], "chisq": chisq, + "unit": experiment_data._data[0]["metadata"]["unit"] } ) @@ -152,6 +153,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "experiment_type": self._type, "qubit": self.physical_qubits[0], "delay": delay, + "unit": self._unit } circuits.append(circ) From a0b30e5f0f88c2413f56629ae9b264d974a6e943 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 18 Mar 2021 18:45:36 +0200 Subject: [PATCH 24/59] lint --- qiskit_experiments/base_experiment.py | 2 +- qiskit_experiments/characterization/t1_experiment.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index 274ce7566e..fb82f4646c 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -73,7 +73,7 @@ def __init__(self, qubits, experiment_type=None, circuit_options=None): physical qubits for the experiment. experiment_type (str): Optional, the experiment type string. circuit_options (Iterable): Optional, list of kwarg names for - the subclasses `circuit` method. + the subclassed `circuit` method. Raises: QiskitError: if qubits is a list and contains duplicates. diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index bc1a88d529..8a68ccd185 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -26,6 +26,7 @@ class T1Analysis(BaseAnalysis): """T1 Experiment result analysis class.""" + # pylint: disable=arguments-differ, unused-argument def _run_analysis( self, experiment_data, t1_guess=None, amplitude_guess=None, offset_guess=None, **kwargs ) -> Tuple[AnalysisResult, None]: @@ -34,9 +35,10 @@ def _run_analysis( Args: experiment_data (ExperimentData): the experiment data to analyze - t1_guess: Optional, an initial guess of T1 - amplitude_guess: Optional, an initial guess of the coefficient of the exponent - offset_guess: Optional, an initial guess of the offset + t1_guess (float): Optional, an initial guess of T1 + amplitude_guess (float): Optional, an initial guess of the coefficient of the exponent + offset_guess (float): Optional, an initial guess of the offset + kwargs: Trailing unused function parameters Returns: The analysis result with the estimated T1 @@ -71,7 +73,7 @@ def _run_analysis( delays, means, stddevs, - p0=[amplitude_guess, t1_guess, offset_guess], + p0=[amplitude_guess, t1_guess, offset_guess] ) analysis_result = AnalysisResult( @@ -127,6 +129,7 @@ def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = self._unit = unit super().__init__([qubit], type(self).__name__) + # pylint: disable=arguments-differ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: """ Return a list of experiment circuits From c443951c82ca7ee816312be9fec501b4ea8eefe5 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 22 Mar 2021 11:00:03 +0200 Subject: [PATCH 25/59] added a test to t1 analysis class --- .../characterization/__init__.py | 2 +- .../characterization/t1_experiment.py | 4 +-- test/test_t1.py | 30 +++++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/characterization/__init__.py b/qiskit_experiments/characterization/__init__.py index 280b38dfb8..6c7921d665 100644 --- a/qiskit_experiments/characterization/__init__.py +++ b/qiskit_experiments/characterization/__init__.py @@ -12,4 +12,4 @@ """Qiskit Experiments Characterziation.""" -from .t1_experiment import T1Experiment +from .t1_experiment import T1Experiment, T1Analysis diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 8a68ccd185..65fb944fc6 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -85,7 +85,7 @@ def _run_analysis( "t1_err": fit_err[1], "offset_err": fit_err[2], "chisq": chisq, - "unit": experiment_data._data[0]["metadata"]["unit"] + "unit": experiment_data._data[0]["metadata"]["unit"], } ) @@ -156,7 +156,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "experiment_type": self._type, "qubit": self.physical_qubits[0], "delay": delay, - "unit": self._unit + "unit": self._unit, } circuits.append(circ) diff --git a/test/test_t1.py b/test/test_t1.py index cb1e9b5ab0..9b36279ece 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -20,8 +20,9 @@ from qiskit.providers import BaseBackend from qiskit.providers.models import BackendConfiguration from qiskit.result import Result +from qiskit_experiments import ExperimentData from qiskit_experiments.composite import ParallelExperiment -from qiskit_experiments.characterization import T1Experiment +from qiskit_experiments.characterization import T1Experiment, T1Analysis class T1Backend(BaseBackend): @@ -133,7 +134,7 @@ class TestT1(unittest.TestCase): Test measurement of T1 """ - def test_t1(self): + def test_t1_end2end(self): """ Test T1 experiment using a simulator. """ @@ -184,6 +185,31 @@ def test_t1_parallel(self): self.assertTrue(sub_res["is_good_fit"]) self.assertAlmostEqual(sub_res["t1"], t1[i], delta=3) + def test_t1_analysis(self): + """ + Test T1Analysis + """ + + data = ExperimentData(None) + numbers = [750, 1800, 2750, 3550, 4250, 4850, 5450, 5900, 6400, 6800, 7000, 7350, 7700] + + for i, count0 in enumerate(numbers): + data._data.append( + { + "counts": {"0": count0, "1": 10000 - count0}, + "metadata": { + "delay": 3 * i + 1, + "experiment_type": "T1Experiment", + "qubit": 0, + "unit": "dt", + }, + } + ) + + res = T1Analysis()._run_analysis(data)[0] + self.assertTrue(res["is_good_fit"]) + self.assertAlmostEqual(res["t1"], 25, delta=3) + if __name__ == "__main__": unittest.main() From be7fcaec337b59cd2b8ed7686cb5992f560ce8a7 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 22 Mar 2021 13:33:52 +0200 Subject: [PATCH 26/59] changed the result structure in t1 --- qiskit_experiments/base_analysis.py | 2 +- .../characterization/t1_experiment.py | 43 ++++++++++--------- test/test_t1.py | 12 +++--- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index b0321a1bf5..2379ab7695 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -122,4 +122,4 @@ def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): fit_err = np.sqrt(np.diag(fit_cov)) - return fit_out, fit_err, chisq + return fit_out, fit_err, fit_cov, chisq diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 65fb944fc6..89b6d3553f 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -68,38 +68,41 @@ def _run_analysis( if amplitude_guess is None: amplitude_guess = means[0] - offset_guess - fit_out, fit_err, chisq = BaseAnalysis.curve_fit_wrapper( + fit_out, fit_err, fit_cov, chisq = BaseAnalysis.curve_fit_wrapper( BaseAnalysis.exp_fit_fun, delays, means, stddevs, - p0=[amplitude_guess, t1_guess, offset_guess] + p0=[amplitude_guess, t1_guess, offset_guess], ) analysis_result = AnalysisResult( { - "amplitude": fit_out[0], - "t1": fit_out[1], - "offset": fit_out[2], - "amplitude_err": fit_err[0], - "t1_err": fit_err[1], - "offset_err": fit_err[2], - "chisq": chisq, + "value": fit_out[1], + "stderr": fit_err[1], "unit": experiment_data._data[0]["metadata"]["unit"], + "label": "T1", + "fit": { + "params": fit_out, + "stderr": fit_err, + "labels": ["amplitude", "T1", "offset"], + "chisq": chisq, + "cov": fit_cov, + }, } ) - analysis_result["is_good_fit"] = ( - abs(analysis_result["amplitude"] - 1.0) < 0.1 - and abs(analysis_result["offset"]) < 0.1 - and analysis_result["chisq"] < 3 - and (analysis_result["amplitude_err"] is None or analysis_result["amplitude_err"] < 0.1) - and (analysis_result["offset_err"] is None or analysis_result["offset_err"] < 0.1) - and ( - analysis_result["t1_err"] is None - or analysis_result["t1_err"] < analysis_result["t1"] - ) - ) + if ( + abs(fit_out[0] - 1.0) < 0.1 + and abs(fit_out[2]) < 0.1 + and chisq < 3 + and (fit_err[0] is None or fit_err[0] < 0.1) + and (fit_err[1] is None or fit_err[1] < fit_out[1]) + and (fit_err[2] is None or fit_err[2] < 0.1) + ): + analysis_result["quality"] = "computer_good" + else: + analysis_result["quality"] = "computer_bad" return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index 9b36279ece..6dd24371ef 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -156,8 +156,8 @@ def test_t1_end2end(self): shots=10000, ).analysis_result(0) - self.assertTrue(res["is_good_fit"]) - self.assertAlmostEqual(res["t1"], t1, delta=3) + self.assertEqual(res["quality"], "computer_good") + self.assertAlmostEqual(res["value"], t1, delta=3) def test_t1_parallel(self): """ @@ -182,8 +182,8 @@ def test_t1_parallel(self): for i in range(2): sub_res = res.component_experiment_data(i).analysis_result(0) - self.assertTrue(sub_res["is_good_fit"]) - self.assertAlmostEqual(sub_res["t1"], t1[i], delta=3) + self.assertTrue(sub_res["quality"], "computer_good") + self.assertAlmostEqual(sub_res["value"], t1[i], delta=3) def test_t1_analysis(self): """ @@ -207,8 +207,8 @@ def test_t1_analysis(self): ) res = T1Analysis()._run_analysis(data)[0] - self.assertTrue(res["is_good_fit"]) - self.assertAlmostEqual(res["t1"], 25, delta=3) + self.assertEqual(res["quality"], "computer_good") + self.assertAlmostEqual(res["value"], 25, delta=3) if __name__ == "__main__": From 3b7d3bdb5678731817b85c19145171034b565c7f Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 22 Mar 2021 14:57:50 +0200 Subject: [PATCH 27/59] moved functions from BaseAnalysis to a separate file --- qiskit_experiments/base_analysis.py | 33 ------------ .../characterization/analysis_functions.py | 51 +++++++++++++++++++ .../characterization/t1_experiment.py | 5 +- 3 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 qiskit_experiments/characterization/analysis_functions.py diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index 2379ab7695..f6329fe203 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -14,8 +14,6 @@ """ from abc import ABC, abstractmethod -import numpy as np -from scipy.optimize import curve_fit from qiskit.exceptions import QiskitError from .experiment_data import ExperimentData, AnalysisResult @@ -92,34 +90,3 @@ def _run_analysis(self, experiment_data, **options): None, a single figure, or a list of figures. """ pass - - @staticmethod - def exp_fit_fun(x, a, tau, c): - """ - Exponential fit function - """ - return a * np.exp(-x / tau) + c - - # pylint: disable = invalid-name - @staticmethod - def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): - """ - A wrapper to curve_fit that calculates and returns fit_err - (square root of the diagonal of the covariance matrix) and chi square. - - Arguments are identical to curve_fit arguments, with the following exceptions: - - The `sigma` parameter is mandatory. - - The order of arguments is slightly different. - - Returns fit_out, fit_err, chisq - """ - fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) - - chisq = 0 - for x, y, sig in zip(xdata, ydata, sigma): - chisq += (f(x, *fit_out) - y) ** 2 / sig ** 2 - chisq /= len(xdata) - - fit_err = np.sqrt(np.diag(fit_cov)) - - return fit_out, fit_err, fit_cov, chisq diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py new file mode 100644 index 0000000000..02aaed925f --- /dev/null +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -0,0 +1,51 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. +""" +Functions for fitting parameters. +""" + +import numpy as np +from scipy.optimize import curve_fit + + +def exp_fit_fun(x, a, tau, c): + """ + Exponential fit function + """ + return a * np.exp(-x / tau) + c + + +# pylint: disable = invalid-name +def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): + """ + A wrapper to curve_fit that calculates and returns fit_err + (square root of the diagonal of the covariance matrix) and chi square. + + Args: + f (callable): see documentation of curve_fit in scipy.optimize + xdata (array_like or object): see documentation of curve_fit in scipy.optimize + ydata (array_like): see documentation of curve_fit in scipy.optimize + sigma (None or 1-dimensional sequence or 2-dimensional array): + kwargs: additional paramters to be passed to curve_fit + + Returns fit_out, fit_err, chisq + """ + fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) + + chisq = 0 + for x, y, sig in zip(xdata, ydata, sigma): + chisq += (f(x, *fit_out) - y) ** 2 / sig ** 2 + chisq /= len(xdata) + + fit_err = np.sqrt(np.diag(fit_cov)) + + return fit_out, fit_err, fit_cov, chisq diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 89b6d3553f..82e8b1f52c 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -21,6 +21,7 @@ from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.base_analysis import BaseAnalysis from qiskit_experiments import AnalysisResult +from .analysis_functions import exp_fit_fun, curve_fit_wrapper class T1Analysis(BaseAnalysis): @@ -68,8 +69,8 @@ def _run_analysis( if amplitude_guess is None: amplitude_guess = means[0] - offset_guess - fit_out, fit_err, fit_cov, chisq = BaseAnalysis.curve_fit_wrapper( - BaseAnalysis.exp_fit_fun, + fit_out, fit_err, fit_cov, chisq = curve_fit_wrapper( + exp_fit_fun, delays, means, stddevs, From e80e7fe6876a23d0c1d17e4a02eec0a54fefb45c Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 22 Mar 2021 16:12:46 +0200 Subject: [PATCH 28/59] lint --- .../characterization/analysis_functions.py | 13 +++++++++---- .../characterization/t1_experiment.py | 1 + test/test_t1.py | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py index 02aaed925f..b88d840179 100644 --- a/qiskit_experiments/characterization/analysis_functions.py +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -32,12 +32,17 @@ def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): Args: f (callable): see documentation of curve_fit in scipy.optimize - xdata (array_like or object): see documentation of curve_fit in scipy.optimize - ydata (array_like): see documentation of curve_fit in scipy.optimize - sigma (None or 1-dimensional sequence or 2-dimensional array): + xdata (list): see documentation of curve_fit in scipy.optimize + ydata (list): see documentation of curve_fit in scipy.optimize + sigma (list): see documentation of curve_fit in scipy.optimize kwargs: additional paramters to be passed to curve_fit - Returns fit_out, fit_err, chisq + Returns: + list: fitted parameters + list: error on fitted parameters + (square root of the diagonal of the covariance matrix) + matrix: the covariance matrix + float: chi-square, which is the function that's minimized when fitting """ fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 82e8b1f52c..5d9658b7b0 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -93,6 +93,7 @@ def _run_analysis( } ) + #pylint: disable = too-many-boolean-expressions if ( abs(fit_out[0] - 1.0) < 0.1 and abs(fit_out[2]) < 0.1 diff --git a/test/test_t1.py b/test/test_t1.py index 6dd24371ef..7f8cba134f 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -56,7 +56,8 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None): self._readout1to0 = readout1to0 super().__init__(configuration) - def run(self, qobj, **kwargs): + #pylint: disable = arguments-differ + def run(self, qobj): """ Run the T1 backend """ From 2daa615010495988534d63b208b457fe1c58f5fb Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 25 Mar 2021 13:40:30 +0200 Subject: [PATCH 29/59] review comments --- .../characterization/analysis_functions.py | 2 +- test/test_t1.py | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py index b88d840179..5aed02f7ca 100644 --- a/qiskit_experiments/characterization/analysis_functions.py +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -42,7 +42,7 @@ def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): list: error on fitted parameters (square root of the diagonal of the covariance matrix) matrix: the covariance matrix - float: chi-square, which is the function that's minimized when fitting + float: chi-square """ fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) diff --git a/test/test_t1.py b/test/test_t1.py index 7f8cba134f..48092f17e7 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -36,18 +36,18 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None): """ configuration = BackendConfiguration( - "t1_simulator", - "0", - int(1e6), - ["barrier", "x", "delay", "measure"], - [], - True, - True, - False, - False, - False, - int(1e6), - None, + backend_name="t1_simulator", + backend_version="0", + n_qubits=int(1e6), + basis_gates=["barrier", "x", "delay", "measure"], + gates=[], + local=True, + simulator=True, + conditional=False, + open_pulse=False, + memory=False, + max_shots=int(1e6), + coupling_map=None, ) self._t1 = t1 From b967814dae4505d243d6fbd1bd59383358ea032a Mon Sep 17 00:00:00 2001 From: yaelbh Date: Mon, 5 Apr 2021 16:36:38 +0300 Subject: [PATCH 30/59] result unit in microseconds if unit is dt --- .../characterization/t1_experiment.py | 26 +++++++++++++-- test/test_t1.py | 33 ++++++++++--------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 5d9658b7b0..6293cc14b8 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -45,13 +45,22 @@ def _run_analysis( The analysis result with the estimated T1 """ + circuit_unit = experiment_data._data[0]["metadata"]["unit"] + dt_factor_in_sec = experiment_data._data[0]["metadata"]["dt_factor_in_sec"] + if dt_factor_in_sec is None: + dt_factor_in_microsec = 1 + result_unit = circuit_unit + else: + dt_factor_in_microsec = dt_factor_in_sec * 1000000 + result_unit = "us" + size = len(experiment_data._data) delays = np.zeros(size, dtype=float) means = np.zeros(size, dtype=float) stddevs = np.zeros(size, dtype=float) for i, circ in enumerate(experiment_data._data): - delays[i] = circ["metadata"]["delay"] + delays[i] = circ["metadata"]["delay"] * dt_factor_in_microsec count0 = circ["counts"].get("0", 0) count1 = circ["counts"].get("1", 0) shots = count0 + count1 @@ -64,6 +73,8 @@ def _run_analysis( if t1_guess is None: t1_guess = np.mean(delays) + else: + t1_guess = t1_guess * dt_factor_in_microsec if offset_guess is None: offset_guess = means[-1] if amplitude_guess is None: @@ -81,7 +92,7 @@ def _run_analysis( { "value": fit_out[1], "stderr": fit_err[1], - "unit": experiment_data._data[0]["metadata"]["unit"], + "unit": result_unit, "label": "T1", "fit": { "params": fit_out, @@ -114,7 +125,7 @@ class T1Experiment(BaseExperiment): __analysis_class__ = T1Analysis - def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = "dt"): + def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = "us"): """ Initialize the T1 experiment class @@ -146,6 +157,14 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: The experiment circuits """ + if self._unit == "dt": + try: + dt_factor_in_sec = getattr( backend.configuration(), "dt") + except AttributeError: + raise AttributeError("Dt parameter is missing in backend configuration") + else: + dt_factor_in_sec = None + circuits = [] for delay in self._delays: @@ -162,6 +181,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "qubit": self.physical_qubits[0], "delay": delay, "unit": self._unit, + "dt_factor_in_sec": dt_factor_in_sec } circuits.append(circ) diff --git a/test/test_t1.py b/test/test_t1.py index 48092f17e7..8c715ec159 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -18,7 +18,7 @@ import unittest import numpy as np from qiskit.providers import BaseBackend -from qiskit.providers.models import BackendConfiguration +from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result from qiskit_experiments import ExperimentData from qiskit_experiments.composite import ParallelExperiment @@ -30,12 +30,12 @@ class T1Backend(BaseBackend): A simple and primitive backend, to be run by the T1 tests """ - def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None): + def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, dt_factor_in_microsec=1e6): """ Initialize the T1 backend """ - configuration = BackendConfiguration( + configuration = QasmBackendConfiguration( backend_name="t1_simulator", backend_version="0", n_qubits=int(1e6), @@ -48,12 +48,14 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None): memory=False, max_shots=int(1e6), coupling_map=None, + dt=dt_factor_in_microsec * 1000 ) self._t1 = t1 self._initial_prob1 = initial_prob1 self._readout0to1 = readout0to1 self._readout1to0 = readout1to0 + self._dt_factor_in_microsec = dt_factor_in_microsec super().__init__(configuration) #pylint: disable = arguments-differ @@ -100,8 +102,8 @@ def run(self, qobj): if op.name == "x": prob1[qubit] = 1 - prob1[qubit] if op.name == "delay": - if self._t1[qubit] is not None: - prob1[qubit] = prob1[qubit] * np.exp(-op.params[0] / self._t1[qubit]) + delay = op.params[0] * self._dt_factor_in_microsec + prob1[qubit] = prob1[qubit] * np.exp(-delay / self._t1[qubit]) if op.name == "measure": meas_res = np.random.binomial( 1, prob1[qubit] * (1 - ro10[qubit]) + (1 - prob1[qubit]) * ro01[qubit] @@ -140,18 +142,21 @@ def test_t1_end2end(self): Test T1 experiment using a simulator. """ + dt_factor_in_microsec = 0.0002 + t1 = 25 + backend = T1Backend([t1], initial_prob1=[0.02], readout0to1=[0.02], readout1to0=[0.02], dt_factor_in_microsec=dt_factor_in_microsec) - delays = list(range(1, 40, 3)) + delays = list(range(int(1/dt_factor_in_microsec), int(40/dt_factor_in_microsec), int(3/dt_factor_in_microsec))) # dummy numbers to avoid exception triggerring - instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] + instruction_durations = [("measure", [0], 3/dt_factor_in_microsec, "dt"), ("x", [0], 3/dt_factor_in_microsec, "dt")] - exp = T1Experiment(0, delays) + exp = T1Experiment(0, delays, unit="dt") res = exp.run( - T1Backend([t1], initial_prob1=[0.02], readout0to1=[0.02], readout1to0=[0.02]), + backend, amplitude_guess=1, - t1_guess=t1, + t1_guess=t1 / dt_factor_in_microsec, offset_guess=0, instruction_durations=instruction_durations, shots=10000, @@ -166,18 +171,13 @@ def test_t1_parallel(self): """ t1 = [25, 15] - delays = list(range(1, 40, 3)) - # dummy numbers to avoid exception triggerring - instruction_durations = [("measure", [0], 3, "dt"), ("x", [0], 3, "dt")] - exp0 = T1Experiment(0, delays) exp2 = T1Experiment(2, delays) par_exp = ParallelExperiment([exp0, exp2]) res = par_exp.run( T1Backend([t1[0], None, t1[1]]), - instruction_durations=instruction_durations, shots=10000, ) @@ -202,7 +202,8 @@ def test_t1_analysis(self): "delay": 3 * i + 1, "experiment_type": "T1Experiment", "qubit": 0, - "unit": "dt", + "unit": "us", + "dt_factor_in_sec": None, }, } ) From 17005fe13e201dd9248011c8575b672a2f10c326 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 10:39:44 +0300 Subject: [PATCH 31/59] a small change to a test --- test/test_t1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_t1.py b/test/test_t1.py index 8c715ec159..e8b9d689e4 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -202,7 +202,7 @@ def test_t1_analysis(self): "delay": 3 * i + 1, "experiment_type": "T1Experiment", "qubit": 0, - "unit": "us", + "unit": "ns", "dt_factor_in_sec": None, }, } From d67fa9dfd7cd8f0ddf3f0cca8a6328c7d5f37509 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 10:49:17 +0300 Subject: [PATCH 32/59] T1Analisys._fit_quality --- qiskit_experiments/characterization/t1_experiment.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 6293cc14b8..19133c317a 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -101,9 +101,14 @@ def _run_analysis( "chisq": chisq, "cov": fit_cov, }, + "quality": self._fit_quality(fit_out, fit_err, chisq) } ) + return analysis_result, None + + @staticmethod + def _fit_quality(fit_out, fit_err, chisq): #pylint: disable = too-many-boolean-expressions if ( abs(fit_out[0] - 1.0) < 0.1 @@ -113,11 +118,9 @@ def _run_analysis( and (fit_err[1] is None or fit_err[1] < fit_out[1]) and (fit_err[2] is None or fit_err[2] < 0.1) ): - analysis_result["quality"] = "computer_good" + return "computer_good" else: - analysis_result["quality"] = "computer_bad" - - return analysis_result, None + return "computer_bad" class T1Experiment(BaseExperiment): From c579517124138faa24f676c0092c0b7e32ae1f76 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 10:53:56 +0300 Subject: [PATCH 33/59] removed unnecessary pylint disable --- qiskit_experiments/characterization/t1_experiment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 19133c317a..24ad31238d 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -178,7 +178,6 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: circ.barrier(0) circ.measure(0, 0) - # pylint: disable = eval-used circ.metadata = { "experiment_type": self._type, "qubit": self.physical_qubits[0], From 5d9b2966ea7128d2e5b3a41a08616befdb582f69 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 11:22:53 +0300 Subject: [PATCH 34/59] test_t1_metadata --- test/test_t1.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/test_t1.py b/test/test_t1.py index e8b9d689e4..2e57358e83 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -212,6 +212,28 @@ def test_t1_analysis(self): self.assertEqual(res["quality"], "computer_good") self.assertAlmostEqual(res["value"], 25, delta=3) + def test_t1_metadata(self): + """ + Test the circuits metadata + """ + + t1 = 25 + delays = list(range(1, 40, 3)) + exp = T1Experiment(0, delays, unit="ms") + circs = exp.circuits() + + self.assertEqual(len(circs), len(delays)) + + for delay, circ in zip(delays, circs): + self.assertEqual(circ.metadata, + { + "experiment_type": "T1Experiment", + "qubit": 0, + "delay": delay, + "unit": "ms", + "dt_factor_in_sec": None + }) + if __name__ == "__main__": unittest.main() From e1a9da63b933044cfcdde876ba88650087c6471b Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 13:31:27 +0300 Subject: [PATCH 35/59] allow the user to set the experiment type --- qiskit_experiments/characterization/t1_experiment.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 24ad31238d..58b9d39ab4 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -128,15 +128,15 @@ class T1Experiment(BaseExperiment): __analysis_class__ = T1Analysis - def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = "us"): + def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: Optional[str] = "us", experiment_type: Optional[str] = None): """ Initialize the T1 experiment class Args: qubit: the qubit whose T1 is to be estimated delays: delay times of the experiments - unit: unit of the duration. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. - Default is ``dt``, i.e. integer time unit depending on the target backend. + unit:Optional, unit of the duration. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. + experiment_type: Optional, the experiment type string. Raises: ValueError: if the number of delays is smaller than 3 @@ -146,7 +146,7 @@ def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: str = self._delays = delays self._unit = unit - super().__init__([qubit], type(self).__name__) + super().__init__([qubit], experiment_type) # pylint: disable=arguments-differ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: From eea32e5e2c49a2f0eb91c87b7f40ec79ef5708aa Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 13:51:59 +0300 Subject: [PATCH 36/59] test_t1_low_quality --- test/test_t1.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/test_t1.py b/test/test_t1.py index 2e57358e83..0e6a6d107b 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -233,6 +233,30 @@ def test_t1_metadata(self): "unit": "ms", "dt_factor_in_sec": None }) + + def test_t1_low_quality(self): + """ + A test where the fit's quality will be low + """ + + data = ExperimentData(None) + + for i in range(3): + data._data.append( + { + "counts": {"0": 10, "1": 10}, + "metadata": { + "delay": i, + "experiment_type": "T1Experiment", + "qubit": 0, + "unit": "ns", + "dt_factor_in_sec": None, + }, + } + ) + + res = T1Analysis()._run_analysis(data)[0] + self.assertEqual(res["quality"], "computer_bad") if __name__ == "__main__": From bd7bf7d56c5407d9730637df7725d24bb0e1476a Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 13:58:45 +0300 Subject: [PATCH 37/59] black and lint --- .../characterization/t1_experiment.py | 19 +++++-- test/test_t1.py | 52 +++++++++++++------ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 58b9d39ab4..e817ca2514 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -101,7 +101,7 @@ def _run_analysis( "chisq": chisq, "cov": fit_cov, }, - "quality": self._fit_quality(fit_out, fit_err, chisq) + "quality": self._fit_quality(fit_out, fit_err, chisq), } ) @@ -109,7 +109,7 @@ def _run_analysis( @staticmethod def _fit_quality(fit_out, fit_err, chisq): - #pylint: disable = too-many-boolean-expressions + # pylint: disable = too-many-boolean-expressions if ( abs(fit_out[0] - 1.0) < 0.1 and abs(fit_out[2]) < 0.1 @@ -128,7 +128,13 @@ class T1Experiment(BaseExperiment): __analysis_class__ = T1Analysis - def __init__(self, qubit: int, delays: Union[List[float], np.array], unit: Optional[str] = "us", experiment_type: Optional[str] = None): + def __init__( + self, + qubit: int, + delays: Union[List[float], np.array], + unit: Optional[str] = "us", + experiment_type: Optional[str] = None, + ): """ Initialize the T1 experiment class @@ -158,11 +164,14 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: Returns: The experiment circuits + + Raises: + AttributeError: if unit is dt but dt parameter is missing in the backend configuration """ if self._unit == "dt": try: - dt_factor_in_sec = getattr( backend.configuration(), "dt") + dt_factor_in_sec = getattr(backend.configuration(), "dt") except AttributeError: raise AttributeError("Dt parameter is missing in backend configuration") else: @@ -183,7 +192,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "qubit": self.physical_qubits[0], "delay": delay, "unit": self._unit, - "dt_factor_in_sec": dt_factor_in_sec + "dt_factor_in_sec": dt_factor_in_sec, } circuits.append(circ) diff --git a/test/test_t1.py b/test/test_t1.py index 0e6a6d107b..1a6ed06964 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -30,7 +30,9 @@ class T1Backend(BaseBackend): A simple and primitive backend, to be run by the T1 tests """ - def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, dt_factor_in_microsec=1e6): + def __init__( + self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, dt_factor_in_microsec=1e6 + ): """ Initialize the T1 backend """ @@ -48,7 +50,7 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, d memory=False, max_shots=int(1e6), coupling_map=None, - dt=dt_factor_in_microsec * 1000 + dt=dt_factor_in_microsec * 1000, ) self._t1 = t1 @@ -58,7 +60,7 @@ def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, d self._dt_factor_in_microsec = dt_factor_in_microsec super().__init__(configuration) - #pylint: disable = arguments-differ + # pylint: disable = arguments-differ def run(self, qobj): """ Run the T1 backend @@ -145,12 +147,27 @@ def test_t1_end2end(self): dt_factor_in_microsec = 0.0002 t1 = 25 - backend = T1Backend([t1], initial_prob1=[0.02], readout0to1=[0.02], readout1to0=[0.02], dt_factor_in_microsec=dt_factor_in_microsec) + backend = T1Backend( + [t1], + initial_prob1=[0.02], + readout0to1=[0.02], + readout1to0=[0.02], + dt_factor_in_microsec=dt_factor_in_microsec, + ) - delays = list(range(int(1/dt_factor_in_microsec), int(40/dt_factor_in_microsec), int(3/dt_factor_in_microsec))) + delays = list( + range( + int(1 / dt_factor_in_microsec), + int(40 / dt_factor_in_microsec), + int(3 / dt_factor_in_microsec), + ) + ) # dummy numbers to avoid exception triggerring - instruction_durations = [("measure", [0], 3/dt_factor_in_microsec, "dt"), ("x", [0], 3/dt_factor_in_microsec, "dt")] + instruction_durations = [ + ("measure", [0], 3 / dt_factor_in_microsec, "dt"), + ("x", [0], 3 / dt_factor_in_microsec, "dt"), + ] exp = T1Experiment(0, delays, unit="dt") res = exp.run( @@ -217,22 +234,23 @@ def test_t1_metadata(self): Test the circuits metadata """ - t1 = 25 delays = list(range(1, 40, 3)) exp = T1Experiment(0, delays, unit="ms") circs = exp.circuits() self.assertEqual(len(circs), len(delays)) - + for delay, circ in zip(delays, circs): - self.assertEqual(circ.metadata, - { - "experiment_type": "T1Experiment", - "qubit": 0, - "delay": delay, - "unit": "ms", - "dt_factor_in_sec": None - }) + self.assertEqual( + circ.metadata, + { + "experiment_type": "T1Experiment", + "qubit": 0, + "delay": delay, + "unit": "ms", + "dt_factor_in_sec": None, + }, + ) def test_t1_low_quality(self): """ @@ -257,7 +275,7 @@ def test_t1_low_quality(self): res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res["quality"], "computer_bad") - + if __name__ == "__main__": unittest.main() From 7eff4b41d4f809a470bb1d7fc846f8ed4949f272 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Tue, 6 Apr 2021 16:25:15 +0300 Subject: [PATCH 38/59] more black and lint --- qiskit_experiments/characterization/analysis_functions.py | 1 + qiskit_experiments/characterization/t1_experiment.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py index 5aed02f7ca..113c4c0abf 100644 --- a/qiskit_experiments/characterization/analysis_functions.py +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -44,6 +44,7 @@ def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): matrix: the covariance matrix float: chi-square """ + # pylint: disable=unbalanced-tuple-unpacking fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) chisq = 0 diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index e817ca2514..889e3e8b43 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -172,8 +172,8 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: if self._unit == "dt": try: dt_factor_in_sec = getattr(backend.configuration(), "dt") - except AttributeError: - raise AttributeError("Dt parameter is missing in backend configuration") + except AttributeError as no_dt: + raise AttributeError("Dt parameter is missing in backend configuration") from no_dt else: dt_factor_in_sec = None From 6000cf89efc13222a9676777711d4a4d3a18ebfc Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 8 Apr 2021 09:45:39 +0300 Subject: [PATCH 39/59] added bounds option to t1 --- .../characterization/t1_experiment.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 889e3e8b43..54784ed095 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -29,7 +29,15 @@ class T1Analysis(BaseAnalysis): # pylint: disable=arguments-differ, unused-argument def _run_analysis( - self, experiment_data, t1_guess=None, amplitude_guess=None, offset_guess=None, **kwargs + self, + experiment_data, + t1_guess=None, + amplitude_guess=None, + offset_guess=None, + t1_bounds=None, + amplitude_bounds=None, + offset_bounds=None, + **kwargs, ) -> Tuple[AnalysisResult, None]: """ Calculate T1 @@ -39,6 +47,9 @@ def _run_analysis( t1_guess (float): Optional, an initial guess of T1 amplitude_guess (float): Optional, an initial guess of the coefficient of the exponent offset_guess (float): Optional, an initial guess of the offset + t1_bounds (list of two floats): Optional, lower bound and uper bound to T1 + amplitude_bounds (list of two floats): Optional, lower bound and uper bound to the amplitude + offset_bounds (list of two floats): Optional, lower bound and uper bound to the offset kwargs: Trailing unused function parameters Returns: @@ -79,6 +90,12 @@ def _run_analysis( offset_guess = means[-1] if amplitude_guess is None: amplitude_guess = means[0] - offset_guess + if t1_bounds is None: + t1_bounds = [-np.inf, np.inf] + if amplitude_bounds is None: + amplitude_bounds = [0, 1] + if offset_bounds is None: + offset_bounds = [0, 1] fit_out, fit_err, fit_cov, chisq = curve_fit_wrapper( exp_fit_fun, From cf7bfbbf53ef0aba0f5dcc6373310c614b9f3424 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 8 Apr 2021 16:30:00 +0300 Subject: [PATCH 40/59] a small fix --- qiskit_experiments/characterization/t1_experiment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 54784ed095..7f39d4575d 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -91,7 +91,7 @@ def _run_analysis( if amplitude_guess is None: amplitude_guess = means[0] - offset_guess if t1_bounds is None: - t1_bounds = [-np.inf, np.inf] + t1_bounds = [0, np.inf] if amplitude_bounds is None: amplitude_bounds = [0, 1] if offset_bounds is None: From 9581589ed03004ddec55d9d1a5e91d16c78d80a1 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 11 Apr 2021 09:19:14 +0300 Subject: [PATCH 41/59] no dt info in metadata if unit is not dt --- qiskit_experiments/characterization/t1_experiment.py | 10 +++++----- test/test_t1.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 7f39d4575d..a34b0f78c7 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -57,7 +57,7 @@ def _run_analysis( """ circuit_unit = experiment_data._data[0]["metadata"]["unit"] - dt_factor_in_sec = experiment_data._data[0]["metadata"]["dt_factor_in_sec"] + dt_factor_in_sec = experiment_data._data[0]["metadata"].get("dt_factor_in_sec", None) if dt_factor_in_sec is None: dt_factor_in_microsec = 1 result_unit = circuit_unit @@ -191,8 +191,6 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: dt_factor_in_sec = getattr(backend.configuration(), "dt") except AttributeError as no_dt: raise AttributeError("Dt parameter is missing in backend configuration") from no_dt - else: - dt_factor_in_sec = None circuits = [] @@ -209,9 +207,11 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "qubit": self.physical_qubits[0], "delay": delay, "unit": self._unit, - "dt_factor_in_sec": dt_factor_in_sec, } - + + if self._unit == "dt": + circ.metadata["dt_factor_in_sec"] = dt_factor_in_sec + circuits.append(circ) return circuits diff --git a/test/test_t1.py b/test/test_t1.py index 1a6ed06964..0c54f1975c 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -248,7 +248,6 @@ def test_t1_metadata(self): "qubit": 0, "delay": delay, "unit": "ms", - "dt_factor_in_sec": None, }, ) From f20ac7b9bc184ab84dc11f1006ae086eee1a9ea5 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 11 Apr 2021 09:24:56 +0300 Subject: [PATCH 42/59] fix docstring of exp_fit_fun --- .../characterization/analysis_functions.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py index 113c4c0abf..af92d3abc2 100644 --- a/qiskit_experiments/characterization/analysis_functions.py +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -20,6 +20,15 @@ def exp_fit_fun(x, a, tau, c): """ Exponential fit function + + Args: + x (float or array): a function parameter + a (float or array): a function parameter (exponent amplitude) + tau (float or array): a function parameter (exponential decrease rate) + c (float or array): a function parameter (exponent offset) + + Returns: + a * np.exp(-x / tau) + c """ return a * np.exp(-x / tau) + c From edebf3e666c9d27fd0dd64639fe86f1ecda24ef5 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 11 Apr 2021 10:02:10 +0300 Subject: [PATCH 43/59] added generate_delays --- qiskit_experiments/characterization/t1_experiment.py | 4 ++++ test/test_t1.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index a34b0f78c7..007c1fd893 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -215,3 +215,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: circuits.append(circ) return circuits + + @staticmethod + def generate_delays(t1_estimate, num_of_points): + return np.geomspace(3, 7*t1_estimate, num_of_points) diff --git a/test/test_t1.py b/test/test_t1.py index 0c54f1975c..400d92e190 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -275,6 +275,12 @@ def test_t1_low_quality(self): res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res["quality"], "computer_bad") + def test_generate_delays(self): + delays = T1Experiment.generate_delays(25, 30) + self.assertEqual(len(delays), 30) + self.assertEqual(delays[0], 3) + self.assertEqual(delays[-1], 175) + if __name__ == "__main__": unittest.main() From 70b3411a61f454cb3800947350f2e33dde2177ab Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 11 Apr 2021 10:07:21 +0300 Subject: [PATCH 44/59] black and lint --- .../characterization/analysis_functions.py | 2 +- .../characterization/t1_experiment.py | 16 +++++++++++++--- test/test_t1.py | 3 +++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py index af92d3abc2..20ec3a13a5 100644 --- a/qiskit_experiments/characterization/analysis_functions.py +++ b/qiskit_experiments/characterization/analysis_functions.py @@ -28,7 +28,7 @@ def exp_fit_fun(x, a, tau, c): c (float or array): a function parameter (exponent offset) Returns: - a * np.exp(-x / tau) + c + float or array: a * np.exp(-x / tau) + c """ return a * np.exp(-x / tau) + c diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 007c1fd893..f4625e0b18 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -208,14 +208,24 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: "delay": delay, "unit": self._unit, } - + if self._unit == "dt": circ.metadata["dt_factor_in_sec"] = dt_factor_in_sec - + circuits.append(circ) return circuits @staticmethod def generate_delays(t1_estimate, num_of_points): - return np.geomspace(3, 7*t1_estimate, num_of_points) + """ + Generate delays from an estimate of T1 + + Args: + t1_estimate (float): estimate of T1 + num_of_points (float): number of delays to generate + + Returns: + float: delays, distributed in a logarithmic scale + """ + return np.geomspace(3, 7 * t1_estimate, num_of_points) diff --git a/test/test_t1.py b/test/test_t1.py index 400d92e190..224451f527 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -276,6 +276,9 @@ def test_t1_low_quality(self): self.assertEqual(res["quality"], "computer_bad") def test_generate_delays(self): + """ + Test the static method T1Experiment.generate_delays + """ delays = T1Experiment.generate_delays(25, 30) self.assertEqual(len(delays), 30) self.assertEqual(delays[0], 3) From 77c54bd1f126da69fdda7eb33321e775448bd184 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 18 Apr 2021 16:53:52 +0300 Subject: [PATCH 45/59] typo fix --- qiskit_experiments/characterization/t1_experiment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index f4625e0b18..a96533f40a 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -47,9 +47,9 @@ def _run_analysis( t1_guess (float): Optional, an initial guess of T1 amplitude_guess (float): Optional, an initial guess of the coefficient of the exponent offset_guess (float): Optional, an initial guess of the offset - t1_bounds (list of two floats): Optional, lower bound and uper bound to T1 - amplitude_bounds (list of two floats): Optional, lower bound and uper bound to the amplitude - offset_bounds (list of two floats): Optional, lower bound and uper bound to the offset + t1_bounds (list of two floats): Optional, lower bound and upper bound to T1 + amplitude_bounds (list of two floats): Optional, lower bound and upper bound to the amplitude + offset_bounds (list of two floats): Optional, lower bound and upper bound to the offset kwargs: Trailing unused function parameters Returns: From 65fc2697e6878f5618c23de8601003926e106022 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 18 Apr 2021 17:14:59 +0300 Subject: [PATCH 46/59] removed generate_delays --- .../characterization/t1_experiment.py | 14 -------------- test/test_t1.py | 9 --------- 2 files changed, 23 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index a96533f40a..55ff4ee9c1 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -215,17 +215,3 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: circuits.append(circ) return circuits - - @staticmethod - def generate_delays(t1_estimate, num_of_points): - """ - Generate delays from an estimate of T1 - - Args: - t1_estimate (float): estimate of T1 - num_of_points (float): number of delays to generate - - Returns: - float: delays, distributed in a logarithmic scale - """ - return np.geomspace(3, 7 * t1_estimate, num_of_points) diff --git a/test/test_t1.py b/test/test_t1.py index 224451f527..0c54f1975c 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -275,15 +275,6 @@ def test_t1_low_quality(self): res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res["quality"], "computer_bad") - def test_generate_delays(self): - """ - Test the static method T1Experiment.generate_delays - """ - delays = T1Experiment.generate_delays(25, 30) - self.assertEqual(len(delays), 30) - self.assertEqual(delays[0], 3) - self.assertEqual(delays[-1], 175) - if __name__ == "__main__": unittest.main() From bfb1e3cfd36b3e5c60478c69b9e855cf9a59f24d Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 21 Apr 2021 20:41:11 +0300 Subject: [PATCH 47/59] make t1 use the helper functions --- .../characterization/analysis_functions.py | 66 ------------------- .../characterization/t1_experiment.py | 63 +++++++----------- test/test_t1.py | 8 +-- 3 files changed, 26 insertions(+), 111 deletions(-) delete mode 100644 qiskit_experiments/characterization/analysis_functions.py diff --git a/qiskit_experiments/characterization/analysis_functions.py b/qiskit_experiments/characterization/analysis_functions.py deleted file mode 100644 index 20ec3a13a5..0000000000 --- a/qiskit_experiments/characterization/analysis_functions.py +++ /dev/null @@ -1,66 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. -""" -Functions for fitting parameters. -""" - -import numpy as np -from scipy.optimize import curve_fit - - -def exp_fit_fun(x, a, tau, c): - """ - Exponential fit function - - Args: - x (float or array): a function parameter - a (float or array): a function parameter (exponent amplitude) - tau (float or array): a function parameter (exponential decrease rate) - c (float or array): a function parameter (exponent offset) - - Returns: - float or array: a * np.exp(-x / tau) + c - """ - return a * np.exp(-x / tau) + c - - -# pylint: disable = invalid-name -def curve_fit_wrapper(f, xdata, ydata, sigma, **kwargs): - """ - A wrapper to curve_fit that calculates and returns fit_err - (square root of the diagonal of the covariance matrix) and chi square. - - Args: - f (callable): see documentation of curve_fit in scipy.optimize - xdata (list): see documentation of curve_fit in scipy.optimize - ydata (list): see documentation of curve_fit in scipy.optimize - sigma (list): see documentation of curve_fit in scipy.optimize - kwargs: additional paramters to be passed to curve_fit - - Returns: - list: fitted parameters - list: error on fitted parameters - (square root of the diagonal of the covariance matrix) - matrix: the covariance matrix - float: chi-square - """ - # pylint: disable=unbalanced-tuple-unpacking - fit_out, fit_cov = curve_fit(f, xdata, ydata, sigma=sigma, **kwargs) - - chisq = 0 - for x, y, sig in zip(xdata, ydata, sigma): - chisq += (f(x, *fit_out) - y) ** 2 / sig ** 2 - chisq /= len(xdata) - - fit_err = np.sqrt(np.diag(fit_cov)) - - return fit_out, fit_err, fit_cov, chisq diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 55ff4ee9c1..84f2bd7f7e 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -20,8 +20,9 @@ from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.base_analysis import BaseAnalysis +from qiskit_experiments.analysis.curve_fitting import process_curve_data, curve_fit +from qiskit_experiments.analysis.data_processing import level2_probability from qiskit_experiments import AnalysisResult -from .analysis_functions import exp_fit_fun, curve_fit_wrapper class T1Analysis(BaseAnalysis): @@ -65,31 +66,17 @@ def _run_analysis( dt_factor_in_microsec = dt_factor_in_sec * 1000000 result_unit = "us" - size = len(experiment_data._data) - delays = np.zeros(size, dtype=float) - means = np.zeros(size, dtype=float) - stddevs = np.zeros(size, dtype=float) - - for i, circ in enumerate(experiment_data._data): - delays[i] = circ["metadata"]["delay"] * dt_factor_in_microsec - count0 = circ["counts"].get("0", 0) - count1 = circ["counts"].get("1", 0) - shots = count0 + count1 - means[i] = count1 / shots - stddevs[i] = np.sqrt(means[i] * (1 - means[i]) / shots) - # problem for the fitter if one of the std points is - # exactly zero - if stddevs[i] == 0: - stddevs[i] = 1e-4 + xdata, ydata, sigma = process_curve_data(experiment_data._data, lambda datum: level2_probability(datum, '1')) + xdata *= dt_factor_in_microsec if t1_guess is None: - t1_guess = np.mean(delays) + t1_guess = np.mean(xdata) else: t1_guess = t1_guess * dt_factor_in_microsec if offset_guess is None: - offset_guess = means[-1] + offset_guess = ydata[-1] if amplitude_guess is None: - amplitude_guess = means[0] - offset_guess + amplitude_guess = ydata[0] - offset_guess if t1_bounds is None: t1_bounds = [0, np.inf] if amplitude_bounds is None: @@ -97,40 +84,34 @@ def _run_analysis( if offset_bounds is None: offset_bounds = [0, 1] - fit_out, fit_err, fit_cov, chisq = curve_fit_wrapper( - exp_fit_fun, - delays, - means, - stddevs, - p0=[amplitude_guess, t1_guess, offset_guess], + fit_result = curve_fit( + lambda x, a, tau, c: a * np.exp(-x / tau) + c, + xdata, + ydata, + [amplitude_guess, t1_guess, offset_guess], + sigma, + tuple([amp_bnd, t1_bnd, offset_bnd] for amp_bnd, t1_bnd, offset_bnd in zip(amplitude_bounds, t1_bounds, offset_bounds)) ) analysis_result = AnalysisResult( { - "value": fit_out[1], - "stderr": fit_err[1], + "value": fit_result["popt"][1], + "stderr": fit_result["popt_err"][1], "unit": result_unit, "label": "T1", - "fit": { - "params": fit_out, - "stderr": fit_err, - "labels": ["amplitude", "T1", "offset"], - "chisq": chisq, - "cov": fit_cov, - }, - "quality": self._fit_quality(fit_out, fit_err, chisq), - } - ) + "fit": fit_result, + "quality": self._fit_quality(fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]), + }) return analysis_result, None @staticmethod - def _fit_quality(fit_out, fit_err, chisq): + def _fit_quality(fit_out, fit_err, reduced_chisq): # pylint: disable = too-many-boolean-expressions if ( abs(fit_out[0] - 1.0) < 0.1 and abs(fit_out[2]) < 0.1 - and chisq < 3 + and reduced_chisq < 3 and (fit_err[0] is None or fit_err[0] < 0.1) and (fit_err[1] is None or fit_err[1] < fit_out[1]) and (fit_err[2] is None or fit_err[2] < 0.1) @@ -205,7 +186,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: circ.metadata = { "experiment_type": self._type, "qubit": self.physical_qubits[0], - "delay": delay, + "xval": delay, "unit": self._unit, } diff --git a/test/test_t1.py b/test/test_t1.py index 0c54f1975c..7619a7a466 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -216,7 +216,7 @@ def test_t1_analysis(self): { "counts": {"0": count0, "1": 10000 - count0}, "metadata": { - "delay": 3 * i + 1, + "xval": 3 * i + 1, "experiment_type": "T1Experiment", "qubit": 0, "unit": "ns", @@ -246,7 +246,7 @@ def test_t1_metadata(self): { "experiment_type": "T1Experiment", "qubit": 0, - "delay": delay, + "xval": delay, "unit": "ms", }, ) @@ -258,12 +258,12 @@ def test_t1_low_quality(self): data = ExperimentData(None) - for i in range(3): + for i in range(10): data._data.append( { "counts": {"0": 10, "1": 10}, "metadata": { - "delay": i, + "xval": i, "experiment_type": "T1Experiment", "qubit": 0, "unit": "ns", From 945fa3f37bf4571ff01926ae721c74ba3861145f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 21 Apr 2021 20:43:01 +0300 Subject: [PATCH 48/59] black --- .../characterization/t1_experiment.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 84f2bd7f7e..eb126cbd73 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -66,7 +66,9 @@ def _run_analysis( dt_factor_in_microsec = dt_factor_in_sec * 1000000 result_unit = "us" - xdata, ydata, sigma = process_curve_data(experiment_data._data, lambda datum: level2_probability(datum, '1')) + xdata, ydata, sigma = process_curve_data( + experiment_data._data, lambda datum: level2_probability(datum, "1") + ) xdata *= dt_factor_in_microsec if t1_guess is None: @@ -90,7 +92,10 @@ def _run_analysis( ydata, [amplitude_guess, t1_guess, offset_guess], sigma, - tuple([amp_bnd, t1_bnd, offset_bnd] for amp_bnd, t1_bnd, offset_bnd in zip(amplitude_bounds, t1_bounds, offset_bounds)) + tuple( + [amp_bnd, t1_bnd, offset_bnd] + for amp_bnd, t1_bnd, offset_bnd in zip(amplitude_bounds, t1_bounds, offset_bounds) + ), ) analysis_result = AnalysisResult( @@ -100,8 +105,11 @@ def _run_analysis( "unit": result_unit, "label": "T1", "fit": fit_result, - "quality": self._fit_quality(fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]), - }) + "quality": self._fit_quality( + fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"] + ), + } + ) return analysis_result, None From 19bdd5af801ec7bd0daa9ee30b1a0d5ebaa555d6 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 22 Apr 2021 16:03:07 +0300 Subject: [PATCH 49/59] T1 to always return results in seconds --- .../characterization/t1_experiment.py | 29 ++++++++-------- test/test_t1.py | 34 +++++++++---------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index eb126cbd73..283aa66df6 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -17,6 +17,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit +from qiskit.utils import apply_prefix from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.base_analysis import BaseAnalysis @@ -57,24 +58,20 @@ def _run_analysis( The analysis result with the estimated T1 """ - circuit_unit = experiment_data._data[0]["metadata"]["unit"] - dt_factor_in_sec = experiment_data._data[0]["metadata"].get("dt_factor_in_sec", None) - if dt_factor_in_sec is None: - dt_factor_in_microsec = 1 - result_unit = circuit_unit - else: - dt_factor_in_microsec = dt_factor_in_sec * 1000000 - result_unit = "us" + unit = experiment_data._data[0]["metadata"]["unit"] + conversion_factor = experiment_data._data[0]["metadata"].get("dt_factor", None) + if conversion_factor is None: + conversion_factor = 1 if unit == "s" else apply_prefix(1, unit) xdata, ydata, sigma = process_curve_data( experiment_data._data, lambda datum: level2_probability(datum, "1") ) - xdata *= dt_factor_in_microsec + xdata *= conversion_factor if t1_guess is None: t1_guess = np.mean(xdata) else: - t1_guess = t1_guess * dt_factor_in_microsec + t1_guess = t1_guess * conversion_factor if offset_guess is None: offset_guess = ydata[-1] if amplitude_guess is None: @@ -102,7 +99,7 @@ def _run_analysis( { "value": fit_result["popt"][1], "stderr": fit_result["popt_err"][1], - "unit": result_unit, + "unit": "s", "label": "T1", "fit": fit_result, "quality": self._fit_quality( @@ -111,6 +108,10 @@ def _run_analysis( } ) + if unit == "dt": + analysis_result["fit"]["dt"] = conversion_factor + analysis_result["fit"]["circuit_unit"] = unit + return analysis_result, None @staticmethod @@ -138,7 +139,7 @@ def __init__( self, qubit: int, delays: Union[List[float], np.array], - unit: Optional[str] = "us", + unit: Optional[str] = "s", experiment_type: Optional[str] = None, ): """ @@ -177,7 +178,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: if self._unit == "dt": try: - dt_factor_in_sec = getattr(backend.configuration(), "dt") + dt_factor = getattr(backend.configuration(), "dt") except AttributeError as no_dt: raise AttributeError("Dt parameter is missing in backend configuration") from no_dt @@ -199,7 +200,7 @@ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: } if self._unit == "dt": - circ.metadata["dt_factor_in_sec"] = dt_factor_in_sec + circ.metadata["dt_factor"] = dt_factor circuits.append(circ) diff --git a/test/test_t1.py b/test/test_t1.py index 7619a7a466..1a26247da3 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -30,13 +30,13 @@ class T1Backend(BaseBackend): A simple and primitive backend, to be run by the T1 tests """ - def __init__( - self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, dt_factor_in_microsec=1e6 - ): + def __init__(self, t1, initial_prob1=None, readout0to1=None, readout1to0=None, dt_factor=None): """ Initialize the T1 backend """ + dt_factor_in_ns = dt_factor * 1e9 if dt_factor is not None else None + configuration = QasmBackendConfiguration( backend_name="t1_simulator", backend_version="0", @@ -50,14 +50,14 @@ def __init__( memory=False, max_shots=int(1e6), coupling_map=None, - dt=dt_factor_in_microsec * 1000, + dt=dt_factor_in_ns, ) self._t1 = t1 self._initial_prob1 = initial_prob1 self._readout0to1 = readout0to1 self._readout1to0 = readout1to0 - self._dt_factor_in_microsec = dt_factor_in_microsec + self._dt_factor = dt_factor super().__init__(configuration) # pylint: disable = arguments-differ @@ -104,7 +104,7 @@ def run(self, qobj): if op.name == "x": prob1[qubit] = 1 - prob1[qubit] if op.name == "delay": - delay = op.params[0] * self._dt_factor_in_microsec + delay = op.params[0] prob1[qubit] = prob1[qubit] * np.exp(-delay / self._t1[qubit]) if op.name == "measure": meas_res = np.random.binomial( @@ -144,36 +144,36 @@ def test_t1_end2end(self): Test T1 experiment using a simulator. """ - dt_factor_in_microsec = 0.0002 + dt_factor = 2e-7 - t1 = 25 + t1 = 25e-6 backend = T1Backend( - [t1], + [t1 / dt_factor], initial_prob1=[0.02], readout0to1=[0.02], readout1to0=[0.02], - dt_factor_in_microsec=dt_factor_in_microsec, + dt_factor=dt_factor, ) delays = list( range( - int(1 / dt_factor_in_microsec), - int(40 / dt_factor_in_microsec), - int(3 / dt_factor_in_microsec), + int(1e-6 / dt_factor), + int(40e-6 / dt_factor), + int(3e-6 / dt_factor), ) ) # dummy numbers to avoid exception triggerring instruction_durations = [ - ("measure", [0], 3 / dt_factor_in_microsec, "dt"), - ("x", [0], 3 / dt_factor_in_microsec, "dt"), + ("measure", [0], 3, "dt"), + ("x", [0], 3, "dt"), ] exp = T1Experiment(0, delays, unit="dt") res = exp.run( backend, amplitude_guess=1, - t1_guess=t1 / dt_factor_in_microsec, + t1_guess=t1 / dt_factor, offset_guess=0, instruction_durations=instruction_durations, shots=10000, @@ -227,7 +227,7 @@ def test_t1_analysis(self): res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res["quality"], "computer_good") - self.assertAlmostEqual(res["value"], 25, delta=3) + self.assertAlmostEqual(res["value"], 25e-9, delta=3) def test_t1_metadata(self): """ From b49393248359509e431884ec2d1c263239810d8b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 27 Apr 2021 16:26:46 +0300 Subject: [PATCH 50/59] an attempt to integrate with results db --- qiskit_experiments/__init__.py | 3 - qiskit_experiments/base_analysis.py | 2 +- qiskit_experiments/base_experiment.py | 8 +- .../characterization/t1_experiment.py | 10 +- .../composite/composite_analysis.py | 3 +- .../composite/composite_experiment_data.py | 2 +- qiskit_experiments/experiment_data.py | 141 ------------------ test/test_t1.py | 2 +- 8 files changed, 14 insertions(+), 157 deletions(-) delete mode 100644 qiskit_experiments/experiment_data.py diff --git a/qiskit_experiments/__init__.py b/qiskit_experiments/__init__.py index 42e800cb3c..7b580ac70b 100644 --- a/qiskit_experiments/__init__.py +++ b/qiskit_experiments/__init__.py @@ -14,9 +14,6 @@ from .version import __version__ -# Base result classes -from .experiment_data import ExperimentData, AnalysisResult - # Experiment modules from . import composite from . import characterization diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index f6329fe203..31f06b2e4f 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -16,7 +16,7 @@ from abc import ABC, abstractmethod from qiskit.exceptions import QiskitError -from .experiment_data import ExperimentData, AnalysisResult +from qiskit.providers.experiment import AnalysisResult, ExperimentData class BaseAnalysis(ABC): diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index fb82f4646c..6627b3eff6 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -19,7 +19,7 @@ from qiskit import transpile, assemble from qiskit.exceptions import QiskitError -from .experiment_data import ExperimentData +from qiskit.providers.experiment import ExperimentDataV1 _TRANSPILE_OPTIONS = { @@ -57,7 +57,7 @@ class BaseExperiment(ABC): __analysis_class__ = None # ExperimentData class for experiment - __experiment_data__ = ExperimentData + __experiment_data__ = ExperimentDataV1 # Custom default transpiler options for experiment subclasses __transpile_defaults__ = {"optimization_level": 0} @@ -89,7 +89,6 @@ def __init__(self, qubits, experiment_type=None, circuit_options=None): self._num_qubits = len(qubits) self._physical_qubits = tuple(qubits) if self._num_qubits != len(set(self._physical_qubits)): - print(self._num_qubits, self._physical_qubits) raise QiskitError("Duplicate qubits in physical qubits list.") # Store options and values @@ -113,7 +112,7 @@ def run(self, backend, experiment_data=None, **kwargs): # Create new experiment data if experiment_data is None: - experiment_data = self.__experiment_data__(self) + experiment_data = self.__experiment_data__(backend, self._type) # Filter kwargs run_options = self.__run_defaults__.copy() @@ -230,3 +229,4 @@ def transpiled_circuits(self, backend=None, **kwargs): circuits = transpile(circuits, backend=backend, **transpile_options) return circuits + diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index f4625e0b18..c00c91d099 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -20,8 +20,8 @@ from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.base_analysis import BaseAnalysis -from qiskit_experiments import AnalysisResult from .analysis_functions import exp_fit_fun, curve_fit_wrapper +from qiskit.providers.experiment import AnalysisResultV1 class T1Analysis(BaseAnalysis): @@ -38,7 +38,7 @@ def _run_analysis( amplitude_bounds=None, offset_bounds=None, **kwargs, - ) -> Tuple[AnalysisResult, None]: + ) -> Tuple[AnalysisResultV1, None]: """ Calculate T1 @@ -56,8 +56,8 @@ def _run_analysis( The analysis result with the estimated T1 """ - circuit_unit = experiment_data._data[0]["metadata"]["unit"] - dt_factor_in_sec = experiment_data._data[0]["metadata"].get("dt_factor_in_sec", None) + circuit_unit = experiment_data.data(0)["metadata"]["unit"] + dt_factor_in_sec = experiment_data.data(0)["metadata"].get("dt_factor_in_sec", None) if dt_factor_in_sec is None: dt_factor_in_microsec = 1 result_unit = circuit_unit @@ -70,7 +70,7 @@ def _run_analysis( means = np.zeros(size, dtype=float) stddevs = np.zeros(size, dtype=float) - for i, circ in enumerate(experiment_data._data): + for i, circ in enumerate(experiment_data.data()): delays[i] = circ["metadata"]["delay"] * dt_factor_in_microsec count0 = circ["counts"].get("0", 0) count1 = circ["counts"].get("1", 0) diff --git a/qiskit_experiments/composite/composite_analysis.py b/qiskit_experiments/composite/composite_analysis.py index 625215e976..7b0eeddcaf 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -14,8 +14,9 @@ """ from qiskit.exceptions import QiskitError -from qiskit_experiments.base_analysis import BaseAnalysis, AnalysisResult +from qiskit_experiments.base_analysis import BaseAnalysis from .composite_experiment_data import CompositeExperimentData +from qiskit.providers.experiment import AnalysisResult class CompositeAnalysis(BaseAnalysis): diff --git a/qiskit_experiments/composite/composite_experiment_data.py b/qiskit_experiments/composite/composite_experiment_data.py index 48dee73d8c..dfb7f4f127 100644 --- a/qiskit_experiments/composite/composite_experiment_data.py +++ b/qiskit_experiments/composite/composite_experiment_data.py @@ -14,7 +14,7 @@ """ from qiskit.result import marginal_counts -from qiskit_experiments.experiment_data import ExperimentData +from qiskit.providers.experiment import ExperimentData class CompositeExperimentData(ExperimentData): diff --git a/qiskit_experiments/experiment_data.py b/qiskit_experiments/experiment_data.py deleted file mode 100644 index b7cbda8394..0000000000 --- a/qiskit_experiments/experiment_data.py +++ /dev/null @@ -1,141 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. -""" -Experiment Data class -""" - -import uuid - -from qiskit.result import Result -from qiskit.providers import Job, BaseJob -from qiskit.exceptions import QiskitError - - -class AnalysisResult(dict): - """Placeholder class""" - - -class ExperimentData: - """ExperimentData container class""" - - def __init__(self, experiment): - """Initialize the analysis object. - - Args: - experiment (BaseExperiment): experiment object that - generated the data. - """ - # Experiment identification metadata - self._id = str(uuid.uuid4()) - self._experiment = experiment - - # Experiment Data - self._data = [] - - # Analysis - self._analysis_results = [] - - def __repr__(self): - line = 51 * "-" - n_res = len(self._analysis_results) - ret = line - ret += f"\nExperiment: {self._experiment._type}" - ret += f"\nExperiment ID: {self.experiment_id}" - ret += "\nStatus: COMPLETE" - ret += f"\nCircuits: {len(self._data)}" - ret += f"\nAnalysis Results: {n_res}" - ret += "\n" + line - if n_res: - ret += "\nLast Analysis Result" - for key, value in self._analysis_results[-1].items(): - ret += f"\n- {key}: {value}" - return ret - - @property - def experiment_id(self): - """Return the experiment id""" - return self._id - - def experiment(self): - """Return Experiment object""" - return self._experiment - - def analysis_result(self, index): - """Return stored analysis results - - Args: - index (int or slice): the result or range of results to return. - - Returns: - AnalysisResult: the result for an integer index. - List[AnalysisResult]: a list of results for slice index. - """ - return self._analysis_results[index] - - def add_analysis_result(self, result): - """Add an Analysis Result - - Args: - result (AnalysisResult): the analysis result to add. - """ - self._analysis_results.append(result) - - @property - def data(self): - """Return stored experiment data""" - return self._data - - def add_data(self, data): - """Add data to the experiment. - - Args: - data (Result or Job or dict or list): the circuit execution data - to add. This can be a Result, Job, or dict object, or a list - of Result, Job, or dict objects. - - Raises: - QiskitError: if the data is not a valid format. - """ - if isinstance(data, dict): - self._add_single_data(data) - elif isinstance(data, Result): - self._add_result_data(data) - elif isinstance(data, (Job, BaseJob)): - self._add_result_data(data.result()) - elif isinstance(data, list): - for dat in data: - self.add_data(dat) - else: - raise QiskitError("Invalid data format.") - - def _add_result_data(self, result: Result): - """Add data from qiskit Result object""" - num_data = len(result.results) - for i in range(num_data): - metadata = result.results[i].header.metadata - if metadata.get("experiment_type") == self._experiment._type: - data = result.data(i) - data["metadata"] = metadata - if "counts" in data: - # Format to Counts object rather than hex dict - data["counts"] = result.get_counts(i) - self._add_single_data(data) - - def _add_single_data(self, data): - """Add a single data dictionary to the experiment. - - Args: - data (dict): a data dictionary for a single circuit exection. - """ - # This method is intended to be overriden by subclasses when necessary. - if data.get("metadata", {}).get("experiment_type") == self._experiment._type: - self._data.append(data) diff --git a/test/test_t1.py b/test/test_t1.py index 224451f527..ef449be485 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -20,7 +20,7 @@ from qiskit.providers import BaseBackend from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result -from qiskit_experiments import ExperimentData +from qiskit.providers.experiment import ExperimentDataV1 from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T1Experiment, T1Analysis From a3cfa28f47c8b66fcf54102bec0f996b819f5557 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 27 Apr 2021 21:16:23 +0300 Subject: [PATCH 51/59] replaced if by elif --- test/test_t1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_t1.py b/test/test_t1.py index 1a26247da3..75f794db11 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -103,10 +103,10 @@ def run(self, qobj): qubit = op.qubits[0] if op.name == "x": prob1[qubit] = 1 - prob1[qubit] - if op.name == "delay": + elif op.name == "delay": delay = op.params[0] prob1[qubit] = prob1[qubit] * np.exp(-delay / self._t1[qubit]) - if op.name == "measure": + elif op.name == "measure": meas_res = np.random.binomial( 1, prob1[qubit] * (1 - ro10[qubit]) + (1 - prob1[qubit]) * ro01[qubit] ) From 5ec66219bfcf4e8247cfbeaf5e685ef1da643aa5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 28 Apr 2021 22:23:11 +0300 Subject: [PATCH 52/59] Update qiskit_experiments/characterization/t1_experiment.py Co-authored-by: Christopher J. Wood --- qiskit_experiments/characterization/t1_experiment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 283aa66df6..93a6b36e85 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -148,7 +148,7 @@ def __init__( Args: qubit: the qubit whose T1 is to be estimated delays: delay times of the experiments - unit:Optional, unit of the duration. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. + unit: Optional, unit of the delay times. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. experiment_type: Optional, the experiment type string. Raises: From f6380600034316b1fa466170270b18c509002557 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 28 Apr 2021 22:26:42 +0300 Subject: [PATCH 53/59] removed experiment_type from t1 --- qiskit_experiments/characterization/t1_experiment.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 93a6b36e85..771011e736 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -140,7 +140,6 @@ def __init__( qubit: int, delays: Union[List[float], np.array], unit: Optional[str] = "s", - experiment_type: Optional[str] = None, ): """ Initialize the T1 experiment class @@ -149,7 +148,6 @@ def __init__( qubit: the qubit whose T1 is to be estimated delays: delay times of the experiments unit: Optional, unit of the delay times. Supported units: 's', 'ms', 'us', 'ns', 'ps', 'dt'. - experiment_type: Optional, the experiment type string. Raises: ValueError: if the number of delays is smaller than 3 @@ -159,7 +157,7 @@ def __init__( self._delays = delays self._unit = unit - super().__init__([qubit], experiment_type) + super().__init__([qubit]) # pylint: disable=arguments-differ def circuits(self, backend: Optional["Backend"] = None) -> List[QuantumCircuit]: From e0439186cd9c5b07c81904369ca303a76e3a8140 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 28 Apr 2021 22:35:22 +0300 Subject: [PATCH 54/59] circuit_unit should appear in the result for every unit --- qiskit_experiments/characterization/t1_experiment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 771011e736..e1b23dc74d 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -108,9 +108,9 @@ def _run_analysis( } ) + analysis_result["fit"]["circuit_unit"] = unit if unit == "dt": analysis_result["fit"]["dt"] = conversion_factor - analysis_result["fit"]["circuit_unit"] = unit return analysis_result, None From 73a379b1e85d2b44507513c8da435638c84bbdb7 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 29 Apr 2021 20:31:54 +0300 Subject: [PATCH 55/59] use AnalysisResultV1 --- qiskit_experiments/analysis/curve_fitting.py | 16 ++++----- qiskit_experiments/analysis/plotting.py | 10 +++--- .../characterization/t1_experiment.py | 35 +++++++++++-------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/qiskit_experiments/analysis/curve_fitting.py b/qiskit_experiments/analysis/curve_fitting.py index b2d2e83801..bea491db7e 100644 --- a/qiskit_experiments/analysis/curve_fitting.py +++ b/qiskit_experiments/analysis/curve_fitting.py @@ -19,7 +19,6 @@ import numpy as np import scipy.optimize as opt from qiskit.exceptions import QiskitError -from qiskit_experiments.base_analysis import AnalysisResult from qiskit_experiments.analysis.data_processing import filter_data @@ -31,7 +30,7 @@ def curve_fit( sigma: Optional[np.ndarray] = None, bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None, **kwargs, -) -> AnalysisResult: +) -> Dict: r"""Perform a non-linear least squares to fit This solves the optimization problem @@ -110,8 +109,8 @@ def fit_func(x, *params): kwargs["absolute_sigma"] = True # Run curve fit - # TODO: Add error handling so if fitting fails we can return an analysis - # result containing this information + # TODO: Add error handling so if fitting fails we can return a + # dictionary containing this information # pylint: disable = unbalanced-tuple-unpacking popt, pcov = opt.curve_fit( fit_func, xdata, ydata, sigma=sigma, p0=param_p0, bounds=param_bounds, **kwargs @@ -138,7 +137,7 @@ def fit_func(x, *params): "xrange": xdata_range, } - return AnalysisResult(result) + return result def multi_curve_fit( @@ -151,7 +150,7 @@ def multi_curve_fit( weights: Optional[np.ndarray] = None, bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None, **kwargs, -) -> AnalysisResult: +) -> Dict: r"""Perform a linearized multi-objective non-linear least squares fit. This solves the optimization problem @@ -230,10 +229,7 @@ def f(x, *params): return y # Run linearized curve_fit - analysis_result = curve_fit(f, xdata, ydata, p0, sigma=wsigma, bounds=bounds, **kwargs) - - return analysis_result - + return curve_fit(f, xdata, ydata, p0, sigma=wsigma, bounds=bounds, **kwargs) def process_curve_data( data: List[Dict[str, any]], data_processor: Callable, x_key: str = "xval", **filters diff --git a/qiskit_experiments/analysis/plotting.py b/qiskit_experiments/analysis/plotting.py index bd9312e17a..4f9cdd1be4 100644 --- a/qiskit_experiments/analysis/plotting.py +++ b/qiskit_experiments/analysis/plotting.py @@ -13,11 +13,9 @@ Plotting functions for experiment analysis """ import functools -from typing import Callable, Optional +from typing import Callable, Optional, Dict import numpy as np -from qiskit_experiments.base_analysis import AnalysisResult - try: from matplotlib import pyplot as plt @@ -44,7 +42,7 @@ def wrapped(*args, **kwargs): @requires_matplotlib def plot_curve_fit( func: Callable, - result: AnalysisResult, + result: Dict, confidence_interval: bool = True, ax: Optional["AxesSubplot"] = None, num_fit_points: int = 100, @@ -52,13 +50,13 @@ def plot_curve_fit( grid: bool = True, **kwargs, ) -> "AxesSubplot": - """Generate plot of a curve fit analysis result. + """Generate plot of a curve fitresult. Wraps ``matplotlib.pyplot.plot``. Args: func: the fit funcion for curve_fit. - result: an AnalysisResult from curve_fit. + result: a result dictionary from curve_fit. confidence_interval: if True plot the confidence interval from popt_err. ax: Optional, a matplotlib axes to add the plot to. num_fit_points: the number of points to plot for xrange. diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 589c301d47..5bc80ebaf2 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -22,6 +22,7 @@ from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.base_analysis import BaseAnalysis from qiskit.providers.experiment import AnalysisResultV1 +from qiskit.providers.experiment.device_component import Qubit from qiskit_experiments.analysis.curve_fitting import process_curve_data, curve_fit from qiskit_experiments.analysis.data_processing import level2_probability @@ -95,22 +96,28 @@ def _run_analysis( ), ) - analysis_result = AnalysisResult( - { - "value": fit_result["popt"][1], - "stderr": fit_result["popt_err"][1], - "unit": "s", - "label": "T1", - "fit": fit_result, - "quality": self._fit_quality( - fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"] + result_data = { + "value": fit_result["popt"][1], + "stderr": fit_result["popt_err"][1], + "unit": "s", + "label": "T1", + "fit": fit_result, + "quality": self._fit_quality( + fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"] ), } - ) if unit == "dt": - analysis_result["fit"]["dt"] = conversion_factor - analysis_result["fit"]["circuit_unit"] = unit + result_data["fit"]["dt"] = conversion_factor + result_data["fit"]["circuit_unit"] = unit + + analysis_result = AnalysisResultV1( + result_data, + 'T1', + [Qubit()], + experiment_data.id, + quality=result_data['quality'], + verified=True) return analysis_result, None @@ -125,9 +132,9 @@ def _fit_quality(fit_out, fit_err, reduced_chisq): and (fit_err[1] is None or fit_err[1] < fit_out[1]) and (fit_err[2] is None or fit_err[2] < 0.1) ): - return "computer_good" + return "good" else: - return "computer_bad" + return "bad" class T1Experiment(BaseExperiment): From 47fc3e0232df4ed182e61d99e8ba2c2f660e60f9 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 2 May 2021 11:51:15 +0300 Subject: [PATCH 56/59] test_t1_end2end is working --- qiskit_experiments/characterization/t1_experiment.py | 2 +- qiskit_experiments/composite/parallel_experiment.py | 2 +- test/test_t1.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index ef2a900dba..56a9ce30d9 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -114,7 +114,7 @@ def _run_analysis( analysis_result = AnalysisResultV1( result_data, 'T1', - [Qubit()], + [Qubit(experiment_data.data(0)["metadata"]["qubit"])], experiment_data.id, quality=result_data['quality'], verified=True) diff --git a/qiskit_experiments/composite/parallel_experiment.py b/qiskit_experiments/composite/parallel_experiment.py index 152709df27..71de2518be 100644 --- a/qiskit_experiments/composite/parallel_experiment.py +++ b/qiskit_experiments/composite/parallel_experiment.py @@ -22,7 +22,7 @@ class ParallelExperiment(CompositeExperiment): """Parallel Experiment class""" def __init__(self, experiments): - """Initialize the analysis object. + """Initialize the parallel experiments object. Args: experiments (List[BaseExperiment]): a list of experiments. diff --git a/test/test_t1.py b/test/test_t1.py index 4ed26ebb98..1e50ba8e35 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -20,7 +20,7 @@ from qiskit.providers import BaseBackend from qiskit.providers.models import QasmBackendConfiguration from qiskit.result import Result -from qiskit.providers.experiment import ExperimentDataV1 +from qiskit.providers.experiment import ExperimentDataV1, ResultQuality from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T1Experiment, T1Analysis @@ -179,8 +179,8 @@ def test_t1_end2end(self): shots=10000, ).analysis_result(0) - self.assertEqual(res["quality"], "computer_good") - self.assertAlmostEqual(res["value"], t1, delta=3) + self.assertEqual(res.quality, ResultQuality.GOOD) + self.assertAlmostEqual(res.data()["value"], t1, delta=3) def test_t1_parallel(self): """ From c5b45e19ebb2cab46e88b24d5f946ceded3c7727 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 2 May 2021 14:31:03 +0300 Subject: [PATCH 57/59] fixed test_t1_analysis --- .../characterization/t1_experiment.py | 2 +- test/test_t1.py | 31 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 56a9ce30d9..e794947b12 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -46,7 +46,7 @@ def _run_analysis( Calculate T1 Args: - experiment_data (ExperimentData): the experiment data to analyze + experiment_data (ExperimentDataV1): the experiment data to analyze t1_guess (float): Optional, an initial guess of T1 amplitude_guess (float): Optional, an initial guess of the coefficient of the exponent offset_guess (float): Optional, an initial guess of the offset diff --git a/test/test_t1.py b/test/test_t1.py index 1e50ba8e35..6b879be616 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -208,26 +208,39 @@ def test_t1_analysis(self): Test T1Analysis """ - data = ExperimentData(None) - numbers = [750, 1800, 2750, 3550, 4250, 4850, 5450, 5900, 6400, 6800, 7000, 7350, 7700] + backend_res = { + "backend_name": "T1 backend", + "backend_version": "0", + "qobj_id": 0, + "job_id": 0, + "success": True, + "results": [], + } + shots = 10000 + numbers = [750, 1800, 2750, 3550, 4250, 4850, 5450, 5900, 6400, 6800, 7000, 7350, 7700] for i, count0 in enumerate(numbers): - data._data.append( + backend_res["results"].append( { - "counts": {"0": count0, "1": 10000 - count0}, - "metadata": { + "success": True, + "shots": shots, + "data": {"counts": {"0": count0, "1": shots - count0}}, + "header": {"metadata": { "xval": 3 * i + 1, "experiment_type": "T1Experiment", "qubit": 0, "unit": "ns", "dt_factor_in_sec": None, - }, + }}, } ) + data = ExperimentDataV1(None, "T1Experiment") + data.add_data(Result.from_dict(backend_res)) + res = T1Analysis()._run_analysis(data)[0] - self.assertEqual(res["quality"], "computer_good") - self.assertAlmostEqual(res["value"], 25e-9, delta=3) + self.assertEqual(res.quality, ResultQuality.GOOD) + self.assertAlmostEqual(res.data()["value"], 25e-9, delta=3) def test_t1_metadata(self): """ @@ -273,7 +286,7 @@ def test_t1_low_quality(self): ) res = T1Analysis()._run_analysis(data)[0] - self.assertEqual(res["quality"], "computer_bad") + self.assertEqual(res.quality, ResultQuality.BAD) if __name__ == "__main__": From 4543de7385dea4afeec2f7ac9531ced5c081b063 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 2 May 2021 14:31:26 +0300 Subject: [PATCH 58/59] black --- qiskit_experiments/analysis/curve_fitting.py | 1 + qiskit_experiments/base_experiment.py | 1 - .../characterization/t1_experiment.py | 11 ++++++----- test/test_t1.py | 16 +++++++++------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/qiskit_experiments/analysis/curve_fitting.py b/qiskit_experiments/analysis/curve_fitting.py index bea491db7e..72b05b0094 100644 --- a/qiskit_experiments/analysis/curve_fitting.py +++ b/qiskit_experiments/analysis/curve_fitting.py @@ -231,6 +231,7 @@ def f(x, *params): # Run linearized curve_fit return curve_fit(f, xdata, ydata, p0, sigma=wsigma, bounds=bounds, **kwargs) + def process_curve_data( data: List[Dict[str, any]], data_processor: Callable, x_key: str = "xval", **filters ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index 6627b3eff6..62cc0a638f 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -229,4 +229,3 @@ def transpiled_circuits(self, backend=None, **kwargs): circuits = transpile(circuits, backend=backend, **transpile_options) return circuits - diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index e794947b12..9ec13ce2db 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -104,8 +104,8 @@ def _run_analysis( "fit": fit_result, "quality": self._fit_quality( fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"] - ), - } + ), + } result_data["fit"]["circuit_unit"] = unit if unit == "dt": @@ -113,11 +113,12 @@ def _run_analysis( analysis_result = AnalysisResultV1( result_data, - 'T1', + "T1", [Qubit(experiment_data.data(0)["metadata"]["qubit"])], experiment_data.id, - quality=result_data['quality'], - verified=True) + quality=result_data["quality"], + verified=True, + ) return analysis_result, None diff --git a/test/test_t1.py b/test/test_t1.py index 6b879be616..d6b17ea789 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -225,13 +225,15 @@ def test_t1_analysis(self): "success": True, "shots": shots, "data": {"counts": {"0": count0, "1": shots - count0}}, - "header": {"metadata": { - "xval": 3 * i + 1, - "experiment_type": "T1Experiment", - "qubit": 0, - "unit": "ns", - "dt_factor_in_sec": None, - }}, + "header": { + "metadata": { + "xval": 3 * i + 1, + "experiment_type": "T1Experiment", + "qubit": 0, + "unit": "ns", + "dt_factor_in_sec": None, + } + }, } ) From 2304ad901c5d4fde6e971344bb777eababc45e4b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 2 May 2021 14:36:42 +0300 Subject: [PATCH 59/59] fixed test_t1_low_quality --- test/test_t1.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/test/test_t1.py b/test/test_t1.py index d6b17ea789..95aa0f5f12 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -271,22 +271,37 @@ def test_t1_low_quality(self): A test where the fit's quality will be low """ - data = ExperimentData(None) + backend_res = { + "backend_name": "T1 backend", + "backend_version": "0", + "qobj_id": 0, + "job_id": 0, + "success": True, + "results": [], + } + shots = 20 for i in range(10): - data._data.append( + backend_res["results"].append( { - "counts": {"0": 10, "1": 10}, - "metadata": { - "xval": i, - "experiment_type": "T1Experiment", - "qubit": 0, - "unit": "ns", - "dt_factor_in_sec": None, + "success": True, + "shots": shots, + "data": {"counts": {"0": 10, "1": shots - 10}}, + "header": { + "metadata": { + "xval": i, + "experiment_type": "T1Experiment", + "qubit": 0, + "unit": "ns", + "dt_factor_in_sec": None, + } }, } ) + data = ExperimentDataV1(None, "T1Experiment") + data.add_data(Result.from_dict(backend_res)) + res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res.quality, ResultQuality.BAD)