From 0766f54c405afd9ac0cf29b1de28a497692d7140 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 21 Sep 2023 15:22:44 -0400 Subject: [PATCH 01/15] Update qpy qiskit 0.45 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 34 ++++++++++++++----- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index ed858a4cc..de9db8a06 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -25,10 +25,11 @@ from qiskit import circuit as circuit_mod from qiskit import extensions -from qiskit.circuit import library, controlflow, CircuitInstruction +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_gate import SingletonGate from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.instruction import Instruction from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -289,8 +290,10 @@ def _read_instruction( # type: ignore[no-untyped-def] 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) + gate = gate_class(condition, *params, label=label) elif version >= 5 and issubclass(gate_class, ControlledGate): if gate_name in { "MCPhaseGate", @@ -300,9 +303,9 @@ def _read_instruction( # type: ignore[no-untyped-def] "MCXRecursive", "MCXVChain", }: - gate = gate_class(*params, instruction.num_ctrl_qubits) + gate = gate_class(*params, instruction.num_ctrl_qubits, label=label) else: - gate = gate_class(*params) + gate = gate_class(*params, label=label) gate.num_ctrl_qubits = instruction.num_ctrl_qubits gate.ctrl_state = instruction.ctrl_state gate.condition = condition @@ -321,10 +324,19 @@ def _read_instruction( # type: ignore[no-untyped-def] params = [len(qargs)] elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}: params = [len(qargs), len(cargs)] - gate = gate_class(*params) - gate.condition = condition - if instruction.label_size > 0: - gate.label = label + if label is not None: + if issubclass(gate_class, 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): @@ -580,7 +592,6 @@ def _write_instruction( # type: ignore[no-untyped-def] ) or gate_class_name == "Gate" or gate_class_name == "Instruction" - or gate_class_name == "ControlledGate" or isinstance(instruction.operation, library.BlueprintCircuit) ): if instruction.operation.name not in custom_operations: @@ -588,6 +599,11 @@ def _write_instruction( # type: ignore[no-untyped-def] custom_operations_list.append(instruction.operation.name) gate_class_name = instruction.operation.name + elif gate_class_name == "ControlledGate": + gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4()) + custom_operations[gate_class_name] = instruction.operation + custom_operations_list.append(gate_class_name) + elif isinstance(instruction.operation, library.PauliEvolutionGate): gate_class_name = r"###PauliEvolutionGate_" + str(uuid.uuid4()) custom_operations[gate_class_name] = instruction.operation diff --git a/requirements.txt b/requirements.txt index 8c38bd5e9..3e78ac165 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=0.44.1 +qiskit>=0.45 requests>=2.19 requests_ntlm>=1.1.0 numpy>=1.13 diff --git a/setup.py b/setup.py index cc75e7a40..44f1a4a3a 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit>=0.44.1", + "qiskit>=0.45", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy>=1.13", From 0ef57047a368f629b43932472095460962564b46 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 10:35:24 -0400 Subject: [PATCH 02/15] test 0.45.0rc1 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3e78ac165..51e4fa77d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=0.45 +qiskit>=0.45.0rc1 requests>=2.19 requests_ntlm>=1.1.0 numpy>=1.13 diff --git a/setup.py b/setup.py index 44f1a4a3a..d3ef108c1 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit>=0.45", + "qiskit>=0.45.0rc1", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy>=1.13", From c9718b178a33cb6579583c889fea6077b1d276a5 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 12:30:17 -0400 Subject: [PATCH 03/15] copy updates from qiskit/10820 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 107 +++++++++++++---- .../qpy/binary_io/schedules.py | 111 +++++++++++++----- qiskit_ibm_provider/qpy/binary_io/value.py | 95 +++++++++++---- qiskit_ibm_provider/qpy/common.py | 2 +- qiskit_ibm_provider/qpy/formats.py | 16 +++ qiskit_ibm_provider/qpy/interface.py | 44 +++++-- qiskit_ibm_provider/qpy/type_keys.py | 18 +++ 7 files changed, 308 insertions(+), 85 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index de9db8a06..c7fffdef4 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -29,7 +29,7 @@ from qiskit.circuit.classical import expr from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.circuit.gate import Gate -from qiskit.circuit.singleton_gate import SingletonGate +from qiskit.circuit.singleton import SingletonGate from qiskit.circuit.controlledgate import ControlledGate from qiskit.circuit.instruction import Instruction from qiskit.circuit.quantumcircuit import QuantumCircuit @@ -130,7 +130,7 @@ def _read_registers(file_obj, num_registers): # type: ignore[no-untyped-def] def _loads_instruction_parameter( # type: ignore[no-untyped-def] - type_key, data_bytes, version, vectors, registers, circuit + 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) @@ -146,6 +146,7 @@ def _loads_instruction_parameter( # type: ignore[no-untyped-def] vectors=vectors, registers=registers, circuit=circuit, + use_symengine=use_symengine, ) ) elif type_key == type_keys.Value.INTEGER: @@ -166,6 +167,7 @@ def _loads_instruction_parameter( # type: ignore[no-untyped-def] vectors, clbits=circuit.clbits, cregs=registers["c"], + use_symengine=use_symengine, ) return param @@ -180,7 +182,7 @@ def _loads_register_param(data_bytes, circuit, registers): # type: ignore[no-un def _read_instruction( # type: ignore[no-untyped-def] - file_obj, circuit, registers, custom_operations, version, vectors + file_obj, circuit, registers, custom_operations, version, vectors, use_symengine ): if version < 5: instruction = formats.CIRCUIT_INSTRUCTION._make( @@ -214,7 +216,12 @@ def _read_instruction( # type: ignore[no-untyped-def] ) 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"] + 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)) @@ -250,14 +257,26 @@ def _read_instruction( # type: ignore[no-untyped-def] 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 + 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 + custom_operations, + gate_name, + params, + version, + vectors, + registers, + use_symengine, ) inst_obj.condition = condition if instruction.label_size > 0: @@ -268,7 +287,13 @@ def _read_instruction( # type: ignore[no-untyped-def] return None elif gate_name in custom_operations: inst_obj = _parse_custom_operation( - custom_operations, gate_name, params, version, vectors, registers + custom_operations, + gate_name, + params, + version, + vectors, + registers, + use_symengine, ) inst_obj.condition = condition if instruction.label_size > 0: @@ -347,7 +372,7 @@ def _read_instruction( # type: ignore[no-untyped-def] def _parse_custom_operation( # type: ignore[no-untyped-def] - custom_operations, gate_name, params, version, vectors, registers + custom_operations, gate_name, params, version, vectors, registers, use_symengine ): if version >= 5: ( @@ -377,7 +402,13 @@ def _parse_custom_operation( # type: ignore[no-untyped-def] 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 + 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. @@ -545,7 +576,7 @@ def _dumps_register(register, index_map): # type: ignore[no-untyped-def] return b"\x00" + str(index_map["c"][register]).encode(common.ENCODE) -def _dumps_instruction_parameter(param, index_map): # type: ignore[no-untyped-def] +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) @@ -557,7 +588,10 @@ def _dumps_instruction_parameter(param, index_map): # type: ignore[no-untyped-d elif isinstance(param, tuple): type_key = type_keys.Container.TUPLE data_bytes = common.sequence_to_binary( - param, _dumps_instruction_parameter, index_map=index_map + 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. @@ -571,14 +605,16 @@ def _dumps_instruction_parameter(param, index_map): # type: ignore[no-untyped-d type_key = type_keys.Value.REGISTER data_bytes = _dumps_register(param, index_map) else: - type_key, data_bytes = value.dumps_value(param, index_map=index_map) + type_key, data_bytes = value.dumps_value( + param, index_map=index_map, use_symengine=use_symengine + ) return type_key, data_bytes # pylint: disable=too-many-boolean-expressions def _write_instruction( # type: ignore[no-untyped-def] - file_obj, instruction, custom_operations, index_map + file_obj, instruction, custom_operations, index_map, use_symengine ): gate_class_name = instruction.operation.__class__.__name__ custom_operations_list = [] @@ -661,7 +697,7 @@ def _write_instruction( # type: ignore[no-untyped-def] value.write_value(file_obj, op_condition, index_map=index_map) else: file_obj.write(condition_register) - # Encode instruciton args + # Encode instruction args for qbit in instruction.qubits: instruction_arg_raw = struct.pack( formats.CIRCUIT_INSTRUCTION_ARG_PACK, b"q", index_map["q"][qbit] @@ -674,7 +710,9 @@ def _write_instruction( # type: ignore[no-untyped-def] file_obj.write(instruction_arg_raw) # Encode instruction params for param in instruction_params: - type_key, data_bytes = _dumps_instruction_parameter(param, index_map) + type_key, data_bytes = _dumps_instruction_parameter( + param, index_map, use_symengine + ) common.write_generic_typed_data(file_obj, type_key, data_bytes) return custom_operations_list @@ -724,7 +762,7 @@ def _write_elem(buffer, op): # type: ignore[no-untyped-def] def _write_custom_operation( # type: ignore[no-untyped-def] - file_obj, name, operation, custom_operations + file_obj, name, operation, custom_operations, use_symengine ): type_key = type_keys.CircuitInstruction.assign(operation) has_definition = False @@ -768,6 +806,7 @@ def _write_custom_operation( # type: ignore[no-untyped-def] CircuitInstruction(base_gate, (), ()), custom_operations, {}, + use_symengine, ) base_gate_raw = base_gate_buffer.getvalue() name_raw = name.encode(common.ENCODE) @@ -994,7 +1033,9 @@ def _read_layout(file_obj, circuit): # type: ignore[no-untyped-def] circuit._layout = TranspileLayout(initial_layout, input_qubit_mapping, final_layout) -def write_circuit(file_obj, circuit, metadata_serializer=None): # type: ignore[no-untyped-def] +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: @@ -1004,6 +1045,10 @@ def write_circuit(file_obj, circuit, metadata_serializer=None): # type: ignore[ 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 @@ -1044,7 +1089,7 @@ def write_circuit(file_obj, circuit, metadata_serializer=None): # type: ignore[ 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 + instruction_buffer, instruction, custom_operations, index_map, use_symengine ) with io.BytesIO() as custom_operations_buffer: @@ -1056,7 +1101,11 @@ def write_circuit(file_obj, circuit, metadata_serializer=None): # type: ignore[ operation = custom_operations[name] new_custom_operations.extend( _write_custom_operation( - custom_operations_buffer, name, operation, custom_operations + custom_operations_buffer, + name, + operation, + custom_operations, + use_symengine, ) ) @@ -1073,7 +1122,9 @@ def write_circuit(file_obj, circuit, metadata_serializer=None): # type: ignore[ _write_layout(file_obj, circuit) -def read_circuit(file_obj, version, metadata_deserializer=None): # type: ignore[no-untyped-def] +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: @@ -1087,6 +1138,12 @@ def read_circuit(file_obj, version, metadata_deserializer=None): # type: ignore 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. @@ -1108,7 +1165,7 @@ def read_circuit(file_obj, version, metadata_deserializer=None): # type: ignore num_clbits = header["num_clbits"] num_registers = header["num_registers"] num_instructions = header["num_instructions"] - # `out_registers` is two "name: registter" maps segregated by type for the rest of QPY, and + # `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 = [] @@ -1185,7 +1242,13 @@ def read_circuit(file_obj, version, metadata_deserializer=None): # type: ignore 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 + file_obj, + circ, + out_registers, + custom_operations, + version, + vectors, + use_symengine, ) # Read calibrations diff --git a/qiskit_ibm_provider/qpy/binary_io/schedules.py b/qiskit_ibm_provider/qpy/binary_io/schedules.py index a0e67a0c6..0e6c7ffc3 100644 --- a/qiskit_ibm_provider/qpy/binary_io/schedules.py +++ b/qiskit_ibm_provider/qpy/binary_io/schedules.py @@ -110,19 +110,27 @@ def _read_discriminator(file_obj, version): # type: ignore[no-untyped-def] return Discriminator(name=name, **params) -def _loads_symbolic_expr(expr_bytes): # type: ignore[no-untyped-def] - from sympy import parse_expr # pylint: disable=import-outside-toplevel - +def _loads_symbolic_expr(expr_bytes, use_symengine=False): # type: ignore[no-untyped-def] if expr_bytes == b"": return None - expr_txt = zlib.decompress(expr_bytes).decode(common.ENCODE) - expr = parse_expr(expr_txt) + 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)) - if _optional.HAS_SYMENGINE: - from symengine import sympify # pylint: disable=import-outside-toplevel + else: + from sympy import parse_expr # pylint: disable=import-outside-toplevel - return sympify(expr) + 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 @@ -210,7 +218,7 @@ def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def] raise NotImplementedError(f"Unknown class '{class_name}'") -def _read_symbolic_pulse_v6(file_obj, version): # type: ignore[no-untyped-def] +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 @@ -223,10 +231,12 @@ def _read_symbolic_pulse_v6(file_obj, version): # type: ignore[no-untyped-def] ) 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)) + 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) + file_obj.read(header.valid_amp_conditions_size), use_symengine ) parameters = common.read_mapping( file_obj, @@ -284,7 +294,7 @@ def _read_alignment_context(file_obj, version): # type: ignore[no-untyped-def] # pylint: disable=too-many-return-statements -def _loads_operand(type_key, data_bytes, version): # type: ignore[no-untyped-def] +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: @@ -294,7 +304,10 @@ def _loads_operand(type_key, data_bytes, version): # type: ignore[no-untyped-de ) else: return common.data_from_binary( - data_bytes, _read_symbolic_pulse_v6, version=version + 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) @@ -316,14 +329,21 @@ def _loads_operand(type_key, data_bytes, version): # type: ignore[no-untyped-de return value.loads_value(type_key, data_bytes, version, {}) -def _read_element(file_obj, version, metadata_deserializer): # type: ignore[no-untyped-def] +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) + return read_schedule_block( + file_obj, version, metadata_deserializer, use_symengine + ) operands = common.read_sequence( - file_obj, deserializer=_loads_operand, version=version + file_obj, + deserializer=_loads_operand, + version=version, + use_symengine=use_symengine, ) name = value.read_value(file_obj, version, {}) @@ -409,22 +429,28 @@ def _write_discriminator(file_obj, data): # type: ignore[no-untyped-def] value.write_value(file_obj, name) -def _dumps_symbolic_expr(expr): # type: ignore[no-untyped-def] - from sympy import srepr, sympify # pylint: disable=import-outside-toplevel - +def _dumps_symbolic_expr(expr, use_symengine): # type: ignore[no-untyped-def] if expr is None: return b"" - expr_bytes = srepr(sympify(expr)).encode(common.ENCODE) + 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): # type: ignore[no-untyped-def] +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) - constraints_bytes = _dumps_symbolic_expr(data.constraints) - valid_amp_conditions_bytes = _dumps_symbolic_expr(data.valid_amp_conditions) + 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, @@ -460,13 +486,15 @@ def _write_alignment_context(file_obj, context): # type: ignore[no-untyped-def] ) -def _dumps_operand(operand): # type: ignore[no-untyped-def] +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) + 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) @@ -485,7 +513,9 @@ def _dumps_operand(operand): # type: ignore[no-untyped-def] return type_key, data_bytes -def _write_element(file_obj, element, metadata_serializer): # type: ignore[no-untyped-def] +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) @@ -496,6 +526,7 @@ def _write_element(file_obj, element, metadata_serializer): # type: ignore[no-u file_obj, sequence=element.operands, serializer=_dumps_operand, + use_symengine=use_symengine, ) value.write_value(file_obj, element.name) @@ -514,7 +545,9 @@ def _dumps_reference_item(schedule, metadata_serializer): # type: ignore[no-unt return type_key, data_bytes -def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: ignore[no-untyped-def] +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: @@ -528,6 +561,11 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: 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. @@ -553,7 +591,9 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: alignment_context=context, ) for _ in range(data.num_elements): - block_elm = _read_element(file_obj, version, metadata_deserializer) + block_elm = _read_element( + file_obj, version, metadata_deserializer, use_symengine + ) block.append(block_elm, inplace=True) # Load references @@ -577,7 +617,9 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None): # type: return block -def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ignore[no-untyped-def] +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: @@ -588,6 +630,11 @@ def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ig ``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. """ @@ -609,7 +656,7 @@ def write_schedule_block(file_obj, block, metadata_serializer=None): # type: ig _write_alignment_context(file_obj, block.alignment_context) for block_elm in block._blocks: - _write_element(file_obj, block_elm, metadata_serializer) + _write_element(file_obj, block_elm, metadata_serializer, use_symengine) # Write references flat_key_refdict = {} diff --git a/qiskit_ibm_provider/qpy/binary_io/value.py b/qiskit_ibm_provider/qpy/binary_io/value.py index a275a517c..04a1901fe 100644 --- a/qiskit_ibm_provider/qpy/binary_io/value.py +++ b/qiskit_ibm_provider/qpy/binary_io/value.py @@ -53,10 +53,15 @@ def _write_parameter_vec(file_obj, obj): # type: ignore[no-untyped-def] file_obj.write(name_bytes) -def _write_parameter_expression(file_obj, obj): # type: ignore[no-untyped-def] - from sympy import srepr, sympify # pylint: disable=import-outside-toplevel +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) - 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) ) @@ -77,7 +82,7 @@ def _write_parameter_expression(file_obj, obj): # type: ignore[no-untyped-def] value_key = symbol_key value_data = bytes() else: - value_key, value_data = dumps_value(value) + value_key, value_data = dumps_value(value, use_symengine=use_symengine) elem_header = struct.pack( formats.PARAM_EXPR_MAP_ELEM_V3_PACK, @@ -250,11 +255,9 @@ def _read_parameter_expression(file_obj): # type: ignore[no-untyped-def] from sympy.parsing.sympy_parser import parse_expr if _optional.HAS_SYMENGINE: - import symengine # pylint: disable=import-outside-toplevel + from symengine import sympify # pylint: disable=import-outside-toplevel - expr_ = symengine.sympify( - parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)) - ) + 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 = {} @@ -393,7 +396,7 @@ def _read_expr_type(file_obj) -> types.Type: # type: ignore[no-untyped-def] raise exceptions.QpyError(f"Invalid classical-expression Type key '{type_key}'") -def _read_parameter_expression_v3(file_obj, vectors): # type: ignore[no-untyped-def] +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) @@ -402,14 +405,23 @@ def _read_parameter_expression_v3(file_obj, vectors): # type: ignore[no-untyped # pylint: disable=import-outside-toplevel from sympy.parsing.sympy_parser import parse_expr - if _optional.HAS_SYMENGINE: - import symengine # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel - expr_ = symengine.sympify( - parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)) + 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: - expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)) + 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( @@ -441,7 +453,10 @@ def _read_parameter_expression_v3(file_obj, vectors): # type: ignore[no-untyped 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 + binary_data, + _read_parameter_expression_v3, + vectors=vectors, + use_symengine=use_symengine, ) else: raise exceptions.QpyError( @@ -452,13 +467,17 @@ def _read_parameter_expression_v3(file_obj, vectors): # type: ignore[no-untyped return ParameterExpression(symbol_map, expr_) -def dumps_value(obj, *, index_map=None): # type: ignore[no-untyped-def] +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: @@ -484,7 +503,7 @@ def dumps_value(obj, *, index_map=None): # type: ignore[no-untyped-def] 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 + 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"] @@ -499,19 +518,30 @@ def dumps_value(obj, *, index_map=None): # type: ignore[no-untyped-def] return type_key, binary_data -def write_value(file_obj, obj, *, index_map=None): # type: ignore[no-untyped-def] +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) + 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 + type_key, + binary_data, + version, + vectors, + *, + clbits=(), + cregs=None, + use_symengine=False, ) -> Any: """Deserialize input binary data to value object. Args: @@ -521,6 +551,10 @@ def loads_value( # type: ignore[no-untyped-def] 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: @@ -556,7 +590,10 @@ def loads_value( # type: ignore[no-untyped-def] return common.data_from_binary(binary_data, _read_parameter_expression) else: return common.data_from_binary( - binary_data, _read_parameter_expression_v3, vectors=vectors + binary_data, + _read_parameter_expression_v3, + vectors=vectors, + use_symengine=use_symengine, ) if type_key == type_keys.Value.EXPRESSION: return common.data_from_binary( @@ -568,7 +605,7 @@ def loads_value( # type: ignore[no-untyped-def] ) -def read_value(file_obj, version, vectors, *, clbits=(), cregs=None): # type: ignore[no-untyped-def] +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. @@ -576,9 +613,21 @@ def read_value(file_obj, version, vectors, *, clbits=(), cregs=None): # type: i 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) + return loads_value( + type_key, + data, + version, + vectors, + clbits=clbits, + cregs=cregs, + use_symengine=use_symengine, + ) diff --git a/qiskit_ibm_provider/qpy/common.py b/qiskit_ibm_provider/qpy/common.py index 346363fb2..058868658 100644 --- a/qiskit_ibm_provider/qpy/common.py +++ b/qiskit_ibm_provider/qpy/common.py @@ -22,7 +22,7 @@ from typing import Any from . import formats -QPY_VERSION = 9 +QPY_VERSION = 10 ENCODE = "utf8" diff --git a/qiskit_ibm_provider/qpy/formats.py b/qiskit_ibm_provider/qpy/formats.py index ea37a66aa..a343e03b7 100644 --- a/qiskit_ibm_provider/qpy/formats.py +++ b/qiskit_ibm_provider/qpy/formats.py @@ -18,6 +18,22 @@ 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( diff --git a/qiskit_ibm_provider/qpy/interface.py b/qiskit_ibm_provider/qpy/interface.py index c10dd2d78..3150b9a69 100644 --- a/qiskit_ibm_provider/qpy/interface.py +++ b/qiskit_ibm_provider/qpy/interface.py @@ -78,6 +78,7 @@ 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 @@ -123,6 +124,11 @@ 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. + 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. @@ -153,21 +159,26 @@ def dump( # type: ignore[no-untyped-def] 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_PACK, # type: ignore[attr-defined] + 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 + file_obj, + program, + metadata_serializer=metadata_serializer, + use_symengine=use_symengine, ) @@ -222,12 +233,25 @@ def load( # type: ignore[no-untyped-def] QiskitError: if ``file_obj`` is not a valid QPY file TypeError: When invalid data type is loaded. """ - 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] + + # identify file header version + version = struct.unpack("!6sB", file_obj.read(7))[1] + file_obj.seek(0) + + 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__) @@ -267,6 +291,11 @@ def load( # type: ignore[no-untyped-def] 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( @@ -274,6 +303,7 @@ def load( # type: ignore[no-untyped-def] file_obj, data.qpy_version, metadata_deserializer=metadata_deserializer, + use_symengine=use_symengine, ) ) return programs diff --git a/qiskit_ibm_provider/qpy/type_keys.py b/qiskit_ibm_provider/qpy/type_keys.py index bedafd0ab..a77424880 100644 --- a/qiskit_ibm_provider/qpy/type_keys.py +++ b/qiskit_ibm_provider/qpy/type_keys.py @@ -524,3 +524,21 @@ def assign(cls, obj): # type: ignore[no-untyped-def] @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 From 92029410c9117bed742ae79402066f8c3d84e0cf Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 12:39:51 -0400 Subject: [PATCH 04/15] copy updates qiskit/10835 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 29 +++++++++++++++++-- qiskit_ibm_provider/qpy/formats.py | 14 +++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index c7fffdef4..0ac62cd82 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -894,7 +894,7 @@ def _write_registers(file_obj, in_circ_regs, full_bits): # type: ignore[no-unty def _write_layout(file_obj, circuit): # type: ignore[no-untyped-def] if circuit.layout is None: # Write a null header if there is no layout present - file_obj.write(struct.pack(formats.LAYOUT_PACK, False, -1, -1, -1, 0)) + file_obj.write(struct.pack(formats.LAYOUT_V2_PACK, False, -1, -1, -1, 0, 0)) return initial_size = -1 input_qubit_mapping = {} @@ -937,9 +937,13 @@ def _write_layout(file_obj, circuit): # type: ignore[no-untyped-def] virtual_bit = final_layout_physical[i] final_layout_array.append(circuit.find_bit(virtual_bit).index) + input_qubit_count = circuit._layout._input_qubit_count + if input_qubit_count is None: + input_qubit_count = -1 + file_obj.write( struct.pack( - formats.LAYOUT_PACK, + formats.LAYOUT_V2_PACK, True, initial_size, input_qubit_size, @@ -977,6 +981,10 @@ def _read_layout(file_obj, circuit): # type: ignore[no-untyped-def] ) if not header.exists: return + _read_common_layout(file_obj, header, circuit) + + +def _read_common_layout(file_obj, header, circuit): # type: ignore[no-untyped-def] registers = { name: QuantumRegister(len(v[1]), name) for name, v in _read_registers_v4(file_obj, header.extra_registers)["q"].items() @@ -1033,6 +1041,18 @@ def _read_layout(file_obj, circuit): # type: ignore[no-untyped-def] circuit._layout = TranspileLayout(initial_layout, input_qubit_mapping, final_layout) +def _read_layout_v2(file_obj, circuit): # type: ignore[no-untyped-def] + header = formats.LAYOUT_V2._make( + struct.unpack(formats.LAYOUT_V2_PACK, file_obj.read(formats.LAYOUT_V2_SIZE)) + ) + if not header.exists: + return + _read_common_layout(file_obj, header, circuit) + if header.input_qubit_count >= 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 ): @@ -1268,5 +1288,8 @@ def read_circuit( # type: ignore[no-untyped-def] UserWarning, ) if version >= 8: - _read_layout(file_obj, circ) + if version >= 10: + _read_layout_v2(file_obj, circ) + else: + _read_layout(file_obj, circ) return circ diff --git a/qiskit_ibm_provider/qpy/formats.py b/qiskit_ibm_provider/qpy/formats.py index a343e03b7..674f82b5d 100644 --- a/qiskit_ibm_provider/qpy/formats.py +++ b/qiskit_ibm_provider/qpy/formats.py @@ -292,6 +292,20 @@ 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", [ From 5dbbc8fdd8554d83a5f556ddbcbed9b6ea49080a Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 12:49:37 -0400 Subject: [PATCH 05/15] Copy updates qiskit/10875 --- qiskit_ibm_provider/qpy/binary_io/value.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/value.py b/qiskit_ibm_provider/qpy/binary_io/value.py index 04a1901fe..6148a604f 100644 --- a/qiskit_ibm_provider/qpy/binary_io/value.py +++ b/qiskit_ibm_provider/qpy/binary_io/value.py @@ -217,9 +217,7 @@ def _read_parameter(file_obj): # type: ignore[no-untyped-def] ) param_uuid = uuid.UUID(bytes=data.uuid) name = file_obj.read(data.name_size).decode(common.ENCODE) - param = Parameter.__new__(Parameter, name, uuid=param_uuid) - param.__init__(name) # pylint: disable=unnecessary-dunder-call - return param + return Parameter(name, uuid=param_uuid) def _read_parameter_vec(file_obj, vectors): # type: ignore[no-untyped-def] @@ -236,11 +234,8 @@ def _read_parameter_vec(file_obj, vectors): # type: ignore[no-untyped-def] vector = vectors[name][0] if vector[data.index]._uuid != param_uuid: vectors[name][1].add(data.index) - vector._params[data.index] = ParameterVectorElement.__new__( - ParameterVectorElement, vector, data.index, uuid=param_uuid - ) - vector._params[data.index].__init__( # pylint: disable=unnecessary-dunder-call - vector, data.index + vector._params[data.index] = ParameterVectorElement( + vector, data.index, uuid=param_uuid ) return vector[data.index] From 65f090e6583d309cedab72dc63b5e5817c625d5a Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 12:57:33 -0400 Subject: [PATCH 06/15] copy updates qiskit/11014 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index 0ac62cd82..ad4e62d60 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -29,7 +29,7 @@ from qiskit.circuit.classical import expr from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.circuit.gate import Gate -from qiskit.circuit.singleton import SingletonGate +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 @@ -350,7 +350,7 @@ def _read_instruction( # type: ignore[no-untyped-def] elif gate_name in {"BreakLoopOp", "ContinueLoopOp"}: params = [len(qargs), len(cargs)] if label is not None: - if issubclass(gate_class, SingletonGate): + if issubclass(gate_class, (SingletonInstruction, SingletonGate)): gate = gate_class(*params, label=label) else: gate = gate_class(*params) @@ -616,7 +616,7 @@ def _dumps_instruction_parameter(param, index_map, use_symengine): # type: igno def _write_instruction( # type: ignore[no-untyped-def] file_obj, instruction, custom_operations, index_map, use_symengine ): - gate_class_name = instruction.operation.__class__.__name__ + gate_class_name = instruction.operation.base_class.__name__ custom_operations_list = [] if ( ( From c2f0f1d01029654fd2d1816700f1485eacf364a6 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 13:02:53 -0400 Subject: [PATCH 07/15] Copy updates qiskit/10809 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index ad4e62d60..036d95ccf 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -160,12 +160,13 @@ def _loads_instruction_parameter( # type: ignore[no-untyped-def] data_bytes.decode(common.ENCODE), circuit, registers ) else: + clbits = circuit.clbits if circuit is not None else () param = value.loads_value( type_key, data_bytes, version, vectors, - clbits=circuit.clbits, + clbits=clbits, cregs=registers["c"], use_symengine=use_symengine, ) @@ -634,8 +635,20 @@ def _write_instruction( # type: ignore[no-untyped-def] custom_operations[instruction.operation.name] = instruction.operation custom_operations_list.append(instruction.operation.name) gate_class_name = instruction.operation.name + # ucr*_dg gates can have different numbers of parameters, + # the uuid is appended to avoid storing a single definition + # in circuits with multiple ucr*_dg gates. + if instruction.operation.name in ["ucrx_dg", "ucry_dg", "ucrz_dg"]: + gate_class_name += "_" + str(uuid.uuid4()) + + if gate_class_name not in custom_operations: + custom_operations[gate_class_name] = instruction.operation + custom_operations_list.append(gate_class_name) elif gate_class_name == "ControlledGate": + # controlled gates can have the same name but different parameter + # values, the uuid is appended to avoid storing a single definition + # in circuits with multiple controlled gates. gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4()) custom_operations[gate_class_name] = instruction.operation custom_operations_list.append(gate_class_name) From b46a099b94b89a1608bc71613746d9cce7f566d2 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 13:08:09 -0400 Subject: [PATCH 08/15] Copy updates qiskit/10898 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index 036d95ccf..b09e3c57a 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -332,9 +332,15 @@ def _read_instruction( # type: ignore[no-untyped-def] gate = gate_class(*params, instruction.num_ctrl_qubits, label=label) else: gate = gate_class(*params, label=label) - gate.num_ctrl_qubits = instruction.num_ctrl_qubits - gate.ctrl_state = instruction.ctrl_state - gate.condition = condition + 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", From 5c0c745147b59d7273efb82aacdb2b51041babc8 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 20 Oct 2023 13:11:46 -0400 Subject: [PATCH 09/15] Copy updates qiskit/10754 --- qiskit_ibm_provider/qpy/interface.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit_ibm_provider/qpy/interface.py b/qiskit_ibm_provider/qpy/interface.py index 3150b9a69..bfaa7e410 100644 --- a/qiskit_ibm_provider/qpy/interface.py +++ b/qiskit_ibm_provider/qpy/interface.py @@ -22,7 +22,6 @@ from qiskit.pulse import ScheduleBlock from qiskit.exceptions import QiskitError from qiskit.version import __version__ -from qiskit.utils.deprecation import deprecate_arg from . import formats, common, binary_io, type_keys from .exceptions import QpyError @@ -73,7 +72,6 @@ VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE) -@deprecate_arg("circuits", new_alias="programs", since="0.21.0") def dump( # type: ignore[no-untyped-def] programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES], file_obj: BinaryIO, From d4d5d1e076714ed51494eddac9e1397d361949b7 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 2 Nov 2023 15:12:48 -0400 Subject: [PATCH 10/15] copy qiskit/11074 --- qiskit_ibm_provider/qpy/interface.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qiskit_ibm_provider/qpy/interface.py b/qiskit_ibm_provider/qpy/interface.py index bfaa7e410..50e5b7eb0 100644 --- a/qiskit_ibm_provider/qpy/interface.py +++ b/qiskit_ibm_provider/qpy/interface.py @@ -236,6 +236,12 @@ def load( # type: ignore[no-untyped-def] 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( From 8a6d4c82d74a1e79efc454887083c3a874953113 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 2 Nov 2023 15:25:09 -0400 Subject: [PATCH 11/15] fix docs build --- .../transpiler/passes/scheduling/__init__.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py b/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py index c3017e9bc..cc8627234 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py @@ -80,7 +80,7 @@ # Transpile. scheduled_teleport = pm.run(teleport) - scheduled_teleport.draw(output="mpl") + scheduled_teleport.draw(output="mpl", style="iqp") Instead of padding with delays we may also insert a dynamical decoupling sequence @@ -105,7 +105,7 @@ dd_teleport = pm.run(teleport) - dd_teleport.draw(output="mpl") + dd_teleport.draw(output="mpl", style="iqp") When compiling a circuit with Qiskit, it is more efficient and more robust to perform all the transformations in a single transpilation. This has been done above by extending Qiskit's preset @@ -123,7 +123,7 @@ qc_c_if = QuantumCircuit(1, 1) qc_c_if.x(0).c_if(0, 1) - qc_c_if.draw(output="mpl") + qc_c_if.draw(output="mpl", style="iqp") The :class:`.IBMBackend` configures a translation plugin :class:`.IBMTranslationPlugin` to automatically @@ -146,7 +146,7 @@ ) qc_if_dd = pm.run(qc_c_if, backend) - qc_if_dd.draw(output="mpl") + qc_if_dd.draw(output="mpl", style="iqp") If you are not using the transpiler plugin stages to @@ -168,7 +168,7 @@ ) qc_if_dd = pm.run(qc_c_if) - qc_if_dd.draw(output="mpl") + qc_if_dd.draw(output="mpl", style="iqp") Exploiting IBM backend's local parallel "fast-path" @@ -194,7 +194,7 @@ with qc.if_test((1, 1)): qc.x(1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") The circuit below will not use the fast-path as the conditional gate is @@ -207,7 +207,7 @@ with qc.if_test((0, 1)): qc.x(1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") Similarly, the circuit below contains gates on multiple qubits and will not be performed using the fast-path. @@ -220,7 +220,7 @@ qc.x(0) qc.x(1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") A fast-path block may contain multiple gates as long as they are on the fast-path qubit. If there are multiple fast-path blocks being performed in parallel each block will be @@ -238,7 +238,7 @@ with qc.if_test((1, 1)): qc.delay(1600, 1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") This behavior is also applied to the else condition of a fast-path eligible branch. @@ -253,7 +253,7 @@ with else_: qc.delay(1600, 0) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") If a single measurement result is used with several conditional blocks, if there is a fast-path @@ -272,7 +272,7 @@ # Does not use fast-path qc.x(1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") If you wish to prevent the usage of the fast-path you may insert a barrier between the measurement and the conditional branch. @@ -286,7 +286,7 @@ with qc.if_test((0, 1)): qc.x(0) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") Conditional measurements are not eligible for the fast-path. @@ -298,7 +298,7 @@ # Does not use the fast-path qc.measure(0, 1) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") Similarly nested control-flow is not eligible. @@ -312,7 +312,7 @@ with qc.if_test((0, 1)): qc.x(0) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") The scheduler is aware of the fast-path behavior and will not insert delays on idle qubits @@ -345,11 +345,11 @@ qc.delay(1000, 0) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") qc_dd = pm.run(qc) - qc_dd.draw(output="mpl") + qc_dd.draw(output="mpl", style="iqp") .. note:: If there are qubits that are *not* involved in a fast-path decision it is not @@ -374,7 +374,7 @@ # since the condition is compile time evaluated. qc.x(2) - qc.draw(output="mpl") + qc.draw(output="mpl", style="iqp") Scheduling & Dynamical Decoupling From 8862ac521bc5840b339c3b4eeed1c1a0444dcf3b Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 3 Nov 2023 11:33:12 -0400 Subject: [PATCH 12/15] use qiskit 0.45.0 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 51e4fa77d..5a7e211d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=0.45.0rc1 +qiskit>=0.45.0 requests>=2.19 requests_ntlm>=1.1.0 numpy>=1.13 diff --git a/setup.py b/setup.py index d3ef108c1..39753fd72 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit>=0.45.0rc1", + "qiskit>=0.45.0", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy>=1.13", From 7d3617061739579ef02ba3c840d51ed6debe8227 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 3 Nov 2023 11:40:41 -0400 Subject: [PATCH 13/15] add use_symengine param to dump() --- qiskit_ibm_provider/utils/json.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_provider/utils/json.py b/qiskit_ibm_provider/utils/json.py index 9114ff025..ee442006b 100644 --- a/qiskit_ibm_provider/utils/json.py +++ b/qiskit_ibm_provider/utils/json.py @@ -24,6 +24,7 @@ import re import warnings import zlib + from datetime import date from typing import Any, Callable, Dict, List, Union, Tuple @@ -55,6 +56,7 @@ 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 ( _write_parameter, @@ -225,7 +227,7 @@ def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ value = _serialize_and_encode( data=obj, serializer=lambda buff, data: dump( - data, buff, RuntimeEncoder + data, buff, RuntimeEncoder, use_symengine=optionals.HAS_SYMENGINE ), # type: ignore[no-untyped-call] ) return {"__type__": "QuantumCircuit", "__value__": value} @@ -250,7 +252,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), # type: ignore[no-untyped-call] + serializer=lambda buff, data: dump( + data, buff, use_symengine=optionals.HAS_SYMENGINE + ), # type: ignore[no-untyped-call] ) return {"__type__": "Instruction", "__value__": value} if HAS_AER and isinstance(obj, qiskit_aer.noise.NoiseModel): From 4ebc2cf95c07a6acdc114553e6f856fcada26d72 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 4 Jan 2024 14:29:10 -0500 Subject: [PATCH 14/15] copy changes from qiskit/11206 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index b09e3c57a..4f202b820 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -342,9 +342,17 @@ def _read_instruction( # type: ignore[no-untyped-def] if condition: gate = gate.c_if(*condition) else: - if gate_name in { - "Initialize", - "StatePreparation", + 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", From b8ed9a4a5fc37564721800292d472633390bdb4b Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 4 Jan 2024 14:30:42 -0500 Subject: [PATCH 15/15] copy changes qiskit/11261 --- qiskit_ibm_provider/qpy/binary_io/schedules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/schedules.py b/qiskit_ibm_provider/qpy/binary_io/schedules.py index 0e6c7ffc3..c5440dc8b 100644 --- a/qiskit_ibm_provider/qpy/binary_io/schedules.py +++ b/qiskit_ibm_provider/qpy/binary_io/schedules.py @@ -518,7 +518,7 @@ def _write_element( # type: ignore[no-untyped-def] ): if isinstance(element, ScheduleBlock): common.write_type_key(file_obj, type_keys.Program.SCHEDULE_BLOCK) - write_schedule_block(file_obj, element, metadata_serializer) + 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)