Skip to content
Merged
12 changes: 6 additions & 6 deletions qiskit_experiments/analysis/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ def plot_curve_fit(
ImportError: if matplotlib is not installed.
"""
if ax is None:
plt.figure()
ax = plt.gca()
figure = plt.figure()
ax = figure.subplots()

# Result data
popt = result["popt"]
Expand Down Expand Up @@ -132,8 +132,8 @@ def plot_scatter(
AxesSubPlot: the matplotlib axes containing the plot.
"""
if ax is None:
plt.figure()
ax = plt.gca()
figure = plt.figure()
ax = figure.subplots()

# Default plot options
plot_opts = kwargs.copy()
Expand Down Expand Up @@ -178,8 +178,8 @@ def plot_errorbar(
AxesSubPlot: the matplotlib axes containing the plot.
"""
if ax is None:
plt.figure()
ax = plt.gca()
figure = plt.figure()
ax = figure.subplots()

# Default plot options
plot_opts = kwargs.copy()
Expand Down
35 changes: 20 additions & 15 deletions qiskit_experiments/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

from abc import ABC, abstractmethod
from typing import List, Tuple

from qiskit.exceptions import QiskitError

Expand All @@ -26,16 +27,22 @@ class BaseAnalysis(ABC):
# Expected experiment data container for analysis
__experiment_data__ = ExperimentData

def run(self, experiment_data, save=True, return_figures=False, **options):
def run(
self,
experiment_data: ExperimentData,
save: bool = True,
return_figures: bool = False,
**options,
):
"""Run analysis and update stored ExperimentData with analysis result.

Args:
experiment_data (ExperimentData): the experiment data to analyze.
save (bool): if True save analysis results and figures to the
:class:`ExperimentData`.
return_figures (bool): if true return a pair of
``(analysis_results, figures)``,
otherwise return only analysis_results.
experiment_data: the experiment data to analyze.
save: if True save analysis results and figures to the
:class:`ExperimentData`.
return_figures: if true return a pair of
``(analysis_results, figures)``,
otherwise return only analysis_results.
options: kwarg options for analysis function.

Returns:
Expand All @@ -56,10 +63,6 @@ def run(self, experiment_data, save=True, return_figures=False, **options):
f"Invalid experiment data type, expected {self.__experiment_data__.__name__}"
f" but received {type(experiment_data).__name__}"
)

# Wait for experiment job to finish
# experiment_data.block_for_result()

# Run analysis
# pylint: disable=broad-except
try:
Expand All @@ -84,17 +87,19 @@ def run(self, experiment_data, save=True, return_figures=False, **options):
return analysis_results

@abstractmethod
def _run_analysis(self, experiment_data, **options):
def _run_analysis(
self, data: ExperimentData, **options
) -> Tuple[List[AnalysisResult], List["Figure"]]:
"""Run analysis on circuit data.

Args:
experiment_data (ExperimentData): the experiment data to analyze.
experiment_data: the experiment data to analyze.
options: kwarg options for analysis function.

Returns:
tuple: A pair ``(analysis_results, figures)`` where
``analysis_results`` may be a single or list of
AnalysisResult objects, and ``figures`` may be
None, a single figure, or a list of figures.
AnalysisResult objects, and ``figures`` is a list of any
figures for the experiment.
"""
pass
76 changes: 48 additions & 28 deletions qiskit_experiments/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
"""

from abc import ABC, abstractmethod
from typing import Union, Iterable, Optional, Tuple, List
from numbers import Integral

from qiskit import transpile, assemble
from qiskit.exceptions import QiskitError
from qiskit.providers.backend import Backend
from qiskit.providers.basebackend import BaseBackend as LegacyBackend

from .experiment_data import ExperimentData

Expand Down Expand Up @@ -64,15 +67,20 @@ class BaseExperiment(ABC):
# Custom default run (assemble) options for experiment subclasses
__run_defaults__ = {}

def __init__(self, qubits, experiment_type=None, circuit_options=None):
def __init__(
self,
qubits: Union[int, Iterable[int]],
experiment_type: Optional[str] = None,
circuit_options: Optional[Iterable[str]] = None,
):
"""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 (Iterable): Optional, list of kwarg names for
the subclassed `circuit` method.
qubits: the number of qubits or list of physical qubits
for the experiment.
experiment_type: Optional, the experiment type string.
circuit_options: Optional, list of kwarg names for
the subclassed `circuit` method.

Raises:
QiskitError: if qubits is a list and contains duplicates.
Expand All @@ -94,16 +102,21 @@ def __init__(self, qubits, experiment_type=None, circuit_options=None):
# Store options and values
self._circuit_options = set(circuit_options) if circuit_options else set()

def run(self, backend, experiment_data=None, **kwargs):
def run(
self,
backend: "Backend",
analysis: bool = True,
experiment_data: Optional[ExperimentData] = None,
**kwargs,
) -> ExperimentData:
"""Run an experiment and perform analysis.

Args:
backend (Backend): The backend to run the experiment on.
experiment_data (ExperimentData): Optional, add results to existing
experiment data. If None a new ExperimentData object will be
returned.
kwargs: keyword arguments for self.circuit,
qiskit.transpile, and backend.run.
backend: The backend to run the experiment on.
analysis: If True run analysis on experiment data.
experiment_data: Optional, add results to existing experiment data.
If None a new ExperimentData object will be returned.
kwargs: keyword arguments for self.circuit, qiskit.transpile, and backend.run.

Returns:
ExperimentData: the experiment data object.
Expand All @@ -112,7 +125,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__(self, backend=backend)

# Filter kwargs
run_options = self.__run_defaults__.copy()
Expand All @@ -125,32 +138,35 @@ def run(self, backend, experiment_data=None, **kwargs):

# Generate and run circuits
circuits = self.transpiled_circuits(backend, **circuit_options)
qobj = assemble(circuits, backend, **run_options)
job = backend.run(qobj)
if isinstance(backend, LegacyBackend):
qobj = assemble(circuits, backend=backend, **run_options)
job = backend.run(qobj)
else:
job = backend.run(circuits, **run_options)

# Add Job to ExperimentData
experiment_data.add_data(job)

# Queue analysis of data for when job is finished
if self.__analysis_class__ is not None:
if analysis and self.__analysis_class__ is not None:
# pylint: disable = not-callable
self.__analysis_class__().run(experiment_data, **kwargs)

# Return the ExperimentData future
return experiment_data

@property
def num_qubits(self):
def num_qubits(self) -> int:
"""Return the number of qubits for this experiment."""
return self._num_qubits

@property
def physical_qubits(self):
def physical_qubits(self) -> Tuple[int]:
"""Return the physical qubits for this experiment."""
return self._physical_qubits

@classmethod
def analysis(cls, **kwargs):
def analysis(cls, **kwargs) -> "BaseAnalysis":
"""Return the default Analysis class for the experiment."""
if cls.__analysis_class__ is None:
raise QiskitError(
Expand All @@ -160,15 +176,17 @@ def analysis(cls, **kwargs):
return cls.__analysis_class__(**kwargs)

@abstractmethod
def circuits(self, backend=None, **circuit_options):
def circuits(
self, backend: Optional[Backend] = None, **circuit_options
) -> List["QuantumCircuit"]:
"""Return a list of experiment circuits.

Args:
backend (Backend): Optional, a backend object.
backend: Optional, a backend object.
circuit_options: kwarg options for the function.

Returns:
List[QuantumCircuit]: A list of :class:`QuantumCircuit`s.
A list of :class:`QuantumCircuit`s.

.. note:
These circuits should be on qubits ``[0, .., N-1]`` for an
Expand All @@ -180,18 +198,20 @@ def circuits(self, backend=None, **circuit_options):
# This allows these options to have default values, and be
# documented in the methods docstring for the API docs.

def transpiled_circuits(self, backend=None, **kwargs):
def transpiled_circuits(
self, backend: Optional[Backend] = None, **kwargs
) -> List["QuantumCircuit"]:
"""Return a list of experiment circuits.

Args:
backend (Backend): Optional, a backend object to use as the
argument for the :func:`qiskit.transpile`
function.
backend: Optional, a backend object to use as the
argument for the :func:`qiskit.transpile`
function.
kwargs: kwarg options for the :meth:`circuits` method, and
:func:`qiskit.transpile` function.

Returns:
List[QuantumCircuit]: A list of :class:`QuantumCircuit`s.
A list of :class:`QuantumCircuit`s.

Raises:
QiskitError: if an initial layout is specified in the
Expand Down
Loading