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

Add QPY serialization for PauliEvolutionGate #7374

Merged
merged 21 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
65 changes: 33 additions & 32 deletions qiskit/circuit/qpy_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
set and that name prefix it will error. If it's of type ``'p'`` the data payload is defined
as follows:

.. pauli_evo_qpy:
.. _pauli_evo_qpy:

PAULI_EVOLUTION
---------------
Expand All @@ -133,7 +133,7 @@
}

This is immediately followed by ``operator_count`` elements defined by the :ref:`pauli_sum_op`
payload. Following that we have `time_size`` bytes representing the ``time`` attribute. If
payload. Following that we have ``time_size`` bytes representing the ``time`` attribute. If
``standalone_op`` is ``True`` then there must only be a single operator. The
encoding of these bytes is determined by the value of ``time_type``. Possible values of
``time_type`` are ``'f'``, ``'p'``, and ``'e'``. If ``time_type`` is ``'f'`` it's a double,
Expand All @@ -143,15 +143,15 @@
Following that is ``synthesis_size`` bytes which is a utf8 encoded json payload representing
the :class:`.EvolutionSynthesis` class used by the gate.

.. pauli_sum_op
.. _pauli_sum_op:

SPARSE_PAULI_OP_LIST_ELEM
-------------------------

This represents an instance of :class:`~qiskit.opflow.PauliSumOp`.
This represents an instance of :class:`.PauliSumOp`.


.. code-block: c
.. code-block:: c

struct {
uint32_t pauli_op_size;
Expand Down Expand Up @@ -273,7 +273,7 @@
``qr`` would have ``standalone`` set to ``False``.


.. custom_definition:
.. _custom_definition:

CUSTOM_DEFINITIONS
------------------
Expand Down Expand Up @@ -381,7 +381,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom
(see below), and ``'n'`` represents an object from numpy (either an ``ndarray``
or a numpy type) which means the data is .npy format [#f2]_ data.

.. param_struct:
.. _param_struct:

PARAMETER
---------
Expand Down Expand Up @@ -589,7 +589,7 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom
COMPLEX_SIZE = struct.calcsize(COMPLEX_PACK)
# Pauli Evolution Gate
PAULI_EVOLUTION_DEF = namedtuple(
"PAULI_EVOLUTION_DEF", ["operator_size", "time_type", "time_size", "synth_method_size"]
"PAULI_EVOLUTION_DEF", ["operator_size", "standalone_op", "time_type", "time_size", "synth_method_size"]
)
PAULI_EVOLUTION_DEF_PACK = "!Q?1cQQ"
PAULI_EVOLUTION_DEF_SIZE = struct.calcsize(PAULI_EVOLUTION_DEF_PACK)
Expand Down Expand Up @@ -860,9 +860,9 @@ def _read_custom_instructions(file_obj, version):
definition_circuit = None
if has_custom_definition:
definition_buffer = io.BytesIO(file_obj.read(size))
if version < 3 or not name.startswith(r"###PauliEvolutionGate"):
if version < 3 or not name.startswith(r"###PauliEvolutionGate_"):
definition_circuit = _read_circuit(definition_buffer, version)
elif name.startswith(r"###PauliEvolutionGate"):
elif name.startswith(r"###PauliEvolutionGate_"):
definition_circuit = _read_pauli_evolution_gate(definition_buffer)
custom_instructions[name] = (type_str, num_qubits, num_clbits, definition_circuit)
return custom_instructions
Expand Down Expand Up @@ -926,7 +926,7 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
)
or gate_class_name == "Gate"
or gate_class_name == "Instruction"
or isinstance(instruction_tuple[0], (library.BlueprintCircuit))
or isinstance(instruction_tuple[0], library.BlueprintCircuit)
):
if instruction_tuple[0].name not in custom_instructions:
custom_instructions[instruction_tuple[0].name] = instruction_tuple[0]
Expand Down Expand Up @@ -1033,32 +1033,32 @@ def _write_pauli_evolution_gate(file_obj, evolution_gate):
num_operators = len(operator_list)
pauli_data_buf = io.BytesIO()
for operator in operator_list:
element_buf = io.BytesIO()
buf = io.BytesIO()
pauli_list = operator.to_list(array=True)
np.save(buf, pauli_list)
data = buf.getvalue()
element_metadata = struct.pack(SPARSE_PAULI_OP_LIST_ELEM_PACK, len(data))
element_buf.write(element_metadata)
element_buf.write(data)
pauli_data_buf.write(element_buf.getvalue())
with io.BytesIO() as element_buf:
with io.BytesIO() as buf:
Comment on lines +1037 to +1038
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change this, but for the future you can avoid an extra scope by doing

with io.BytesIO() as element_buf, io.BytesIO() as buf:

the scope order is guaranteed to be left-to-right. (Assuming it's not a line-length constraint!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah cool, I didn't realize that

pauli_list = operator.to_list(array=True)
np.save(buf, pauli_list)
data = buf.getvalue()
element_metadata = struct.pack(SPARSE_PAULI_OP_LIST_ELEM_PACK, len(data))
element_buf.write(element_metadata)
element_buf.write(data)
pauli_data_buf.write(element_buf.getvalue())
time = evolution_gate.time
if isinstance(time, float):
time_type = b"f"
time_data = struct.pack("!d", time)
time_size = struct.calcsize("!d")
elif isinstance(time, Parameter):
time_type = b"p"
buf = io.BytesIO()
_write_parameter(buf, time)
time_data = buf.getvalue()
time_size = len(time_data)
with io.BytesIO() as buf:
_write_parameter(buf, time)
time_data = buf.getvalue()
time_size = len(time_data)
elif isinstance(time, ParameterExpression):
time_type = b"e"
buf = io.BytesIO()
_write_parameter_expression(buf, time)
time_data = buf.getvalue()
time_size = len(time_data)
with io.BytesIO() as buf:
_write_parameter_expression(buf, time)
time_data = buf.getvalue()
time_size = len(time_data)
else:
raise TypeError(f"Invalid time type {time} for PauliEvolutionGate")

Expand All @@ -1071,6 +1071,7 @@ def _write_pauli_evolution_gate(file_obj, evolution_gate):
)
file_obj.write(pauli_evolution_raw)
file_obj.write(pauli_data_buf.getvalue())
pauli_data_buf.close()
file_obj.write(time_data)
file_obj.write(synth_data)

Expand Down Expand Up @@ -1099,11 +1100,11 @@ def _read_pauli_evolution_gate(file_obj):
if time_type == b"f":
time = struct.unpack("!d", time_data)[0]
elif time_type == b"p":
buf = io.BytesIO(time_data)
time = _read_parameter(buf)
with io.BytesIO(time_data) as buf:
time = _read_parameter(buf)
elif time_type == b"e":
buf = io.BytesIO(time_data)
time = _read_parameter_expression(buf)
with io.BytesIO(time_data) as buf:
time = _read_parameter_expression(buf)
synth_data = json.loads(file_obj.read(pauli_evolution_raw[4]))
synthesis = getattr(evo_synth, synth_data["class"])(**synth_data["settings"])
return_gate = library.PauliEvolutionGate(pauli_op, time=time, synthesis=synthesis)
Expand Down
1 change: 0 additions & 1 deletion qiskit/test/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ class BaseTestCase(testtools.TestCase):
assertRaises = unittest.TestCase.assertRaises
assertEqual = unittest.TestCase.assertEqual


else:

class BaseTestCase(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ fixes:
available from :mod:`qiskit.synthesis` can be used with a
:class:`~qiskit.circuit.library.PauliEvolutionGate` for qpy serialization.

To fix this the requires a new QPY format version, :ref:`version_3`, which
contains a representation of the
To fix this issue a new QPY format version, :ref:`version_3`, was required.
This new format version includes a representation of the
:class:`~qiskit.circuit.library.PauliEvolutionGate` class which is
described in the `~qiskit.circuit.qpy_serialization` documentation at
described in the :mod:`~qiskit.circuit.qpy_serialization` documentation at
:ref:`pauli_evo_qpy`.