diff --git a/qiskit_ibm_runtime/qpy/binary_io/circuits.py b/qiskit_ibm_runtime/qpy/binary_io/circuits.py index b8fbb07dc3..de849f83f6 100644 --- a/qiskit_ibm_runtime/qpy/binary_io/circuits.py +++ b/qiskit_ibm_runtime/qpy/binary_io/circuits.py @@ -34,7 +34,7 @@ from qiskit.extensions import quantum_initializer from qiskit.quantum_info.operators import SparsePauliOp from qiskit.synthesis import evolution as evo_synth -from .. import common, formats, exceptions, type_keys +from .. import common, formats, type_keys from . import value, schedules @@ -179,22 +179,13 @@ def _read_instruction( # type: ignore[no-untyped-def] params = [] condition_tuple = None if instruction.has_condition: - # If an invalid register name is used assume it's a single bit - # condition and treat the register name as a string of the clbit index - if ClassicalRegister.name_format.match(condition_register) is None: - # If invalid register prefixed with null character it's a clbit - # index for single bit condition - if condition_register[0] == "\x00": - conditional_bit = int(condition_register[1:]) - condition_tuple = ( - circuit.clbits[conditional_bit], - instruction.condition_value, - ) - else: - raise ValueError( - f"Invalid register name: {condition_register} for condition register of " - f"instruction: {gate_name}" - ) + # If register name prefixed with null character it's a clbit index for single bit condition. + if condition_register[0] == "\x00": + conditional_bit = int(condition_register[1:]) + condition_tuple = ( + circuit.clbits[conditional_bit], + instruction.condition_value, + ) else: condition_tuple = ( registers["c"][condition_register], @@ -257,7 +248,7 @@ def _read_instruction( # type: ignore[no-untyped-def] inst_obj.label = label if circuit is None: return inst_obj - circuit._append(CircuitInstruction(inst_obj, qargs, cargs)) + circuit._append(inst_obj, qargs, cargs) return None elif hasattr(library, gate_name): gate_class = getattr(library, gate_name) @@ -275,7 +266,14 @@ def _read_instruction( # type: ignore[no-untyped-def] if gate_name in {"IfElseOp", "WhileLoopOp"}: gate = gate_class(condition_tuple, *params) elif version >= 5 and issubclass(gate_class, ControlledGate): - if gate_name in {"MCPhaseGate", "MCU1Gate"}: + if gate_name in { + "MCPhaseGate", + "MCU1Gate", + "MCXGrayCode", + "MCXGate", + "MCXRecursive", + "MCXVChain", + }: gate = gate_class(*params, instruction.num_ctrl_qubits) else: gate = gate_class(*params) @@ -294,12 +292,12 @@ def _read_instruction( # type: ignore[no-untyped-def] gate.condition = condition_tuple if instruction.label_size > 0: gate.label = label - if circuit is None: - return gate + if circuit is None: + return gate if not isinstance(gate, Instruction): circuit.append(gate, qargs, cargs) else: - circuit._append(gate, qargs, cargs) + circuit._append(CircuitInstruction(gate, qargs, cargs)) return None @@ -420,6 +418,7 @@ def _read_custom_operations(file_obj, version, vectors): # type: ignore[no-unty 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 @@ -676,6 +675,9 @@ def _write_custom_operation( # type: ignore[no-untyped-def] # state is open, and the definition setter (during a subsequent read) uses the "fully # excited" control definition only. has_definition = True + # Build internal definition to support overloaded subclasses by + # calling definition getter on object + operation.definition # pylint: disable=pointless-statement data = common.data_to_binary(operation._definition, write_circuit) size = len(data) num_ctrl_qubits = operation.num_ctrl_qubits @@ -895,128 +897,35 @@ def read_circuit(file_obj, version, metadata_deserializer=None): # type: ignore num_registers = header["num_registers"] num_instructions = header["num_instructions"] out_registers = {"q": {}, "c": {}} + circ = QuantumCircuit( + [Qubit() for _ in [None] * num_qubits], + [Clbit() for _ in [None] * num_clbits], + name=name, + global_phase=global_phase, + metadata=metadata, + ) if num_registers > 0: - circ = QuantumCircuit(name=name, global_phase=global_phase, metadata=metadata) 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), + for bit_type_label, reg_type in [ + ("q", QuantumRegister), + ("c", ClassicalRegister), ]: - register_bits = set() # Add quantum registers and bits - for register_name in registers[bit_type_label]: - standalone, indices, in_circuit = registers[bit_type_label][ - register_name - ] - indices_defined = [x for x in indices if x >= 0] - # If a register has no bits in the circuit skip it - if not indices_defined: - continue - if standalone: - start = min(indices_defined) - count = start - out_of_order = False - for index in indices: - if index < 0: - out_of_order = True - continue - if not out_of_order and index != count: - out_of_order = True - count += 1 - if index in register_bits: - # If we have a bit in the position already it's been - # added by an earlier register in the circuit - # otherwise it's invalid qpy - if not in_circuit: - continue - raise exceptions.QpyError("Duplicate register bits found") - - register_bits.add(index) - - num_reg_bits = len(indices) - # Create a standlone register of the appropriate length (from - # the number of indices in the qpy data) and add it to the circuit - reg = reg_type(num_reg_bits, register_name) - # If any bits from qreg are out of order in the circuit handle - # is case - if out_of_order or not in_circuit: - for index, pos in sorted( - enumerate(x for x in indices if x >= 0), key=lambda x: x[1] - ): - if bit_type_label == "q": - bit_len = len(circ.qubits) - else: - bit_len = len(circ.clbits) - if pos < bit_len: - # If we have a bit in the position already it's been - # added by an earlier register in the circuit - # otherwise it's invalid qpy - if not in_circuit: - continue - raise exceptions.QpyError( - "Duplicate register bits found" - ) - # Fill any holes between the current register bit and the - # next one - if pos > bit_len: - bits = [bit_type() for _ in range(pos - bit_len)] - circ.add_bits(bits) - circ.add_bits([reg[index]]) - if in_circuit: - circ.add_register(reg) - else: - if bit_type_label == "q": - bit_len = len(circ.qubits) - else: - bit_len = len(circ.clbits) - # If there is a hole between the start of the register and the - # current bits and standalone bits to fill the gap. - if start > len(circ.qubits): - bits = [bit_type() for _ in range(start - bit_len)] - circ.add_bits(bit_len) - if in_circuit: - circ.add_register(reg) - out_registers[bit_type_label][register_name] = reg - else: - for index in indices: - if bit_type_label == "q": - bit_len = len(circ.qubits) - else: - bit_len = len(circ.clbits) - # Add any missing bits - bits = [bit_type() for _ in range(index + 1 - bit_len)] - circ.add_bits(bits) - if index in register_bits: - raise exceptions.QpyError("Duplicate register bits found") - register_bits.add(index) - if bit_type_label == "q": - bits = [circ.qubits[i] for i in indices] - else: - bits = [circ.clbits[i] for i in indices] - reg = reg_type(name=register_name, bits=bits) - if in_circuit: - circ.add_register(reg) - out_registers[bit_type_label][register_name] = reg - # If we don't have sufficient bits in the circuit after adding - # all the registers add more bits to fill the circuit - if len(circ.qubits) < num_qubits: - qubits = [Qubit() for _ in range(num_qubits - len(circ.qubits))] - circ.add_bits(qubits) - if len(circ.clbits) < num_clbits: - clbits = [Clbit() for _ in range(num_qubits - len(circ.clbits))] - circ.add_bits(clbits) - else: - circ = QuantumCircuit( - num_qubits, - num_clbits, - name=name, - global_phase=global_phase, - metadata=metadata, - ) + circuit_bits = {"q": circ.qubits, "c": circ.clbits}[bit_type_label] + for register_name, (_, indices, in_circuit) in registers[ + bit_type_label + ].items(): + register = reg_type( + name=register_name, + bits=[circuit_bits[x] for x in indices if x >= 0], + ) + if in_circuit: + circ.add_register(register) + out_registers[bit_type_label][register_name] = register custom_operations = _read_custom_operations(file_obj, version, vectors) for _instruction in range(num_instructions): _read_instruction( diff --git a/qiskit_ibm_runtime/qpy/binary_io/schedules.py b/qiskit_ibm_runtime/qpy/binary_io/schedules.py index cb57f3f340..3260b97fbe 100644 --- a/qiskit_ibm_runtime/qpy/binary_io/schedules.py +++ b/qiskit_ibm_runtime/qpy/binary_io/schedules.py @@ -16,6 +16,7 @@ import json import struct import zlib +import warnings import numpy as np @@ -23,11 +24,16 @@ from qiskit.pulse import library, channels from qiskit.pulse.schedule import ScheduleBlock from qiskit.utils import optionals as _optional - from .. import formats, common, type_keys 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, {}) @@ -73,10 +79,14 @@ def _loads_symbolic_expr(expr_bytes): # type: ignore[no-untyped-def] def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def] - header = formats.SYMBOLIC_PULSE._make( + make = formats.SYMBOLIC_PULSE._make + pack = formats.SYMBOLIC_PULSE_PACK + size = formats.SYMBOLIC_PULSE_SIZE + + header = make( struct.unpack( - formats.SYMBOLIC_PULSE_PACK, - file_obj.read(formats.SYMBOLIC_PULSE_SIZE), + pack, + file_obj.read(size), ) ) pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) @@ -91,35 +101,121 @@ def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def] 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, {}) - # TODO remove this and merge subclasses into a single kind of SymbolicPulse - # We need some refactoring of our codebase, - # mainly removal of isinstance check and name access with self.__class__.__name__. - if pulse_type == "Gaussian": - pulse_cls = library.Gaussian - elif pulse_type == "GaussianSquare": - pulse_cls = library.GaussianSquare - elif pulse_type == "Drag": - pulse_cls = library.Drag - elif pulse_type == "Constant": - pulse_cls = library.Constant + 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: - pulse_cls = library.SymbolicPulse - - # Skip calling constructor to absorb signature mismatch in subclass. - instance = object.__new__(pulse_cls) - instance.duration = duration - instance.name = name - instance._limit_amplitude = header.amp_limited - instance._pulse_type = pulse_type - instance._params = parameters - instance._envelope = envelope - instance._constraints = constraints - instance._valid_amp_conditions = valid_amp_conditions + raise NotImplementedError(f"Unknown class '{class_name}'") - return instance + +def _read_symbolic_pulse_v6(file_obj, version): # 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)) + 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={}, + ) + + 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] @@ -143,9 +239,14 @@ def _loads_operand(type_key, data_bytes, version): # type: ignore[no-untyped-de 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: - return common.data_from_binary( - data_bytes, _read_symbolic_pulse, version=version - ) + 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 + ) if type_key == type_keys.ScheduleOperand.CHANNEL: return common.data_from_binary(data_bytes, _read_channel, version=version) @@ -159,9 +260,7 @@ def _read_element(file_obj, version, metadata_deserializer): # type: ignore[no- return read_schedule_block(file_obj, version, metadata_deserializer) operands = common.read_sequence( - file_obj, - deserializer=_loads_operand, - version=version, + file_obj, deserializer=_loads_operand, version=version ) name = value.read_value(file_obj, version, {}) @@ -204,13 +303,15 @@ def _dumps_symbolic_expr(expr): # type: ignore[no-untyped-def] def _write_symbolic_pulse(file_obj, data): # 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) constraints_bytes = _dumps_symbolic_expr(data.constraints) valid_amp_conditions_bytes = _dumps_symbolic_expr(data.valid_amp_conditions) header_bytes = struct.pack( - formats.SYMBOLIC_PULSE_PACK, + formats.SYMBOLIC_PULSE_PACK_V2, + len(class_name_bytes), len(pulse_type_bytes), len(envelope_bytes), len(constraints_bytes), @@ -218,6 +319,7 @@ def _write_symbolic_pulse(file_obj, data): # type: ignore[no-untyped-def] 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) @@ -274,6 +376,7 @@ def _write_element(file_obj, element, metadata_serializer): # type: ignore[no-u def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: ignore[no-untyped-def] """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. @@ -284,12 +387,15 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: 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. + 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. """ + if version < 5: QiskitError(f"QPY version {version} does not support ScheduleBlock.") @@ -318,6 +424,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ignore[no-untyped-def] """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. @@ -325,6 +432,7 @@ def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ig 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. + Raises: TypeError: If any of the instructions is invalid data format. """ diff --git a/qiskit_ibm_runtime/qpy/common.py b/qiskit_ibm_runtime/qpy/common.py index 1319d35b8b..4242548d53 100644 --- a/qiskit_ibm_runtime/qpy/common.py +++ b/qiskit_ibm_runtime/qpy/common.py @@ -21,7 +21,7 @@ from . import formats -QPY_VERSION = 5 +QPY_VERSION = 6 ENCODE = "utf8" diff --git a/qiskit_ibm_runtime/qpy/formats.py b/qiskit_ibm_runtime/qpy/formats.py index 45376fc8f3..c99336980f 100644 --- a/qiskit_ibm_runtime/qpy/formats.py +++ b/qiskit_ibm_runtime/qpy/formats.py @@ -207,6 +207,21 @@ 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" diff --git a/qiskit_ibm_runtime/qpy/interface.py b/qiskit_ibm_runtime/qpy/interface.py index 76b5918b53..2f1113ea88 100644 --- a/qiskit_ibm_runtime/qpy/interface.py +++ b/qiskit_ibm_runtime/qpy/interface.py @@ -124,6 +124,7 @@ def dump( # type: ignore[no-untyped-def] 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. + Raises: QpyError: When multiple data format is mixed in the output. TypeError: When invalid data type is input. @@ -215,10 +216,6 @@ def load( # type: ignore[no-untyped-def] the default ``JSONDecoder`` class. Returns: - list: List of ``QuantumCircuit`` - The list of :class:`~qiskit.circuit.QuantumCircuit` objects - contained in the QPY data. A list is always returned, even if there - is only 1 circuit in the QPY data. 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. @@ -235,21 +232,20 @@ def load( # type: ignore[no-untyped-def] if data.preface.decode(common.ENCODE) != "QISKIT": raise QiskitError("Input file is not a valid QPY file") version_match = VERSION_PATTERN_REGEX.search(__version__) - version_parts = [int(x) for x in version_match.group("release").split(".")] - - header_version_parts = [data.major_version, data.minor_version, data.patch_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 ( - version_parts[0] < header_version_parts[0] + env_qiskit_version[0] < qiskit_version[0] or ( - version_parts[0] == header_version_parts[0] - and header_version_parts[1] > version_parts[1] + env_qiskit_version[0] == qiskit_version[0] + and qiskit_version[1] > env_qiskit_version[1] ) or ( - version_parts[0] == header_version_parts[0] - and header_version_parts[1] == version_parts[1] - and header_version_parts[2] > version_parts[2] + 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( @@ -257,7 +253,7 @@ def load( # type: ignore[no-untyped-def] "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 header_version_parts]), __version__) + "version" % (".".join([str(x) for x in qiskit_version]), __version__) ) if data.qpy_version < 5: @@ -276,7 +272,9 @@ def load( # type: ignore[no-untyped-def] for _ in range(data.num_programs): programs.append( loader( # type: ignore[no-untyped-call] - file_obj, data.qpy_version, metadata_deserializer=metadata_deserializer + file_obj, + data.qpy_version, + metadata_deserializer=metadata_deserializer, ) ) return programs diff --git a/qiskit_ibm_runtime/qpy/type_keys.py b/qiskit_ibm_runtime/qpy/type_keys.py index 59a09c8871..fabfb777d4 100644 --- a/qiskit_ibm_runtime/qpy/type_keys.py +++ b/qiskit_ibm_runtime/qpy/type_keys.py @@ -44,6 +44,7 @@ SetPhase, ShiftPhase, RelativeBarrier, + TimeBlockade, ) from qiskit.pulse.library import Waveform, SymbolicPulse from qiskit.pulse.schedule import ScheduleBlock @@ -227,10 +228,11 @@ class ScheduleInstruction(TypeKeyBase): SET_PHASE = b"q" SHIFT_PHASE = b"r" BARRIER = b"b" + TIME_BLOCKADE = b"t" # 's' is reserved by ScheduleBlock, i.e. block can be nested as an element. # Call instructon is not supported by QPY. - # This instruction is excluded from ScheduleBlock instructions with + # 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. @@ -253,6 +255,8 @@ def assign(cls, obj): # type: ignore[no-untyped-def] return cls.SHIFT_PHASE if isinstance(obj, RelativeBarrier): return cls.BARRIER + if isinstance(obj, TimeBlockade): + return cls.TIME_BLOCKADE raise exceptions.QpyError( f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." @@ -276,6 +280,8 @@ def retrieve(cls, type_key): # type: ignore[no-untyped-def] return ShiftPhase if type_key == cls.BARRIER: return RelativeBarrier + if type_key == cls.TIME_BLOCKADE: + return TimeBlockade raise exceptions.QpyError( f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace." diff --git a/requirements.txt b/requirements.txt index d05a7cdb9a..de2658e288 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit-terra>=0.22 +qiskit-terra>=0.23.1 requests>=2.19 requests_ntlm>=1.1.0 numpy<1.24 diff --git a/setup.py b/setup.py index 3c7af0984a..d9020dd8e4 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit-terra>=0.22", + "qiskit-terra>=0.23.1", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy<1.24",