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

Remove pulse support in QPY in 2.0 #13814

Merged
merged 20 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
04e2c79
Handle ScheduleBlock and pulse gates loading
eliarbel Feb 9, 2025
39d12b4
Add documentation and remove redundant code
eliarbel Feb 9, 2025
ddb4f56
Limit QPY version when generating circuits for compatibility test
eliarbel Feb 9, 2025
06a8551
Handle QPY compatibility testing. Misc other fixes
eliarbel Feb 11, 2025
32ffb79
Merge branch 'main' into remove-pulse-qpy
eliarbel Feb 11, 2025
9ea2fd3
Update qiskit/qpy/binary_io/circuits.py
eliarbel Feb 12, 2025
7aff7e2
Merge branch 'main' into remove-pulse-qpy
eliarbel Feb 12, 2025
2478527
Merge remote-tracking branch 'upstream/main' into remove-pulse-qpy
eliarbel Feb 16, 2025
17ffa6f
Avoid generating pulse circuits in load_qpy & version >= 2.0
eliarbel Feb 16, 2025
86bbeaf
Merge branch 'remove-pulse-qpy' of github.com:eliarbel/qiskit into re…
eliarbel Feb 16, 2025
30fd28e
Merge remote-tracking branch 'upstream/main' into remove-pulse-qpy
eliarbel Feb 19, 2025
46510ac
Raise QpyError when loading ScheduleBlock payloads
eliarbel Feb 19, 2025
f9475a3
Fix lint
eliarbel Feb 19, 2025
10aad9d
Merge branch 'main' into remove-pulse-qpy
mtreinish Feb 19, 2025
5908647
Merge remote-tracking branch 'upstream/main' into remove-pulse-qpy
eliarbel Feb 24, 2025
bf6c5bd
Merge remote-tracking branch 'upstream/main' into remove-pulse-qpy
eliarbel Feb 25, 2025
4d51cce
Merge branch 'remove-pulse-qpy' of github.com:eliarbel/qiskit into re…
eliarbel Feb 25, 2025
6137910
Apply review comments
eliarbel Feb 25, 2025
ec0598c
import from qiskit.qpy only in load command (i.e. dev version)
eliarbel Feb 25, 2025
78c855d
Fix lint
eliarbel Feb 25, 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
48 changes: 23 additions & 25 deletions qiskit/qpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@

.. currentmodule:: qiskit.qpy

QPY is a binary serialization format for :class:`~.QuantumCircuit` and
:class:`~.ScheduleBlock` objects that is designed to be cross-platform,
Python version agnostic, and backwards compatible moving forward. QPY should
be used if you need a mechanism to save or copy between systems a
:class:`~.QuantumCircuit` or :class:`~.ScheduleBlock` that preserves the full
Qiskit object structure (except for custom attributes defined outside of
Qiskit code). This differs from other serialization formats like
QPY is a binary serialization format for :class:`~.QuantumCircuit`
objects that is designed to be cross-platform, Python version agnostic,
and backwards compatible moving forward. QPY should be used if you need
a mechanism to save or copy between systems a :class:`~.QuantumCircuit`
that preserves the full Qiskit object structure (except for custom attributes
defined outside of Qiskit code). This differs from other serialization formats like
`OpenQASM <https://github.com/openqasm/openqasm>`__ (2.0 or 3.0) which has a
different abstraction model and can result in a loss of information contained
in the original circuit (or is unable to represent some aspects of the
Expand Down Expand Up @@ -170,6 +169,14 @@ def open(*args):
it to QPY setting ``use_symengine=False``. The resulting file can then be loaded by any later
version of Qiskit.

.. note::

Starting with Qiskit version 2.0.0, which removed the Pulse module from the library, QPY provides
limited support for loading payloads that include pulse data. Loading a ``ScheduleBlock`` payload,
a :class:`.QpyError` exception will be raised. Loading a payload for a circuit that contained pulse
gates, the output circuit will contain custom instructions **without** calibration data attached
for each pulse gate, leaving them undefined.

QPY format version history
--------------------------

Expand Down Expand Up @@ -902,7 +909,7 @@ def open(*args):
---------

Version 7 adds support for :class:`.~Reference` instruction and serialization of
a :class:`.~ScheduleBlock` program while keeping its reference to subroutines::
a ``ScheduleBlock`` program while keeping its reference to subroutines::

from qiskit import pulse
from qiskit import qpy
Expand Down Expand Up @@ -974,12 +981,12 @@ def open(*args):
Version 5
---------

Version 5 changes from :ref:`qpy_version_4` by adding support for :class:`.~ScheduleBlock`
Version 5 changes from :ref:`qpy_version_4` by adding support for ``ScheduleBlock``
and changing two payloads the INSTRUCTION metadata payload and the CUSTOM_INSTRUCTION block.
These now have new fields to better account for :class:`~.ControlledGate` objects in a circuit.
In addition, new payload MAP_ITEM is defined to implement the :ref:`qpy_mapping` block.

With the support of :class:`.~ScheduleBlock`, now :class:`~.QuantumCircuit` can be
With the support of ``ScheduleBlock``, now :class:`~.QuantumCircuit` can be
serialized together with :attr:`~.QuantumCircuit.calibrations`, or
`Pulse Gates <https://docs.quantum.ibm.com/guides/pulse>`_.
In QPY version 5 and above, :ref:`qpy_circuit_calibrations` payload is
Expand All @@ -996,7 +1003,7 @@ def open(*args):
immediately follows the file header block to represent the program type stored in the file.

- When ``type==c``, :class:`~.QuantumCircuit` payload follows
- When ``type==s``, :class:`~.ScheduleBlock` payload follows
- When ``type==s``, ``ScheduleBlock`` payload follows

.. note::

Expand All @@ -1009,12 +1016,10 @@ def open(*args):
SCHEDULE_BLOCK
~~~~~~~~~~~~~~

:class:`~.ScheduleBlock` is first supported in QPY Version 5. This allows
``ScheduleBlock`` is first supported in QPY Version 5. This allows
users to save pulse programs in the QPY binary format as follows:

.. plot::
:include-source:
:nofigs:
.. code-block:: python

from qiskit import pulse, qpy

Expand All @@ -1027,13 +1032,6 @@ def open(*args):
with open('schedule.qpy', 'rb') as fd:
new_schedule = qpy.load(fd)[0]

.. plot::
:nofigs:

# This block is hidden from readers. It's cleanup code.
from pathlib import Path
Path("schedule.qpy").unlink()

Note that circuit and schedule block are serialized and deserialized through
the same QPY interface. Input data type is implicitly analyzed and
no extra option is required to save the schedule block.
Expand All @@ -1043,7 +1041,7 @@ def open(*args):
SCHEDULE_BLOCK_HEADER
~~~~~~~~~~~~~~~~~~~~~

:class:`~.ScheduleBlock` block starts with the following header:
``ScheduleBlock`` block starts with the following header:

.. code-block:: c

Expand Down Expand Up @@ -1243,8 +1241,8 @@ def open(*args):
and ``num_params`` length of INSTRUCTION_PARAM payload for parameters
associated to the custom instruction.
The ``type`` indicates the class of pulse program which is either, in principle,
:class:`~.ScheduleBlock` or :class:`~.Schedule`. As of QPY Version 5,
only :class:`~.ScheduleBlock` payload is supported.
``ScheduleBlock`` or :class:`~.Schedule`. As of QPY Version 5,
only ``ScheduleBlock`` payload is supported.
Finally, :ref:`qpy_schedule_block` payload is packed for each CALIBRATION_DEF entry.

.. _qpy_instruction_v5:
Expand Down
1 change: 0 additions & 1 deletion qiskit/qpy/binary_io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@
_read_instruction,
)
from .schedules import (
write_schedule_block,
read_schedule_block,
)
69 changes: 20 additions & 49 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,8 +648,7 @@ def _read_custom_operations(file_obj, version, vectors):


def _read_calibrations(file_obj, version, vectors, metadata_deserializer):
calibrations = {}

"""Consume calibrations data, make the file handle point to the next section"""
header = formats.CALIBRATION._make(
struct.unpack(formats.CALIBRATION_PACK, file_obj.read(formats.CALIBRATION_SIZE))
)
Expand All @@ -658,21 +657,20 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer):
struct.unpack(formats.CALIBRATION_DEF_PACK, file_obj.read(formats.CALIBRATION_DEF_SIZE))
)
name = file_obj.read(defheader.name_size).decode(common.ENCODE)
qubits = tuple(
struct.unpack("!q", file_obj.read(struct.calcsize("!q")))[0]
for _ in range(defheader.num_qubits)
)
params = tuple(
value.read_value(file_obj, version, vectors) for _ in range(defheader.num_params)
)
schedule = schedules.read_schedule_block(file_obj, version, metadata_deserializer)
if name:
warnings.warn(
category=UserWarning,
message="Support for loading pulse gates has been removed in Qiskit 2.0. "
f"If `{name}` is in the circuit it will be left as an opaque instruction.",
)

if name not in calibrations:
calibrations[name] = {(qubits, params): schedule}
else:
calibrations[name][(qubits, params)] = schedule
for _ in range(defheader.num_qubits): # read qubits info
file_obj.read(struct.calcsize("!q"))

for _ in range(defheader.num_params): # read params info
value.read_value(file_obj, version, vectors)

return calibrations
schedules.read_schedule_block(file_obj, version, metadata_deserializer)


def _dumps_register(register, index_map):
Expand Down Expand Up @@ -1003,34 +1001,6 @@ def _write_custom_operation(
return new_custom_instruction


def _write_calibrations(file_obj, calibrations, metadata_serializer, version):
flatten_dict = {}
for gate, caldef in calibrations.items():
for (qubits, params), schedule in caldef.items():
key = (gate, qubits, params)
flatten_dict[key] = schedule
header = struct.pack(formats.CALIBRATION_PACK, len(flatten_dict))
file_obj.write(header)
for (name, qubits, params), schedule in flatten_dict.items():
# In principle ScheduleBlock and Schedule can be supported.
# As of version 5 only ScheduleBlock is supported.
name_bytes = name.encode(common.ENCODE)
defheader = struct.pack(
formats.CALIBRATION_DEF_PACK,
len(name_bytes),
len(qubits),
len(params),
type_keys.Program.assign(schedule),
)
file_obj.write(defheader)
file_obj.write(name_bytes)
for qubit in qubits:
file_obj.write(struct.pack("!q", qubit))
for param in params:
value.write_value(file_obj, param, version=version)
schedules.write_schedule_block(file_obj, schedule, metadata_serializer, version=version)


def _write_registers(file_obj, in_circ_regs, full_bits):
bitmap = {bit: index for index, bit in enumerate(full_bits)}

Expand Down Expand Up @@ -1331,8 +1301,11 @@ def write_circuit(
file_obj.write(instruction_buffer.getvalue())
instruction_buffer.close()

# Write calibrations
_write_calibrations(file_obj, circuit._calibrations_prop, metadata_serializer, version=version)
# Pulse has been removed in Qiskit 2.0. As long as we keep QPY at version 13,
# we need to write an empty calibrations header since read_circuit expects it
header = struct.pack(formats.CALIBRATION_PACK, 0)
file_obj.write(header)

_write_layout(file_obj, circuit)


Expand Down Expand Up @@ -1460,11 +1433,9 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
standalone_var_indices,
)

# Read calibrations
# Consume calibrations, but don't use them since pulse gates are not supported as of Qiskit 2.0
if version >= 5:
circ._calibrations_prop = _read_calibrations(
file_obj, version, vectors, metadata_deserializer
)
_read_calibrations(file_obj, version, vectors, metadata_deserializer)

for vec_name, (vector, initialized_params) in vectors.items():
if len(initialized_params) != len(vector):
Expand Down
Loading