Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QPY Qiskit 1.0 updates #1365

Closed
wants to merge 15 commits into from
22 changes: 11 additions & 11 deletions qiskit_ibm_runtime/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 @@ -301,12 +299,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 @@ -620,15 +616,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 @@ -673,7 +671,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 @@ -686,6 +684,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
56 changes: 26 additions & 30 deletions qiskit_ibm_runtime/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,25 +111,17 @@ 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
return expr


def _read_symbolic_pulse(file_obj, version): # type: ignore[no-untyped-def]
Expand Down Expand Up @@ -167,21 +157,15 @@ 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 @@ -256,6 +240,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 @@ -424,7 +421,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
31 changes: 8 additions & 23 deletions qiskit_ibm_runtime/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 All @@ -53,7 +57,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 @@ -233,12 +236,7 @@ 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 @@ -369,26 +367,13 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): # type: ig
data = formats.PARAMETER_EXPR(
*struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE))
)
# pylint: disable=import-outside-toplevel
from sympy.parsing.sympy_parser import parse_expr

# pylint: disable=import-outside-toplevel

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

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

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
7 changes: 6 additions & 1 deletion qiskit_ibm_runtime/qpy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

"""Exception for errors raised by the pulse module."""
from typing import Any
from qiskit.qpy.exceptions import QpyError
from qiskit.qpy.exceptions import QpyError, QiskitWarning
kt474 marked this conversation as resolved.
Show resolved Hide resolved
from ..exceptions import IBMError


Expand All @@ -27,3 +27,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_runtime/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
Loading