diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index 62bd96e56..920435c1b 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -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: @@ -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: @@ -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("= 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 @@ -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, + ) diff --git a/qiskit_ibm_provider/qpy/binary_io/value.py b/qiskit_ibm_provider/qpy/binary_io/value.py index fbf49eec8..93c6dd4f3 100644 --- a/qiskit_ibm_provider/qpy/binary_io/value.py +++ b/qiskit_ibm_provider/qpy/binary_io/value.py @@ -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 @@ -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) @@ -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 diff --git a/qiskit_ibm_provider/qpy/common.py b/qiskit_ibm_provider/qpy/common.py index 4242548d5..22a1fa42a 100644 --- a/qiskit_ibm_provider/qpy/common.py +++ b/qiskit_ibm_provider/qpy/common.py @@ -21,7 +21,7 @@ from . import formats -QPY_VERSION = 6 +QPY_VERSION = 7 ENCODE = "utf8" diff --git a/qiskit_ibm_provider/qpy/interface.py b/qiskit_ibm_provider/qpy/interface.py index 2f1113ea8..904692999 100644 --- a/qiskit_ibm_provider/qpy/interface.py +++ b/qiskit_ibm_provider/qpy/interface.py @@ -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 @@ -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, diff --git a/qiskit_ibm_provider/qpy/type_keys.py b/qiskit_ibm_provider/qpy/type_keys.py index fabfb777d..ea63853a5 100644 --- a/qiskit_ibm_provider/qpy/type_keys.py +++ b/qiskit_ibm_provider/qpy/type_keys.py @@ -21,7 +21,15 @@ import numpy as np -from qiskit.circuit import Gate, Instruction, QuantumCircuit, ControlledGate +from qiskit.circuit import ( + Gate, + Instruction, + QuantumCircuit, + ControlledGate, + CASE_DEFAULT, + Clbit, + ClassicalRegister, +) from qiskit.circuit.library import PauliEvolutionGate from qiskit.circuit.parameter import Parameter from qiskit.circuit.parameterexpression import ParameterExpression @@ -45,6 +53,7 @@ ShiftPhase, RelativeBarrier, TimeBlockade, + Reference, ) from qiskit.pulse.library import Waveform, SymbolicPulse from qiskit.pulse.schedule import ScheduleBlock @@ -95,6 +104,8 @@ class Value(TypeKeyBase): PARAMETER_EXPRESSION = b"e" STRING = b"s" NULL = b"z" + CASE_DEFAULT = b"d" + REGISTER = b"R" @classmethod def assign(cls, obj): # type: ignore[no-untyped-def] @@ -114,8 +125,12 @@ def assign(cls, obj): # type: ignore[no-untyped-def] return cls.PARAMETER_EXPRESSION if isinstance(obj, str): return cls.STRING + if isinstance(obj, (Clbit, ClassicalRegister)): + return cls.REGISTER if obj is None: return cls.NULL + if obj is CASE_DEFAULT: + return cls.CASE_DEFAULT raise exceptions.QpyError( f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." @@ -229,6 +244,7 @@ class ScheduleInstruction(TypeKeyBase): SHIFT_PHASE = b"r" BARRIER = b"b" TIME_BLOCKADE = b"t" + REFERENCE = b"y" # 's' is reserved by ScheduleBlock, i.e. block can be nested as an element. # Call instructon is not supported by QPY. @@ -257,6 +273,8 @@ def assign(cls, obj): # type: ignore[no-untyped-def] return cls.BARRIER if isinstance(obj, TimeBlockade): return cls.TIME_BLOCKADE + if isinstance(obj, Reference): + return cls.REFERENCE raise exceptions.QpyError( f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." @@ -282,6 +300,8 @@ def retrieve(cls, type_key): # type: ignore[no-untyped-def] return RelativeBarrier if type_key == cls.TIME_BLOCKADE: return TimeBlockade + if type_key == cls.REFERENCE: + return Reference raise exceptions.QpyError( f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace." @@ -299,6 +319,12 @@ class ScheduleOperand(TypeKeyBase): # Data format of these object is somewhat opaque and not defiend well. # It's rarely used in the Qiskit experiements. Of course these can be added later. + # We need to have own string type definition for operands of schedule instruction. + # Note that string type is already defined in the Value namespace, + # but its key "s" conflicts with the SYMBOLIC_PULSE in the ScheduleOperand namespace. + # New in QPY version 7. + OPERAND_STR = b"o" + @classmethod def assign(cls, obj): # type: ignore[no-untyped-def] if isinstance(obj, Waveform): diff --git a/requirements.txt b/requirements.txt index 9436b61b6..121b38d47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit-terra>=0.23.1 +qiskit-terra>=0.24.0 requests>=2.19 requests_ntlm>=1.1.0 numpy>=1.13 diff --git a/setup.py b/setup.py index 0d7dc9f4d..b4dca71ec 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit-terra>=0.23.1", + "qiskit-terra>=0.24.0", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy>=1.13",