-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix
Channel.__hash__
in multiprocessing contexts (#11251)
* Fix `Channel.__hash__` in multiprocessing contexts Storing an explicit hash key is fragile in cases that a channel might be created in a different process to where it might be compared or the hash used, because the hash seeding can vary depending on how the new interpreter process was created, especially if it's not done by `fork`. In this case, transmitting the stored `_hash` over pickle meant that a `DriveChannel(0)` created in the main process of a macOS runner could compare equal to a `DriveChannel(0)` created in a separate process (standard start method `spawn`) and pickled over the wire to the main process, but have different hashes, violating the Python data model. Instead, we can just use the standard Python behaviour of creating the hash on demand when requested; this should typically be preferred unless absolutely necessary for critical performance reasons, because it will generally fail safe. * Fix `hash` and equality in other pulse objects This removes all caching of items' `hash`es. This practice is quite fraught in multiprocessing contexts, and should only be done when it is absolutely performance critical. In a couple of cases, the pulse objects were using the cached `hash` as the main component of their `__eq__` methods, which is not correct; it's totally valid to have hash collisions without implying that two objects are equal. (cherry picked from commit 3c1a87c) # Conflicts: # qiskit/pulse/model/frames.py # qiskit/pulse/model/mixed_frames.py # qiskit/pulse/model/pulse_target.py
- Loading branch information
1 parent
31a84c5
commit 3747119
Showing
6 changed files
with
443 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) |
Oops, something went wrong.