Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Update QPY to match Terra 0.24 #606

Merged
merged 2 commits into from
May 4, 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
90 changes: 62 additions & 28 deletions qiskit_ibm_provider/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ def _read_registers(file_obj, num_registers): # type: ignore[no-untyped-def]
return registers


def _loads_instruction_parameter(type_key, data_bytes, version, vectors): # type: ignore[no-untyped-def]
def _loads_instruction_parameter( # type: ignore[no-untyped-def]
type_key, data_bytes, version, vectors, registers, circuit
):
if type_key == type_keys.Program.CIRCUIT:
param = common.data_from_binary(data_bytes, read_circuit, version=version)
elif type_key == type_keys.Container.RANGE:
Expand All @@ -138,6 +140,8 @@ def _loads_instruction_parameter(type_key, data_bytes, version, vectors): # typ
_loads_instruction_parameter,
version=version,
vectors=vectors,
registers=registers,
circuit=circuit,
)
)
elif type_key == type_keys.Value.INTEGER:
Expand All @@ -146,12 +150,24 @@ def _loads_instruction_parameter(type_key, data_bytes, version, vectors): # typ
elif type_key == type_keys.Value.FLOAT:
# TODO This uses little endian. Should be fixed in the next QPY version.
param = struct.unpack("<d", data_bytes)[0]
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)

return param


def _loads_register_param(data_bytes, circuit, registers): # type: ignore[no-untyped-def]
# If register name prefixed with null character it's a clbit index for single bit condition.
if data_bytes[0] == "\x00":
conditional_bit = int(data_bytes[1:])
return circuit.clbits[conditional_bit]
return registers["c"][data_bytes]


def _read_instruction( # type: ignore[no-untyped-def]
file_obj, circuit, registers, custom_operations, version, vectors
):
Expand Down Expand Up @@ -180,17 +196,10 @@ def _read_instruction( # type: ignore[no-untyped-def]
condition_tuple = None
if instruction.has_condition:
# If register name prefixed with null character it's a clbit index for single bit condition.
if condition_register[0] == "\x00":
conditional_bit = int(condition_register[1:])
condition_tuple = (
circuit.clbits[conditional_bit],
instruction.condition_value,
)
else:
condition_tuple = (
registers["c"][condition_register],
instruction.condition_value,
)
condition_tuple = (
_loads_register_param(condition_register, circuit, registers),
instruction.condition_value,
)
if circuit is not None:
qubit_indices = dict(enumerate(circuit.qubits))
clbit_indices = dict(enumerate(circuit.clbits))
Expand Down Expand Up @@ -224,7 +233,9 @@ def _read_instruction( # type: ignore[no-untyped-def]
# Load Parameters
for _param in range(instruction.num_parameters):
type_key, data_bytes = common.read_generic_typed_data(file_obj)
param = _loads_instruction_parameter(type_key, data_bytes, version, vectors)
param = _loads_instruction_parameter(
type_key, data_bytes, version, vectors, registers, circuit
)
params.append(param)

# Load Gate object
Expand Down Expand Up @@ -281,7 +292,13 @@ def _read_instruction( # type: ignore[no-untyped-def]
gate.ctrl_state = instruction.ctrl_state
gate.condition = condition_tuple
else:
if gate_name in {"Initialize", "UCRXGate", "UCRYGate", "UCRZGate"}:
if gate_name in {
"Initialize",
"StatePreparation",
"UCRXGate",
"UCRYGate",
"UCRZGate",
}:
gate = gate_class(params)
else:
if gate_name == "Barrier":
Expand Down Expand Up @@ -493,7 +510,14 @@ def _read_calibrations( # type: ignore[no-untyped-def]
return calibrations


def _dumps_instruction_parameter(param): # type: ignore[no-untyped-def]
def _dumps_register(register, index_map): # type: ignore[no-untyped-def]
if isinstance(register, ClassicalRegister):
return register.name.encode(common.ENCODE)
# Clbit.
return b"\x00" + str(index_map["c"][register]).encode(common.ENCODE)


def _dumps_instruction_parameter(param, index_map): # type: ignore[no-untyped-def]
if isinstance(param, QuantumCircuit):
type_key = type_keys.Program.CIRCUIT
data_bytes = common.data_to_binary(param, write_circuit)
Expand All @@ -504,7 +528,9 @@ def _dumps_instruction_parameter(param): # type: ignore[no-untyped-def]
)
elif isinstance(param, tuple):
type_key = type_keys.Container.TUPLE
data_bytes = common.sequence_to_binary(param, _dumps_instruction_parameter)
data_bytes = common.sequence_to_binary(
param, _dumps_instruction_parameter, index_map=index_map
)
elif isinstance(param, int):
# TODO This uses little endian. This should be fixed in next QPY version.
type_key = type_keys.Value.INTEGER
Expand All @@ -513,6 +539,9 @@ def _dumps_instruction_parameter(param): # type: ignore[no-untyped-def]
# TODO This uses little endian. This should be fixed in next QPY version.
type_key = type_keys.Value.FLOAT
data_bytes = struct.pack("<d", param)
elif isinstance(param, (Clbit, ClassicalRegister)):
type_key = type_keys.Value.REGISTER
data_bytes = _dumps_register(param, index_map)
else:
type_key, data_bytes = value.dumps_value(param)

Expand Down Expand Up @@ -553,15 +582,10 @@ def _write_instruction( # type: ignore[no-untyped-def]
condition_value = 0
if getattr(instruction.operation, "condition", None):
has_condition = True
if isinstance(instruction.operation.condition[0], Clbit):
bit_index = index_map["c"][instruction.operation.condition[0]]
condition_register = b"\x00" + str(bit_index).encode(common.ENCODE)
condition_value = int(instruction.operation.condition[1])
else:
condition_register = instruction.operation.condition[0].name.encode(
common.ENCODE
)
condition_value = instruction.operation.condition[1]
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 All @@ -570,13 +594,23 @@ def _write_instruction( # type: ignore[no-untyped-def]
else:
label_raw = b""

# The instruction params we store are about being able to reconstruct the objects; they don't
# necessarily need to match one-to-one to the `params` field.
if isinstance(instruction.operation, controlflow.SwitchCaseOp):
instruction_params = [
instruction.operation.target,
tuple(instruction.operation.cases_specifier()),
]
else:
instruction_params = instruction.operation.params

num_ctrl_qubits = getattr(instruction.operation, "num_ctrl_qubits", 0)
ctrl_state = getattr(instruction.operation, "ctrl_state", 0)
instruction_raw = struct.pack(
formats.CIRCUIT_INSTRUCTION_V2_PACK,
len(gate_class_name),
len(label_raw),
len(instruction.operation.params),
len(instruction_params),
instruction.operation.num_qubits,
instruction.operation.num_clbits,
has_condition,
Expand All @@ -601,8 +635,8 @@ def _write_instruction( # type: ignore[no-untyped-def]
)
file_obj.write(instruction_arg_raw)
# Encode instruction params
for param in instruction.operation.params:
type_key, data_bytes = _dumps_instruction_parameter(param)
for param in instruction_params:
type_key, data_bytes = _dumps_instruction_parameter(param, index_map)
common.write_generic_typed_data(file_obj, type_key, data_bytes)
return custom_operations_list

Expand Down
75 changes: 73 additions & 2 deletions qiskit_ibm_provider/qpy/binary_io/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
import numpy as np

from qiskit.exceptions import QiskitError
from qiskit.pulse import library, channels
from qiskit.pulse import library, channels, instructions
from qiskit.pulse.schedule import ScheduleBlock
from qiskit.utils import optionals as _optional
from .. import formats, common, type_keys
from ..exceptions import QpyError
from . import value


Expand Down Expand Up @@ -249,6 +250,8 @@ def _loads_operand(type_key, data_bytes, version): # type: ignore[no-untyped-de
)
if type_key == type_keys.ScheduleOperand.CHANNEL:
return common.data_from_binary(data_bytes, _read_channel, version=version)
if type_key == type_keys.ScheduleOperand.OPERAND_STR:
return data_bytes.decode(common.ENCODE)

return value.loads_value(type_key, data_bytes, version, {})

Expand All @@ -272,6 +275,26 @@ def _read_element(file_obj, version, metadata_deserializer): # type: ignore[no-
return instance


def _loads_reference_item( # type: ignore[no-untyped-def]
type_key, data_bytes, version, metadata_deserializer
):
if type_key == type_keys.Value.NULL:
return None
if type_key == type_keys.Program.SCHEDULE_BLOCK:
return common.data_from_binary(
data_bytes,
deserializer=read_schedule_block,
version=version,
metadata_deserializer=metadata_deserializer,
)

raise QpyError(
f"Loaded schedule reference item is neither None nor ScheduleBlock. "
f"Type key {type_key} is not valid data type for a reference items. "
"This data cannot be loaded. Please check QPY version."
)


def _write_channel(file_obj, data): # type: ignore[no-untyped-def]
type_key = type_keys.ScheduleChannel.assign(data)
common.write_type_key(file_obj, type_key)
Expand Down Expand Up @@ -353,6 +376,9 @@ def _dumps_operand(operand): # type: ignore[no-untyped-def]
elif isinstance(operand, channels.Channel):
type_key = type_keys.ScheduleOperand.CHANNEL
data_bytes = common.data_to_binary(operand, _write_channel)
elif isinstance(operand, str):
type_key = type_keys.ScheduleOperand.OPERAND_STR
data_bytes = operand.encode(common.ENCODE)
else:
type_key, data_bytes = value.dumps_value(operand)

Expand All @@ -374,6 +400,20 @@ def _write_element(file_obj, element, metadata_serializer): # type: ignore[no-u
value.write_value(file_obj, element.name)


def _dumps_reference_item(schedule, metadata_serializer): # type: ignore[no-untyped-def]
if schedule is None:
type_key = type_keys.Value.NULL
data_bytes = b""
else:
type_key = type_keys.Program.SCHEDULE_BLOCK
data_bytes = common.data_to_binary(
obj=schedule,
serializer=write_schedule_block,
metadata_serializer=metadata_serializer,
)
return type_key, data_bytes


def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: ignore[no-untyped-def]
"""Read a single ScheduleBlock from the file like object.

Expand Down Expand Up @@ -419,6 +459,24 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type:
block_elm = _read_element(file_obj, version, metadata_deserializer)
block.append(block_elm, inplace=True)

# Load references
if version >= 7:
flat_key_refdict = common.read_mapping(
file_obj=file_obj,
deserializer=_loads_reference_item,
version=version,
metadata_deserializer=metadata_deserializer,
)
ref_dict = {}
for key_str, schedule in flat_key_refdict.items():
if schedule is not None:
composite_key = tuple(
key_str.split(instructions.Reference.key_delimiter)
)
ref_dict[composite_key] = schedule
if ref_dict:
block.assign_references(ref_dict, inplace=True)

return block


Expand Down Expand Up @@ -453,5 +511,18 @@ def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ig
file_obj.write(metadata)

_write_alignment_context(file_obj, block.alignment_context)
for block_elm in block.blocks:
for block_elm in block._blocks:
_write_element(file_obj, block_elm, metadata_serializer)

# Write references
flat_key_refdict = {}
for ref_keys, schedule in block._reference_manager.items():
# Do not call block.reference. This returns the reference of most outer program by design.
key_str = instructions.Reference.key_delimiter.join(ref_keys)
flat_key_refdict[key_str] = schedule
common.write_mapping(
file_obj=file_obj,
mapping=flat_key_refdict,
serializer=_dumps_reference_item,
metadata_serializer=metadata_serializer,
)
5 changes: 4 additions & 1 deletion qiskit_ibm_provider/qpy/binary_io/value.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import numpy as np

from qiskit.circuit import CASE_DEFAULT
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
Expand Down Expand Up @@ -245,7 +246,7 @@ def dumps_value(obj): # type: ignore[no-untyped-def]
binary_data = common.data_to_binary(obj, np.save)
elif type_key == type_keys.Value.STRING:
binary_data = obj.encode(common.ENCODE)
elif type_key == type_keys.Value.NULL:
elif type_key in (type_keys.Value.NULL, type_keys.Value.CASE_DEFAULT):
binary_data = b""
elif type_key == type_keys.Value.PARAMETER_VECTOR:
binary_data = common.data_to_binary(obj, _write_parameter_vec)
Expand Down Expand Up @@ -302,6 +303,8 @@ def loads_value(type_key, binary_data, version, vectors): # type: ignore[no-unt
return binary_data.decode(common.ENCODE)
if type_key == type_keys.Value.NULL:
return None
if type_key == type_keys.Value.CASE_DEFAULT:
return CASE_DEFAULT
if type_key == type_keys.Value.PARAMETER_VECTOR:
return common.data_from_binary(
binary_data, _read_parameter_vec, vectors=vectors
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_provider/qpy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from . import formats

QPY_VERSION = 6
QPY_VERSION = 7
ENCODE = "utf8"


Expand Down
4 changes: 2 additions & 2 deletions qiskit_ibm_provider/qpy/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qiskit.pulse import ScheduleBlock
from qiskit.exceptions import QiskitError
from qiskit.version import __version__
from qiskit.utils.deprecation import deprecate_arguments
from qiskit.utils.deprecation import deprecate_arg

from . import formats, common, binary_io, type_keys
from .exceptions import QpyError
Expand Down Expand Up @@ -74,7 +74,7 @@
VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)


@deprecate_arguments({"circuits": "programs"})
@deprecate_arg("circuits", new_alias="programs", since="0.21.0")
def dump( # type: ignore[no-untyped-def]
programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES],
file_obj: BinaryIO,
Expand Down
Loading