Skip to content

Commit

Permalink
Update pulse instruction interface to the new pulse model (#11726)
Browse files Browse the repository at this point in the history
* Update instruction interface

* Some more test updates

* Qubit ref fix

* Hopefully avoid circular references.

* Corrections after review

* Change FrameInstruction to FrameUpdate

* update to reflect qiskit.test removal

* Test update
  • Loading branch information
TsafrirA committed Feb 12, 2024
1 parent 8edc216 commit 8fb185b
Show file tree
Hide file tree
Showing 45 changed files with 1,976 additions and 1,204 deletions.
4 changes: 3 additions & 1 deletion qiskit/assembler/assemble_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ def _assemble_instructions(

if not is_backend_supported:
instruction = instructions.Play(
instruction.pulse.get_waveform(), instruction.channel, name=instruction.name
instruction.pulse.get_waveform(),
channel=instruction.channel,
name=instruction.name,
)

if isinstance(instruction.pulse, library.Waveform):
Expand Down
16 changes: 8 additions & 8 deletions qiskit/pulse/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,7 @@ def delay(duration: int, channel: chans.Channel, name: str | None = None):
channel: Channel to delay on.
name: Name of the instruction.
"""
append_instruction(instructions.Delay(duration, channel, name=name))
append_instruction(instructions.Delay(duration, channel=channel, name=name))


def play(pulse: library.Pulse | np.ndarray, channel: chans.PulseChannel, name: str | None = None):
Expand All @@ -1523,7 +1523,7 @@ def play(pulse: library.Pulse | np.ndarray, channel: chans.PulseChannel, name: s
if not isinstance(pulse, library.Pulse):
pulse = library.Waveform(pulse)

append_instruction(instructions.Play(pulse, channel, name=name))
append_instruction(instructions.Play(pulse, channel=channel, name=name))


class _MetaDataType(TypedDict, total=False):
Expand Down Expand Up @@ -1577,11 +1577,11 @@ def acquire(

if isinstance(register, chans.MemorySlot):
append_instruction(
instructions.Acquire(duration, qubit_or_channel, mem_slot=register, **metadata)
instructions.Acquire(duration, channel=qubit_or_channel, mem_slot=register, **metadata)
)
elif isinstance(register, chans.RegisterSlot):
append_instruction(
instructions.Acquire(duration, qubit_or_channel, reg_slot=register, **metadata)
instructions.Acquire(duration, channel=qubit_or_channel, reg_slot=register, **metadata)
)
else:
raise exceptions.PulseError(f'Register of type: "{type(register)}" is not supported')
Expand All @@ -1606,7 +1606,7 @@ def set_frequency(frequency: float, channel: chans.PulseChannel, name: str | Non
channel: Channel to set frequency of.
name: Name of the instruction.
"""
append_instruction(instructions.SetFrequency(frequency, channel, name=name))
append_instruction(instructions.SetFrequency(frequency, channel=channel, name=name))


def shift_frequency(frequency: float, channel: chans.PulseChannel, name: str | None = None):
Expand All @@ -1629,7 +1629,7 @@ def shift_frequency(frequency: float, channel: chans.PulseChannel, name: str | N
channel: Channel to shift frequency of.
name: Name of the instruction.
"""
append_instruction(instructions.ShiftFrequency(frequency, channel, name=name))
append_instruction(instructions.ShiftFrequency(frequency, channel=channel, name=name))


def set_phase(phase: float, channel: chans.PulseChannel, name: str | None = None):
Expand All @@ -1654,7 +1654,7 @@ def set_phase(phase: float, channel: chans.PulseChannel, name: str | None = None
channel: Channel to set phase of.
name: Name of the instruction.
"""
append_instruction(instructions.SetPhase(phase, channel, name=name))
append_instruction(instructions.SetPhase(phase, channel=channel, name=name))


def shift_phase(phase: float, channel: chans.PulseChannel, name: str | None = None):
Expand All @@ -1678,7 +1678,7 @@ def shift_phase(phase: float, channel: chans.PulseChannel, name: str | None = No
channel: Channel to shift phase of.
name: Name of the instruction.
"""
append_instruction(instructions.ShiftPhase(phase, channel, name))
append_instruction(instructions.ShiftPhase(phase, channel=channel, name=name))


def snapshot(label: str, snapshot_type: str = "statevector"):
Expand Down
66 changes: 42 additions & 24 deletions qiskit/pulse/instructions/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
from qiskit.pulse.configuration import Kernel, Discriminator
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.instructions.instruction import Instruction
from qiskit.pulse.model import Qubit
from qiskit.pulse import model


class Acquire(Instruction):
"""The Acquire instruction is used to trigger the ADC associated with a particular qubit;
e.g. instantiated with AcquireChannel(0), the Acquire command will trigger data collection
for the channel associated with qubit 0 readout. This instruction also provides acquisition
metadata:
"""The Acquire instruction is used to trigger the ADC associated with a particular qubit.
This instruction also provides acquisition metadata:
* the number of cycles during which to acquire (in terms of dt),
Expand All @@ -41,7 +41,9 @@ class Acquire(Instruction):
def __init__(
self,
duration: int | ParameterExpression,
channel: AcquireChannel,
*,
qubit: model.Qubit | None = None,
channel: AcquireChannel | None = None,
mem_slot: MemorySlot | None = None,
reg_slot: RegisterSlot | None = None,
kernel: Kernel | None = None,
Expand All @@ -50,8 +52,12 @@ def __init__(
):
"""Create a new Acquire instruction.
The instruction can be instantiated either with respect to :class:`~.qiskit.pulse.Qubit`
logical element, or :class:`AcquireChannel`.
Args:
duration: Length of time to acquire data in terms of dt.
qubit: The qubit that will be acquired.
channel: The channel that will acquire data.
mem_slot: The classical memory slot in which to store the classified readout result.
reg_slot: The fast-access register slot in which to store the classified readout
Expand All @@ -60,38 +66,50 @@ def __init__(
discriminator: A ``Discriminator`` for discriminating kerneled IQ data into 0/1
results.
name: Name of the instruction for display purposes.
"""
super().__init__(
operands=(duration, channel, mem_slot, reg_slot, kernel, discriminator),
name=name,
)

def _validate(self):
"""Called after initialization to validate instruction data.
Raises:
PulseError: If the input ``qubit`` is not type :class:`~.qiskit.pulse.Qubit`.
PulseError: If the input ``channel`` is not type :class:`AcquireChannel`.
PulseError: If the input ``mem_slot`` is not type :class:`MemorySlot`.
PulseError: If the input ``reg_slot`` is not type :class:`RegisterSlot`.
PulseError: When memory slot and register slot are both empty.
PulseError: When both or none of ``qubit`` and ``channel`` are provided.
"""
if not isinstance(self.channel, AcquireChannel):
raise PulseError(f"Expected an acquire channel, got {self.channel} instead.")
if qubit is not None and not isinstance(qubit, Qubit):
raise PulseError(f"Expected a qubit, got {qubit} instead.")

if channel is not None and not isinstance(channel, AcquireChannel):
raise PulseError(f"Expected an acquire channel, got {channel} instead.")

if (qubit is not None) + (channel is not None) != 1:
raise PulseError("Expected exactly one of qubit and channel.")

if self.mem_slot and not isinstance(self.mem_slot, MemorySlot):
raise PulseError(f"Expected a memory slot, got {self.mem_slot} instead.")
if mem_slot and not isinstance(mem_slot, MemorySlot):
raise PulseError(f"Expected a memory slot, got {mem_slot} instead.")

if self.reg_slot and not isinstance(self.reg_slot, RegisterSlot):
raise PulseError(f"Expected a register slot, got {self.reg_slot} instead.")
if reg_slot and not isinstance(reg_slot, RegisterSlot):
raise PulseError(f"Expected a register slot, got {reg_slot} instead.")

if self.mem_slot is None and self.reg_slot is None:
if mem_slot is None and reg_slot is None:
raise PulseError("Neither MemorySlots nor RegisterSlots were supplied.")

super().__init__(
operands=(duration, channel or qubit, mem_slot, reg_slot, kernel, discriminator),
name=name,
)

@property
def channel(self) -> AcquireChannel:
def channel(self) -> AcquireChannel | None:
"""Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is
scheduled on.
"""
if isinstance(self.operands[1], AcquireChannel):
return self.operands[1]
return None

@property
def qubit(self) -> AcquireChannel | model.Qubit:
"""Return the element acquired by this instruction."""
return self.operands[1]

@property
Expand All @@ -115,11 +133,11 @@ def discriminator(self) -> Discriminator:
return self._operands[5]

@property
def acquire(self) -> AcquireChannel:
def acquire(self) -> AcquireChannel | model.Qubit:
"""Acquire channel to acquire data. The ``AcquireChannel`` index maps trivially to
qubit index.
"""
return self.channel
return self.operands[1]

@property
def mem_slot(self) -> MemorySlot:
Expand All @@ -141,7 +159,7 @@ def __repr__(self) -> str:
return "{}({}{}{}{}{}{})".format(
self.__class__.__name__,
self.duration,
", " + str(self.channel),
", " + str(self.qubit),
", " + str(self.mem_slot) if self.mem_slot else "",
", " + str(self.reg_slot) if self.reg_slot else "",
", " + str(self.kernel) if self.kernel else "",
Expand Down
51 changes: 47 additions & 4 deletions qiskit/pulse/instructions/delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from qiskit.circuit import ParameterExpression
from qiskit.pulse.channels import Channel
from qiskit.pulse.instructions.instruction import Instruction
from qiskit.pulse.model import Frame, PulseTarget, MixedFrame
from qiskit.pulse.exceptions import PulseError


class Delay(Instruction):
Expand All @@ -37,26 +39,67 @@ class Delay(Instruction):
def __init__(
self,
duration: int | ParameterExpression,
channel: Channel,
*,
target: PulseTarget | None = None,
frame: Frame | None = None,
mixed_frame: MixedFrame | None = None,
channel: Channel | None = None,
name: str | None = None,
):
"""Create a new delay instruction.
No other instruction may be scheduled within a ``Delay``.
The delay can be set on a ``MixedFrame`` (=``Channel``) or a ``PulseTarget``. For the latter,
provide only the ``target`` argument, and the delay will be broadcasted to all ``MixedFrame``s
involving the ``PulseTarget``. For the former, provide exactly one of ``mixed_frame``,
``channel`` or the duo ``target`` and ``frame``, and the delay will apply only to the
specified ``MixedFrame``.
Args:
duration: Length of time of the delay in terms of dt.
target: The target that will have the delay.
frame: The frame which in conjunction with ``target`` defines the mixed frame that will
have the delay.
mixed_frame: The mixed_frame that will have the delay.
channel: The channel that will have the delay.
name: Name of the delay for display purposes.
Raises:
PulseError: If the combination of ``target``, ``frame``, ``mixed_frame`` and ``channel``
doesn't specify a unique ``MixedFrame`` or ``PulseTarget``.
PulseError: If the inputs to ``target``, ``frame``, ``mixed_frame`` and ``channel``
are not of the appropriate type.
"""
super().__init__(operands=(duration, channel), name=name)
if (channel is not None) + (mixed_frame is not None) + (target is not None) != 1:
raise PulseError("Exactly one of mixed_frame, channel or target must be provided")

if frame is not None:
if target is None:
raise PulseError("frame must be provided with target")
inst_target = MixedFrame(target, frame)
else:
inst_target = mixed_frame or channel or target

if not isinstance(inst_target, (Channel, MixedFrame, PulseTarget)):
raise PulseError(
f"Expected a MixedFrame, Channel or PulseTarget, got {inst_target} instead."
)

super().__init__(operands=(duration, inst_target), name=name)

@property
def channel(self) -> Channel:
def inst_target(self) -> MixedFrame | PulseTarget | Channel:
"""Return the object targeted by this delay instruction."""
return self.operands[1]

@property
def channel(self) -> Channel | None:
"""Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is
scheduled on.
"""
return self.operands[1]
if isinstance(self.operands[1], Channel):
return self.operands[1]
return None

@property
def channels(self) -> tuple[Channel]:
Expand Down
24 changes: 16 additions & 8 deletions qiskit/pulse/instructions/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from qiskit.pulse import channels as chans
from qiskit.pulse.instructions import instruction
from qiskit.pulse.exceptions import PulseError
from qiskit.pulse.channels import Channel


class Directive(instruction.Instruction, ABC):
Expand Down Expand Up @@ -45,7 +46,15 @@ def __init__(self, *channels: chans.Channel, name: str | None = None):
Args:
channels: The channel that the barrier applies to.
name: Name of the directive for display purposes.
Raises:
PulseError: If any of channels is not a Channel.
"""

for channel in channels:
if not isinstance(channel, Channel):
raise PulseError(f"Expected a channel, got {channel} instead.")

super().__init__(operands=tuple(channels), name=name)

@property
Expand Down Expand Up @@ -114,20 +123,19 @@ def __init__(
duration: Length of time of the occupation in terms of dt.
channel: The channel that will be the occupied.
name: Name of the time blockade for display purposes.
"""
super().__init__(operands=(duration, channel), name=name)

def _validate(self):
"""Called after initialization to validate instruction data.
Raises:
PulseError: If the input ``duration`` is not integer value.
PulseError: If ``duration`` is not a positive integer value.
"""
if not isinstance(self.duration, int):

if not isinstance(duration, int) or duration < 0:
raise PulseError(
"TimeBlockade duration cannot be parameterized. Specify an integer duration value."
"TimeBlockade duration cannot be parameterized. Specify a positive integer"
"duration value."
)

super().__init__(operands=(duration, channel), name=name)

@property
def channel(self) -> chans.Channel:
"""Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is
Expand Down

0 comments on commit 8fb185b

Please sign in to comment.