Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fast transpilation method to BaseExperiment #1459

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,13 @@
from qiskit import QuantumCircuit
from qiskit.providers.options import Options
from qiskit.pulse import ScheduleBlock
from qiskit.transpiler import StagedPassManager, PassManager, Layout, CouplingMap
from qiskit.transpiler.passes import (
EnlargeWithAncilla,
FullAncillaAllocation,
ApplyLayout,
SetLayout,
)

from qiskit_experiments.calibration_management.calibrations import Calibrations
from qiskit_experiments.calibration_management.update_library import BaseUpdater
from qiskit_experiments.framework.base_analysis import BaseAnalysis
from qiskit_experiments.framework.base_experiment import BaseExperiment
from qiskit_experiments.framework.experiment_data import ExperimentData
from qiskit_experiments.framework.transpilation import map_qubits, minimal_transpile
from qiskit_experiments.exceptions import CalibrationError

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -198,20 +192,6 @@ def _default_experiment_options(cls) -> Options:
options.update_options(result_index=-1, group="default")
return options

@classmethod
def _default_transpile_options(cls) -> Options:
"""Return empty default transpile options as optimization_level is not used."""
return Options()

def set_transpile_options(self, **fields):
r"""Add a warning message.

.. note::
If your experiment has overridden `_transpiled_circuits` and needs
transpile options then please also override `set_transpile_options`.
"""
warnings.warn(f"Transpile options are not used in {self.__class__.__name__ }.")

def update_calibrations(self, experiment_data: ExperimentData):
"""Update parameter values in the :class:`.Calibrations` instance.

Expand Down Expand Up @@ -295,42 +275,13 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]:
Returns:
A list of transpiled circuits.
"""
transpiled = []
for circ in self.circuits():
circ = self._map_to_physical_qubits(circ)
circuits = [map_qubits(c, self.physical_qubits) for c in self.circuits()]
for circ in circuits:
self._attach_calibrations(circ)

transpiled.append(circ)
transpiled = minimal_transpile(circuits, self.backend, self.transpile_options)

return transpiled

def _map_to_physical_qubits(self, circuit: QuantumCircuit) -> QuantumCircuit:
"""Map program qubits to physical qubits.

Args:
circuit: The quantum circuit to map to device qubits.

Returns:
A quantum circuit that has the same number of qubits as the backend and where
the physical qubits of the experiment have been properly mapped.
"""
initial_layout = Layout.from_intlist(list(self.physical_qubits), *circuit.qregs)

coupling_map = self._backend_data.coupling_map
if coupling_map is not None:
coupling_map = CouplingMap(self._backend_data.coupling_map)

layout = PassManager(
[
SetLayout(initial_layout),
FullAncillaAllocation(coupling_map),
EnlargeWithAncilla(),
ApplyLayout(),
]
)

return StagedPassManager(["layout"], layout=layout).run(circuit)

@abstractmethod
def _attach_calibrations(self, circuit: QuantumCircuit):
"""Attach the calibrations to the quantum circuit.
Expand Down
43 changes: 36 additions & 7 deletions qiskit_experiments/framework/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@
Base Experiment class.
"""

from abc import ABC, abstractmethod
import copy
from abc import ABC, abstractmethod
from collections import OrderedDict
from typing import Sequence, Optional, Tuple, List, Dict, Union

from qiskit import transpile, QuantumCircuit
from qiskit import QuantumCircuit
from qiskit.providers import Job, Backend
from qiskit.exceptions import QiskitError
from qiskit.qobj.utils import MeasLevel
from qiskit.providers.options import Options
from qiskit_experiments.framework import BackendData
from qiskit_experiments.framework.store_init_args import StoreInitArgs
from qiskit_experiments.framework.transpilation import (
DEFAULT_TRANSPILE_OPTIONS,
map_qubits,
minimal_transpile,
)
from qiskit_experiments.framework.base_analysis import BaseAnalysis
from qiskit_experiments.framework.experiment_data import ExperimentData
from qiskit_experiments.framework.configs import ExperimentConfig
Expand Down Expand Up @@ -373,9 +378,8 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]:

This function can be overridden to define custom transpilation.
"""
transpile_opts = copy.copy(self.transpile_options.__dict__)
transpile_opts["initial_layout"] = list(self.physical_qubits)
transpiled = transpile(self.circuits(), self.backend, **transpile_opts)
circuits = [map_qubits(c, self.physical_qubits) for c in self.circuits()]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you don't take actual qubit size from the backend when available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because I had never seen it matter that the circuit size matched the backend. We can set it if it matters (for qasm3?).

transpiled = minimal_transpile(circuits, self.backend, self.transpile_options)

return transpiled

Expand Down Expand Up @@ -418,11 +422,36 @@ def set_experiment_options(self, **fields):

@classmethod
def _default_transpile_options(cls) -> Options:
"""Default transpiler options for transpilation of circuits"""
"""Default transpiler options for transpilation of circuits

Transpile Options:
optimization_level (int): Optimization level to pass to
:func:`qiskit.transpile`.
num_processes (int): Number of processes to use during
transpilation on Qiskit >= 1.0.
full_transpile (bool): If ``True``,
``BaseExperiment._transpiled_circuits`` (called by
:meth:`BaseExperiment.run` if not overridden by a subclass)
will call :func:`qiskit.transpile` on the output of
:meth:`BaseExperiment.circuits` before executing the circuits.
If ``False``, ``BaseExperiment._transpiled_circuits`` will
reindex the qubits in the output of
:meth:`BaseExperiment.circuits` using the experiments'
:meth:`BaseExperiment.physical_qubits`. Then it will check if
the circuit operations are all defined in the
:class:`qiskit.transpiler.Target` of the experiment's backend
or in the indiivdual circuit calibrations. If not, it will use
:class:`qiskit.transpiler.passes.BasisTranslator` to map the
circuit instructions to the backend. Additionally,
the :class:`qiskit.transpiler.passes.PulseGates` transpiler
pass will be run if the :class:`qiskit.transpiler.Target`
contains any custom pulse gate calibrations.

"""
# Experiment subclasses can override this method if they need
# to set specific default transpiler options to transpile the
# experiment circuits.
return Options(optimization_level=0)
return copy.copy(DEFAULT_TRANSPILE_OPTIONS)

@property
def transpile_options(self) -> Options:
Expand Down
9 changes: 4 additions & 5 deletions qiskit_experiments/framework/experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,11 +866,10 @@ def _add_job_data(
LOG.warning("Job was cancelled before completion [Job ID: %s]", jid)
return jid, False
if status == JobStatus.ERROR:
LOG.error(
"Job data not added for errored job [Job ID: %s]\nError message: %s",
jid,
job.error_message(),
)
msg = f"Job data not added for errored job [Job ID: {jid}]"
if hasattr(job, "error_message"):
msg += f"\nError message: {job.error_message()}"
LOG.error(msg)
return jid, False
LOG.warning("Adding data from job failed [Job ID: %s]", job.job_id())
raise ex
Expand Down
Loading