Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
1a68a65
* First draft of spectroscopy.
eggerdj Apr 30, 2021
c6497e9
* Changed counts to memory for Level1 data.
eggerdj Apr 30, 2021
fa1ee89
* Switched to _physical_qubits[0]
eggerdj May 6, 2021
2e54b61
* Added user provided initial guesses.
eggerdj May 6, 2021
21b9c74
* Black.
eggerdj May 6, 2021
8eaccf0
* Added fit boundaries.
eggerdj May 6, 2021
7f7e8aa
* Black and Lint.
eggerdj May 6, 2021
fbb81f5
* Improved docstring.
eggerdj May 6, 2021
3c33538
* Fixed default parameters.
eggerdj May 6, 2021
b60de51
* Fixed array bug.
eggerdj May 6, 2021
1f19f3d
* Added optional to type hints.
eggerdj May 10, 2021
c9791c8
* Black.
eggerdj May 10, 2021
a8ba063
* Added tests
eggerdj May 10, 2021
3301a17
* Updated guesses.
eggerdj May 11, 2021
b167e23
* Changed default to / 4 instead of / 5
eggerdj May 11, 2021
fa334da
* Added circuit information to the metadata.
eggerdj May 11, 2021
846af31
* Made frequency a parameter in the gate.
eggerdj May 11, 2021
2937795
* Added dt to metadata and improved docstring.
eggerdj May 11, 2021
44f7400
* Improved algorithmic criteria for the fit quality.
eggerdj May 16, 2021
4ddb9b9
Merge branch 'main' into spectroscopy
eggerdj May 16, 2021
9dd5bf8
* Moved spectroscopy to characterization.
eggerdj May 16, 2021
3b0f35d
* Added better defaults for fit quality.
eggerdj May 16, 2021
47f4e50
* Black.
eggerdj May 16, 2021
7f9a5b1
* Updated to recent ExperimentData change.
eggerdj May 17, 2021
1bfe7d6
* Test.
eggerdj May 17, 2021
de6eac3
* Added trailling kwargs
eggerdj May 17, 2021
aaa0c40
* Lint.
eggerdj May 17, 2021
cb1f52b
* Temporary commit to debug CI.
eggerdj May 17, 2021
887f012
* Removing previous commit.
eggerdj May 17, 2021
7a80b2a
* Added TestJob to the test.
eggerdj May 17, 2021
43d1603
Merge branch 'main' into spectroscopy
eggerdj May 17, 2021
6466f0a
* Removed self._absolute in the metadata.
eggerdj May 18, 2021
e8fbbf5
Merge branch 'spectroscopy' of github.com:eggerdj/qiskit-experiments …
eggerdj May 18, 2021
6173531
* physical_qubits.
eggerdj May 18, 2021
66b49ca
* Changed bounds from list to tuple.
eggerdj May 18, 2021
fa13a8a
* Temporarily removed Excpet to figure out why CI is failing.
eggerdj May 18, 2021
3cf6c6e
* Added HAS_MATPLOTLIB
eggerdj May 18, 2021
fd934a1
* Added back the exception.
eggerdj May 18, 2021
d1b1c9b
* Fixed sqrt issue on sigma.
eggerdj May 19, 2021
7138dae
Merge branch 'main' into spectroscopy
eggerdj May 19, 2021
6c7275f
* Small refactoring of fitting.
eggerdj May 19, 2021
fcc0489
Merge branch 'main' into spectroscopy
eggerdj May 19, 2021
4085945
* Built in SVD.
eggerdj May 20, 2021
ec81cb5
* Working on tests.
eggerdj May 20, 2021
5307ae6
* Improved tests.
eggerdj May 20, 2021
58a53db
* Doctrings.
eggerdj May 21, 2021
3cf9d65
* Lint and plotting.
eggerdj May 21, 2021
42886fa
* Fixed docstring in tests.
eggerdj May 21, 2021
69087dd
* Made sure data processor error returns the standard deviation.
eggerdj May 21, 2021
d4b8670
* Singled out reusable part of spectroscopy test.
eggerdj May 22, 2021
10aeb05
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into spec…
eggerdj May 30, 2021
c7de4c1
* Added pulse parameters as kwargs.
eggerdj May 30, 2021
cb8e5e2
* Drive channel.
eggerdj May 30, 2021
3f43c74
* Renamed Spectroscopy to QubitSpectroscopy.
eggerdj May 30, 2021
717dce0
* Outsourced the getting of a data processor to a class method in Dat…
eggerdj May 30, 2021
d5b1128
* Fixed the docs.
eggerdj May 30, 2021
37a6d5c
* Obtaining dt from the backend.
eggerdj May 30, 2021
0ca6ce1
Merge branch 'main' into spectroscopy
eggerdj May 30, 2021
b7521b6
Merge branch 'main' into spectroscopy
eggerdj May 31, 2021
75e4063
* Moved pulse options into experiment options and out of run options.
eggerdj May 31, 2021
4409442
Merge branch 'spectroscopy' of github.com:eggerdj/qiskit-experiments …
eggerdj May 31, 2021
9b14031
* Started a data processor library.
eggerdj Jun 1, 2021
42b5cc6
* Added test for the average on spectroscopy.
eggerdj Jun 1, 2021
c83a2fc
Merge branch 'main' into spectroscopy
eggerdj Jun 1, 2021
026b71d
Update qiskit_experiments/characterization/qubit_spectroscopy.py
eggerdj Jun 1, 2021
dd23d10
* removed np.random.seed(0).
eggerdj Jun 2, 2021
1b5641b
Merge branch 'main' into spectroscopy
eggerdj Jun 2, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions qiskit_experiments/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

T1Experiment
T2StarExperiment
QubitSpectroscopy


Analysis
Expand All @@ -34,6 +35,8 @@

T1Analysis
T2StarAnalysis
SpectroscopyAnalysis
"""
from .t1_experiment import T1Experiment, T1Analysis
from .qubit_spectroscopy import QubitSpectroscopy, SpectroscopyAnalysis
from .t2star_experiment import T2StarExperiment, T2StarAnalysis
455 changes: 455 additions & 0 deletions qiskit_experiments/characterization/qubit_spectroscopy.py

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions qiskit_experiments/data_processing/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,18 @@ def _process(
error_value = np.sqrt(
(error[idx][0] * np.cos(angle)) ** 2 + (error[idx][1] * np.sin(angle)) ** 2
)
processed_error.append(error_value)
processed_error.append(error_value / self.scales[idx])

return np.array(processed_data), processed_error
if len(processed_data) == 1:
if error is None:
return processed_data[0], None
else:
return processed_data[0], processed_error[0]

if error is None:
return np.array(processed_data), None
else:
return np.array(processed_data), np.array(processed_error)

def train(self, data: List[Any]):
"""Train the SVD on the given data.
Expand Down Expand Up @@ -396,11 +405,11 @@ def _process(self, datum: Dict[str, Any], error: Optional[Dict] = None) -> Tuple
adding the corresponding probabilities.

Returns:
processed data: A dict with the populations.
processed data: A dict with the populations and standard deviation.
"""

shots = sum(datum.values())
p_mean = datum.get(self._outcome, 0.0) / shots
p_var = p_mean * (1 - p_mean) / shots

return p_mean, p_var
return p_mean, np.sqrt(p_var)
46 changes: 46 additions & 0 deletions qiskit_experiments/data_processing/processor_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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.

"""A collection of functions that return various data processors."""

from qiskit.qobj.utils import MeasLevel

from qiskit_experiments.data_processing.exceptions import DataProcessorError
from qiskit_experiments.data_processing.data_processor import DataProcessor
from qiskit_experiments.data_processing.nodes import AverageData, Probability, SVD


def get_to_signal_processor(
meas_level: MeasLevel = MeasLevel.CLASSIFIED, meas_return: str = "avg"
) -> DataProcessor:
"""Get a DataProcessor that produces a continuous signal given the options.

Args:
meas_level: The measurement level of the data to process.
meas_return: The measurement return (single or avg) of the data to process.

Returns:
An instance of DataProcessor capable of dealing with the given options.

Raises:
DataProcessorError: if the measurement level is not supported.
"""
if meas_level == MeasLevel.CLASSIFIED:
return DataProcessor("counts", [Probability("1")])

if meas_level == MeasLevel.KERNELED:
if meas_return == "single":
return DataProcessor("memory", [AverageData(), SVD()])
else:
return DataProcessor("memory", [SVD()])

raise DataProcessorError(f"Unsupported measurement level {meas_level}.")
15 changes: 15 additions & 0 deletions qiskit_experiments/test/__init__.py
Original file line number Diff line number Diff line change
@@ -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.

"""
Test tools for experiment.
"""
95 changes: 95 additions & 0 deletions qiskit_experiments/test/mock_iq_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# 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.

"""An mock IQ backend for testing."""

from typing import Dict, List, Tuple
import numpy as np

from qiskit.providers.backend import BackendV1 as Backend
from qiskit.providers import JobV1
from qiskit.providers.models import QasmBackendConfiguration
from qiskit.result import Result


class TestJob(JobV1):
"""Job for testing."""

def __init__(self, backend: Backend, result: Dict):
"""Setup a job for testing."""
super().__init__(backend, "test-id")
self._result = result

def result(self) -> Result:
"""Return a result."""
return Result.from_dict(self._result)

def submit(self):
pass

def status(self):
pass

def cancel(self):
pass


class IQTestBackend(Backend):
"""An abstract backend for testing that can mock IQ data."""

__configuration__ = {
"backend_name": "simulator",
"backend_version": "0",
"n_qubits": int(1),
"basis_gates": [],
"gates": [],
"local": True,
"simulator": True,
"conditional": False,
"open_pulse": False,
"memory": True,
"max_shots": int(1e6),
"coupling_map": [],
"dt": 0.1,
}

def __init__(
self,
iq_cluster_centers: Tuple[float, float, float, float] = (1.0, 1.0, -1.0, -1.0),
iq_cluster_width: float = 1.0,
):
"""
Initialize the backend.
"""
self._iq_cluster_centers = iq_cluster_centers
self._iq_cluster_width = iq_cluster_width

self._rng = np.random.default_rng(0)

super().__init__(QasmBackendConfiguration(**self.__configuration__))

def _default_options(self):
"""Default options of the test backend."""

def _draw_iq_shot(self, prob) -> List[List[float]]:
"""Produce an IQ shot."""

rand_i = self._rng.normal(0, self._iq_cluster_width)
rand_q = self._rng.normal(0, self._iq_cluster_width)

if self._rng.binomial(1, prob) > 0.5:
return [[self._iq_cluster_centers[0] + rand_i, self._iq_cluster_centers[1] + rand_q]]
else:
return [[self._iq_cluster_centers[2] + rand_i, self._iq_cluster_centers[3] + rand_q]]

def run(self, run_input, **options) -> TestJob:
"""Subclasses will need to override this."""
6 changes: 3 additions & 3 deletions test/data_processing/test_data_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def test_populations(self):
new_data, error = processor(self.exp_data_lvl2.data(0))

self.assertEqual(new_data, 0.4)
self.assertEqual(error, 0.4 * (1 - 0.4) / 10)
self.assertEqual(error, np.sqrt(0.4 * (1 - 0.4) / 10))

def test_validation(self):
"""Test the validation mechanism."""
Expand Down Expand Up @@ -377,10 +377,10 @@ def test_averaging_and_svd(self):
# Test the x90p rotation
processed, error = processor(self.data.data(2))
self.assertTrue(np.allclose(processed, np.array([0, 0])))
self.assertTrue(np.allclose(error, np.array([0.5, 0.5])))
self.assertTrue(np.allclose(error, np.array([0.25, 0.25])))

# Test the x45p rotation
processed, error = processor(self.data.data(3))
expected_std = np.array([np.std([1, 1, 1, -1, 1, 1, 1, -1]) / np.sqrt(8.0)] * 2)
expected_std = np.array([np.std([1, 1, 1, -1, 1, 1, 1, -1]) / np.sqrt(8.0) / 2] * 2)
self.assertTrue(np.allclose(processed, np.array([0.5, -0.5]) / np.sqrt(2.0)))
self.assertTrue(np.allclose(error, expected_std))
149 changes: 149 additions & 0 deletions test/test_qubit_spectroscopy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# 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.

"""Spectroscopy tests."""

from typing import Tuple

import numpy as np
from qiskit.qobj.utils import MeasLevel
from qiskit.test import QiskitTestCase

from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy
from qiskit_experiments.test.mock_iq_backend import TestJob, IQTestBackend


class SpectroscopyBackend(IQTestBackend):
"""
A simple and primitive backend to test spectroscopy experiments.
"""

def __init__(
self,
line_width: float = 2e6,
freq_offset: float = 0.0,
iq_cluster_centers: Tuple[float, float, float, float] = (1.0, 1.0, -1.0, -1.0),
iq_cluster_width: float = 0.2,
):
"""Initialize the spectroscopy backend."""

self.__configuration__["basis_gates"] = ["spec"]

self._linewidth = line_width
self._freq_offset = freq_offset

super().__init__(iq_cluster_centers, iq_cluster_width)

# pylint: disable = arguments-differ
def run(
self, circuits, shots=1024, meas_level=MeasLevel.KERNELED, meas_return="single", **options
):
"""Run the spectroscopy backend."""

result = {
"backend_name": "spectroscopy backend",
"backend_version": "0",
"qobj_id": 0,
"job_id": 0,
"success": True,
"results": [],
}

for circ in circuits:

run_result = {
"shots": shots,
"success": True,
"header": {"metadata": circ.metadata},
}

set_freq = float(circ.data[0][0].params[0])
delta_freq = set_freq - self._freq_offset
prob = np.exp(-(delta_freq ** 2) / (2 * self._linewidth ** 2))

if meas_level == MeasLevel.CLASSIFIED:
counts = {"1": 0, "0": 0}

for _ in range(shots):
counts[str(self._rng.binomial(1, prob))] += 1

run_result["data"] = {"counts": counts}
else:
memory = [self._draw_iq_shot(prob) for _ in range(shots)]

if meas_return == "avg":
memory = np.average(np.array(memory), axis=0).tolist()

run_result["data"] = {"memory": memory}

result["results"].append(run_result)

return TestJob(self, result)


class TestQubitSpectroscopy(QiskitTestCase):
"""Test spectroscopy experiment."""

def test_spectroscopy_end2end_classified(self):
"""End to end test of the spectroscopy experiment."""

backend = SpectroscopyBackend(line_width=2e6)

spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz")
spec.set_run_options(meas_level=MeasLevel.CLASSIFIED)
result = spec.run(backend).analysis_result(0)

self.assertTrue(abs(result["value"]) < 1e6)
self.assertTrue(result["success"])
self.assertEqual(result["quality"], "computer_good")

# Test if we find still find the peak when it is shifted by 5 MHz.
backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6)

spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz")
spec.set_run_options(meas_level=MeasLevel.CLASSIFIED)
result = spec.run(backend).analysis_result(0)

self.assertTrue(result["value"] < 5.1e6)
self.assertTrue(result["value"] > 4.9e6)
self.assertEqual(result["quality"], "computer_good")

def test_spectroscopy_end2end_kerneled(self):
"""End to end test of the spectroscopy experiment on IQ data."""

backend = SpectroscopyBackend(line_width=2e6)

spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz")
result = spec.run(backend).analysis_result(0)

self.assertTrue(abs(result["value"]) < 1e6)
self.assertTrue(result["success"])
self.assertEqual(result["quality"], "computer_good")

# Test if we find still find the peak when it is shifted by 5 MHz.
backend = SpectroscopyBackend(line_width=2e6, freq_offset=5.0e6)

spec = QubitSpectroscopy(3, np.linspace(-10.0, 10.0, 21), unit="MHz")
result = spec.run(backend).analysis_result(0)

self.assertTrue(result["value"] < 5.1e6)
self.assertTrue(result["value"] > 4.9e6)
self.assertEqual(result["quality"], "computer_good")
self.assertTrue(result["ydata_err"] is not None)

spec.set_run_options(meas_return="avg")
result = spec.run(backend).analysis_result(0)

self.assertTrue(result["value"] < 5.1e6)
self.assertTrue(result["value"] > 4.9e6)
self.assertEqual(result["quality"], "computer_good")
self.assertTrue(result["ydata_err"] is None)