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

Qiskit 2.0 Compatibility #2116

Merged
merged 27 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bbdd68c
Replace EfficientSU2
kt474 Jan 27, 2025
8e7dc98
Remove channel related methods
kt474 Jan 30, 2025
12b8181
Fix backend config
kt474 Jan 31, 2025
0c1eca3
Remove need of PulseDefaults
kt474 Feb 3, 2025
fcae41e
Merge branch 'main' into qiskit-1.3-removals
kt474 Feb 3, 2025
a648f3a
Add release note
kt474 Feb 4, 2025
d2f3f7f
Merge branch 'main' into qiskit-1.3-removals
ptristan3 Feb 13, 2025
39b3af3
Handle c_if and condition_bit removals
kt474 Feb 26, 2025
7cc033e
lint
kt474 Feb 26, 2025
b1fba11
Merge branch 'main' into qiskit-1.3-removals
kt474 Mar 4, 2025
20f897a
Merge branch 'main' into qiskit-1.3-removals
kt474 Mar 6, 2025
0132cfa
Merge branch 'main' into qiskit-1.3-removals
kt474 Mar 6, 2025
6018cea
more pulse imports
kt474 Mar 6, 2025
6cd65a9
Merge branch 'main' into handle-cif-condition-removals
kt474 Mar 6, 2025
4575fb1
add back controlflowop check
kt474 Mar 6, 2025
28c42b8
duration related removals
kt474 Mar 7, 2025
140b8cb
remove pulse test & fake circuit_runner
kt474 Mar 7, 2025
f86ebc7
Merge branch 'handle-cif-condition-removals' into qiskit-1.3-removals
kt474 Mar 11, 2025
0e73f99
qiskit.circuit.bit removal & lint
kt474 Mar 11, 2025
93864ed
minor docs updates
kt474 Mar 11, 2025
98aa43f
Update release note
kt474 Mar 11, 2025
09f01fd
address comments
kt474 Mar 12, 2025
9ae4fd0
add back & rename tests
kt474 Mar 12, 2025
be9646e
remove pulsebackendconfiguration
kt474 Mar 12, 2025
3e8d3ca
fix typing & update to_dict
kt474 Mar 12, 2025
18603fb
fix defaults.to_dict()
kt474 Mar 13, 2025
e9abae5
reformat test_scheduler
kt474 Mar 13, 2025
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ If you do not specify an instance, then the code will select one in the followin

## Access your IBM Quantum backends

A **backend** is a quantum device or simulator capable of running quantum circuits or pulse schedules.
A **backend** is a quantum device or simulator capable of running quantum circuits.

You can query for the backends you have access to. Attributes and methods of the returned instances
provide information, such as qubit counts, error rates, and statuses, of the backends.
Expand Down
163 changes: 6 additions & 157 deletions qiskit_ibm_runtime/fake_provider/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@
"""
import logging
import warnings
import collections
import json
import os
import re

from typing import List, Iterable, Union

from qiskit import circuit, QuantumCircuit
from qiskit import QuantumCircuit

from qiskit.providers import BackendV2
from qiskit import pulse
from qiskit.exceptions import QiskitError
from qiskit.utils import optionals as _optionals
from qiskit.transpiler import Target
from qiskit.providers import Options
Expand All @@ -50,7 +44,6 @@
PulseDefaults,
BackendStatus,
QasmBackendConfiguration,
PulseBackendConfiguration,
)
from ..models.exceptions import (
BackendPropertyError,
Expand Down Expand Up @@ -104,36 +97,6 @@ def __init__(self) -> None:
self._target = None
self.sim = None

if "channels" in self._conf_dict:
self._parse_channels(self._conf_dict["channels"])

def _parse_channels(self, channels: dict) -> None:
type_map = {
"acquire": pulse.AcquireChannel,
"drive": pulse.DriveChannel,
"measure": pulse.MeasureChannel,
"control": pulse.ControlChannel,
}
identifier_pattern = re.compile(r"\D+(?P<index>\d+)")

channels_map = { # type: ignore
"acquire": collections.defaultdict(list),
"drive": collections.defaultdict(list),
"measure": collections.defaultdict(list),
"control": collections.defaultdict(list),
}
for identifier, spec in channels.items():
channel_type = spec["type"]
out = re.match(identifier_pattern, identifier)
if out is None:
# Identifier is not a valid channel name format
continue
channel_index = int(out.groupdict()["index"])
qubit_index = tuple(spec["operates"]["qubits"])
chan_obj = type_map[channel_type](channel_index)
channels_map[channel_type][qubit_index].append(chan_obj)
setattr(self, "channels_map", channels_map)

def _setup_sim(self) -> None:
if _optionals.HAS_AER:
from qiskit_aer import AerSimulator # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -226,7 +189,7 @@ def defaults(self, refresh: bool = False) -> PulseDefaults:
return PulseDefaults.from_dict(self._defs_dict) # type: ignore[unreachable]
return None

def configuration(self) -> Union[QasmBackendConfiguration, PulseBackendConfiguration]:
def configuration(self) -> QasmBackendConfiguration:
"""Return the backend configuration."""
return BackendConfiguration.from_dict(self._conf_dict)

Expand Down Expand Up @@ -280,14 +243,10 @@ def target(self) -> Target:
props = None
if self._props_dict is not None:
props = BackendProperties.from_dict(self._props_dict) # type: ignore
defaults = None
if self._defs_dict is not None:
defaults = PulseDefaults.from_dict(self._defs_dict) # type: ignore

self._target = convert_to_target(
configuration=conf,
properties=props,
defaults=defaults,
# Fake backends use the simulator backend.
# This doesn't have the exclusive constraint.
include_control_flow=True,
Expand Down Expand Up @@ -339,89 +298,10 @@ def dtm(self) -> float:
else:
return None

@property
def meas_map(self) -> List[List[int]]:
"""Return the grouping of measurements which are multiplexed
This is required to be implemented if the backend supports Pulse
scheduling.

Returns:
The grouping of measurements which are multiplexed
"""
return self._conf_dict.get("meas_map")

def drive_channel(self, qubit: int): # type: ignore
"""Return the drive channel for the given qubit.

This is required to be implemented if the backend supports Pulse
scheduling.

Returns:
DriveChannel: The Qubit drive channel
"""
drive_channels_map = getattr(self, "channels_map", {}).get("drive", {})
qubits = (qubit,)
if qubits in drive_channels_map:
return drive_channels_map[qubits][0]
return None

def measure_channel(self, qubit: int): # type: ignore
"""Return the measure stimulus channel for the given qubit.

This is required to be implemented if the backend supports Pulse
scheduling.

Returns:
MeasureChannel: The Qubit measurement stimulus line
"""
measure_channels_map = getattr(self, "channels_map", {}).get("measure", {})
qubits = (qubit,)
if qubits in measure_channels_map:
return measure_channels_map[qubits][0]
return None

def acquire_channel(self, qubit: int): # type: ignore
"""Return the acquisition channel for the given qubit.

This is required to be implemented if the backend supports Pulse
scheduling.

Returns:
AcquireChannel: The Qubit measurement acquisition line.
"""
acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {})
qubits = (qubit,)
if qubits in acquire_channels_map:
return acquire_channels_map[qubits][0]
return None

def control_channel(self, qubits: Iterable[int]): # type: ignore
"""Return the secondary drive channel for the given qubit

This is typically utilized for controlling multiqubit interactions.
This channel is derived from other channels.

This is required to be implemented if the backend supports Pulse
scheduling.

Args:
qubits: Tuple or list of qubits of the form
``(control_qubit, target_qubit)``.

Returns:
List[ControlChannel]: The multi qubit control line.
"""
control_channels_map = getattr(self, "channels_map", {}).get("control", {})
qubits = tuple(qubits)
if qubits in control_channels_map:
return control_channels_map[qubits]
return []

def run(self, run_input, **options): # type: ignore
"""Run on the fake backend using a simulator.

This method runs circuit jobs (an individual or a list of QuantumCircuit
) and pulse jobs (an individual or a list of Schedule or ScheduleBlock)
This method runs circuit jobs (an individual or a list of QuantumCircuit)
using BasicSimulator or Aer simulator and returns a
:class:`~qiskit.providers.Job` object.

Expand All @@ -433,11 +313,9 @@ def run(self, run_input, **options): # type: ignore
FakeBackendV2.

Args:
run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An
run_input (QuantumCircuit or list): An
individual or a list of
:class:`~qiskit.circuit.QuantumCircuit`,
:class:`~qiskit.pulse.ScheduleBlock`, or
:class:`~qiskit.pulse.Schedule` objects to run on the backend.
:class:`~qiskit.circuit.QuantumCircuit`
options: Any kwarg options to pass to the backend for running the
config. If a key is also present in the options
attribute/object then the expectation is that the value
Expand All @@ -446,38 +324,11 @@ def run(self, run_input, **options): # type: ignore

Returns:
Job: The job object for the run

Raises:
QiskitError: If a pulse job is supplied and qiskit-aer is not installed.
"""
circuits = run_input
pulse_job = None
if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)):
pulse_job = True
elif isinstance(circuits, circuit.QuantumCircuit):
pulse_job = False
elif isinstance(circuits, list):
if circuits:
if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits):
pulse_job = True
elif all(isinstance(x, circuit.QuantumCircuit) for x in circuits):
pulse_job = False
if pulse_job is None: # submitted job is invalid
raise QiskitError(
"Invalid input object %s, must be either a "
"QuantumCircuit, Schedule, or a list of either" % circuits
)
if pulse_job: # pulse job
raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.")
# circuit job
if not _optionals.HAS_AER:
warnings.warn(
"Aer not found, using qiskit.BasicSimulator and no noise.", RuntimeWarning
)
if self.sim is None:
self._setup_sim()
self.sim._options = self._options
job = self.sim.run(circuits, **options)
job = self.sim.run(run_input, **options)
return job

def _get_noise_model_from_backend_v2( # type: ignore
Expand Down Expand Up @@ -625,12 +476,10 @@ def refresh(self, service: QiskitRuntimeService) -> None:

updated_configuration = BackendConfiguration.from_dict(self._conf_dict)
updated_properties = BackendProperties.from_dict(self._props_dict)
updated_defaults = PulseDefaults.from_dict(self._defs_dict)

self._target = convert_to_target(
configuration=updated_configuration,
properties=updated_properties,
defaults=updated_defaults,
include_control_flow=True,
include_fractional_gates=True,
)
Expand Down
61 changes: 6 additions & 55 deletions qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,14 @@
"""Module for interfacing with an IBM Quantum Backend."""

import logging
from typing import Iterable, Union, Optional, Any, List
from typing import Union, Optional, Any, List
from datetime import datetime as python_datetime
from copy import deepcopy
from packaging.version import Version

from qiskit import QuantumCircuit, __version__ as qiskit_version
from qiskit.providers.backend import BackendV2 as Backend
from qiskit.providers.options import Options
from qiskit.pulse.channels import (
AcquireChannel,
ControlChannel,
DriveChannel,
MeasureChannel,
)
from qiskit.transpiler.target import Target

from .models import (
Expand All @@ -35,7 +29,6 @@
PulseDefaults,
GateConfig,
QasmBackendConfiguration,
PulseBackendConfiguration,
)

from . import qiskit_runtime_service # pylint: disable=unused-import,cyclic-import
Expand All @@ -58,7 +51,7 @@
if Version(qiskit_version).major >= 2:
from qiskit.result import MeasLevel, MeasReturnType
else:
from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit.qobj.utils import MeasLevel, MeasReturnType # pylint: disable=import-error


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -164,7 +157,7 @@ class IBMBackend(Backend):

def __init__(
self,
configuration: Union[QasmBackendConfiguration, PulseBackendConfiguration],
configuration: QasmBackendConfiguration,
service: "qiskit_runtime_service.QiskitRuntimeService",
api_client: RuntimeClient,
instance: Optional[str] = None,
Expand Down Expand Up @@ -239,7 +232,6 @@ def _convert_to_target(self, refresh: bool = False) -> None:
self._target = convert_to_target(
configuration=self._configuration, # type: ignore[arg-type]
properties=self._properties,
defaults=self._defaults,
)

@classmethod
Expand Down Expand Up @@ -284,7 +276,7 @@ def dtm(self) -> float:
def max_circuits(self) -> int:
"""The maximum number of circuits

The maximum number of circuits (or Pulse schedules) that can be
The maximum number of circuits that can be
run in a single job. If there is no limit this will return None.
"""
return self._max_circuits
Expand Down Expand Up @@ -319,12 +311,10 @@ def target_history(self, datetime: Optional[python_datetime] = None) -> Target:
Returns:
Target with properties found on `datetime`
"""
self.defaults()

return convert_to_target(
configuration=self._configuration, # type: ignore[arg-type]
properties=self.properties(datetime=datetime), # pylint: disable=unexpected-keyword-arg
defaults=self._defaults,
)

def refresh(self) -> None:
Expand Down Expand Up @@ -443,7 +433,7 @@ def defaults(self, refresh: bool = False) -> Optional[PulseDefaults]:

def configuration(
self,
) -> Union[QasmBackendConfiguration, PulseBackendConfiguration]:
) -> QasmBackendConfiguration:
"""Return the backend configuration.

Backend configuration contains fixed information about the backend, such
Expand All @@ -467,45 +457,6 @@ def configuration(
"""
return self._configuration

def drive_channel(self, qubit: int) -> DriveChannel:
"""Return the drive channel for the given qubit.

Returns:
DriveChannel: The Qubit drive channel
"""
return self._configuration.drive(qubit=qubit)

def measure_channel(self, qubit: int) -> MeasureChannel:
"""Return the measure stimulus channel for the given qubit.

Returns:
MeasureChannel: The Qubit measurement stimulus line
"""
return self._configuration.measure(qubit=qubit)

def acquire_channel(self, qubit: int) -> AcquireChannel:
"""Return the acquisition channel for the given qubit.

Returns:
AcquireChannel: The Qubit measurement acquisition line.
"""
return self._configuration.acquire(qubit=qubit)

def control_channel(self, qubits: Iterable[int]) -> List[ControlChannel]:
"""Return the secondary drive channel for the given qubit

This is typically utilized for controlling multiqubit interactions.
This channel is derived from other channels.

Args:
qubits: Tuple or list of qubits of the form
``(control_qubit, target_qubit)``.

Returns:
List[ControlChannel]: The Qubit measurement acquisition line.
"""
return self._configuration.control(qubits=qubits)

def __repr__(self) -> str:
return "<{}('{}')>".format(self.__class__.__name__, self.name)

Expand Down Expand Up @@ -609,7 +560,7 @@ class IBMRetiredBackend(IBMBackend):

def __init__(
self,
configuration: Union[QasmBackendConfiguration, PulseBackendConfiguration],
configuration: QasmBackendConfiguration,
service: "qiskit_runtime_service.QiskitRuntimeService",
api_client: Optional[RuntimeClient] = None,
) -> None:
Expand Down
Loading