Skip to content

Commit

Permalink
Revert "Revert pulse model (#11348)"
Browse files Browse the repository at this point in the history
This reverts commit c0702bf.
  • Loading branch information
nkanazawa1989 committed Jan 18, 2024
1 parent 828ad4d commit 4aeb4c4
Show file tree
Hide file tree
Showing 9 changed files with 784 additions and 1 deletion.
15 changes: 15 additions & 0 deletions qiskit/pulse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
.. automodule:: qiskit.pulse.schedule
.. automodule:: qiskit.pulse.transforms
.. automodule:: qiskit.pulse.builder
.. automodule:: qiskit.pulse.model
.. currentmodule:: qiskit.pulse
Expand Down Expand Up @@ -156,3 +158,16 @@
)
from qiskit.pulse.library.samplers.decorators import functional_pulse
from qiskit.pulse.schedule import Schedule, ScheduleBlock

from qiskit.pulse.model import (
PulseTarget,
Port,
LogicalElement,
Qubit,
Coupler,
Frame,
GenericFrame,
QubitFrame,
MeasurementFrame,
MixedFrame,
)
102 changes: 102 additions & 0 deletions qiskit/pulse/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# 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.

r"""
==========================================================
Pulse Targets & Frames (:mod:`qiskit.pulse.model`)
==========================================================
Pulse is meant to be agnostic to the underlying hardware implementation, while still allowing
low-level control. Qiskit Pulse's pulse targets and frames create a flexible framework
to define where pulse instructions are applied, and what would be their carrier frequency and phase
(because typically AC pulses are used). Each :class:`.PulseTarget` represents a separate component
in the quantum computing system on which instructions could be applied. On the other hand,
each :class:`.Frame` represents a frequency and phase duo for the carrier of the pulse.
While :class:`.PulseTarget` includes a :class`.Port` variant allowing for direct control over
hardware ports, an abstraction layer is provided by :class:`.LogicalElement`.
The abstraction allows to write pulse level programs with less knowledge of the hardware, and in
a level which is more similar to the circuit level programing. i.e., instead of specifying
ports, one can use Qubits, Couplers, etc.
This logical and virtual representation allows the user to write template pulse
programs without worrying about the exact details of the hardware implementation
(are the pulses to be played via the same port? Which NCO is used?), while still
allowing for effective utilization of the quantum hardware. The burden of mapping
the different combinations of :class:`.LogicalElement` and :class:`.Frame`
to hardware aware objects is left to the Pulse Compiler.
.. _pulse_targets:
PulseTarget
================
:class:`.PulseTarget` includes :class:`.Port` who's objects are identified by a unique string identifier
defined by the control system, and :class:`.LogicalElement` who's objects are identified by their type
and index. Currently, the most prominent example of a :class:`.LogicalElement` is the
:class:`~.pulse.Qubit`.
.. autosummary::
:toctree: ../stubs/
Port
Qubit
Coupler
.. _frames:
Frame
=============
:class:`.Frame` s are identified by their type and unique identifier. A :class:`.GenericFrame` is used to
specify custom frequency
and phase duos, while :class:`.QubitFrame` and :class:`.MeasurementFrame` are used to indicate that
backend defaults are to be used (for the qubit's driving frequency and measurement frequency
respectively).
.. autosummary::
:toctree: ../stubs/
GenericFrame
QubitFrame
MeasurementFrame
.. _mixed_frames:
MixedFrame
=============
The combination of a :class:`.LogicalElement` and :class:`.Frame` is dubbed a :class:`.MixedFrame`.
.. autosummary::
:toctree: ../stubs/
MixedFrame
"""

from .pulse_target import (
PulseTarget,
Port,
LogicalElement,
Qubit,
Coupler,
)

from .frames import (
Frame,
GenericFrame,
QubitFrame,
MeasurementFrame,
)

from .mixed_frames import (
MixedFrame,
)
155 changes: 155 additions & 0 deletions qiskit/pulse/model/frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# 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.

"""
Frames
"""

from abc import ABC

import numpy as np

from qiskit.pulse.exceptions import PulseError


class Frame(ABC):
"""Base class for pulse module frame.
Because pulses used in Quantum hardware are typically AC pulses, the carrier frequency and phase
must be defined. The :class:`Frame` is the object which identifies the frequency and phase for
the carrier.
and each pulse and most other instructions are associated with a frame. The different types of frames
dictate how the frequency and phase duo are defined.
The default initial phase for every frame is 0.
"""


class GenericFrame(Frame):
"""Pulse module GenericFrame.
The :class:`GenericFrame` is used for custom user defined frames, which are not associated with any
backend defaults. It is especially useful when the frame doesn't correspond to any frame of
the typical qubit model, like qudit control for example. Because no backend defaults exist for
these frames, during compilation an initial frequency and phase will need to be provided.
:class:`GenericFrame` objects are identified by their unique name.
"""

def __init__(self, name: str):
"""Create ``GenericFrame``.
Args:
name: A unique identifier used to identify the frame.
"""
self._name = name

@property
def name(self) -> str:
"""Return the name of the frame."""
return self._name

def __repr__(self) -> str:
return f"GenericFrame({self._name})"

def __eq__(self, other):
return type(self) is type(other) and self._name == other._name

def __hash__(self):
return hash((type(self), self._name))


class QubitFrame(Frame):
"""A frame associated with the driving of a qubit.
:class:`QubitFrame` is a frame associated with the driving of a specific qubit.
The initial frequency of
the frame will be taken as the default driving frequency provided by the backend
during compilation.
"""

def __init__(self, index: int):
"""Create ``QubitFrame``.
Args:
index: The index of the qubit represented by the frame.
"""
self._validate_index(index)
self._index = index

@property
def index(self) -> int:
"""Return the qubit index of the qubit frame."""
return self._index

def _validate_index(self, index) -> None:
"""Raise a ``PulseError`` if the qubit index is invalid. Namely, check if the index is a
non-negative integer.
Raises:
PulseError: If ``identifier`` (index) is a negative integer.
"""
if not isinstance(index, (int, np.integer)) or index < 0:
raise PulseError("Qubit index must be a non-negative integer")

def __repr__(self) -> str:
return f"QubitFrame({self._index})"

def __eq__(self, other):
return type(self) is type(other) and self._index == other._index

def __hash__(self):
return hash((type(self), self._index))


class MeasurementFrame(Frame):
"""A frame associated with the measurement of a qubit.
``MeasurementFrame`` is a frame associated with the readout of a specific qubit,
which requires a stimulus tone driven at frequency off resonant to qubit drive.
If not set otherwise, the initial frequency of the frame will be taken as the default
measurement frequency provided by the backend during compilation.
"""

def __init__(self, index: int):
"""Create ``MeasurementFrame``.
Args:
index: The index of the qubit represented by the frame.
"""
self._validate_index(index)
self._index = index

@property
def index(self) -> int:
"""Return the qubit index of the measurement frame."""
return self._index

def _validate_index(self, index) -> None:
"""Raise a ``PulseError`` if the qubit index is invalid. Namely, check if the index is a
non-negative integer.
Raises:
PulseError: If ``index`` is a negative integer.
"""
if not isinstance(index, (int, np.integer)) or index < 0:
raise PulseError("Qubit index must be a non-negative integer")

def __repr__(self) -> str:
return f"MeasurementFrame({self._index})"

def __eq__(self, other):
return type(self) is type(other) and self._index == other._index

def __hash__(self):
return hash((type(self), self._index))
77 changes: 77 additions & 0 deletions qiskit/pulse/model/mixed_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# 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.

"""
Mixed Frames
"""

from .frames import Frame
from .pulse_target import PulseTarget


class MixedFrame:
"""Representation of a :class:`PulseTarget` and :class:`Frame` combination.
Most instructions need to be associated with both a :class:`PulseTarget` and a
:class:`Frame`. The combination
of the two is called a mixed frame and is represented by a :class:`MixedFrame` object.
In most cases the :class:`MixedFrame` is used more by the compiler, and a pulse program
can be written without :class:`MixedFrame` s, by setting :class:`PulseTarget` and
:class:`Frame` independently. However, in some cases using :class:`MixedFrame` s can
better convey the meaning of the code, and change the compilation process. One example
is the use of the shift/set frequency/phase instructions which are not broadcasted to other
:class:`MixedFrame` s if applied on a specific :class:`MixedFrame` (unlike the behavior
of :class:`Frame`). User can also use a subclass of :class:`MixedFrame` for a particular
combination of logical elements and frames as if a syntactic sugar. This might
increase the readability of a user pulse program. As an example consider the cross
resonance architecture, in which a pulse is played on a target qubit frame and applied
to a control qubit logical element.
"""

def __init__(self, pulse_target: PulseTarget, frame: Frame):
"""Create ``MixedFrame``.
Args:
pulse_target: The ``PulseTarget`` associated with the mixed frame.
frame: The frame associated with the mixed frame.
"""
self._pulse_target = pulse_target
self._frame = frame

@property
def pulse_target(self) -> PulseTarget:
"""Return the target of this mixed frame."""
return self._pulse_target

@property
def frame(self) -> Frame:
"""Return the ``Frame`` of this mixed frame."""
return self._frame

def __repr__(self) -> str:
return f"MixedFrame({self.pulse_target},{self.frame})"

def __eq__(self, other: "MixedFrame") -> bool:
"""Return True iff self and other are equal, specifically, iff they have the same target
and frame.
Args:
other: The mixed frame to compare to this one.
Returns:
True iff equal.
"""
return self._pulse_target == other._pulse_target and self._frame == other._frame

def __hash__(self) -> int:
return hash((self._pulse_target, self._frame, type(self)))
Loading

0 comments on commit 4aeb4c4

Please sign in to comment.