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 Expr support to QPY for conditions and targets #10392

Merged
merged 3 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
147 changes: 147 additions & 0 deletions qiskit/qpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,151 @@
by ``num_circuits`` in the file header). There is no padding between the
circuits in the data.


.. _qpy_version_9:

Version 9
=========

Version 9 addds support for classical :class:`~.expr.Expr` nodes and their associated
:class:`~.types.Type`\\ s.


EXPRESSION
----------

An :class:`~.expr.Expr` node is represented by a stream of variable-width data. A node itself is
represented by (in order in the byte stream):

#. a one-byte type code discriminator;
#. an EXPR_TYPE object;
#. a type-code-specific additional payload;
#. a type-code-specific number of child EXPRESSION payloads (the number of these is implied by the
type code and not explicitly stored).

Each of these are described in the following table:

====================== ========= ======================================================= ========
Qiskit class Type code Payload Children
====================== ========= ======================================================= ========
:class:`~.expr.Var` ``x`` One EXPR_VAR. 0

:class:`~.expr.Value` ``v`` One EXPR_VALUE. 0

:class:`~.expr.Cast` ``c`` One ``_Bool`` that corresponds to the value of 1
``implicit``.

:class:`~.expr.Unary` ``u`` One ``uint8_t`` with the same numeric value as the 1
:class:`.Unary.Op`.

:class:`~.expr.Binary` ``b`` One ``uint8_t`` with the same numeric value as the 2
:class:`.Binary.Op`.
====================== ========= ======================================================= ========


EXPR_TYPE
---------

A :class:`~.types.Type` is encoded by a single-byte ASCII ``char`` that encodes the kind of type,
followed by a payload that varies depending on the type. The defined codes are:

====================== ========= =================================================================
Qiskit class Type code Payload
====================== ========= =================================================================
:class:`~.types.Bool` ``b`` None.

:class:`~.types.Uint` ``u`` One ``uint32_t width``.
====================== ========= =================================================================


EXPR_VAR
--------

This represents a runtime variable of a :class:`~.expr.Var` node. These are a type code, followed
by a type-code-specific payload:

=========================== ========= ============================================================
Python class Type code Payload
=========================== ========= ============================================================
:class:`.Clbit` ``C`` One ``uint32_t index`` that is the index of the
:class:`.Clbit` in the containing circuit.

:class:`.ClassicalRegister` ``R`` One ``uint16_t reg_name_size``, followed by that many bytes
of UTF-8 string data of the register name.
=========================== ========= ============================================================


EXPR_VALUE
----------

This represents a literal object in the classical type system, such as an integer. Currently there
are very few such literals. These are encoded as a type code, followed by a type-code-specific
payload.

=========== ========= ============================================================================
Python type Type code Payload
=========== ========= ============================================================================
``bool`` ``b`` One ``_Bool value``.

``int`` ``i`` One ``uint8_t num_bytes``, followed by the integer encoded into that many
many bytes (network order) in a two's complement representation.
=========== ========= ============================================================================



Changes to INSTRUCTION
----------------------

To support the use of :class:`~.expr.Expr` nodes in the fields :attr:`.IfElseOp.condition`,
:attr:`.WhileLoopOp.condition` and :attr:`.SwitchCaseOp.target`, the INSTRUCTION struct is changed
in an ABI compatible-manner to :ref:`its previous definition <qpy_instruction_v5>`. The new struct
is the C struct:

.. code-block:: c

struct {
uint16_t name_size;
uint16_t label_size;
uint16_t num_parameters;
uint32_t num_qargs;
uint32_t num_cargs;
uint8_t conditional_key;
uint16_t conditional_reg_name_size;
int64_t conditional_value;
uint32_t num_ctrl_qubits;
uint32_t ctrl_state;
}

where the only change is that a ``uint8_t conditional_key`` entry has replaced ``_Bool
has_conditional``. This new ``conditional_key`` takes the following numeric values, with these
effects:

===== =============================================================================================
Value Effects
===== =============================================================================================
0 The instruction has its ``.condition`` field set to ``None``. The
``conditional_reg_name_size`` and ``conditional_value`` fields should be ignored.

1 The instruction has its ``.condition`` field set to a 2-tuple of either a :class:`.Clbit`
or a :class:`.ClassicalRegister`, and a integer of value ``conditional_value``. The
INSTRUCTION payload, including its trailing data is parsed exactly as it would be in QPY
versions less than 8.

2 The instruction has its ``.condition`` field set to a :class:`~.expr.Expr` node. The
``conditional_reg_name_size`` and ``conditional_value`` fields should be ignored. The data
following the struct is followed (as in QPY versions less than 8) by ``name_size`` bytes of
UTF-8 string data for the class name and ``label_size`` bytes of UTF-8 string data for the
label (if any). Then, there is one INSTRUCTION_PARAM, which will contain an EXPRESSION. After
that, parsing continues with the INSTRUCTION_ARG structs, as in previous versions of QPY.
===== =============================================================================================


Changes to INSTRUCTION_PARAM
----------------------------

A new type code ``x`` is added that defines an EXPRESSION parameter.


.. _qpy_version_8:

Version 8
Expand Down Expand Up @@ -525,6 +670,8 @@
only :class:`~.ScheduleBlock` payload is supported.
Finally, :ref:`qpy_schedule_block` payload is packed for each CALIBRATION_DEF entry.

.. _qpy_instruction_v5:

INSTRUCTION
-----------

Expand Down
49 changes: 32 additions & 17 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from qiskit import circuit as circuit_mod
from qiskit import extensions
from qiskit.circuit import library, controlflow, CircuitInstruction
from qiskit.circuit.classical import expr
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
from qiskit.circuit.gate import Gate
from qiskit.circuit.controlledgate import ControlledGate
Expand Down Expand Up @@ -149,7 +150,9 @@ def _loads_instruction_parameter(type_key, data_bytes, version, vectors, registe
elif type_key == type_keys.Value.REGISTER:
param = _loads_register_param(data_bytes.decode(common.ENCODE), circuit, registers)
else:
param = value.loads_value(type_key, data_bytes, version, vectors)
param = value.loads_value(
type_key, data_bytes, version, vectors, clbits=circuit.clbits, cregs=registers["c"]
)

return param

Expand Down Expand Up @@ -183,12 +186,18 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
qargs = []
cargs = []
params = []
condition_tuple = None
if instruction.has_condition:
condition_tuple = (
condition = None
if (version < 5 and instruction.has_condition) or (
version >= 5 and instruction.conditional_key == type_keys.Condition.TWO_TUPLE
):
condition = (
_loads_register_param(condition_register, circuit, registers),
instruction.condition_value,
)
elif version >= 5 and instruction.conditional_key == type_keys.Condition.EXPRESSION:
condition = value.read_value(
file_obj, version, vectors, clbits=circuit.clbits, cregs=registers["c"]
)
if circuit is not None:
qubit_indices = dict(enumerate(circuit.qubits))
clbit_indices = dict(enumerate(circuit.clbits))
Expand Down Expand Up @@ -232,7 +241,7 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
inst_obj = _parse_custom_operation(
custom_operations, gate_name, params, version, vectors, registers
)
inst_obj.condition = condition_tuple
inst_obj.condition = condition
if instruction.label_size > 0:
inst_obj.label = label
if circuit is None:
Expand All @@ -243,7 +252,7 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
inst_obj = _parse_custom_operation(
custom_operations, gate_name, params, version, vectors, registers
)
inst_obj.condition = condition_tuple
inst_obj.condition = condition
if instruction.label_size > 0:
inst_obj.label = label
if circuit is None:
Expand All @@ -264,7 +273,7 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
raise AttributeError("Invalid instruction type: %s" % gate_name)

if gate_name in {"IfElseOp", "WhileLoopOp"}:
gate = gate_class(condition_tuple, *params)
gate = gate_class(condition, *params)
elif version >= 5 and issubclass(gate_class, ControlledGate):
if gate_name in {
"MCPhaseGate",
Expand All @@ -279,7 +288,7 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
gate = gate_class(*params)
gate.num_ctrl_qubits = instruction.num_ctrl_qubits
gate.ctrl_state = instruction.ctrl_state
gate.condition = condition_tuple
gate.condition = condition
else:
if gate_name in {
"Initialize",
Expand All @@ -296,7 +305,7 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}:
params = [len(qargs), len(cargs)]
gate = gate_class(*params)
gate.condition = condition_tuple
gate.condition = condition
if instruction.label_size > 0:
gate.label = label
if circuit is None:
Expand Down Expand Up @@ -512,7 +521,7 @@ def _dumps_instruction_parameter(param, index_map):
type_key = type_keys.Value.REGISTER
data_bytes = _dumps_register(param, index_map)
else:
type_key, data_bytes = value.dumps_value(param)
type_key, data_bytes = value.dumps_value(param, index_map=index_map)

return type_key, data_bytes

Expand Down Expand Up @@ -544,13 +553,16 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map):
custom_operations[gate_class_name] = instruction.operation
custom_operations_list.append(gate_class_name)

has_condition = False
condition_type = type_keys.Condition.NONE
condition_register = b""
condition_value = 0
if getattr(instruction.operation, "condition", None):
has_condition = True
condition_register = _dumps_register(instruction.operation.condition[0], index_map)
condition_value = int(instruction.operation.condition[1])
if (op_condition := getattr(instruction.operation, "condition", None)) is not None:
if isinstance(op_condition, expr.Expr):
condition_type = type_keys.Condition.EXPRESSION
else:
condition_type = type_keys.Condition.TWO_TUPLE
condition_register = _dumps_register(instruction.operation.condition[0], index_map)
condition_value = int(instruction.operation.condition[1])

gate_class_name = gate_class_name.encode(common.ENCODE)
label = getattr(instruction.operation, "label")
Expand Down Expand Up @@ -578,7 +590,7 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map):
len(instruction_params),
instruction.operation.num_qubits,
instruction.operation.num_clbits,
has_condition,
condition_type.value,
len(condition_register),
condition_value,
num_ctrl_qubits,
Expand All @@ -587,7 +599,10 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map):
file_obj.write(instruction_raw)
file_obj.write(gate_class_name)
file_obj.write(label_raw)
file_obj.write(condition_register)
if condition_type is type_keys.Condition.EXPRESSION:
value.write_value(file_obj, op_condition, index_map=index_map)
else:
file_obj.write(condition_register)
# Encode instruciton args
for qbit in instruction.qubits:
instruction_arg_raw = struct.pack(
Expand Down
Loading