Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion qiskit_experiments/characterization/qubit_spectroscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def _default_options(cls):
sigma_bounds=None,
freq_bounds=None,
offset_bounds=(-2, 2),
dimensionality_reduction="SVD",
)

# pylint: disable=arguments-differ, unused-argument
Expand All @@ -98,6 +99,7 @@ def _run_analysis(
offset_bounds: Tuple[float, float] = (-2, 2),
plot: bool = True,
ax: Optional["AxesSubplot"] = None,
dimensionality_reduction: str = "SVD",
**kwargs,
) -> Tuple[AnalysisResult, None]:
"""Analyze the given data by fitting it to a Gaussian.
Expand Down Expand Up @@ -145,7 +147,11 @@ def _run_analysis(

# Pick a data processor.
if data_processor is None:
data_processor = get_to_signal_processor(meas_level=meas_level, meas_return=meas_return)
data_processor = get_to_signal_processor(
meas_level=meas_level,
meas_return=meas_return,
dimensionality_reduction=dimensionality_reduction
)
data_processor.train(experiment_data.data())

y_sigmas = np.array([data_processor(datum) for datum in experiment_data.data()])
Expand Down
110 changes: 110 additions & 0 deletions qiskit_experiments/characterization/resonator_spectroscopy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# 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 experiment class for resonators."""

from typing import Optional

from qiskit import QuantumCircuit
from qiskit.circuit import Gate, Parameter
from qiskit.exceptions import QiskitError
from qiskit.providers import Backend
import qiskit.pulse as pulse
from qiskit.providers.options import Options
from qiskit.pulse.transforms import block_to_schedule

from qiskit_experiments.characterization.qubit_spectroscopy import QubitSpectroscopy, SpectroscopyAnalysis


class ResonatorSpectroscopyAnalysis(SpectroscopyAnalysis):
"""Class to analysis resonator spectroscopy."""

@classmethod
def _default_options(cls):
options = super()._default_options()
options.dimensionality_reduction="ToAbs"
return options


class ResonatorSpectroscopy(QubitSpectroscopy):
"""Perform spectroscopy on the readout resonator."""

__analysis_class__ = ResonatorSpectroscopyAnalysis

@classmethod
def _default_experiment_options(cls) -> Options:
"""Default option values used for the spectroscopy pulse."""
return Options(
amp=0.1,
duration=2688,
sigma=64,
width=2432,
)

def circuits(self, backend: Optional[Backend] = None):
"""Create the circuit for the spectroscopy experiment.

The circuits are based on a GaussianSquare pulse and a frequency_shift instruction
encapsulated in a gate.

Args:
backend: A backend object.

Returns:
circuits: The circuits that will run the spectroscopy experiment.

Raises:
QiskitError:
- If relative frequencies are used but no backend was given.
- If the backend configuration does not define dt.
"""
if not backend and not self._absolute:
raise QiskitError("Cannot run spectroscopy relative to resonator without a backend.")

# Create a template circuit
freq_param = Parameter("frequency")
duration = self.experiment_options.duration
qubit = self.physical_qubits[0]

# TODO get this to work with circuits
circuits = []
for freq in self._frequencies:
with pulse.build(backend=backend, name="spectroscopy") as circuit:
pulse.set_frequency(freq, pulse.measure_channel(qubit))
pulse.play(
pulse.GaussianSquare(duration, 0.1, 64, duration - 4 * 64),
pulse.measure_channel(qubit)
)
pulse.acquire(duration, qubit, pulse.MemorySlot(qubit))

circuit.metadata = {
"experiment_type": self._type,
"qubit": self.physical_qubits[0],
"xval": freq,
"unit": "Hz",
"amplitude": self.experiment_options.amp,
"duration": self.experiment_options.duration,
"sigma": self.experiment_options.sigma,
"width": self.experiment_options.width,
#"schedule": str(circuit),
"meas_level": self.run_options.meas_level,
"meas_return": self.run_options.meas_return,
}

try:
circuit.metadata["dt"] = getattr(backend.configuration(), "dt")
except AttributeError as no_dt:
raise QiskitError("Dt parameter is missing in backend configuration") from no_dt

circuits.append(block_to_schedule(circuit))

return circuits
31 changes: 31 additions & 0 deletions qiskit_experiments/data_processing/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,37 @@ def _process(self, datum: np.array, error: Optional[np.array] = None) -> np.arra
return datum[..., 1] * self.scale, None


class ToAbs(IQPart):
"""IQ data post-processing. Take the absolute of the IQ point."""

def _process(self, datum: np.array, error: Optional[np.array] = None) -> np.array:
"""Take the imaginary part of the IQ data.

Args:
datum: A 2D or 3D array of shots, qubits, and a complex IQ point as [real, imaginary].
error: An optional 2D or 3D array of shots, qubits, and an error on a complex IQ point
as [real, imaginary].

Returns:
A 1D or 2D array, each entry is the absolute value of the given IQ data and error.
"""
if error is not None:
data = np.sqrt(datum[..., 0] ** 2 + datum[..., 1] ** 2) * self.scale
error = np.sqrt(error[..., 0] ** 2 + error[..., 1] ** 2) * self.scale

if len(data) == 1:
data, error = data[0], error[0]

return data, error
else:
data = np.sqrt(datum[..., 0] ** 2 + datum[..., 1] ** 2) * self.scale

if len(data) == 1:
data = data[0]

return data, None


class Probability(DataAction):
"""Count data post processing. This returns the probabilities of the outcome string
used to initialize an instance of Probability."""
Expand Down
19 changes: 15 additions & 4 deletions qiskit_experiments/data_processing/processor_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,42 @@

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
from qiskit_experiments.data_processing.nodes import AverageData, Probability, SVD, ToAbs


def get_to_signal_processor(
meas_level: MeasLevel = MeasLevel.CLASSIFIED, meas_return: str = "avg"
meas_level: MeasLevel = MeasLevel.CLASSIFIED,
meas_return: str = "avg",
dimensionality_reduction = "SVD",
) -> 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.
dimensionality_reduction: The method to use to reduce the dimensionality of the data.

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

Raises:
DataProcessorError: if the measurement level is not supported.
"""
projectors = {
"SVD": SVD,
"ToAbs": ToAbs,
}

if meas_level == MeasLevel.CLASSIFIED:
return DataProcessor("counts", [Probability("1")])

if meas_level == MeasLevel.KERNELED:

projector = projectors[dimensionality_reduction]

if meas_return == "single":
return DataProcessor("memory", [AverageData(), SVD()])
return DataProcessor("memory", [AverageData(), projector()])
else:
return DataProcessor("memory", [SVD()])
return DataProcessor("memory", [projector()])

raise DataProcessorError(f"Unsupported measurement level {meas_level}.")