Skip to content

Commit

Permalink
Enable support for 0.45.0rc1 with QPY version 9
Browse files Browse the repository at this point in the history
The qiskit-ibm-provider package maintains a fork of the QPY
serialization module from upstream qiskit. This is done primarily
because Qiskit only supports writing a single qpy format version per
release (whatever the most recent is). However, since the server side of
the ibm runtime version does not always support the latest QPY format
version immediately and tends to lag a bit behind what upstream Qiskit
is emitting. The fork in qiskit-ibm-provider is used to control the
QPY format version that is used for job submission so it ensure the
leading edge only moves as quickly as the server side is updated.
However, for the upcoming qiskit 0.45.0 release there were some internal
object model changes and the fork of qpy (which is a copy of the qpy
module from qiskit-terra 0.25.x/qiskit 0.44.x) is incompatible with
those changes. This results in errors during the serialization because
the qpy module is trying to serialize the circuit with assumptions of
the code that do not hold with the upcoming 0.45.0 release. To further
complicate matters qiskit 0.45.0 also introduced QPY version 10 which is
completely incompatible with version 9 being used by the ibm runtime
currently. This means the normal strategy of porting all the changes
from qiskit during the upcoming release to the provider isn't viable in
the short term, at least until the server side is updated to support QPY
version 10.

To address this compatibility in the short term, this commit is a partial
backport of the changes made in qiskit 0.45.0 to the qpy module, but only
those changes made to accomodate the internal qiskit changes during the
release. It's also done in a manner where it doesn't require the 0.45.0
release, so that the provider can be used with either qiskit 0.44.x and
0.45.0. However, this is only a short term solution, once the server side
is updated to support QPY we should use #736 and release a new minor
version of the provider package to just use QPY format version 10.

This commit is explicitly only for the 0.7.x release series and is being
proposed directly to the stable/0.7 branch. For 0.8.0 #736 will port
over the upstream changes to QPY including QPY format version 10 which
is a more sustianable fix longer term. But this is blocked until the
server side has support for QPY format version 10. To simplify the
dependency tree, and to enable users to submit ibm runtime jobs using
0.45.0rc1 this targets solely compatibility between qiskit 0.45.0 and
qiskit-ibm-provider 0.7.x.

Fixes #755
  • Loading branch information
mtreinish committed Oct 24, 2023
1 parent 94fe136 commit 0dc1664
Showing 1 changed file with 47 additions and 12 deletions.
59 changes: 47 additions & 12 deletions qiskit_ibm_provider/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

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
Expand Down Expand Up @@ -158,12 +158,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"],
)

Expand Down Expand Up @@ -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",
Expand All @@ -300,12 +303,19 @@ 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.num_ctrl_qubits = instruction.num_ctrl_qubits
gate.ctrl_state = instruction.ctrl_state
gate.condition = condition
gate = gate_class(*params, label=label)
if (
gate.num_ctrl_qubits != instruction.num_ctrl_qubits
or gate.ctrl_state != instruction.ctrl_state
):
if hasattr(gate, "to_mutable"):
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",
Expand All @@ -321,8 +331,15 @@ 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 label is not None:
gate = gate_class(*params, label=label)
else:
gate = gate_class(*params)
if condition:
if not isinstance(gate, ControlFlowOp):
gate = gate.c_if(*condition)
else:
gate.condition = condition
if instruction.label_size > 0:
gate.label = label
if circuit is None:
Expand Down Expand Up @@ -568,7 +585,11 @@ def _dumps_instruction_parameter(param, index_map): # type: ignore[no-untyped-d
def _write_instruction( # type: ignore[no-untyped-def]
file_obj, instruction, custom_operations, index_map
):
gate_class_name = instruction.operation.__class__.__name__
base_class = getattr(instruction.operation, "base_class", None)
if base_class is None:
gate_class_name = instruction.operation.__class__.__name__
else:
gate_class_name = instruction.operation.base_class.__name__
custom_operations_list = []
if (
(
Expand All @@ -580,13 +601,27 @@ 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:
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)

elif isinstance(instruction.operation, library.PauliEvolutionGate):
gate_class_name = r"###PauliEvolutionGate_" + str(uuid.uuid4())
Expand Down

0 comments on commit 0dc1664

Please sign in to comment.