From dc31ed06f9e7ba46be8f24e73c76cc88e6dfc88b Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Wed, 31 Jan 2024 16:41:26 -0500 Subject: [PATCH 1/5] Update QPY for Qiskit 1.0 --- qiskit_ibm_provider/qpy/binary_io/circuits.py | 22 ++++---- .../qpy/binary_io/schedules.py | 54 +++++++++---------- qiskit_ibm_provider/qpy/binary_io/value.py | 35 +++++------- qiskit_ibm_provider/qpy/exceptions.py | 7 ++- qiskit_ibm_provider/qpy/interface.py | 2 +- 5 files changed, 55 insertions(+), 65 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/circuits.py b/qiskit_ibm_provider/qpy/binary_io/circuits.py index eff608b02..b7c136abe 100644 --- a/qiskit_ibm_provider/qpy/binary_io/circuits.py +++ b/qiskit_ibm_provider/qpy/binary_io/circuits.py @@ -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 @@ -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 @@ -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) @@ -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" @@ -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: @@ -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 diff --git a/qiskit_ibm_provider/qpy/binary_io/schedules.py b/qiskit_ibm_provider/qpy/binary_io/schedules.py index c5440dc8b..75f741d75 100644 --- a/qiskit_ibm_provider/qpy/binary_io/schedules.py +++ b/qiskit_ibm_provider/qpy/binary_io/schedules.py @@ -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, {}) @@ -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 @@ -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" @@ -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, @@ -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 diff --git a/qiskit_ibm_provider/qpy/binary_io/value.py b/qiskit_ibm_provider/qpy/binary_io/value.py index 6148a604f..df3e6a815 100644 --- a/qiskit_ibm_provider/qpy/binary_io/value.py +++ b/qiskit_ibm_provider/qpy/binary_io/value.py @@ -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 @@ -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 @@ -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( @@ -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 ( + 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( diff --git a/qiskit_ibm_provider/qpy/exceptions.py b/qiskit_ibm_provider/qpy/exceptions.py index a537ed9a1..b8b7cfa6c 100644 --- a/qiskit_ibm_provider/qpy/exceptions.py +++ b/qiskit_ibm_provider/qpy/exceptions.py @@ -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 from ..exceptions import IBMError @@ -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.""" diff --git a/qiskit_ibm_provider/qpy/interface.py b/qiskit_ibm_provider/qpy/interface.py index 50e5b7eb0..e2678b1d7 100644 --- a/qiskit_ibm_provider/qpy/interface.py +++ b/qiskit_ibm_provider/qpy/interface.py @@ -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 From 0c14ffead5c86bc1721cd570b1f766e0561a1c16 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Wed, 31 Jan 2024 19:30:21 -0500 Subject: [PATCH 2/5] fix lint --- qiskit_ibm_provider/qpy/binary_io/value.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_provider/qpy/binary_io/value.py b/qiskit_ibm_provider/qpy/binary_io/value.py index df3e6a815..65f92f843 100644 --- a/qiskit_ibm_provider/qpy/binary_io/value.py +++ b/qiskit_ibm_provider/qpy/binary_io/value.py @@ -401,9 +401,9 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine): # type: ig if use_symengine: expr_ = load_basic(payload) else: - from sympy.parsing.sympy_parser import ( + from sympy.parsing.sympy_parser import ( # pylint: disable=import-outside-toplevel parse_expr, - ) # pylint: disable=import-outside-toplevel + ) expr_ = symengine.sympify(parse_expr(payload.decode(common.ENCODE))) symbol_map = {} From 5e3da183ef705bdff78a75d6d1c233a33a892812 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sat, 3 Feb 2024 09:17:22 +0100 Subject: [PATCH 3/5] optinally support QiskitWarning --- qiskit_ibm_provider/qpy/exceptions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit_ibm_provider/qpy/exceptions.py b/qiskit_ibm_provider/qpy/exceptions.py index b8b7cfa6c..2949941ef 100644 --- a/qiskit_ibm_provider/qpy/exceptions.py +++ b/qiskit_ibm_provider/qpy/exceptions.py @@ -12,7 +12,12 @@ """Exception for errors raised by the pulse module.""" from typing import Any -from qiskit.qpy.exceptions import QpyError, QiskitWarning +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 From 086d58d34f88e9df31bd6aaeb314aab9043e4471 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sun, 4 Feb 2024 21:25:07 +0100 Subject: [PATCH 4/5] fix docs --- qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py b/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py index cc8627234..91938b544 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/__init__.py @@ -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() From af4cbfeb8784bf9eafd4764b1a09c214ea1b5f7c Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Sun, 4 Feb 2024 21:56:18 +0100 Subject: [PATCH 5/5] fake backends are needed for docs --- .../transpiler/passes/scheduling/dynamical_decoupling.py | 2 +- requirements-dev.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py index acae82ce4..139d7ce5b 100644 --- a/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit_ibm_provider/transpiler/passes/scheduling/dynamical_decoupling.py @@ -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 diff --git a/requirements-dev.txt b/requirements-dev.txt index 7da5e83ea..8ee30a0bf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -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 \ No newline at end of file +sphinxcontrib-katex==0.9.9 +qiskit-ibm-runtime>=0.18