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

Commit

Permalink
terra 0.24 qpy updates (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt474 committed May 4, 2023
1 parent dcc9581 commit 96f7271
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 37 deletions.
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

0 comments on commit 96f7271

Please sign in to comment.