From 4d9b99ad84adef1995c91f5979600b123ff5507b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 5 Feb 2024 14:40:19 -0500 Subject: [PATCH 1/3] Handle qpy serialization across versions and delete vendored fork For the next qiskit-ibm-runtime release it is desired to have it support both Qiskit 0.45.x/0.46.x and Qiskit 1.0.0. To do this with QPY it means we need to emit a fixed qpy version of 10 as that's currently the maximum version the server side can parse. The easiest way to manage this across versions is actually to rely on Qiskit's qpy module again. Starting in Qiskit 1.0.0 the qiskit.qpy.dump() function a new kwarg, version exists to specify the version of qpy emitted. We can use this to control the rollout of QPY format versions as the server side is updated. For right now this is fixed to version 10 which is the same as the QPY format version emitted by 0.45.x and 0.46.0. Because the versions are the same we can just use qiskit's qpy module moving forward as either we're on qiskit 0.45.x/0.46.x and will emit version 10 (as there is no option) or we're on >=1.0.0 and we explicitly set the qpy version to one that's compatible with the server side. The qpy internals usage to serialize parameters and parameter expressions are left in place (although expanded on to include #1355 for parameter expressions) as an alternative needs to be considered for this. It is not a good idea to rely on qpy internals and explicitly private functions to do partial serialization. This commit also deletes the vendored fork as it's never been included in a released version of qiskit-ibm-runtime and nothing is using it anymore. Related to: #1375 --- qiskit_ibm_runtime/qpy/__init__.py | 87 -- qiskit_ibm_runtime/qpy/binary_io/__init__.py | 38 - qiskit_ibm_runtime/qpy/binary_io/circuits.py | 1280 ----------------- qiskit_ibm_runtime/qpy/binary_io/schedules.py | 656 --------- qiskit_ibm_runtime/qpy/binary_io/value.py | 586 -------- qiskit_ibm_runtime/qpy/common.py | 310 ---- qiskit_ibm_runtime/qpy/exceptions.py | 29 - qiskit_ibm_runtime/qpy/formats.py | 374 ----- qiskit_ibm_runtime/qpy/interface.py | 312 ---- qiskit_ibm_runtime/qpy/type_keys.py | 540 ------- qiskit_ibm_runtime/utils/json.py | 21 +- 11 files changed, 16 insertions(+), 4217 deletions(-) delete mode 100644 qiskit_ibm_runtime/qpy/__init__.py delete mode 100644 qiskit_ibm_runtime/qpy/binary_io/__init__.py delete mode 100644 qiskit_ibm_runtime/qpy/binary_io/circuits.py delete mode 100644 qiskit_ibm_runtime/qpy/binary_io/schedules.py delete mode 100644 qiskit_ibm_runtime/qpy/binary_io/value.py delete mode 100644 qiskit_ibm_runtime/qpy/common.py delete mode 100644 qiskit_ibm_runtime/qpy/exceptions.py delete mode 100644 qiskit_ibm_runtime/qpy/formats.py delete mode 100644 qiskit_ibm_runtime/qpy/interface.py delete mode 100644 qiskit_ibm_runtime/qpy/type_keys.py diff --git a/qiskit_ibm_runtime/qpy/__init__.py b/qiskit_ibm_runtime/qpy/__init__.py deleted file mode 100644 index 14c75d674..000000000 --- a/qiskit_ibm_runtime/qpy/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -########################################################### -QPY serialization (:mod:`qiskit_ibm_runtime.qpy`) -########################################################### - -.. currentmodule:: qiskit_ibm_runtime.qpy - -********* -Using QPY -********* - -This is downstream fork of the :mod:`qiskit.qpy` module. - -Using QPY is defined to be straightforward and mirror the user API of the -serializers in Python's standard library, ``pickle`` and ``json``. There are -2 user facing functions: :func:`qiskit.circuit.qpy_serialization.dump` and -:func:`qiskit.circuit.qpy_serialization.load` which are used to dump QPY data -to a file object and load circuits from QPY data in a file object respectively. -For example:: - - from qiskit.circuit import QuantumCircuit - from qiskit import qpy - - qc = QuantumCircuit(2, name='Bell', metadata={'test': True}) - qc.h(0) - qc.cx(0, 1) - qc.measure_all() - - with open('bell.qpy', 'wb') as fd: - qpy.dump(qc, fd) - - with open('bell.qpy', 'rb') as fd: - new_qc = qpy.load(fd)[0] - -API documentation -================= - -.. autosummary:: - :toctree: ../stubs/ - - load - dump - -QPY Compatibility -================= - -The QPY format is designed to be backwards compatible moving forward. This means -you should be able to load a QPY with any newer Qiskit version than the one -that generated it. However, loading a QPY file with an older Qiskit version is -not supported and may not work. - -For example, if you generated a QPY file using qiskit-terra 0.18.1 you could -load that QPY file with qiskit-terra 0.19.0 and a hypothetical qiskit-terra -0.29.0. However, loading that QPY file with 0.18.0 is not supported and may not -work. - -********** -QPY Format -********** - -https://qiskit.org/documentation/apidoc/qpy.html#qpy-format -""" - -from .interface import dump, load - -# For backward compatibility. Provide, Runtime, Experiment call these private functions. -from .binary_io import ( - _write_instruction, - _read_instruction, - _write_parameter_expression, - _read_parameter_expression, - _read_parameter_expression_v3, - _write_parameter, - _read_parameter, -) diff --git a/qiskit_ibm_runtime/qpy/binary_io/__init__.py b/qiskit_ibm_runtime/qpy/binary_io/__init__.py deleted file mode 100644 index 566ebbae5..000000000 --- a/qiskit_ibm_runtime/qpy/binary_io/__init__.py +++ /dev/null @@ -1,38 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Read and write QPY-serializable objects.""" - -from .value import ( - dumps_value, - loads_value, - write_value, - read_value, - # for backward compatibility; provider, runtime, experiment call this private methods. - _write_parameter, - _write_parameter_expression, - _read_parameter_expression, - _read_parameter_expression_v3, - _read_parameter, -) - -from .circuits import ( - write_circuit, - read_circuit, - # for backward compatibility; provider calls this private methods. - _write_instruction, - _read_instruction, -) -from .schedules import ( - write_schedule_block, - read_schedule_block, -) diff --git a/qiskit_ibm_runtime/qpy/binary_io/circuits.py b/qiskit_ibm_runtime/qpy/binary_io/circuits.py deleted file mode 100644 index f51f48b7d..000000000 --- a/qiskit_ibm_runtime/qpy/binary_io/circuits.py +++ /dev/null @@ -1,1280 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name - -"""Binary IO for circuit objects.""" - -import io -import json -import struct -import uuid -import warnings - -from collections import defaultdict -import numpy as np - -from qiskit import circuit as circuit_mod -from qiskit.circuit import library, controlflow, CircuitInstruction, ControlFlowOp -from qiskit.circuit.classical import expr -from qiskit.circuit.classicalregister import ClassicalRegister, Clbit -from qiskit.circuit.gate import Gate -from qiskit.circuit.singleton import SingletonInstruction, SingletonGate -from qiskit.circuit.controlledgate import ControlledGate -from qiskit.circuit.instruction import Instruction -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit.quantumregister import QuantumRegister, Qubit -from qiskit.quantum_info.operators import SparsePauliOp -from qiskit.synthesis import evolution as evo_synth -from qiskit.transpiler.layout import Layout, TranspileLayout -from .. import common, formats, type_keys -from . import value, schedules - - -def _read_header_v2( # type: ignore[no-untyped-def] - file_obj, version, vectors, metadata_deserializer=None -): - data = formats.CIRCUIT_HEADER_V2._make( - struct.unpack( - formats.CIRCUIT_HEADER_V2_PACK, - file_obj.read(formats.CIRCUIT_HEADER_V2_SIZE), - ) - ) - name = file_obj.read(data.name_size).decode(common.ENCODE) - global_phase = value.loads_value( - data.global_phase_type, - file_obj.read(data.global_phase_size), - version=version, - vectors=vectors, - ) - header = { - "global_phase": global_phase, - "num_qubits": data.num_qubits, - "num_clbits": data.num_clbits, - "num_registers": data.num_registers, - "num_instructions": data.num_instructions, - } - metadata_raw = file_obj.read(data.metadata_size) - metadata = json.loads(metadata_raw, cls=metadata_deserializer) - return header, name, metadata - - -def _read_header(file_obj, metadata_deserializer=None): # type: ignore[no-untyped-def] - data = formats.CIRCUIT_HEADER._make( - struct.unpack(formats.CIRCUIT_HEADER_PACK, file_obj.read(formats.CIRCUIT_HEADER_SIZE)) - ) - name = file_obj.read(data.name_size).decode(common.ENCODE) - header = { - "global_phase": data.global_phase, - "num_qubits": data.num_qubits, - "num_clbits": data.num_clbits, - "num_registers": data.num_registers, - "num_instructions": data.num_instructions, - } - metadata_raw = file_obj.read(data.metadata_size) - metadata = json.loads(metadata_raw, cls=metadata_deserializer) - return header, name, metadata - - -def _read_registers_v4(file_obj, num_registers): # type: ignore[no-untyped-def] - registers = {"q": {}, "c": {}} - for _reg in range(num_registers): - data = formats.REGISTER_V4._make( - struct.unpack( - formats.REGISTER_V4_PACK, - file_obj.read(formats.REGISTER_V4_SIZE), - ) - ) - name = file_obj.read(data.name_size).decode("utf8") - REGISTER_ARRAY_PACK = "!%sq" % data.size - bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK)) - bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw)) - if data.type.decode("utf8") == "q": - registers["q"][name] = (data.standalone, bit_indices, data.in_circuit) - else: - registers["c"][name] = (data.standalone, bit_indices, data.in_circuit) - return registers - - -def _read_registers(file_obj, num_registers): # type: ignore[no-untyped-def] - registers = {"q": {}, "c": {}} - for _reg in range(num_registers): - data = formats.REGISTER._make( - struct.unpack( - formats.REGISTER_PACK, - file_obj.read(formats.REGISTER_SIZE), - ) - ) - name = file_obj.read(data.name_size).decode("utf8") - REGISTER_ARRAY_PACK = "!%sI" % data.size - bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK)) - bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw)) - if data.type.decode("utf8") == "q": - registers["q"][name] = (data.standalone, bit_indices, True) - else: - registers["c"][name] = (data.standalone, bit_indices, True) - return registers - - -def _loads_instruction_parameter( # type: ignore[no-untyped-def] - type_key, data_bytes, version, vectors, registers, circuit, use_symengine -): - 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: - data = formats.RANGE._make(struct.unpack(formats.RANGE_PACK, data_bytes)) - param = range(data.start, data.stop, data.step) - elif type_key == type_keys.Container.TUPLE: - param = tuple( - common.sequence_from_binary( - data_bytes, - _loads_instruction_parameter, - version=version, - vectors=vectors, - registers=registers, - circuit=circuit, - use_symengine=use_symengine, - ) - ) - elif type_key == type_keys.Value.INTEGER: - # TODO This uses little endian. Should be fixed in the next QPY version. - param = struct.unpack("= 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"], - use_symengine=use_symengine, - ) - if circuit is not None: - qubit_indices = dict(enumerate(circuit.qubits)) - clbit_indices = dict(enumerate(circuit.clbits)) - else: - qubit_indices = {} - clbit_indices = {} - - # Load Arguments - if circuit is not None: - for _qarg in range(instruction.num_qargs): - qarg = formats.CIRCUIT_INSTRUCTION_ARG._make( - struct.unpack( - formats.CIRCUIT_INSTRUCTION_ARG_PACK, - file_obj.read(formats.CIRCUIT_INSTRUCTION_ARG_SIZE), - ) - ) - if qarg.type.decode(common.ENCODE) == "c": - raise TypeError("Invalid input carg prior to all qargs") - qargs.append(qubit_indices[qarg.size]) - for _carg in range(instruction.num_cargs): - carg = formats.CIRCUIT_INSTRUCTION_ARG._make( - struct.unpack( - formats.CIRCUIT_INSTRUCTION_ARG_PACK, - file_obj.read(formats.CIRCUIT_INSTRUCTION_ARG_SIZE), - ) - ) - if carg.type.decode(common.ENCODE) == "q": - raise TypeError("Invalid input qarg after all qargs") - cargs.append(clbit_indices[carg.size]) - - # 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, - registers, - circuit, - use_symengine, - ) - params.append(param) - - # Load Gate object - if gate_name in {"Gate", "Instruction", "ControlledGate"}: - inst_obj = _parse_custom_operation( - custom_operations, - gate_name, - params, - version, - vectors, - registers, - use_symengine, - ) - inst_obj.condition = condition - if instruction.label_size > 0: - inst_obj.label = label - if circuit is None: - return inst_obj - circuit._append(inst_obj, qargs, cargs) - return None - elif gate_name in custom_operations: - inst_obj = _parse_custom_operation( - custom_operations, - gate_name, - params, - version, - vectors, - registers, - use_symengine, - ) - inst_obj.condition = condition - if instruction.label_size > 0: - inst_obj.label = label - if circuit is None: - return inst_obj - circuit._append(inst_obj, qargs, cargs) - return None - elif hasattr(library, gate_name): - gate_class = getattr(library, gate_name) - elif hasattr(circuit_mod, gate_name): - gate_class = getattr(circuit_mod, gate_name) - elif hasattr(controlflow, gate_name): - gate_class = getattr(controlflow, gate_name) - else: - raise AttributeError("Invalid instruction type: %s" % gate_name) - - if instruction.label_size <= 0: - label = None - if gate_name in {"IfElseOp", "WhileLoopOp"}: - gate = gate_class(condition, *params, label=label) - elif version >= 5 and issubclass(gate_class, ControlledGate): - if gate_name in { - "MCPhaseGate", - "MCU1Gate", - "MCXGrayCode", - "MCXGate", - "MCXRecursive", - "MCXVChain", - }: - gate = gate_class(*params, instruction.num_ctrl_qubits, label=label) - else: - gate = gate_class(*params, label=label) - if ( - gate.num_ctrl_qubits != instruction.num_ctrl_qubits - or gate.ctrl_state != instruction.ctrl_state - ): - gate = gate.to_mutable() - gate.num_ctrl_qubits = instruction.num_ctrl_qubits - gate.ctrl_state = instruction.ctrl_state - if condition: - gate = gate.c_if(*condition) - else: - if gate_name in {"Initialize", "StatePreparation"}: - if isinstance(params[0], str): - # the params are the labels of the initial state - gate = gate_class("".join(label for label in params)) - elif instruction.num_parameters == 1: - # the params is the integer indicating which qubits to initialize - gate = gate_class(int(params[0].real), instruction.num_qargs) - else: - # the params represent a list of complex amplitudes - gate = gate_class(params) - elif gate_name in { - "UCRXGate", - "UCRYGate", - "UCRZGate", - "DiagonalGate", - }: - gate = gate_class(params) - else: - if gate_name == "Barrier": - params = [len(qargs)] - elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}: - params = [len(qargs), len(cargs)] - if label is not None: - if issubclass(gate_class, (SingletonInstruction, SingletonGate)): - gate = gate_class(*params, label=label) - else: - gate = gate_class(*params) - gate.label = label - else: - gate = gate_class(*params) - if condition: - if not isinstance(gate, ControlFlowOp): - gate = gate.c_if(*condition) - else: - gate.condition = condition - if circuit is None: - return gate - if not isinstance(gate, Instruction): - circuit.append(gate, qargs, cargs) - else: - circuit._append(CircuitInstruction(gate, qargs, cargs)) - return None - - -def _parse_custom_operation( # type: ignore[no-untyped-def] - custom_operations, gate_name, params, version, vectors, registers, use_symengine -): - if version >= 5: - ( - type_str, - num_qubits, - num_clbits, - definition, - num_ctrl_qubits, - ctrl_state, - base_gate_raw, - ) = custom_operations[gate_name] - else: - type_str, num_qubits, num_clbits, definition = custom_operations[gate_name] - type_key = type_keys.CircuitInstruction(type_str) - - if type_key == type_keys.CircuitInstruction.INSTRUCTION: - inst_obj = Instruction(gate_name, num_qubits, num_clbits, params) - if definition is not None: - inst_obj.definition = definition - return inst_obj - - if type_key == type_keys.CircuitInstruction.GATE: - inst_obj = Gate(gate_name, num_qubits, params) - inst_obj.definition = definition - return inst_obj - - if version >= 5 and type_key == type_keys.CircuitInstruction.CONTROLLED_GATE: - with io.BytesIO(base_gate_raw) as base_gate_obj: - base_gate = _read_instruction( - base_gate_obj, - None, - registers, - custom_operations, - version, - vectors, - use_symengine, - ) - if ctrl_state < 2**num_ctrl_qubits - 1: - # If open controls, we need to discard the control suffix when setting the name. - gate_name = gate_name.rsplit("_", 1)[0] - inst_obj = ControlledGate( - gate_name, - num_qubits, - params, - num_ctrl_qubits=num_ctrl_qubits, - ctrl_state=ctrl_state, - base_gate=base_gate, - ) - inst_obj.definition = definition - return inst_obj - - if type_key == type_keys.CircuitInstruction.PAULI_EVOL_GATE: - return definition - - raise ValueError("Invalid custom instruction type '%s'" % type_str) - - -def _read_pauli_evolution_gate(file_obj, version, vectors): # type: ignore[no-untyped-def] - pauli_evolution_def = formats.PAULI_EVOLUTION_DEF._make( - struct.unpack( - formats.PAULI_EVOLUTION_DEF_PACK, - file_obj.read(formats.PAULI_EVOLUTION_DEF_SIZE), - ) - ) - if pauli_evolution_def.operator_size != 1 and pauli_evolution_def.standalone_op: - raise ValueError( - "Can't have a standalone operator with {pauli_evolution_raw[0]} operators in the payload" - ) - - operator_list = [] - for _ in range(pauli_evolution_def.operator_size): - op_elem = formats.SPARSE_PAULI_OP_LIST_ELEM._make( - struct.unpack( - formats.SPARSE_PAULI_OP_LIST_ELEM_PACK, - file_obj.read(formats.SPARSE_PAULI_OP_LIST_ELEM_SIZE), - ) - ) - op_raw_data = common.data_from_binary(file_obj.read(op_elem.size), np.load) - operator_list.append(SparsePauliOp.from_list(op_raw_data)) - - if pauli_evolution_def.standalone_op: - pauli_op = operator_list[0] - else: - pauli_op = operator_list - - time = value.loads_value( - pauli_evolution_def.time_type, - file_obj.read(pauli_evolution_def.time_size), - version=version, - vectors=vectors, - ) - synth_data = json.loads(file_obj.read(pauli_evolution_def.synth_method_size)) - synthesis = getattr(evo_synth, synth_data["class"])(**synth_data["settings"]) - return_gate = library.PauliEvolutionGate(pauli_op, time=time, synthesis=synthesis) - return return_gate - - -def _read_custom_operations(file_obj, version, vectors): # type: ignore[no-untyped-def] - custom_operations = {} - custom_definition_header = formats.CUSTOM_CIRCUIT_DEF_HEADER._make( - struct.unpack( - formats.CUSTOM_CIRCUIT_DEF_HEADER_PACK, - file_obj.read(formats.CUSTOM_CIRCUIT_DEF_HEADER_SIZE), - ) - ) - if custom_definition_header.size > 0: - for _ in range(custom_definition_header.size): - if version < 5: - data = formats.CUSTOM_CIRCUIT_INST_DEF._make( - struct.unpack( - formats.CUSTOM_CIRCUIT_INST_DEF_PACK, - file_obj.read(formats.CUSTOM_CIRCUIT_INST_DEF_SIZE), - ) - ) - else: - data = formats.CUSTOM_CIRCUIT_INST_DEF_V2._make( - struct.unpack( - formats.CUSTOM_CIRCUIT_INST_DEF_V2_PACK, - file_obj.read(formats.CUSTOM_CIRCUIT_INST_DEF_V2_SIZE), - ) - ) - - name = file_obj.read(data.gate_name_size).decode(common.ENCODE) - type_str = data.type - definition_circuit = None - if data.custom_definition: - def_binary = file_obj.read(data.size) - if version < 3 or not name.startswith(r"###PauliEvolutionGate_"): - definition_circuit = common.data_from_binary( - def_binary, read_circuit, version=version - ) - elif name.startswith(r"###PauliEvolutionGate_"): - definition_circuit = common.data_from_binary( - def_binary, - _read_pauli_evolution_gate, - version=version, - vectors=vectors, - ) - if version < 5: - data_payload = ( - type_str, - data.num_qubits, - data.num_clbits, - definition_circuit, - ) - else: - base_gate = file_obj.read(data.base_gate_size) - data_payload = ( - type_str, - data.num_qubits, - data.num_clbits, - definition_circuit, - data.num_ctrl_qubits, - data.ctrl_state, - base_gate, - ) - custom_operations[name] = data_payload - return custom_operations - - -def _read_calibrations( # type: ignore[no-untyped-def] - file_obj, version, vectors, metadata_deserializer -): - calibrations = {} - - header = formats.CALIBRATION._make( - struct.unpack(formats.CALIBRATION_PACK, file_obj.read(formats.CALIBRATION_SIZE)) - ) - for _ in range(header.num_cals): - defheader = formats.CALIBRATION_DEF._make( - struct.unpack( - formats.CALIBRATION_DEF_PACK, - file_obj.read(formats.CALIBRATION_DEF_SIZE), - ) - ) - name = file_obj.read(defheader.name_size).decode(common.ENCODE) - qubits = tuple( - struct.unpack("!q", file_obj.read(struct.calcsize("!q")))[0] - for _ in range(defheader.num_qubits) - ) - params = tuple( - value.read_value(file_obj, version, vectors) for _ in range(defheader.num_params) - ) - schedule = schedules.read_schedule_block(file_obj, version, metadata_deserializer) - - if name not in calibrations: - calibrations[name] = {(qubits, params): schedule} - else: - calibrations[name][(qubits, params)] = schedule - - return calibrations - - -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, use_symengine): # type: ignore[no-untyped-def] - if isinstance(param, QuantumCircuit): - type_key = type_keys.Program.CIRCUIT - data_bytes = common.data_to_binary(param, write_circuit) - elif isinstance(param, range): - type_key = type_keys.Container.RANGE - data_bytes = struct.pack(formats.RANGE_PACK, param.start, param.stop, param.step) - elif isinstance(param, tuple): - type_key = type_keys.Container.TUPLE - data_bytes = common.sequence_to_binary( - param, - _dumps_instruction_parameter, - index_map=index_map, - use_symengine=use_symengine, - ) - elif isinstance(param, int): - # TODO This uses little endian. This should be fixed in next QPY version. - type_key = type_keys.Value.INTEGER - data_bytes = struct.pack("= 0: - circuit._layout._input_qubit_count = header.input_qubit_count - circuit._layout._output_qubit_list = circuit.qubits - - -def write_circuit( # type: ignore[no-untyped-def] - file_obj, circuit, metadata_serializer=None, use_symengine=False -): - """Write a single QuantumCircuit object in the file like object. - - Args: - file_obj (FILE): The file like object to write the circuit data in. - circuit (QuantumCircuit): The circuit data to write. - metadata_serializer (JSONEncoder): An optional JSONEncoder class that - will be passed the :attr:`.QuantumCircuit.metadata` dictionary for - ``circuit`` and will be used as the ``cls`` kwarg - on the ``json.dump()`` call to JSON serialize that dictionary. - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - """ - metadata_raw = json.dumps( - circuit.metadata, separators=(",", ":"), cls=metadata_serializer - ).encode(common.ENCODE) - metadata_size = len(metadata_raw) - num_instructions = len(circuit) - circuit_name = circuit.name.encode(common.ENCODE) - global_phase_type, global_phase_data = value.dumps_value(circuit.global_phase) - - with io.BytesIO() as reg_buf: - num_qregs = _write_registers(reg_buf, circuit.qregs, circuit.qubits) - num_cregs = _write_registers(reg_buf, circuit.cregs, circuit.clbits) - registers_raw = reg_buf.getvalue() - num_registers = num_qregs + num_cregs - - # Write circuit header - header_raw = formats.CIRCUIT_HEADER_V2( - name_size=len(circuit_name), - global_phase_type=global_phase_type, - global_phase_size=len(global_phase_data), - num_qubits=circuit.num_qubits, - num_clbits=circuit.num_clbits, - metadata_size=metadata_size, - num_registers=num_registers, - num_instructions=num_instructions, - ) - header = struct.pack(formats.CIRCUIT_HEADER_V2_PACK, *header_raw) - file_obj.write(header) - file_obj.write(circuit_name) - file_obj.write(global_phase_data) - file_obj.write(metadata_raw) - # Write header payload - file_obj.write(registers_raw) - instruction_buffer = io.BytesIO() - custom_operations = {} - index_map = {} - index_map["q"] = {bit: index for index, bit in enumerate(circuit.qubits)} - index_map["c"] = {bit: index for index, bit in enumerate(circuit.clbits)} - for instruction in circuit.data: - _write_instruction( - instruction_buffer, instruction, custom_operations, index_map, use_symengine - ) - - with io.BytesIO() as custom_operations_buffer: - new_custom_operations = list(custom_operations.keys()) - while new_custom_operations: - operations_to_serialize = new_custom_operations.copy() - new_custom_operations = [] - for name in operations_to_serialize: - operation = custom_operations[name] - new_custom_operations.extend( - _write_custom_operation( - custom_operations_buffer, - name, - operation, - custom_operations, - use_symengine, - ) - ) - - file_obj.write(struct.pack(formats.CUSTOM_CIRCUIT_DEF_HEADER_PACK, len(custom_operations))) - file_obj.write(custom_operations_buffer.getvalue()) - - file_obj.write(instruction_buffer.getvalue()) - instruction_buffer.close() - - # Write calibrations - _write_calibrations(file_obj, circuit.calibrations, metadata_serializer) - _write_layout(file_obj, circuit) - - -def read_circuit( # type: ignore[no-untyped-def] - file_obj, version, metadata_deserializer=None, use_symengine=False -): - """Read a single QuantumCircuit object from the file like object. - - Args: - file_obj (FILE): The file like object to read the circuit data from. - version (int): QPY version. - metadata_deserializer (JSONDecoder): An optional JSONDecoder class - that will be used for the ``cls`` kwarg on the internal - ``json.load`` call used to deserialize the JSON payload used for - the :attr:`.QuantumCircuit.metadata` attribute for a circuit - in the file-like object. If this is not specified the circuit metadata will - be parsed as JSON with the stdlib ``json.load()`` function using - the default ``JSONDecoder`` class. - - use_symengine (bool): If True, symbolic objects will be de-serialized using - symengine's native mechanism. This is a faster serialization alternative, but not - supported in all platforms. Please check that your target platform is supported by - the symengine library before setting this option, as it will be required by qpy to - deserialize the payload. - - Returns: - QuantumCircuit: The circuit object from the file. - - Raises: - QpyError: Invalid register. - """ - vectors = {} - if version < 2: - header, name, metadata = _read_header(file_obj, metadata_deserializer=metadata_deserializer) - else: - header, name, metadata = _read_header_v2( - file_obj, version, vectors, metadata_deserializer=metadata_deserializer - ) - - global_phase = header["global_phase"] - num_qubits = header["num_qubits"] - num_clbits = header["num_clbits"] - num_registers = header["num_registers"] - num_instructions = header["num_instructions"] - # `out_registers` is two "name: register" maps segregated by type for the rest of QPY, and - # `all_registers` is the complete ordered list used to construct the `QuantumCircuit`. - out_registers = {"q": {}, "c": {}} - all_registers = [] - out_bits = {"q": [None] * num_qubits, "c": [None] * num_clbits} - if num_registers > 0: - if version < 4: - registers = _read_registers(file_obj, num_registers) - else: - registers = _read_registers_v4(file_obj, num_registers) - - for bit_type_label, bit_type, reg_type in [ - ("q", Qubit, QuantumRegister), - ("c", Clbit, ClassicalRegister), - ]: - # This does two passes through the registers. In the first, we're actually just - # constructing the `Bit` instances: any register that is `standalone` "owns" all its - # bits in the old Qiskit data model, so we have to construct those by creating the - # register and taking the bits from them. That's the case even if that register isn't - # actually in the circuit, which is why we stored them (with `in_circuit=False`) in QPY. - # - # Since there's no guarantees in QPY about the ordering of registers, we have to pass - # through all registers to create the bits first, because we can't reliably know if a - # non-standalone register contains bits from a standalone one until we've seen all - # standalone registers. - typed_bits = out_bits[bit_type_label] - typed_registers = registers[bit_type_label] - for register_name, ( - standalone, - indices, - _incircuit, - ) in typed_registers.items(): - if not standalone: - continue - register = reg_type(len(indices), register_name) - out_registers[bit_type_label][register_name] = register - for owned, index in zip(register, indices): - # Negative indices are for bits that aren't in the circuit. - if index >= 0: - typed_bits[index] = owned - # Any remaining unset bits aren't owned, so we can construct them in the standard way. - typed_bits = [bit if bit is not None else bit_type() for bit in typed_bits] - # Finally _properly_ construct all the registers. Bits can be in more than one - # register, including bits that are old-style "owned" by a register. - for register_name, ( - standalone, - indices, - in_circuit, - ) in typed_registers.items(): - if standalone: - register = out_registers[bit_type_label][register_name] - else: - register = reg_type( - name=register_name, - bits=[typed_bits[x] if x >= 0 else bit_type() for x in indices], - ) - out_registers[bit_type_label][register_name] = register - if in_circuit: - all_registers.append(register) - out_bits[bit_type_label] = typed_bits - else: - out_bits = { - "q": [Qubit() for _ in out_bits["q"]], - "c": [Clbit() for _ in out_bits["c"]], - } - circ = QuantumCircuit( - out_bits["q"], - out_bits["c"], - *all_registers, - name=name, - global_phase=global_phase, - metadata=metadata, - ) - - custom_operations = _read_custom_operations(file_obj, version, vectors) - for _instruction in range(num_instructions): - _read_instruction( - file_obj, - circ, - out_registers, - custom_operations, - version, - vectors, - use_symengine, - ) - - # Read calibrations - if version >= 5: - circ.calibrations = _read_calibrations(file_obj, version, vectors, metadata_deserializer) - - for vec_name, (vector, initialized_params) in vectors.items(): - if len(initialized_params) != len(vector): - warnings.warn( - f"The ParameterVector: '{vec_name}' is not fully identical to its " - "pre-serialization state. Elements " - f"{', '.join([str(x) for x in set(range(len(vector))) - initialized_params])} " - "in the ParameterVector will be not equal to the pre-serialized ParameterVector " - f"as they weren't used in the circuit: {circ.name}", - UserWarning, - ) - if version >= 8: - if version >= 10: - _read_layout_v2(file_obj, circ) - else: - _read_layout(file_obj, circ) - return circ diff --git a/qiskit_ibm_runtime/qpy/binary_io/schedules.py b/qiskit_ibm_runtime/qpy/binary_io/schedules.py deleted file mode 100644 index 183bb50df..000000000 --- a/qiskit_ibm_runtime/qpy/binary_io/schedules.py +++ /dev/null @@ -1,656 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name - -"""Read and write schedule and schedule instructions.""" -import json -import struct -import zlib -import warnings - -from io import BytesIO -import numpy as np - -from qiskit.pulse import library, channels, instructions -from qiskit.pulse.schedule import ScheduleBlock -from qiskit.utils import optionals as _optional -from qiskit.pulse.configuration import Kernel, Discriminator -from .. import formats, common, type_keys -from ..exceptions import QpyError -from . import value - - -if _optional.HAS_SYMENGINE: - import symengine as sym -else: - import sympy as sym - - -def _read_channel(file_obj, version): # type: ignore[no-untyped-def] - type_key = common.read_type_key(file_obj) - index = value.read_value(file_obj, version, {}) - - channel_cls = type_keys.ScheduleChannel.retrieve(type_key) - - return channel_cls(index) - - -def _read_waveform(file_obj, version): # type: ignore[no-untyped-def] - header = formats.WAVEFORM._make( - struct.unpack( - formats.WAVEFORM_PACK, - file_obj.read(formats.WAVEFORM_SIZE), - ) - ) - samples_raw = file_obj.read(header.data_size) - samples = common.data_from_binary(samples_raw, np.load) - name = value.read_value(file_obj, version, {}) - - return library.Waveform( - samples=samples, - name=name, - epsilon=header.epsilon, - limit_amplitude=header.amp_limited, - ) - - -def _loads_obj(type_key, binary_data, version, vectors): # type: ignore[no-untyped-def] - """Wraps `value.loads_value` to deserialize binary data to dictionary - or list objects which are not supported by `value.loads_value`. - """ - if type_key == b"D": - with BytesIO(binary_data) as container: - return common.read_mapping( - file_obj=container, - deserializer=_loads_obj, - version=version, - vectors=vectors, - ) - elif type_key == b"l": - with BytesIO(binary_data) as container: - return common.read_sequence( - file_obj=container, - deserializer=_loads_obj, - version=version, - vectors=vectors, - ) - else: - return value.loads_value(type_key, binary_data, version, vectors) - - -def _read_kernel(file_obj, version): # type: ignore[no-untyped-def] - params = common.read_mapping( - file_obj=file_obj, - deserializer=_loads_obj, - version=version, - vectors={}, - ) - name = value.read_value(file_obj, version, {}) - return Kernel(name=name, **params) - - -def _read_discriminator(file_obj, version): # type: ignore[no-untyped-def] - params = common.read_mapping( - file_obj=file_obj, - deserializer=_loads_obj, - version=version, - vectors={}, - ) - name = value.read_value(file_obj, version, {}) - return Discriminator(name=name, **params) - - -def _loads_symbolic_expr(expr_bytes, use_symengine=False): # type: ignore[no-untyped-def] - if expr_bytes == b"": - return None - - if use_symengine: - _optional.HAS_SYMENGINE.require_now("load a symengine expression") - from symengine.lib.symengine_wrapper import ( # pylint: disable=import-outside-toplevel, no-name-in-module - load_basic, - ) - - expr = load_basic(zlib.decompress(expr_bytes)) - - else: - from sympy import parse_expr # pylint: disable=import-outside-toplevel - - expr_txt = zlib.decompress(expr_bytes).decode(common.ENCODE) - expr = parse_expr(expr_txt) - if _optional.HAS_SYMENGINE: - from symengine import sympify # pylint: disable=import-outside-toplevel - - return sympify(expr) - return expr - - -def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def] - make = formats.SYMBOLIC_PULSE._make - pack = formats.SYMBOLIC_PULSE_PACK - size = formats.SYMBOLIC_PULSE_SIZE - - header = make( - struct.unpack( - pack, - file_obj.read(size), - ) - ) - pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) - envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size)) - constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size)) - valid_amp_conditions = _loads_symbolic_expr(file_obj.read(header.valid_amp_conditions_size)) - parameters = common.read_mapping( - file_obj, - deserializer=value.loads_value, - version=version, - vectors={}, - ) - - # In the transition to Qiskit Terra 0.23 (QPY version 6), the representation of library pulses - # was changed from complex "amp" to float "amp" and "angle". The existing library pulses in - # previous versions are handled here separately to conform with the new representation. To - # avoid role assumption for "amp" for custom pulses, only the library pulses are handled this - # way. - - # List of pulses in the library in QPY version 5 and below: - legacy_library_pulses = ["Gaussian", "GaussianSquare", "Drag", "Constant"] - class_name = "SymbolicPulse" # Default class name, if not in the library - - if pulse_type in legacy_library_pulses: - # Once complex amp support will be deprecated we will need: - # parameters["angle"] = np.angle(parameters["amp"]) - # parameters["amp"] = np.abs(parameters["amp"]) - - # In the meanwhile we simply add: - parameters["angle"] = 0 - _amp, _angle = sym.symbols("amp, angle") - envelope = envelope.subs(_amp, _amp * sym.exp(sym.I * _angle)) - - # And warn that this will change in future releases: - warnings.warn( - "Complex amp support for symbolic library pulses will be deprecated. " - "Once deprecated, library pulses loaded from old QPY files (Terra version < 0.23)," - " will be converted automatically to float (amp,angle) representation.", - PendingDeprecationWarning, - ) - class_name = "ScalableSymbolicPulse" - - duration = value.read_value(file_obj, version, {}) - name = value.read_value(file_obj, version, {}) - - if class_name == "SymbolicPulse": - return library.SymbolicPulse( - pulse_type=pulse_type, - duration=duration, - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - elif class_name == "ScalableSymbolicPulse": - return library.ScalableSymbolicPulse( - pulse_type=pulse_type, - duration=duration, - amp=parameters["amp"], - angle=parameters["angle"], - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - else: - raise NotImplementedError(f"Unknown class '{class_name}'") - - -def _read_symbolic_pulse_v6(file_obj, version, use_symengine): # type: ignore[no-untyped-def] - make = formats.SYMBOLIC_PULSE_V2._make - pack = formats.SYMBOLIC_PULSE_PACK_V2 - size = formats.SYMBOLIC_PULSE_SIZE_V2 - - header = make( - struct.unpack( - pack, - file_obj.read(size), - ) - ) - class_name = file_obj.read(header.class_name_size).decode(common.ENCODE) - pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) - envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine) - constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine) - valid_amp_conditions = _loads_symbolic_expr( - file_obj.read(header.valid_amp_conditions_size), use_symengine - ) - parameters = common.read_mapping( - file_obj, - deserializer=value.loads_value, - version=version, - vectors={}, - ) - - duration = value.read_value(file_obj, version, {}) - name = value.read_value(file_obj, version, {}) - - if class_name == "SymbolicPulse": - return library.SymbolicPulse( - pulse_type=pulse_type, - duration=duration, - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - elif class_name == "ScalableSymbolicPulse": - return library.ScalableSymbolicPulse( - pulse_type=pulse_type, - duration=duration, - amp=parameters["amp"], - angle=parameters["angle"], - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - else: - raise NotImplementedError(f"Unknown class '{class_name}'") - - -def _read_alignment_context(file_obj, version): # type: ignore[no-untyped-def] - type_key = common.read_type_key(file_obj) - - context_params = common.read_sequence( - file_obj, - deserializer=value.loads_value, - version=version, - vectors={}, - ) - context_cls = type_keys.ScheduleAlignment.retrieve(type_key) - - instance = object.__new__(context_cls) - instance._context_params = tuple(context_params) - - return instance - - -# pylint: disable=too-many-return-statements -def _loads_operand(type_key, data_bytes, version, use_symengine): # type: ignore[no-untyped-def] - if type_key == type_keys.ScheduleOperand.WAVEFORM: - return common.data_from_binary(data_bytes, _read_waveform, version=version) - if type_key == type_keys.ScheduleOperand.SYMBOLIC_PULSE: - if version < 6: - return common.data_from_binary(data_bytes, _read_symbolic_pulse, version=version) - else: - return common.data_from_binary( - data_bytes, - _read_symbolic_pulse_v6, - version=version, - use_symengine=use_symengine, - ) - 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) - if type_key == type_keys.ScheduleOperand.KERNEL: - return common.data_from_binary( - data_bytes, - _read_kernel, - version=version, - ) - if type_key == type_keys.ScheduleOperand.DISCRIMINATOR: - return common.data_from_binary( - data_bytes, - _read_discriminator, - version=version, - ) - - return value.loads_value(type_key, data_bytes, version, {}) - - -def _read_element( # type: ignore[no-untyped-def] - file_obj, version, metadata_deserializer, use_symengine -): - type_key = common.read_type_key(file_obj) - - if type_key == type_keys.Program.SCHEDULE_BLOCK: - return read_schedule_block(file_obj, version, metadata_deserializer, use_symengine) - - operands = common.read_sequence( - file_obj, - deserializer=_loads_operand, - version=version, - use_symengine=use_symengine, - ) - name = value.read_value(file_obj, version, {}) - - instance = object.__new__(type_keys.ScheduleInstruction.retrieve(type_key)) - instance._operands = tuple(operands) - instance._name = name - instance._hash = None - - 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) - value.write_value(file_obj, data.index) - - -def _write_waveform(file_obj, data): # type: ignore[no-untyped-def] - samples_bytes = common.data_to_binary(data.samples, np.save) - - header = struct.pack( - formats.WAVEFORM_PACK, - data.epsilon, - len(samples_bytes), - data._limit_amplitude, - ) - file_obj.write(header) - file_obj.write(samples_bytes) - value.write_value(file_obj, data.name) - - -def _dumps_obj(obj): # type: ignore[no-untyped-def] - """Wraps `value.dumps_value` to serialize dictionary and list objects - which are not supported by `value.dumps_value`. - """ - if isinstance(obj, dict): - with BytesIO() as container: - common.write_mapping(file_obj=container, mapping=obj, serializer=_dumps_obj) - binary_data = container.getvalue() - return b"D", binary_data - elif isinstance(obj, list): - with BytesIO() as container: - common.write_sequence(file_obj=container, sequence=obj, serializer=_dumps_obj) - binary_data = container.getvalue() - return b"l", binary_data - else: - return value.dumps_value(obj) - - -def _write_kernel(file_obj, data): # type: ignore[no-untyped-def] - name = data.name - params = data.params - common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj) - value.write_value(file_obj, name) - - -def _write_discriminator(file_obj, data): # type: ignore[no-untyped-def] - name = data.name - params = data.params - common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj) - value.write_value(file_obj, name) - - -def _dumps_symbolic_expr(expr, use_symengine): # type: ignore[no-untyped-def] - if expr is None: - return b"" - - if use_symengine: - _optional.HAS_SYMENGINE.require_now("dump a symengine expression") - expr_bytes = expr.__reduce__()[1][0] - else: - from sympy import srepr, sympify # pylint: disable=import-outside-toplevel - - expr_bytes = srepr(sympify(expr)).encode(common.ENCODE) - return zlib.compress(expr_bytes) - - -def _write_symbolic_pulse(file_obj, data, use_symengine): # type: ignore[no-untyped-def] - class_name_bytes = data.__class__.__name__.encode(common.ENCODE) - pulse_type_bytes = data.pulse_type.encode(common.ENCODE) - envelope_bytes = _dumps_symbolic_expr(data.envelope, use_symengine) - constraints_bytes = _dumps_symbolic_expr(data.constraints, use_symengine) - valid_amp_conditions_bytes = _dumps_symbolic_expr(data.valid_amp_conditions, use_symengine) - - header_bytes = struct.pack( - formats.SYMBOLIC_PULSE_PACK_V2, - len(class_name_bytes), - len(pulse_type_bytes), - len(envelope_bytes), - len(constraints_bytes), - len(valid_amp_conditions_bytes), - data._limit_amplitude, - ) - file_obj.write(header_bytes) - file_obj.write(class_name_bytes) - file_obj.write(pulse_type_bytes) - file_obj.write(envelope_bytes) - file_obj.write(constraints_bytes) - file_obj.write(valid_amp_conditions_bytes) - common.write_mapping( - file_obj, - mapping=data._params, - serializer=value.dumps_value, - ) - value.write_value(file_obj, data.duration) - value.write_value(file_obj, data.name) - - -def _write_alignment_context(file_obj, context): # type: ignore[no-untyped-def] - type_key = type_keys.ScheduleAlignment.assign(context) - common.write_type_key(file_obj, type_key) - common.write_sequence( - file_obj, - sequence=context._context_params, - serializer=value.dumps_value, - ) - - -def _dumps_operand(operand, use_symengine): # type: ignore[no-untyped-def] - if isinstance(operand, library.Waveform): - type_key = type_keys.ScheduleOperand.WAVEFORM - data_bytes = common.data_to_binary(operand, _write_waveform) - elif isinstance(operand, library.SymbolicPulse): - type_key = type_keys.ScheduleOperand.SYMBOLIC_PULSE - data_bytes = common.data_to_binary( - operand, _write_symbolic_pulse, use_symengine=use_symengine - ) - 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) - elif isinstance(operand, Kernel): - type_key = type_keys.ScheduleOperand.KERNEL - data_bytes = common.data_to_binary(operand, _write_kernel) - elif isinstance(operand, Discriminator): - type_key = type_keys.ScheduleOperand.DISCRIMINATOR - data_bytes = common.data_to_binary(operand, _write_discriminator) - else: - type_key, data_bytes = value.dumps_value(operand) - - return type_key, data_bytes - - -def _write_element( # type: ignore[no-untyped-def] - file_obj, element, metadata_serializer, use_symengine -): - if isinstance(element, ScheduleBlock): - common.write_type_key(file_obj, type_keys.Program.SCHEDULE_BLOCK) - write_schedule_block(file_obj, element, metadata_serializer, use_symengine) - else: - type_key = type_keys.ScheduleInstruction.assign(element) - common.write_type_key(file_obj, type_key) - common.write_sequence( - file_obj, - sequence=element.operands, - serializer=_dumps_operand, - use_symengine=use_symengine, - ) - 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( # type: ignore[no-untyped-def] - file_obj, version, metadata_deserializer=None, use_symengine=False -): - """Read a single ScheduleBlock from the file like object. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - version (int): QPY version. - metadata_deserializer (JSONDecoder): An optional JSONDecoder class - that will be used for the ``cls`` kwarg on the internal - ``json.load`` call used to deserialize the JSON payload used for - the :attr:`.ScheduleBlock.metadata` attribute for a schdule block - in the file-like object. If this is not specified the circuit metadata will - be parsed as JSON with the stdlib ``json.load()`` function using - the default ``JSONDecoder`` class. - - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - - Returns: - ScheduleBlock: The schedule block object from the file. - - Raises: - TypeError: If any of the instructions is invalid data format. - QiskitError: QPY version is earlier than block support. - """ - - data = formats.SCHEDULE_BLOCK_HEADER._make( - struct.unpack( - formats.SCHEDULE_BLOCK_HEADER_PACK, - file_obj.read(formats.SCHEDULE_BLOCK_HEADER_SIZE), - ) - ) - name = file_obj.read(data.name_size).decode(common.ENCODE) - metadata_raw = file_obj.read(data.metadata_size) - metadata = json.loads(metadata_raw, cls=metadata_deserializer) - context = _read_alignment_context(file_obj, version) - - block = ScheduleBlock( - name=name, - metadata=metadata, - alignment_context=context, - ) - for _ in range(data.num_elements): - block_elm = _read_element(file_obj, version, metadata_deserializer, use_symengine) - 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 - - -def write_schedule_block( # type: ignore[no-untyped-def] - file_obj, block, metadata_serializer=None, use_symengine=False -): - """Write a single ScheduleBlock object in the file like object. - - Args: - file_obj (File): The file like object to write the circuit data in. - block (ScheduleBlock): A schedule block data to write. - metadata_serializer (JSONEncoder): An optional JSONEncoder class that - will be passed the :attr:`.ScheduleBlock.metadata` dictionary for - ``block`` and will be used as the ``cls`` kwarg - on the ``json.dump()`` call to JSON serialize that dictionary. - - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - - Raises: - TypeError: If any of the instructions is invalid data format. - """ - metadata = json.dumps(block.metadata, separators=(",", ":"), cls=metadata_serializer).encode( - common.ENCODE - ) - block_name = block.name.encode(common.ENCODE) - - # Write schedule block header - header_raw = formats.SCHEDULE_BLOCK_HEADER( - name_size=len(block_name), - metadata_size=len(metadata), - num_elements=len(block), - ) - header = struct.pack(formats.SCHEDULE_BLOCK_HEADER_PACK, *header_raw) - file_obj.write(header) - file_obj.write(block_name) - file_obj.write(metadata) - - _write_alignment_context(file_obj, block.alignment_context) - for block_elm in block._blocks: - _write_element(file_obj, block_elm, metadata_serializer, use_symengine) - - # 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_runtime/qpy/binary_io/value.py b/qiskit_ibm_runtime/qpy/binary_io/value.py deleted file mode 100644 index ca89d7ab3..000000000 --- a/qiskit_ibm_runtime/qpy/binary_io/value.py +++ /dev/null @@ -1,586 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Binary IO for any value objects, such as numbers, string, parameters.""" -# pylint: disable=line-too-long - -from __future__ import annotations - -import collections.abc -import struct -import uuid - -from typing import Any -import numpy as np - -from qiskit.circuit import CASE_DEFAULT, Clbit, ClassicalRegister -from qiskit.circuit.classical import expr, types -from qiskit.circuit.parameter import Parameter -from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement -from qiskit.utils import optionals as _optional - -from .. import common, formats, exceptions, type_keys - - -def _write_parameter(file_obj, obj): # type: ignore[no-untyped-def] - name_bytes = obj._name.encode(common.ENCODE) - file_obj.write(struct.pack(formats.PARAMETER_PACK, len(name_bytes), obj._uuid.bytes)) - file_obj.write(name_bytes) - - -def _write_parameter_vec(file_obj, obj): # type: ignore[no-untyped-def] - name_bytes = obj._vector._name.encode(common.ENCODE) - file_obj.write( - struct.pack( - formats.PARAMETER_VECTOR_ELEMENT_PACK, - len(name_bytes), - obj._vector._size, - obj._uuid.bytes, - obj._index, - ) - ) - file_obj.write(name_bytes) - - -def _write_parameter_expression(file_obj, obj, use_symengine): # type: ignore[no-untyped-def] - if use_symengine: - _optional.HAS_SYMENGINE.require_now("write_parameter_expression") - expr_bytes = obj._symbol_expr.__reduce__()[1][0] - else: - from sympy import srepr, sympify # pylint: disable=import-outside-toplevel - - expr_bytes = srepr(sympify(obj._symbol_expr)).encode(common.ENCODE) - - param_expr_header_raw = struct.pack( - formats.PARAMETER_EXPR_PACK, len(obj._parameter_symbols), len(expr_bytes) - ) - file_obj.write(param_expr_header_raw) - file_obj.write(expr_bytes) - - for symbol, value in obj._parameter_symbols.items(): - symbol_key = type_keys.Value.assign(symbol) - - # serialize key - if symbol_key == type_keys.Value.PARAMETER_VECTOR: - symbol_data = common.data_to_binary(symbol, _write_parameter_vec) - else: - symbol_data = common.data_to_binary(symbol, _write_parameter) - - # serialize value - if value == symbol._symbol_expr: - value_key = symbol_key - value_data = bytes() - else: - value_key, value_data = dumps_value(value, use_symengine=use_symengine) - - elem_header = struct.pack( - formats.PARAM_EXPR_MAP_ELEM_V3_PACK, - symbol_key, - value_key, - len(value_data), - ) - file_obj.write(elem_header) - file_obj.write(symbol_data) - file_obj.write(value_data) - - -class _ExprWriter(expr.ExprVisitor[None]): - __slots__ = ("file_obj", "clbit_indices") - - def __init__(self, file_obj, clbit_indices): # type: ignore[no-untyped-def] - self.file_obj = file_obj - self.clbit_indices = clbit_indices - - def visit_generic(self, node, /): # type: ignore[no-untyped-def] - raise exceptions.QpyError(f"unhandled Expr object '{node}'") - - def visit_var(self, node, /): # type: ignore[no-untyped-def] - self.file_obj.write(type_keys.Expression.VAR) - _write_expr_type(self.file_obj, node.type) - if isinstance(node.var, Clbit): - self.file_obj.write(type_keys.ExprVar.CLBIT) - self.file_obj.write( - struct.pack( - formats.EXPR_VAR_CLBIT_PACK, - *formats.EXPR_VAR_CLBIT(self.clbit_indices[node.var]), - ) - ) - elif isinstance(node.var, ClassicalRegister): - self.file_obj.write(type_keys.ExprVar.REGISTER) - self.file_obj.write( - struct.pack( - formats.EXPR_VAR_REGISTER_PACK, - *formats.EXPR_VAR_REGISTER(len(node.var.name)), - ) - ) - self.file_obj.write(node.var.name.encode(common.ENCODE)) - else: - raise exceptions.QpyError(f"unhandled Var object '{node.var}'") - - def visit_value(self, node, /): # type: ignore[no-untyped-def] - self.file_obj.write(type_keys.Expression.VALUE) - _write_expr_type(self.file_obj, node.type) - if node.value is True or node.value is False: - self.file_obj.write(type_keys.ExprValue.BOOL) - self.file_obj.write( - struct.pack(formats.EXPR_VALUE_BOOL_PACK, *formats.EXPR_VALUE_BOOL(node.value)) - ) - elif isinstance(node.value, int): - self.file_obj.write(type_keys.ExprValue.INT) - if node.value == 0: - num_bytes = 0 - buffer = b"" - else: - # This wastes a byte for `-(2 ** (8*n - 1))` for natural `n`, but they'll still - # decode fine so it's not worth another special case. They'll encode to - # b"\xff\x80\x00\x00...", but we could encode them to b"\x80\x00\x00...". - num_bytes = (node.value.bit_length() // 8) + 1 - buffer = node.value.to_bytes(num_bytes, "big", signed=True) - self.file_obj.write( - struct.pack(formats.EXPR_VALUE_INT_PACK, *formats.EXPR_VALUE_INT(num_bytes)) - ) - self.file_obj.write(buffer) - else: - raise exceptions.QpyError(f"unhandled Value object '{node.value}'") - - def visit_cast(self, node, /): # type: ignore[no-untyped-def] - self.file_obj.write(type_keys.Expression.CAST) - _write_expr_type(self.file_obj, node.type) - self.file_obj.write( - struct.pack(formats.EXPRESSION_CAST_PACK, *formats.EXPRESSION_CAST(node.implicit)) - ) - node.operand.accept(self) - - def visit_unary(self, node, /): # type: ignore[no-untyped-def] - self.file_obj.write(type_keys.Expression.UNARY) - _write_expr_type(self.file_obj, node.type) - self.file_obj.write( - struct.pack(formats.EXPRESSION_UNARY_PACK, *formats.EXPRESSION_UNARY(node.op.value)) - ) - node.operand.accept(self) - - def visit_binary(self, node, /): # type: ignore[no-untyped-def] - self.file_obj.write(type_keys.Expression.BINARY) - _write_expr_type(self.file_obj, node.type) - self.file_obj.write( - struct.pack(formats.EXPRESSION_BINARY_PACK, *formats.EXPRESSION_UNARY(node.op.value)) - ) - node.left.accept(self) - node.right.accept(self) - - -def _write_expr( # type: ignore[no-untyped-def] - file_obj, node: expr.Expr, clbit_indices: collections.abc.Mapping[Clbit, int] -) -> None: - node.accept(_ExprWriter(file_obj, clbit_indices)) # type: ignore[no-untyped-call] - - -def _write_expr_type(file_obj, type_: types.Type): # type: ignore[no-untyped-def] - if type_.kind is types.Bool: - file_obj.write(type_keys.ExprType.BOOL) - elif type_.kind is types.Uint: - file_obj.write(type_keys.ExprType.UINT) - file_obj.write( - struct.pack( - formats.EXPR_TYPE_UINT_PACK, # type: ignore[attr-defined] - *formats.EXPR_TYPE_UINT(type_.width), # type: ignore[attr-defined] - ) - ) - else: - raise exceptions.QpyError(f"unhandled Type object '{type_};") - - -def _read_parameter(file_obj): # type: ignore[no-untyped-def] - data = formats.PARAMETER( - *struct.unpack(formats.PARAMETER_PACK, file_obj.read(formats.PARAMETER_SIZE)) - ) - param_uuid = uuid.UUID(bytes=data.uuid) - name = file_obj.read(data.name_size).decode(common.ENCODE) - return Parameter(name, uuid=param_uuid) - - -def _read_parameter_vec(file_obj, vectors): # type: ignore[no-untyped-def] - data = formats.PARAMETER_VECTOR_ELEMENT( - *struct.unpack( - formats.PARAMETER_VECTOR_ELEMENT_PACK, - file_obj.read(formats.PARAMETER_VECTOR_ELEMENT_SIZE), - ), - ) - param_uuid = uuid.UUID(bytes=data.uuid) - name = file_obj.read(data.vector_name_size).decode(common.ENCODE) - if name not in vectors: - vectors[name] = (ParameterVector(name, data.vector_size), set()) - vector = vectors[name][0] - if vector[data.index]._uuid != param_uuid: - vectors[name][1].add(data.index) - vector._params[data.index] = ParameterVectorElement(vector, data.index, uuid=param_uuid) - return vector[data.index] - - -def _read_parameter_expression(file_obj): # type: ignore[no-untyped-def] - data = formats.PARAMETER_EXPR( - *struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE)) - ) - # pylint: disable=import-outside-toplevel - from sympy.parsing.sympy_parser import parse_expr - - if _optional.HAS_SYMENGINE: - from symengine import sympify # pylint: disable=import-outside-toplevel - - expr_ = sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))) - else: - expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)) - symbol_map = {} - for _ in range(data.map_elements): - elem_data = formats.PARAM_EXPR_MAP_ELEM( - *struct.unpack( - formats.PARAM_EXPR_MAP_ELEM_PACK, - file_obj.read(formats.PARAM_EXPR_MAP_ELEM_SIZE), - ) - ) - symbol = _read_parameter(file_obj) - - elem_key = type_keys.Value(elem_data.type) - binary_data = file_obj.read(elem_data.size) - if elem_key == type_keys.Value.INTEGER: - value = struct.unpack("!q", binary_data) - elif elem_key == type_keys.Value.FLOAT: - value = struct.unpack("!d", binary_data) - elif elem_key == type_keys.Value.COMPLEX: - value = complex(*struct.unpack(formats.COMPLEX_PACK, binary_data)) - elif elem_key == type_keys.Value.PARAMETER: - value = symbol._symbol_expr - elif elem_key == type_keys.Value.PARAMETER_EXPRESSION: - value = common.data_from_binary(binary_data, _read_parameter_expression) - else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key) - symbol_map[symbol] = value - - return ParameterExpression(symbol_map, expr_) - - -# pylint: disable=too-many-return-statements -def _read_expr( # type: ignore[no-untyped-def] - file_obj, - clbits: collections.abc.Sequence[Clbit], - cregs: collections.abc.Mapping[str, ClassicalRegister], -) -> expr.Expr: # pylint: disable=too-many-return-statements - type_key = file_obj.read(formats.EXPRESSION_DISCRIMINATOR_SIZE) # type: ignore[attr-defined] - type_ = _read_expr_type(file_obj) - if type_key == type_keys.Expression.VAR: - var_type_key = file_obj.read(formats.EXPR_VAR_DISCRIMINATOR_SIZE) # type: ignore[attr-defined] - if var_type_key == type_keys.ExprVar.CLBIT: - payload = formats.EXPR_VAR_CLBIT._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPR_VAR_CLBIT_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPR_VAR_CLBIT_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Var(clbits[payload.index], type_) - if var_type_key == type_keys.ExprVar.REGISTER: - payload = formats.EXPR_VAR_REGISTER._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPR_VAR_REGISTER_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPR_VAR_REGISTER_SIZE), # type: ignore[attr-defined] - ) - ) - name = file_obj.read(payload.reg_name_size).decode(common.ENCODE) - return expr.Var(cregs[name], type_) - raise exceptions.QpyError("Invalid classical-expression Var key '{var_type_key}'") - if type_key == type_keys.Expression.VALUE: - value_type_key = file_obj.read(formats.EXPR_VALUE_DISCRIMINATOR_SIZE) # type: ignore[attr-defined] - if value_type_key == type_keys.ExprValue.BOOL: - payload = formats.EXPR_VALUE_BOOL._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPR_VALUE_BOOL_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPR_VALUE_BOOL_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Value(payload.value, type_) - if value_type_key == type_keys.ExprValue.INT: - payload = formats.EXPR_VALUE_INT._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPR_VALUE_INT_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPR_VALUE_INT_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Value( - int.from_bytes(file_obj.read(payload.num_bytes), "big", signed=True), - type_, - ) - raise exceptions.QpyError("Invalid classical-expression Value key '{value_type_key}'") - if type_key == type_keys.Expression.CAST: - payload = formats.EXPRESSION_CAST._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPRESSION_CAST_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPRESSION_CAST_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Cast(_read_expr(file_obj, clbits, cregs), type_, implicit=payload.implicit) - if type_key == type_keys.Expression.UNARY: - payload = formats.EXPRESSION_UNARY._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPRESSION_UNARY_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPRESSION_UNARY_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Unary(expr.Unary.Op(payload.opcode), _read_expr(file_obj, clbits, cregs), type_) - if type_key == type_keys.Expression.BINARY: - payload = formats.EXPRESSION_BINARY._make( # type: ignore[attr-defined] - struct.unpack( - formats.EXPRESSION_BINARY_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPRESSION_BINARY_SIZE), # type: ignore[attr-defined] - ) - ) - return expr.Binary( - expr.Binary.Op(payload.opcode), - _read_expr(file_obj, clbits, cregs), - _read_expr(file_obj, clbits, cregs), - type_, - ) - raise exceptions.QpyError("Invalid classical-expression Expr key '{type_key}'") - - -def _read_expr_type(file_obj) -> types.Type: # type: ignore[no-untyped-def] - type_key = file_obj.read(formats.EXPR_TYPE_DISCRIMINATOR_SIZE) # type: ignore[no-untyped-def, attr-defined] - if type_key == type_keys.ExprType.BOOL: - return types.Bool() - if type_key == type_keys.ExprType.UINT: # type: ignore[no-untyped-def, attr-defined] - elem = formats.EXPR_TYPE_UINT._make( # type: ignore[no-untyped-def, attr-defined] - struct.unpack( # type: ignore[no-untyped-def] - formats.EXPR_TYPE_UINT_PACK, # type: ignore[attr-defined] - file_obj.read(formats.EXPR_TYPE_UINT_SIZE), # type: ignore[no-untyped-def, attr-defined] - ) - ) - return types.Uint(elem.width) - raise exceptions.QpyError(f"Invalid classical-expression Type key '{type_key}'") - - -def _read_parameter_expression_v3(file_obj, vectors, use_symengine): # type: ignore[no-untyped-def] - data = formats.PARAMETER_EXPR( - *struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE)) - ) - # pylint: disable=import-outside-toplevel - from sympy.parsing.sympy_parser import parse_expr - - # pylint: disable=import-outside-toplevel - - payload = file_obj.read(data.expr_size) - if use_symengine: - _optional.HAS_SYMENGINE.require_now("read_parameter_expression_v3") - from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module - load_basic, - ) - - expr_ = load_basic(payload) - else: - if _optional.HAS_SYMENGINE: - from symengine import sympify - - expr_ = sympify(parse_expr(payload.decode(common.ENCODE))) - else: - expr_ = parse_expr(payload.decode(common.ENCODE)) - symbol_map = {} - for _ in range(data.map_elements): - elem_data = formats.PARAM_EXPR_MAP_ELEM_V3( - *struct.unpack( - formats.PARAM_EXPR_MAP_ELEM_V3_PACK, - file_obj.read(formats.PARAM_EXPR_MAP_ELEM_V3_SIZE), - ) - ) - symbol_key = type_keys.Value(elem_data.symbol_type) - - if symbol_key == type_keys.Value.PARAMETER: - symbol = _read_parameter(file_obj) - elif symbol_key == type_keys.Value.PARAMETER_VECTOR: - symbol = _read_parameter_vec(file_obj, vectors) - else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % symbol_key) - - elem_key = type_keys.Value(elem_data.type) - binary_data = file_obj.read(elem_data.size) - if elem_key == type_keys.Value.INTEGER: - value = struct.unpack("!q", binary_data) - elif elem_key == type_keys.Value.FLOAT: - value = struct.unpack("!d", binary_data) - elif elem_key == type_keys.Value.COMPLEX: - value = complex(*struct.unpack(formats.COMPLEX_PACK, binary_data)) - elif elem_key in (type_keys.Value.PARAMETER, type_keys.Value.PARAMETER_VECTOR): - value = symbol._symbol_expr - elif elem_key == type_keys.Value.PARAMETER_EXPRESSION: - value = common.data_from_binary( - binary_data, - _read_parameter_expression_v3, - vectors=vectors, - use_symengine=use_symengine, - ) - else: - raise exceptions.QpyError("Invalid parameter expression map type: %s" % elem_key) - symbol_map[symbol] = value - - return ParameterExpression(symbol_map, expr_) - - -def dumps_value(obj, *, index_map=None, use_symengine=False): # type: ignore[no-untyped-def] - """Serialize input value object. - Args: - obj (any): Arbitrary value object to serialize. - index_map (dict): Dictionary with two keys, "q" and "c". Each key has a value that is a - dictionary mapping :class:`.Qubit` or :class:`.Clbit` instances (respectively) to their - integer indices. - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - Returns: - tuple: TypeKey and binary data. - Raises: - QpyError: Serializer for given format is not ready. - """ - type_key = type_keys.Value.assign(obj) - - if type_key == type_keys.Value.INTEGER: - binary_data = struct.pack("!q", obj) - elif type_key == type_keys.Value.FLOAT: - binary_data = struct.pack("!d", obj) - elif type_key == type_keys.Value.COMPLEX: - binary_data = struct.pack(formats.COMPLEX_PACK, obj.real, obj.imag) - elif type_key == type_keys.Value.NUMPY_OBJ: - 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 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) - elif type_key == type_keys.Value.PARAMETER: - binary_data = common.data_to_binary(obj, _write_parameter) - elif type_key == type_keys.Value.PARAMETER_EXPRESSION: - binary_data = common.data_to_binary( - obj, _write_parameter_expression, use_symengine=use_symengine - ) # type: ignore[no-untyped-call] - elif type_key == type_keys.Value.EXPRESSION: - clbit_indices = {} if index_map is None else index_map["c"] - binary_data = common.data_to_binary(obj, _write_expr, clbit_indices=clbit_indices) - else: - raise exceptions.QpyError(f"Serialization for {type_key} is not implemented in value I/O.") - - return type_key, binary_data - - -def write_value(file_obj, obj, *, index_map=None, use_symengine=False): # type: ignore[no-untyped-def] - """Write a value to the file like object. - Args: - file_obj (File): A file like object to write data. - obj (any): Value to write. - index_map (dict): Dictionary with two keys, "q" and "c". - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - """ - type_key, data = dumps_value(obj, index_map=index_map, use_symengine=use_symengine) - common.write_generic_typed_data(file_obj, type_key, data) - - -def loads_value( # type: ignore[no-untyped-def] - type_key, - binary_data, - version, - vectors, - *, - clbits=(), - cregs=None, - use_symengine=False, -) -> Any: - """Deserialize input binary data to value object. - Args: - type_key (ValueTypeKey): Type enum information. - binary_data (bytes): Data to deserialize. - version (int): QPY version. - vectors (dict): ParameterVector in current scope. - clbits (Sequence[Clbit]): Clbits in the current scope. - cregs (Mapping[str, ClassicalRegister]): Classical registers in the current scope. - use_symengine (bool): If True, symbolic objects will be de-serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - Returns: - any: Deserialized value object. - Raises: - QpyError: Serializer for given format is not ready. - """ - # pylint: disable=too-many-return-statements - - if isinstance(type_key, bytes): - type_key = type_keys.Value(type_key) - - if type_key == type_keys.Value.INTEGER: - return struct.unpack("!q", binary_data)[0] - if type_key == type_keys.Value.FLOAT: - return struct.unpack("!d", binary_data)[0] - if type_key == type_keys.Value.COMPLEX: - return complex(*struct.unpack(formats.COMPLEX_PACK, binary_data)) # type: ignore[attr-defined] - if type_key == type_keys.Value.NUMPY_OBJ: - return common.data_from_binary(binary_data, np.load) - if type_key == type_keys.Value.STRING: - 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) - if type_key == type_keys.Value.PARAMETER: - return common.data_from_binary(binary_data, _read_parameter) - if type_key == type_keys.Value.PARAMETER_EXPRESSION: - if version < 3: - return common.data_from_binary(binary_data, _read_parameter_expression) - else: - return common.data_from_binary( - binary_data, - _read_parameter_expression_v3, - vectors=vectors, - use_symengine=use_symengine, - ) - if type_key == type_keys.Value.EXPRESSION: - return common.data_from_binary(binary_data, _read_expr, clbits=clbits, cregs=cregs or {}) - - raise exceptions.QpyError(f"Serialization for {type_key} is not implemented in value I/O.") - - -def read_value(file_obj, version, vectors, *, clbits=(), cregs=None, use_symengine=False): # type: ignore[no-untyped-def] - """Read a value from the file like object. - Args: - file_obj (File): A file like object to write data. - version (int): QPY version. - vectors (dict): ParameterVector in current scope. - clbits (Sequence[Clbit]): Clbits in the current scope. - cregs (Mapping[str, ClassicalRegister]): Classical registers in the current scope. - use_symengine (bool): If True, symbolic objects will be de-serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - Returns: - any: Deserialized value object. - """ - type_key, data = common.read_generic_typed_data(file_obj) - - return loads_value( - type_key, - data, - version, - vectors, - clbits=clbits, - cregs=cregs, - use_symengine=use_symengine, - ) diff --git a/qiskit_ibm_runtime/qpy/common.py b/qiskit_ibm_runtime/qpy/common.py deleted file mode 100644 index dce023f93..000000000 --- a/qiskit_ibm_runtime/qpy/common.py +++ /dev/null @@ -1,310 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=too-many-return-statements - -""" -Common functions across several serialization and deserialization modules. -""" - -import io -import struct - -from typing import Any -from . import formats - -QPY_VERSION = 10 -ENCODE = "utf8" - - -def read_generic_typed_data(file_obj): # type: ignore[no-untyped-def] - """Read a single data chunk from the file like object. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - - Returns: - tuple: Tuple of type key binary and the bytes object of the single data. - """ - data = formats.INSTRUCTION_PARAM._make( - struct.unpack( - formats.INSTRUCTION_PARAM_PACK, - file_obj.read(formats.INSTRUCTION_PARAM_SIZE), - ) - ) - - return data.type, file_obj.read(data.size) - - -def read_sequence(file_obj, deserializer, **kwargs): # type: ignore[no-untyped-def] - """Read a sequence of data from the file like object. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - deserializer (Callable): Deserializer callback that can handle input object type. - This must take type key and binary data of the element and return object. - **kwargs: Options set to the deserializer. - - Returns: - list: Deserialized object. - """ - sequence = [] - - data = formats.SEQUENCE._make( - struct.unpack(formats.SEQUENCE_PACK, file_obj.read(formats.SEQUENCE_SIZE)) - ) - for _ in range(data.num_elements): - type_key, datum_bytes = read_generic_typed_data(file_obj) - sequence.append(deserializer(type_key, datum_bytes, **kwargs)) - - return sequence - - -def read_mapping(file_obj, deserializer, **kwargs): # type: ignore[no-untyped-def] - """Read a mapping from the file like object. - - .. note:: - - This function must be used to make a binary data of mapping - which include QPY serialized values. - It's easier to use JSON serializer followed by encoding for standard data formats. - This only supports flat dictionary and key must be string. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - deserializer (Callable): Deserializer callback that can handle mapping item. - This must take type key and binary data of the mapping value and return object. - **kwargs: Options set to the deserializer. - - Returns: - dict: Deserialized object. - """ - mapping = {} - - data = formats.SEQUENCE._make( - struct.unpack(formats.SEQUENCE_PACK, file_obj.read(formats.SEQUENCE_SIZE)) - ) - for _ in range(data.num_elements): - map_header = formats.MAP_ITEM._make( - struct.unpack(formats.MAP_ITEM_PACK, file_obj.read(formats.MAP_ITEM_SIZE)) - ) - key = file_obj.read(map_header.key_size).decode(ENCODE) - datum = deserializer(map_header.type, file_obj.read(map_header.size), **kwargs) - mapping[key] = datum - - return mapping - - -def read_type_key(file_obj): # type: ignore[no-untyped-def] - """Read a type key from the file like object. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - - Returns: - bytes: Type key. - """ - key_size = struct.calcsize("!1c") - return struct.unpack("!1c", file_obj.read(key_size))[0] - - -def write_generic_typed_data(file_obj, type_key, data_binary): # type: ignore[no-untyped-def] - """Write statically typed binary data to the file like object. - - Args: - file_obj (File): A file like object to write data. - type_key (Enum): Object type of the data. - data_binary (bytes): Binary data to write. - """ - data_header = struct.pack(formats.INSTRUCTION_PARAM_PACK, type_key, len(data_binary)) - file_obj.write(data_header) - file_obj.write(data_binary) - - -def write_sequence(file_obj, sequence, serializer, **kwargs): # type: ignore[no-untyped-def] - """Write a sequence of data in the file like object. - - Args: - file_obj (File): A file like object to write data. - sequence (Sequence): Object to serialize. - serializer (Callable): Serializer callback that can handle input object type. - This must return type key and binary data of each element. - **kwargs: Options set to the serializer. - """ - num_elements = len(sequence) - - file_obj.write(struct.pack(formats.SEQUENCE_PACK, num_elements)) - for datum in sequence: - type_key, datum_bytes = serializer(datum, **kwargs) - write_generic_typed_data(file_obj, type_key, datum_bytes) - - -def write_mapping(file_obj, mapping, serializer, **kwargs): # type: ignore[no-untyped-def] - """Write a mapping in the file like object. - - .. note:: - - This function must be used to make a binary data of mapping - which include QPY serialized values. - It's easier to use JSON serializer followed by encoding for standard data formats. - This only supports flat dictionary and key must be string. - - Args: - file_obj (File): A file like object to write data. - mapping (Mapping): Object to serialize. - serializer (Callable): Serializer callback that can handle mapping item. - This must return type key and binary data of the mapping value. - **kwargs: Options set to the serializer. - """ - num_elements = len(mapping) - - file_obj.write(struct.pack(formats.SEQUENCE_PACK, num_elements)) - for key, datum in mapping.items(): - key_bytes = key.encode(ENCODE) - type_key, datum_bytes = serializer(datum, **kwargs) - item_header = struct.pack(formats.MAP_ITEM_PACK, len(key_bytes), type_key, len(datum_bytes)) - file_obj.write(item_header) - file_obj.write(key_bytes) - file_obj.write(datum_bytes) - - -def write_type_key(file_obj, type_key): # type: ignore[no-untyped-def] - """Write a type key in the file like object. - - Args: - file_obj (File): A file like object that contains the QPY binary data. - type_key (bytes): Type key to write. - """ - file_obj.write(struct.pack("!1c", type_key)) - - -def data_to_binary(obj, serializer, **kwargs): # type: ignore[no-untyped-def] - """Convert object into binary data with specified serializer. - - Args: - obj (any): Object to serialize. - serializer (Callable): Serializer callback that can handle input object type. - **kwargs: Options set to the serializer. - - Returns: - bytes: Binary data. - """ - with io.BytesIO() as container: - serializer(container, obj, **kwargs) - binary_data = container.getvalue() - - return binary_data - - -def sequence_to_binary(sequence, serializer, **kwargs): # type: ignore[no-untyped-def] - """Convert sequence into binary data with specified serializer. - - Args: - sequence (Sequence): Object to serialize. - serializer (Callable): Serializer callback that can handle input object type. - This must return type key and binary data of each element. - **kwargs: Options set to the serializer. - - Returns: - bytes: Binary data. - """ - with io.BytesIO() as container: - write_sequence(container, sequence, serializer, **kwargs) - binary_data = container.getvalue() - - return binary_data - - -def mapping_to_binary(mapping, serializer, **kwargs): # type: ignore[no-untyped-def] - """Convert mapping into binary data with specified serializer. - - .. note:: - - This function must be used to make a binary data of mapping - which include QPY serialized values. - It's easier to use JSON serializer followed by encoding for standard data formats. - This only supports flat dictionary and key must be string. - - Args: - mapping (Mapping): Object to serialize. - serializer (Callable): Serializer callback that can handle mapping item. - This must return type key and binary data of the mapping value. - **kwargs: Options set to the serializer. - - Returns: - bytes: Binary data. - """ - with io.BytesIO() as container: - write_mapping(container, mapping, serializer, **kwargs) - binary_data = container.getvalue() - - return binary_data - - -def data_from_binary(binary_data, deserializer, **kwargs) -> Any: # type: ignore[no-untyped-def] - """Load object from binary data with specified deserializer. - - Args: - binary_data (bytes): Binary data to deserialize. - deserializer (Callable): Deserializer callback that can handle input object type. - **kwargs: Options set to the deserializer. - - Returns: - any: Deserialized object. - """ - with io.BytesIO(binary_data) as container: - container.seek(0) - obj = deserializer(container, **kwargs) - return obj - - -def sequence_from_binary(binary_data, deserializer, **kwargs): # type: ignore[no-untyped-def] - """Load object from binary sequence with specified deserializer. - - Args: - binary_data (bytes): Binary data to deserialize. - deserializer (Callable): Deserializer callback that can handle input object type. - This must take type key and binary data of the element and return object. - **kwargs: Options set to the deserializer. - - Returns: - any: Deserialized sequence. - """ - with io.BytesIO(binary_data) as container: - sequence = read_sequence(container, deserializer, **kwargs) - - return sequence - - -def mapping_from_binary(binary_data, deserializer, **kwargs): # type: ignore[no-untyped-def] - """Load object from binary mapping with specified deserializer. - - .. note:: - - This function must be used to make a binary data of mapping - which include QPY serialized values. - It's easier to use JSON serializer followed by encoding for standard data formats. - This only supports flat dictionary and key must be string. - - Args: - binary_data (bytes): Binary data to deserialize. - deserializer (Callable): Deserializer callback that can handle mapping item. - This must take type key and binary data of the mapping value and return object. - **kwargs: Options set to the deserializer. - - Returns: - dict: Deserialized object. - """ - with io.BytesIO(binary_data) as container: - mapping = read_mapping(container, deserializer, **kwargs) - - return mapping diff --git a/qiskit_ibm_runtime/qpy/exceptions.py b/qiskit_ibm_runtime/qpy/exceptions.py deleted file mode 100644 index a537ed9a1..000000000 --- a/qiskit_ibm_runtime/qpy/exceptions.py +++ /dev/null @@ -1,29 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised by the pulse module.""" -from typing import Any -from qiskit.qpy.exceptions import QpyError -from ..exceptions import IBMError - - -class IBMQpyError(QpyError, IBMError): - """Errors raised by the qpy module.""" - - def __init__(self, *message: Any): - """Set the error message.""" - super().__init__(*message) - self.message = " ".join(message) - - def __str__(self) -> str: - """Return the message.""" - return repr(self.message) diff --git a/qiskit_ibm_runtime/qpy/formats.py b/qiskit_ibm_runtime/qpy/formats.py deleted file mode 100644 index 133a931b4..000000000 --- a/qiskit_ibm_runtime/qpy/formats.py +++ /dev/null @@ -1,374 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=invalid-name -# type: ignore[name-match] - -"""Binary format definition.""" - -import struct -from collections import namedtuple - -# FILE_HEADER_V10 -FILE_HEADER_V10 = namedtuple( - "FILE_HEADER", - [ - "preface", - "qpy_version", - "major_version", - "minor_version", - "patch_version", - "num_programs", - "symbolic_encoding", - ], -) -FILE_HEADER_V10_PACK = "!6sBBBBQc" -FILE_HEADER_V10_SIZE = struct.calcsize(FILE_HEADER_V10_PACK) - - -# FILE_HEADER -FILE_HEADER = namedtuple( - "FILE_HEADER", - [ - "preface", - "qpy_version", - "major_version", - "minor_version", - "patch_version", - "num_programs", - ], -) -FILE_HEADER_PACK = "!6sBBBBQ" -FILE_HEADER_SIZE = struct.calcsize(FILE_HEADER_PACK) - -# CIRCUIT_HEADER_V2 -CIRCUIT_HEADER_V2 = namedtuple( - "HEADER", - [ - "name_size", - "global_phase_type", - "global_phase_size", - "num_qubits", - "num_clbits", - "metadata_size", - "num_registers", - "num_instructions", - ], -) -CIRCUIT_HEADER_V2_PACK = "!H1cHIIQIQ" -CIRCUIT_HEADER_V2_SIZE = struct.calcsize(CIRCUIT_HEADER_V2_PACK) - -# CIRCUIT_HEADER -CIRCUIT_HEADER = namedtuple( - "HEADER", - [ - "name_size", - "global_phase", - "num_qubits", - "num_clbits", - "metadata_size", - "num_registers", - "num_instructions", - ], -) -CIRCUIT_HEADER_PACK = "!HdIIQIQ" -CIRCUIT_HEADER_SIZE = struct.calcsize(CIRCUIT_HEADER_PACK) - -# REGISTER -REGISTER_V4 = namedtuple("REGISTER", ["type", "standalone", "size", "name_size", "in_circuit"]) -REGISTER_V4_PACK = "!1c?IH?" -REGISTER_V4_SIZE = struct.calcsize(REGISTER_V4_PACK) - -REGISTER = namedtuple("REGISTER", ["type", "standalone", "size", "name_size"]) -REGISTER_PACK = "!1c?IH" -REGISTER_SIZE = struct.calcsize(REGISTER_PACK) - -# CIRCUIT_INSTRUCTION -CIRCUIT_INSTRUCTION = namedtuple( - "CIRCUIT_INSTRUCTION", - [ - "name_size", - "label_size", - "num_parameters", - "num_qargs", - "num_cargs", - "has_condition", - "condition_register_size", - "condition_value", - ], -) -CIRCUIT_INSTRUCTION_PACK = "!HHHII?Hq" -CIRCUIT_INSTRUCTION_SIZE = struct.calcsize(CIRCUIT_INSTRUCTION_PACK) - -# CIRCUIT_INSTRUCTION_V2 -CIRCUIT_INSTRUCTION_V2 = namedtuple( - "CIRCUIT_INSTRUCTION", - [ - "name_size", - "label_size", - "num_parameters", - "num_qargs", - "num_cargs", - "conditional_key", - "condition_register_size", - "condition_value", - "num_ctrl_qubits", - "ctrl_state", - ], -) -CIRCUIT_INSTRUCTION_V2_PACK = "!HHHIIBHqII" -CIRCUIT_INSTRUCTION_V2_SIZE = struct.calcsize(CIRCUIT_INSTRUCTION_V2_PACK) - - -# CIRCUIT_INSTRUCTION_ARG -CIRCUIT_INSTRUCTION_ARG = namedtuple("CIRCUIT_INSTRUCTION_ARG", ["type", "size"]) -CIRCUIT_INSTRUCTION_ARG_PACK = "!1cI" -CIRCUIT_INSTRUCTION_ARG_SIZE = struct.calcsize(CIRCUIT_INSTRUCTION_ARG_PACK) - -# SparsePauliOp List -SPARSE_PAULI_OP_LIST_ELEM = namedtuple("SPARSE_PAULI_OP_LIST_ELEMENT", ["size"]) -SPARSE_PAULI_OP_LIST_ELEM_PACK = "!Q" -SPARSE_PAULI_OP_LIST_ELEM_SIZE = struct.calcsize(SPARSE_PAULI_OP_LIST_ELEM_PACK) - -# Pauli Evolution Gate -PAULI_EVOLUTION_DEF = namedtuple( - "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) - -# CUSTOM_CIRCUIT_DEF_HEADER -CUSTOM_CIRCUIT_DEF_HEADER = namedtuple("CUSTOM_CIRCUIT_DEF_HEADER", ["size"]) -CUSTOM_CIRCUIT_DEF_HEADER_PACK = "!Q" -CUSTOM_CIRCUIT_DEF_HEADER_SIZE = struct.calcsize(CUSTOM_CIRCUIT_DEF_HEADER_PACK) - -# CUSTOM_CIRCUIT_INST_DEF_V2 -CUSTOM_CIRCUIT_INST_DEF_V2 = namedtuple( - "CUSTOM_CIRCUIT_INST_DEF", - [ - "gate_name_size", - "type", - "num_qubits", - "num_clbits", - "custom_definition", - "size", - "num_ctrl_qubits", - "ctrl_state", - "base_gate_size", - ], -) -CUSTOM_CIRCUIT_INST_DEF_V2_PACK = "!H1cII?QIIQ" -CUSTOM_CIRCUIT_INST_DEF_V2_SIZE = struct.calcsize(CUSTOM_CIRCUIT_INST_DEF_V2_PACK) - -# CUSTOM_CIRCUIT_INST_DEF -CUSTOM_CIRCUIT_INST_DEF = namedtuple( - "CUSTOM_CIRCUIT_INST_DEF", - ["gate_name_size", "type", "num_qubits", "num_clbits", "custom_definition", "size"], -) -CUSTOM_CIRCUIT_INST_DEF_PACK = "!H1cII?Q" -CUSTOM_CIRCUIT_INST_DEF_SIZE = struct.calcsize(CUSTOM_CIRCUIT_INST_DEF_PACK) - -# CALIBRATION -CALIBRATION = namedtuple("CALIBRATION", ["num_cals"]) -CALIBRATION_PACK = "!H" -CALIBRATION_SIZE = struct.calcsize(CALIBRATION_PACK) - -# CALIBRATION_DEF -CALIBRATION_DEF = namedtuple("CALIBRATION_DEF", ["name_size", "num_qubits", "num_params", "type"]) -CALIBRATION_DEF_PACK = "!HHH1c" -CALIBRATION_DEF_SIZE = struct.calcsize(CALIBRATION_DEF_PACK) - -# SCHEDULE_BLOCK binary format -SCHEDULE_BLOCK_HEADER = namedtuple( - "SCHEDULE_BLOCK", - [ - "name_size", - "metadata_size", - "num_elements", - ], -) -SCHEDULE_BLOCK_HEADER_PACK = "!HQH" -SCHEDULE_BLOCK_HEADER_SIZE = struct.calcsize(SCHEDULE_BLOCK_HEADER_PACK) - -# WAVEFORM binary format -WAVEFORM = namedtuple("WAVEFORM", ["epsilon", "data_size", "amp_limited"]) -WAVEFORM_PACK = "!fI?" -WAVEFORM_SIZE = struct.calcsize(WAVEFORM_PACK) - -# SYMBOLIC_PULSE -SYMBOLIC_PULSE = namedtuple( - "SYMBOLIC_PULSE", - [ - "type_size", - "envelope_size", - "constraints_size", - "valid_amp_conditions_size", - "amp_limited", - ], -) -SYMBOLIC_PULSE_PACK = "!HHHH?" -SYMBOLIC_PULSE_SIZE = struct.calcsize(SYMBOLIC_PULSE_PACK) - -# SYMBOLIC_PULSE_V2 -SYMBOLIC_PULSE_V2 = namedtuple( - "SYMBOLIC_PULSE", - [ - "class_name_size", - "type_size", - "envelope_size", - "constraints_size", - "valid_amp_conditions_size", - "amp_limited", - ], -) -SYMBOLIC_PULSE_PACK_V2 = "!HHHHH?" -SYMBOLIC_PULSE_SIZE_V2 = struct.calcsize(SYMBOLIC_PULSE_PACK_V2) - -# INSTRUCTION_PARAM -INSTRUCTION_PARAM = namedtuple("INSTRUCTION_PARAM", ["type", "size"]) -INSTRUCTION_PARAM_PACK = "!1cQ" -INSTRUCTION_PARAM_SIZE = struct.calcsize(INSTRUCTION_PARAM_PACK) - -# PARAMETER -PARAMETER = namedtuple("PARAMETER", ["name_size", "uuid"]) -PARAMETER_PACK = "!H16s" -PARAMETER_SIZE = struct.calcsize(PARAMETER_PACK) - -# COMPLEX -COMPLEX = namedtuple("COMPLEX", ["real", "imag"]) -COMPLEX_PACK = "!dd" -COMPLEX_SIZE = struct.calcsize(COMPLEX_PACK) - -# PARAMETER_VECTOR_ELEMENT -PARAMETER_VECTOR_ELEMENT = namedtuple( - "PARAMETER_VECTOR_ELEMENT", ["vector_name_size", "vector_size", "uuid", "index"] -) -PARAMETER_VECTOR_ELEMENT_PACK = "!HQ16sQ" -PARAMETER_VECTOR_ELEMENT_SIZE = struct.calcsize(PARAMETER_VECTOR_ELEMENT_PACK) - -# PARAMETER_EXPR -PARAMETER_EXPR = namedtuple("PARAMETER_EXPR", ["map_elements", "expr_size"]) -PARAMETER_EXPR_PACK = "!QQ" -PARAMETER_EXPR_SIZE = struct.calcsize(PARAMETER_EXPR_PACK) - -# PARAMETER_EXPR_MAP_ELEM_V3 -PARAM_EXPR_MAP_ELEM_V3 = namedtuple("PARAMETER_EXPR_MAP_ELEM", ["symbol_type", "type", "size"]) -PARAM_EXPR_MAP_ELEM_V3_PACK = "!ccQ" -PARAM_EXPR_MAP_ELEM_V3_SIZE = struct.calcsize(PARAM_EXPR_MAP_ELEM_V3_PACK) - -# PARAMETER_EXPR_MAP_ELEM -PARAM_EXPR_MAP_ELEM = namedtuple("PARAMETER_EXPR_MAP_ELEM", ["type", "size"]) -PARAM_EXPR_MAP_ELEM_PACK = "!cQ" -PARAM_EXPR_MAP_ELEM_SIZE = struct.calcsize(PARAM_EXPR_MAP_ELEM_PACK) - -# RANGE -RANGE = namedtuple("RANGE", ["start", "stop", "step"]) -RANGE_PACK = "!qqq" -RANGE_SIZE = struct.calcsize(RANGE_PACK) - -# SEQUENCE -SEQUENCE = namedtuple("SEQUENCE", ["num_elements"]) -SEQUENCE_PACK = "!Q" -SEQUENCE_SIZE = struct.calcsize(SEQUENCE_PACK) - -# MAP_ITEM -MAP_ITEM = namedtuple("MAP_ITEM", ["key_size", "type", "size"]) -MAP_ITEM_PACK = "!H1cH" -MAP_ITEM_SIZE = struct.calcsize(MAP_ITEM_PACK) - -LAYOUT_V2 = namedtuple( - "LAYOUT", - [ - "exists", - "initial_layout_size", - "input_mapping_size", - "final_layout_size", - "extra_registers", - "input_qubit_count", - ], -) -LAYOUT_V2_PACK = "!?iiiIi" -LAYOUT_V2_SIZE = struct.calcsize(LAYOUT_V2_PACK) - -LAYOUT = namedtuple( - "LAYOUT", - [ - "exists", - "initial_layout_size", - "input_mapping_size", - "final_layout_size", - "extra_registers", - ], -) -LAYOUT_PACK = "!?iiiI" -LAYOUT_SIZE = struct.calcsize(LAYOUT_PACK) - -INITIAL_LAYOUT_BIT = namedtuple("INITIAL_LAYOUT_BIT", ["index", "register_size"]) -INITIAL_LAYOUT_BIT_PACK = "!ii" -INITIAL_LAYOUT_BIT_SIZE = struct.calcsize(INITIAL_LAYOUT_BIT_PACK) - - -# EXPRESSION - -EXPRESSION_DISCRIMINATOR_SIZE = 1 - -EXPRESSION_CAST = namedtuple("EXPRESSION_CAST", ["implicit"]) -EXPRESSION_CAST_PACK = "!?" -EXPRESSION_CAST_SIZE = struct.calcsize(EXPRESSION_CAST_PACK) - -EXPRESSION_UNARY = namedtuple("EXPRESSION_UNARY", ["opcode"]) -EXPRESSION_UNARY_PACK = "!B" -EXPRESSION_UNARY_SIZE = struct.calcsize(EXPRESSION_UNARY_PACK) - -EXPRESSION_BINARY = namedtuple("EXPRESSION_BINARY", ["opcode"]) -EXPRESSION_BINARY_PACK = "!B" -EXPRESSION_BINARY_SIZE = struct.calcsize(EXPRESSION_BINARY_PACK) - - -# EXPR_TYPE - -EXPR_TYPE_DISCRIMINATOR_SIZE = 1 - -EXPR_TYPE_BOOL = namedtuple("EXPR_TYPE_BOOL", []) -EXPR_TYPE_BOOL_PACK = "!" -EXPR_TYPE_BOOL_SIZE = struct.calcsize(EXPR_TYPE_BOOL_PACK) - -EXPR_TYPE_UINT = namedtuple("EXPR_TYPE_UINT", ["width"]) # type: ignore[no-untyped-def] -EXPR_TYPE_UINT_PACK = "!L" -EXPR_TYPE_UINT_SIZE = struct.calcsize(EXPR_TYPE_UINT_PACK) - - -# EXPR_VAR - -EXPR_VAR_DISCRIMINATOR_SIZE = 1 - -EXPR_VAR_CLBIT = namedtuple("EXPR_VAR_CLBIT", ["index"]) -EXPR_VAR_CLBIT_PACK = "!L" -EXPR_VAR_CLBIT_SIZE = struct.calcsize(EXPR_VAR_CLBIT_PACK) - -EXPR_VAR_REGISTER = namedtuple("EXPR_VAR_REGISTER", ["reg_name_size"]) -EXPR_VAR_REGISTER_PACK = "!H" -EXPR_VAR_REGISTER_SIZE = struct.calcsize(EXPR_VAR_REGISTER_PACK) - - -# EXPR_VALUE - -EXPR_VALUE_DISCRIMINATOR_SIZE = 1 - -EXPR_VALUE_BOOL = namedtuple("EXPR_VALUE_BOOL", ["value"]) -EXPR_VALUE_BOOL_PACK = "!?" -EXPR_VALUE_BOOL_SIZE = struct.calcsize(EXPR_VALUE_BOOL_PACK) - -EXPR_VALUE_INT = namedtuple("EXPR_VALUE_INT", ["num_bytes"]) -EXPR_VALUE_INT_PACK = "!B" -EXPR_VALUE_INT_SIZE = struct.calcsize(EXPR_VALUE_INT_PACK) diff --git a/qiskit_ibm_runtime/qpy/interface.py b/qiskit_ibm_runtime/qpy/interface.py deleted file mode 100644 index 6a77697d6..000000000 --- a/qiskit_ibm_runtime/qpy/interface.py +++ /dev/null @@ -1,312 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""User interface of qpy serializer.""" - -from json import JSONEncoder, JSONDecoder -from typing import Union, List, BinaryIO, Type, Optional -from collections.abc import Iterable -import struct -import warnings -import re - -from qiskit.circuit import QuantumCircuit -from qiskit.pulse import ScheduleBlock -from qiskit.exceptions import QiskitError -from qiskit.version import __version__ - -from . import formats, common, binary_io, type_keys -from .exceptions import QpyError - - -# pylint: disable=invalid-name -QPY_SUPPORTED_TYPES = Union[QuantumCircuit, ScheduleBlock] - - -# This version pattern is taken from the pypa packaging project: -# https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254 -# which is dual licensed Apache 2.0 and BSD see the source for the original -# authors and other details -VERSION_PATTERN = ( - "^" - + r""" - v? - (?: - (?:(?P[0-9]+)!)? # epoch - (?P[0-9]+(?:\.[0-9]+)*) # release segment - (?P
                                          # pre-release
-            [-_\.]?
-            (?P(a|b|c|rc|alpha|beta|pre|preview))
-            [-_\.]?
-            (?P[0-9]+)?
-        )?
-        (?P                                         # post release
-            (?:-(?P[0-9]+))
-            |
-            (?:
-                [-_\.]?
-                (?Ppost|rev|r)
-                [-_\.]?
-                (?P[0-9]+)?
-            )
-        )?
-        (?P                                          # dev release
-            [-_\.]?
-            (?Pdev)
-            [-_\.]?
-            (?P[0-9]+)?
-        )?
-    )
-    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
-"""
-    + "$"
-)
-VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
-
-
-def dump(  # type: ignore[no-untyped-def]
-    programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES],
-    file_obj: BinaryIO,
-    metadata_serializer: Optional[Type[JSONEncoder]] = None,
-    use_symengine: bool = False,
-):
-    """Write QPY binary data to a file
-
-    This function is used to save a circuit to a file for later use or transfer
-    between machines. The QPY format is backwards compatible and can be
-    loaded with future versions of Qiskit.
-
-    For example:
-
-    .. code-block:: python
-
-        from qiskit.circuit import QuantumCircuit
-        from qiskit import qpy
-
-        qc = QuantumCircuit(2, name='Bell', metadata={'test': True})
-        qc.h(0)
-        qc.cx(0, 1)
-        qc.measure_all()
-
-    from this you can write the qpy data to a file:
-
-    .. code-block:: python
-
-        with open('bell.qpy', 'wb') as fd:
-            qpy.dump(qc, fd)
-
-    or a gzip compressed file:
-
-    .. code-block:: python
-
-        import gzip
-
-        with gzip.open('bell.qpy.gz', 'wb') as fd:
-            qpy.dump(qc, fd)
-
-    Which will save the qpy serialized circuit to the provided file.
-
-    Args:
-        programs: QPY supported object(s) to store in the specified file like object.
-            QPY supports :class:`.QuantumCircuit` and :class:`.ScheduleBlock`.
-            Different data types must be separately serialized.
-        file_obj: The file like object to write the QPY data too
-        metadata_serializer: An optional JSONEncoder class that
-            will be passed the ``.metadata`` attribute for each program in ``programs`` and will be
-            used as the ``cls`` kwarg on the `json.dump()`` call to JSON serialize that dictionary.
-        use_symengine: If True, all objects containing symbolic expressions will be serialized
-            using symengine's native mechanism. This is a faster serialization alternative,
-            but not supported in all platforms. Please check that your target platform is supported
-            by the symengine library before setting this option, as it will be required by qpy to
-            deserialize the payload. For this reason, the option defaults to False.
-
-    Raises:
-        QpyError: When multiple data format is mixed in the output.
-        TypeError: When invalid data type is input.
-    """
-    if not isinstance(programs, Iterable):
-        programs = [programs]
-
-    program_types = set()
-    for program in programs:
-        program_types.add(type(program))
-
-    if len(program_types) > 1:
-        raise QpyError(
-            "Input programs contain multiple data types. "
-            "Different data type must be serialized separately."
-        )
-    program_type = next(iter(program_types))
-
-    if issubclass(program_type, QuantumCircuit):
-        type_key = type_keys.Program.CIRCUIT
-        writer = binary_io.write_circuit
-    elif program_type is ScheduleBlock:
-        type_key = type_keys.Program.SCHEDULE_BLOCK
-        writer = binary_io.write_schedule_block
-    else:
-        raise TypeError(f"'{program_type}' is not supported data type.")
-
-    version_match = VERSION_PATTERN_REGEX.search(__version__)
-    version_parts = [int(x) for x in version_match.group("release").split(".")]
-    encoding = type_keys.SymExprEncoding.assign(use_symengine)  # type: ignore[no-untyped-call]
-    header = struct.pack(
-        formats.FILE_HEADER_V10_PACK,  # type: ignore[attr-defined]
-        b"QISKIT",
-        common.QPY_VERSION,
-        version_parts[0],
-        version_parts[1],
-        version_parts[2],
-        len(programs),  # type: ignore[arg-type]
-        encoding,
-    )
-    file_obj.write(header)
-    common.write_type_key(file_obj, type_key)  # type: ignore[no-untyped-call]
-
-    for program in programs:
-        writer(  # type: ignore[no-untyped-call]
-            file_obj,
-            program,
-            metadata_serializer=metadata_serializer,
-            use_symengine=use_symengine,
-        )
-
-
-def load(  # type: ignore[no-untyped-def]
-    file_obj: BinaryIO,
-    metadata_deserializer: Optional[Type[JSONDecoder]] = None,
-) -> List[QPY_SUPPORTED_TYPES]:
-    """Load a QPY binary file
-
-    This function is used to load a serialized QPY Qiskit program file and create
-    :class:`~qiskit.circuit.QuantumCircuit` objects or
-    :class:`~qiskit.pulse.schedule.ScheduleBlock` objects from its contents.
-    For example:
-
-    .. code-block:: python
-
-        from qiskit import qpy
-
-        with open('bell.qpy', 'rb') as fd:
-            circuits = qpy.load(fd)
-
-    or with a gzip compressed file:
-
-    .. code-block:: python
-
-        import gzip
-        from qiskit import qpy
-
-        with gzip.open('bell.qpy.gz', 'rb') as fd:
-            circuits = qpy.load(fd)
-
-    which will read the contents of the qpy and return a list of
-    :class:`~qiskit.circuit.QuantumCircuit` objects or
-    :class:`~qiskit.pulse.schedule.ScheduleBlock` objects from the file.
-
-    Args:
-        file_obj: A file like object that contains the QPY binary
-            data for a circuit or pulse schedule.
-        metadata_deserializer: An optional JSONDecoder class
-            that will be used for the ``cls`` kwarg on the internal
-            ``json.load`` call used to deserialize the JSON payload used for
-            the ``.metadata`` attribute for any programs in the QPY file.
-            If this is not specified the circuit metadata will
-            be parsed as JSON with the stdlib ``json.load()`` function using
-            the default ``JSONDecoder`` class.
-
-    Returns:
-        The list of Qiskit programs contained in the QPY data.
-        A list is always returned, even if there is only 1 program in the QPY data.
-
-    Raises:
-        QiskitError: if ``file_obj`` is not a valid QPY file
-        TypeError: When invalid data type is loaded.
-    """
-
-    # identify file header version
-    version = struct.unpack("!6sB", file_obj.read(7))[1]
-    file_obj.seek(0)
-
-    if version > common.QPY_VERSION:
-        raise QiskitError(
-            f"The QPY format version being read, {version}, isn't supported by "
-            "this Qiskit version. Please upgrade your version of Qiskit to load this QPY payload"
-        )
-
-    if version < 10:
-        data = formats.FILE_HEADER._make(  # type: ignore[attr-defined]
-            struct.unpack(
-                formats.FILE_HEADER_PACK,  # type: ignore[attr-defined]
-                file_obj.read(formats.FILE_HEADER_SIZE),  # type: ignore[attr-defined]
-            )
-        )
-    else:
-        data = formats.FILE_HEADER_V10._make(  # type: ignore[attr-defined]
-            struct.unpack(
-                formats.FILE_HEADER_V10_PACK,  # type: ignore[attr-defined]
-                file_obj.read(formats.FILE_HEADER_V10_SIZE),  # type: ignore[attr-defined]
-            )
-        )
-    if data.preface.decode(common.ENCODE) != "QISKIT":
-        raise QiskitError("Input file is not a valid QPY file")
-    version_match = VERSION_PATTERN_REGEX.search(__version__)
-    env_qiskit_version = [int(x) for x in version_match.group("release").split(".")]
-
-    qiskit_version = (data.major_version, data.minor_version, data.patch_version)
-    # pylint: disable=too-many-boolean-expressions
-    if (
-        env_qiskit_version[0] < qiskit_version[0]
-        or (
-            env_qiskit_version[0] == qiskit_version[0] and qiskit_version[1] > env_qiskit_version[1]
-        )
-        or (
-            env_qiskit_version[0] == qiskit_version[0]
-            and qiskit_version[1] == env_qiskit_version[1]
-            and qiskit_version[2] > env_qiskit_version[2]
-        )
-    ):
-        warnings.warn(
-            "The qiskit version used to generate the provided QPY "
-            "file, %s, is newer than the current qiskit version %s. "
-            "This may result in an error if the QPY file uses "
-            "instructions not present in this current qiskit "
-            "version" % (".".join([str(x) for x in qiskit_version]), __version__)
-        )
-
-    if data.qpy_version < 5:
-        type_key = type_keys.Program.CIRCUIT
-    else:
-        type_key = common.read_type_key(file_obj)  # type: ignore[no-untyped-call]
-
-    if type_key == type_keys.Program.CIRCUIT:
-        loader = binary_io.read_circuit
-    elif type_key == type_keys.Program.SCHEDULE_BLOCK:
-        loader = binary_io.read_schedule_block
-    else:
-        raise TypeError(f"Invalid payload format data kind '{type_key}'.")
-
-    if data.qpy_version < 10:
-        use_symengine = False
-    else:
-        use_symengine = data.symbolic_encoding == type_keys.SymExprEncoding.SYMENGINE
-
-    programs = []
-    for _ in range(data.num_programs):
-        programs.append(
-            loader(  # type: ignore[no-untyped-call]
-                file_obj,
-                data.qpy_version,
-                metadata_deserializer=metadata_deserializer,
-                use_symengine=use_symengine,
-            )
-        )
-    return programs
diff --git a/qiskit_ibm_runtime/qpy/type_keys.py b/qiskit_ibm_runtime/qpy/type_keys.py
deleted file mode 100644
index 4c5b424d5..000000000
--- a/qiskit_ibm_runtime/qpy/type_keys.py
+++ /dev/null
@@ -1,540 +0,0 @@
-# This code is part of Qiskit.
-#
-# (C) Copyright IBM 2022.
-#
-# This code is licensed under the Apache License, Version 2.0. You may
-# obtain a copy of this license in the LICENSE.txt file in the root directory
-# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
-#
-# Any modifications or derivative works of this code must retain this
-# copyright notice, and modified files need to carry a notice indicating
-# that they have been altered from the originals.
-
-# pylint: disable=too-many-return-statements
-
-"""
-QPY Type keys for several namespace.
-"""
-
-from abc import abstractmethod
-from enum import Enum, IntEnum
-
-import numpy as np
-
-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.classical import expr, types
-from qiskit.circuit.parameterexpression import ParameterExpression
-from qiskit.circuit.parametervector import ParameterVectorElement
-from qiskit.pulse.channels import (
-    Channel,
-    DriveChannel,
-    MeasureChannel,
-    ControlChannel,
-    AcquireChannel,
-    MemorySlot,
-    RegisterSlot,
-)
-from qiskit.pulse.configuration import Discriminator, Kernel
-from qiskit.pulse.instructions import (
-    Acquire,
-    Play,
-    Delay,
-    SetFrequency,
-    ShiftFrequency,
-    SetPhase,
-    ShiftPhase,
-    RelativeBarrier,
-    TimeBlockade,
-    Reference,
-)
-from qiskit.pulse.library import Waveform, SymbolicPulse
-from qiskit.pulse.schedule import ScheduleBlock
-from qiskit.pulse.transforms.alignments import (
-    AlignLeft,
-    AlignRight,
-    AlignSequential,
-    AlignEquispaced,
-)
-from . import exceptions
-
-
-class TypeKeyBase(bytes, Enum):
-    """Abstract baseclass for type key Enums."""
-
-    @classmethod
-    @abstractmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        """Assign type key to given object.
-        Args:
-            obj (any): Arbitrary object to evaluate.
-        Returns:
-            TypeKey: Corresponding key object.
-        """
-        pass
-
-    @classmethod
-    @abstractmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        """Get a class from given type key.
-        Args:
-            type_key (bytes): Object type key.
-        Returns:
-            any: Corresponding class.
-        """
-        pass
-
-
-class Value(TypeKeyBase):
-    """Type key enum for value object."""
-
-    INTEGER = b"i"
-    FLOAT = b"f"
-    COMPLEX = b"c"
-    NUMPY_OBJ = b"n"
-    PARAMETER = b"p"
-    PARAMETER_VECTOR = b"v"
-    PARAMETER_EXPRESSION = b"e"
-    STRING = b"s"
-    NULL = b"z"
-    EXPRESSION = b"x"
-    CASE_DEFAULT = b"d"
-    REGISTER = b"R"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, int):
-            return cls.INTEGER
-        if isinstance(obj, float):
-            return cls.FLOAT
-        if isinstance(obj, complex):
-            return cls.COMPLEX
-        if isinstance(obj, (np.integer, np.floating, np.complexfloating, np.ndarray)):
-            return cls.NUMPY_OBJ
-        if isinstance(obj, ParameterVectorElement):
-            return cls.PARAMETER_VECTOR
-        if isinstance(obj, Parameter):
-            return cls.PARAMETER
-        if isinstance(obj, ParameterExpression):
-            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
-        if isinstance(obj, expr.Expr):
-            return cls.EXPRESSION
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class Condition(IntEnum):
-    """Type keys for the ``conditional_key`` field of the INSTRUCTION struct."""
-
-    # This class is deliberately raw integers and not in terms of ASCII characters for backwards
-    # compatiblity in the form as an old Boolean value was expanded; `NONE` and `TWO_TUPLE` must
-    # have the enumeration values 0 and 1.
-
-    NONE = 0
-    TWO_TUPLE = 1
-    EXPRESSION = 2
-
-
-class Container(TypeKeyBase):
-    """Typle key enum for container-like object."""
-
-    RANGE = b"r"
-    TUPLE = b"t"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, range):
-            return cls.RANGE
-        if isinstance(obj, tuple):
-            return cls.TUPLE
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class CircuitInstruction(TypeKeyBase):
-    """Type key enum for circuit instruction object."""
-
-    INSTRUCTION = b"i"
-    GATE = b"g"
-    PAULI_EVOL_GATE = b"p"
-    CONTROLLED_GATE = b"c"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, PauliEvolutionGate):
-            return cls.PAULI_EVOL_GATE
-        if isinstance(obj, ControlledGate):
-            return cls.CONTROLLED_GATE
-        if isinstance(obj, Gate):
-            return cls.GATE
-        if isinstance(obj, Instruction):
-            return cls.INSTRUCTION
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class ScheduleAlignment(TypeKeyBase):
-    """Type key enum for schedule block alignment context object."""
-
-    LEFT = b"l"
-    RIGHT = b"r"
-    SEQUENTIAL = b"s"
-    EQUISPACED = b"e"
-
-    # AlignFunc is not serializable due to the callable in context parameter
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, AlignLeft):
-            return cls.LEFT
-        if isinstance(obj, AlignRight):
-            return cls.RIGHT
-        if isinstance(obj, AlignSequential):
-            return cls.SEQUENTIAL
-        if isinstance(obj, AlignEquispaced):
-            return cls.EQUISPACED
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        if type_key == cls.LEFT:
-            return AlignLeft
-        if type_key == cls.RIGHT:
-            return AlignRight
-        if type_key == cls.SEQUENTIAL:
-            return AlignSequential
-        if type_key == cls.EQUISPACED:
-            return AlignEquispaced
-
-        raise exceptions.QpyError(
-            f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace."
-        )
-
-
-class ScheduleInstruction(TypeKeyBase):
-    """Type key enum for schedule instruction object."""
-
-    ACQUIRE = b"a"
-    PLAY = b"p"
-    DELAY = b"d"
-    SET_FREQUENCY = b"f"
-    SHIFT_FREQUENCY = b"g"
-    SET_PHASE = b"q"
-    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.
-    # This instruction has been excluded from ScheduleBlock instructions with
-    # qiskit-terra/#8005 and new instruction Reference will be added instead.
-    # Call is only applied to Schedule which is not supported by QPY.
-    # Also snapshot is not suppored because of its limited usecase.
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, Acquire):
-            return cls.ACQUIRE
-        if isinstance(obj, Play):
-            return cls.PLAY
-        if isinstance(obj, Delay):
-            return cls.DELAY
-        if isinstance(obj, SetFrequency):
-            return cls.SET_FREQUENCY
-        if isinstance(obj, ShiftFrequency):
-            return cls.SHIFT_FREQUENCY
-        if isinstance(obj, SetPhase):
-            return cls.SET_PHASE
-        if isinstance(obj, ShiftPhase):
-            return cls.SHIFT_PHASE
-        if isinstance(obj, RelativeBarrier):
-            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."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        if type_key == cls.ACQUIRE:
-            return Acquire
-        if type_key == cls.PLAY:
-            return Play
-        if type_key == cls.DELAY:
-            return Delay
-        if type_key == cls.SET_FREQUENCY:
-            return SetFrequency
-        if type_key == cls.SHIFT_FREQUENCY:
-            return ShiftFrequency
-        if type_key == cls.SET_PHASE:
-            return SetPhase
-        if type_key == cls.SHIFT_PHASE:
-            return ShiftPhase
-        if type_key == cls.BARRIER:
-            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."
-        )
-
-
-class ScheduleOperand(TypeKeyBase):
-    """Type key enum for schedule instruction operand object."""
-
-    WAVEFORM = b"w"
-    SYMBOLIC_PULSE = b"s"
-    CHANNEL = b"c"
-    KERNEL = b"k"
-    DISCRIMINATOR = b"d"
-
-    # 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):
-            return cls.WAVEFORM
-        if isinstance(obj, SymbolicPulse):
-            return cls.SYMBOLIC_PULSE
-        if isinstance(obj, Channel):
-            return cls.CHANNEL
-        if isinstance(obj, str):
-            return cls.OPERAND_STR
-        if isinstance(obj, Kernel):
-            return cls.KERNEL
-        if isinstance(obj, Discriminator):
-            return cls.DISCRIMINATOR
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class ScheduleChannel(TypeKeyBase):
-    """Type key enum for schedule channel object."""
-
-    DRIVE = b"d"
-    CONTROL = b"c"
-    MEASURE = b"m"
-    ACQURE = b"a"
-    MEM_SLOT = b"e"
-    REG_SLOT = b"r"
-
-    # SnapShot channel is not defined because of its limited usecase.
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, DriveChannel):
-            return cls.DRIVE
-        if isinstance(obj, ControlChannel):
-            return cls.CONTROL
-        if isinstance(obj, MeasureChannel):
-            return cls.MEASURE
-        if isinstance(obj, AcquireChannel):
-            return cls.ACQURE
-        if isinstance(obj, MemorySlot):
-            return cls.MEM_SLOT
-        if isinstance(obj, RegisterSlot):
-            return cls.REG_SLOT
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        if type_key == cls.DRIVE:
-            return DriveChannel
-        if type_key == cls.CONTROL:
-            return ControlChannel
-        if type_key == cls.MEASURE:
-            return MeasureChannel
-        if type_key == cls.ACQURE:
-            return AcquireChannel
-        if type_key == cls.MEM_SLOT:
-            return MemorySlot
-        if type_key == cls.REG_SLOT:
-            return RegisterSlot
-
-        raise exceptions.QpyError(
-            f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace."
-        )
-
-
-class Program(TypeKeyBase):
-    """Typle key enum for program that QPY supports."""
-
-    CIRCUIT = b"q"
-    SCHEDULE_BLOCK = b"s"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, QuantumCircuit):
-            return cls.CIRCUIT
-        if isinstance(obj, ScheduleBlock):
-            return cls.SCHEDULE_BLOCK
-
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class Expression(TypeKeyBase):
-    """Type keys for the ``EXPRESSION`` QPY item."""
-
-    VAR = b"x"
-    VALUE = b"v"
-    CAST = b"c"
-    UNARY = b"u"
-    BINARY = b"b"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if (
-            isinstance(obj, expr.Expr)
-            and (key := getattr(cls, obj.__class__.__name__.upper(), None)) is not None
-        ):
-            return key
-        raise exceptions.QpyError(f"Object '{obj}' is not supported in {cls.__name__} namespace.")
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class ExprType(TypeKeyBase):
-    """Type keys for the ``EXPR_TYPE`` QPY item."""
-
-    BOOL = b"b"
-    UINT = b"u"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if (
-            isinstance(obj, types.Type)
-            and (key := getattr(cls, obj.__class__.__name__.upper(), None)) is not None
-        ):
-            return key
-        raise exceptions.QpyError(f"Object '{obj}' is not supported in {cls.__name__} namespace.")
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class ExprVar(TypeKeyBase):
-    """Type keys for the ``EXPR_VAR`` QPY item."""
-
-    CLBIT = b"C"
-    REGISTER = b"R"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, Clbit):
-            return cls.CLBIT
-        if isinstance(obj, ClassicalRegister):
-            return cls.REGISTER
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class ExprValue(TypeKeyBase):
-    """Type keys for the ``EXPR_VALUE`` QPY item."""
-
-    BOOL = b"b"
-    INT = b"i"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if isinstance(obj, bool):
-            return cls.BOOL
-        if isinstance(obj, int):
-            return cls.INT
-        raise exceptions.QpyError(
-            f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace."
-        )
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
-
-
-class SymExprEncoding(TypeKeyBase):
-    """Type keys for the symbolic encoding field in the file header."""
-
-    SYMPY = b"p"
-    SYMENGINE = b"e"
-
-    @classmethod
-    def assign(cls, obj):  # type: ignore[no-untyped-def]
-        if obj is True:
-            return cls.SYMENGINE
-        else:
-            return cls.SYMPY
-
-    @classmethod
-    def retrieve(cls, type_key):  # type: ignore[no-untyped-def]
-        raise NotImplementedError
diff --git a/qiskit_ibm_runtime/utils/json.py b/qiskit_ibm_runtime/utils/json.py
index b31246a59..5536bd18b 100644
--- a/qiskit_ibm_runtime/utils/json.py
+++ b/qiskit_ibm_runtime/utils/json.py
@@ -55,16 +55,16 @@
 from qiskit.circuit.parametertable import ParameterView
 from qiskit.result import Result
 from qiskit.version import __version__ as _terra_version_string
-from qiskit.qpy import load
 from qiskit.utils import optionals
 
-from ..qpy import (
+from qiskit.qpy import (
     _write_parameter,
     _write_parameter_expression,
     _read_parameter_expression,
     _read_parameter_expression_v3,
     _read_parameter,
-    dump,
+    load,
+    dump
 )
 
 _TERRA_VERSION = tuple(
@@ -217,10 +217,15 @@ def default(self, obj: Any) -> Any:  # pylint: disable=arguments-differ
         if hasattr(obj, "to_json"):
             return {"__type__": "to_json", "__value__": obj.to_json()}
         if isinstance(obj, QuantumCircuit):
+            kwargs = {"use_symengine": optionals.HAS_SYMENGINE}
+            if _TERRA_VERSION[0] >= 1:
+                # NOTE: This can be updated only after the server side has
+                # updated to a newer qiskit version.
+                kwargs["version"] = 10
             value = _serialize_and_encode(
                 data=obj,
                 serializer=lambda buff, data: dump(
-                    data, buff, RuntimeEncoder, use_symengine=optionals.HAS_SYMENGINE
+                    data, buff, RuntimeEncoder, **kwargs
                 ),  # type: ignore[no-untyped-call]
             )
             return {"__type__": "QuantumCircuit", "__value__": value}
@@ -236,18 +241,24 @@ def default(self, obj: Any) -> Any:  # pylint: disable=arguments-differ
                 data=obj,
                 serializer=_write_parameter_expression,
                 compress=False,
+                use_symengine=optionals.HAS_SYMENGINE,
             )
             return {"__type__": "ParameterExpression", "__value__": value}
         if isinstance(obj, ParameterView):
             return obj.data
         if isinstance(obj, Instruction):
+            kwargs = {"use_symengine": optionals.HAS_SYMENGINE}
+            if _TERRA_VERSION[0] >= 1:
+                # NOTE: This can be updated only after the server side has
+                # updated to a newer qiskit version.
+                kwargs["version"] = 10
             # Append instruction to empty circuit
             quantum_register = QuantumRegister(obj.num_qubits)
             quantum_circuit = QuantumCircuit(quantum_register)
             quantum_circuit.append(obj, quantum_register)
             value = _serialize_and_encode(
                 data=quantum_circuit,
-                serializer=lambda buff, data: dump(data, buff),  # type: ignore[no-untyped-call]
+                serializer=lambda buff, data: dump(data, buff, **kwargs),  # type: ignore[no-untyped-call]
             )
             return {"__type__": "Instruction", "__value__": value}
         if HAS_AER and isinstance(obj, qiskit_aer.noise.NoiseModel):

From 1ea9ba35b9d9b6c7bd254d0584446ede05a27743 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Mon, 5 Feb 2024 15:08:29 -0500
Subject: [PATCH 2/3] Fix lint

---
 qiskit_ibm_runtime/utils/json.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/qiskit_ibm_runtime/utils/json.py b/qiskit_ibm_runtime/utils/json.py
index 5536bd18b..3bc80bd88 100644
--- a/qiskit_ibm_runtime/utils/json.py
+++ b/qiskit_ibm_runtime/utils/json.py
@@ -56,16 +56,15 @@
 from qiskit.result import Result
 from qiskit.version import __version__ as _terra_version_string
 from qiskit.utils import optionals
-
 from qiskit.qpy import (
-    _write_parameter,
     _write_parameter_expression,
     _read_parameter_expression,
     _read_parameter_expression_v3,
     _read_parameter,
     load,
-    dump
+    dump,
 )
+from qiskit.qpy.binary_io.value import _write_parameter
 
 _TERRA_VERSION = tuple(
     int(x) for x in re.match(r"\d+\.\d+\.\d", _terra_version_string).group(0).split(".")[:3]

From 5325f6a611831e67f560f3bf7f984180da069b72 Mon Sep 17 00:00:00 2001
From: Matthew Treinish 
Date: Mon, 5 Feb 2024 15:12:41 -0500
Subject: [PATCH 3/3] Fix lint again

---
 qiskit_ibm_runtime/utils/json.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/qiskit_ibm_runtime/utils/json.py b/qiskit_ibm_runtime/utils/json.py
index 3bc80bd88..1c2e7c287 100644
--- a/qiskit_ibm_runtime/utils/json.py
+++ b/qiskit_ibm_runtime/utils/json.py
@@ -60,11 +60,10 @@
     _write_parameter_expression,
     _read_parameter_expression,
     _read_parameter_expression_v3,
-    _read_parameter,
     load,
     dump,
 )
-from qiskit.qpy.binary_io.value import _write_parameter
+from qiskit.qpy.binary_io.value import _write_parameter, _read_parameter
 
 _TERRA_VERSION = tuple(
     int(x) for x in re.match(r"\d+\.\d+\.\d", _terra_version_string).group(0).split(".")[:3]
@@ -257,7 +256,9 @@ def default(self, obj: Any) -> Any:  # pylint: disable=arguments-differ
             quantum_circuit.append(obj, quantum_register)
             value = _serialize_and_encode(
                 data=quantum_circuit,
-                serializer=lambda buff, data: dump(data, buff, **kwargs),  # type: ignore[no-untyped-call]
+                serializer=lambda buff, data: dump(
+                    data, buff, **kwargs
+                ),  # type: ignore[no-untyped-call]
             )
             return {"__type__": "Instruction", "__value__": value}
         if HAS_AER and isinstance(obj, qiskit_aer.noise.NoiseModel):