Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Update QPY for Qiskit 1.0 #803

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions qiskit_ibm_provider/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import numpy as np

from qiskit import circuit as circuit_mod
from qiskit import extensions
from qiskit.circuit import library, controlflow, CircuitInstruction, ControlFlowOp
from qiskit.circuit.classical import expr
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
Expand All @@ -34,8 +33,7 @@
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumregister import QuantumRegister, Qubit
from qiskit.extensions import quantum_initializer
from qiskit.quantum_info.operators import SparsePauliOp
from qiskit.quantum_info.operators import SparsePauliOp, Clifford
from qiskit.synthesis import evolution as evo_synth
from qiskit.transpiler.layout import Layout, TranspileLayout
from .. import common, formats, type_keys
Expand Down Expand Up @@ -307,12 +305,10 @@ def _read_instruction( # type: ignore[no-untyped-def]
gate_class = getattr(library, gate_name)
elif hasattr(circuit_mod, gate_name):
gate_class = getattr(circuit_mod, gate_name)
elif hasattr(extensions, gate_name):
gate_class = getattr(extensions, gate_name)
elif hasattr(quantum_initializer, gate_name):
gate_class = getattr(quantum_initializer, gate_name)
elif hasattr(controlflow, gate_name):
gate_class = getattr(controlflow, gate_name)
elif gate_name == "Clifford":
gate_class = Clifford
else:
raise AttributeError("Invalid instruction type: %s" % gate_name)

Expand Down Expand Up @@ -631,15 +627,17 @@ 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.base_class.__name__
if isinstance(instruction.operation, Instruction):
gate_class_name = instruction.operation.base_class.__name__
else:
gate_class_name = instruction.operation.__class__.__name__
custom_operations_list = []
if (
(
not hasattr(library, gate_class_name)
and not hasattr(circuit_mod, gate_class_name)
and not hasattr(extensions, gate_class_name)
and not hasattr(quantum_initializer, gate_class_name)
and not hasattr(controlflow, gate_class_name)
and gate_class_name != "Clifford"
)
or gate_class_name == "Gate"
or gate_class_name == "Instruction"
Expand Down Expand Up @@ -686,7 +684,7 @@ def _write_instruction( # type: ignore[no-untyped-def]
condition_value = int(instruction.operation.condition[1])

gate_class_name = gate_class_name.encode(common.ENCODE)
label = getattr(instruction.operation, "label")
label = getattr(instruction.operation, "label", None)
if label:
label_raw = label.encode(common.ENCODE)
else:
Expand All @@ -699,6 +697,8 @@ def _write_instruction( # type: ignore[no-untyped-def]
instruction.operation.target,
tuple(instruction.operation.cases_specifier()),
]
elif isinstance(instruction.operation, Clifford):
instruction_params = [instruction.operation.tableau]
else:
instruction_params = instruction.operation.params

Expand Down
54 changes: 25 additions & 29 deletions qiskit_ibm_provider/qpy/binary_io/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,19 @@
from io import BytesIO
import numpy as np

import symengine as sym
from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module
load_basic,
)

from qiskit.pulse import library, channels, instructions
from qiskit.pulse.schedule import ScheduleBlock
from qiskit.utils import optionals as _optional
from qiskit.pulse.configuration import Kernel, Discriminator
from .. import formats, common, type_keys
from ..exceptions import QpyError
from . import value


if _optional.HAS_SYMENGINE:
import symengine as sym
else:
import sympy as sym


def _read_channel(file_obj, version): # type: ignore[no-untyped-def]
type_key = common.read_type_key(file_obj)
index = value.read_value(file_obj, version, {})
Expand Down Expand Up @@ -113,24 +111,15 @@ def _read_discriminator(file_obj, version): # type: ignore[no-untyped-def]
def _loads_symbolic_expr(expr_bytes, use_symengine=False): # type: ignore[no-untyped-def]
if expr_bytes == b"":
return None

expr_bytes = zlib.decompress(expr_bytes)
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))
return load_basic(expr_bytes)

else:
from sympy import parse_expr # pylint: disable=import-outside-toplevel

expr_txt = zlib.decompress(expr_bytes).decode(common.ENCODE)
expr = parse_expr(expr_txt)
if _optional.HAS_SYMENGINE:
from symengine import sympify # pylint: disable=import-outside-toplevel

return sympify(expr)
return expr


Expand Down Expand Up @@ -169,21 +158,16 @@ def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def]
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
parameters["angle"] = np.angle(parameters["amp"])
parameters["amp"] = np.abs(parameters["amp"])
_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,
f"Library pulses with complex amp are no longer supported. "
f"{pulse_type} with complex amp was converted to (amp,angle) representation.",
UserWarning,
)
class_name = "ScalableSymbolicPulse"

Expand Down Expand Up @@ -260,6 +244,19 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine): # type: ignore[n
valid_amp_conditions=valid_amp_conditions,
)
elif class_name == "ScalableSymbolicPulse":
# Between Qiskit 0.40 and 0.46, the (amp, angle) representation was present,
# but complex amp was still allowed. In Qiskit 1.0 and beyond complex amp
# is no longer supported and so the amp needs to be checked and converted.
# Once QPY version is bumped, a new reader function can be introduced without
# this check.
if isinstance(parameters["amp"], complex):
parameters["angle"] = np.angle(parameters["amp"])
parameters["amp"] = np.abs(parameters["amp"])
warnings.warn(
f"ScalableSymbolicPulse with complex amp are no longer supported. "
f"{pulse_type} with complex amp was converted to (amp,angle) representation.",
UserWarning,
)
return library.ScalableSymbolicPulse(
pulse_type=pulse_type,
duration=duration,
Expand Down Expand Up @@ -434,7 +431,6 @@ def _dumps_symbolic_expr(expr, use_symengine): # type: ignore[no-untyped-def]
return b""

if use_symengine:
_optional.HAS_SYMENGINE.require_now("dump a symengine expression")
expr_bytes = expr.__reduce__()[1][0]
else:
from sympy import srepr, sympify # pylint: disable=import-outside-toplevel
Expand Down
35 changes: 12 additions & 23 deletions qiskit_ibm_provider/qpy/binary_io/value.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
from typing import Any
import numpy as np

import symengine
from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module
load_basic,
)

from qiskit.circuit import CASE_DEFAULT, Clbit, ClassicalRegister
from qiskit.circuit.classical import expr, types
from qiskit.circuit.parameter import Parameter
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
from qiskit.utils import optionals as _optional

from .. import common, formats, exceptions, type_keys

Expand Down Expand Up @@ -55,7 +59,6 @@ def _write_parameter_vec(file_obj, obj): # type: ignore[no-untyped-def]

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
Expand Down Expand Up @@ -249,12 +252,9 @@ def _read_parameter_expression(file_obj): # type: ignore[no-untyped-def]
# pylint: disable=import-outside-toplevel
from sympy.parsing.sympy_parser import parse_expr

if _optional.HAS_SYMENGINE:
from symengine import sympify # pylint: disable=import-outside-toplevel

expr_ = sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
else:
expr_ = parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))
expr_ = symengine.sympify(
parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))
)
symbol_map = {}
for _ in range(data.map_elements):
elem_data = formats.PARAM_EXPR_MAP_ELEM(
Expand Down Expand Up @@ -397,26 +397,15 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): # type: ig
formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE)
)
)
# pylint: disable=import-outside-toplevel
from sympy.parsing.sympy_parser import parse_expr

# pylint: disable=import-outside-toplevel

payload = file_obj.read(data.expr_size)
if use_symengine:
_optional.HAS_SYMENGINE.require_now("read_parameter_expression_v3")
from symengine.lib.symengine_wrapper import ( # pylint: disable = no-name-in-module
load_basic,
)

expr_ = load_basic(payload)
else:
if _optional.HAS_SYMENGINE:
from symengine import sympify
from sympy.parsing.sympy_parser import ( # pylint: disable=import-outside-toplevel
parse_expr,
)

expr_ = sympify(parse_expr(payload.decode(common.ENCODE)))
else:
expr_ = parse_expr(payload.decode(common.ENCODE))
expr_ = symengine.sympify(parse_expr(payload.decode(common.ENCODE)))
symbol_map = {}
for _ in range(data.map_elements):
elem_data = formats.PARAM_EXPR_MAP_ELEM_V3(
Expand Down
10 changes: 10 additions & 0 deletions qiskit_ibm_provider/qpy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
"""Exception for errors raised by the pulse module."""
from typing import Any
from qiskit.qpy.exceptions import QpyError
# QiskitWarning was introduced in qiskit 1.0.0
try:
from qiskit.exceptions import QiskitWarning
except ImportError:
QiskitWarning = UserWarning
from ..exceptions import IBMError


Expand All @@ -27,3 +32,8 @@ def __init__(self, *message: Any):
def __str__(self) -> str:
"""Return the message."""
return repr(self.message)


class QPYLoadingDeprecatedFeatureWarning(QiskitWarning):
"""Visible deprecation warning for QPY loading functions without
a stable point in the call stack."""
2 changes: 1 addition & 1 deletion qiskit_ibm_provider/qpy/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,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,
use_symengine: bool = True,
):
"""Write QPY binary data to a file

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from qiskit_ibm_provider.transpiler.passes.scheduling import DynamicCircuitInstructionDurations
from qiskit_ibm_provider.transpiler.passes.scheduling import ALAPScheduleAnalysis
from qiskit_ibm_provider.transpiler.passes.scheduling import PadDelay
from qiskit.providers.fake_provider import FakeJakarta
from qiskit_ibm_runtime.fake_provider import FakeJakarta


backend = FakeJakarta()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qiskit.circuit.reset import Reset
from qiskit.dagcircuit import DAGCircuit, DAGNode, DAGInNode, DAGOpNode
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer
from qiskit.synthesis import OneQubitEulerDecomposer
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations
from qiskit.transpiler.passes.optimization import Optimize1qGates
Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ jupyter-sphinx
Sphinx>=6.0.0
sphinx-autodoc-typehints<=1.19.2
reno>=2.11.0
sphinxcontrib-katex==0.9.9
sphinxcontrib-katex==0.9.9
qiskit-ibm-runtime>=0.18
Loading