diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index 2b02212ccf0c56b2447f52f2d72d3dbdf86a50b7..eed3fe622727b2dc52e72c70ace36a1d21ace6a9 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -21,8 +21,6 @@ from copy import deepcopy from math import pi from typing import Union, Sequence, Optional -import numpy as np - from cqlib.circuits import gates from cqlib.circuits.barrier import Barrier from cqlib.circuits.instruction import Instruction @@ -30,6 +28,7 @@ from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.measure import Measure from cqlib.circuits.parameter import Parameter from cqlib.circuits.qubit import Qubit +from cqlib.utils.typing import ArrayLike # Type Alias Definition Qubits = Union[Qubit, int, Sequence[Union[Qubit, int]]] @@ -693,19 +692,17 @@ class Circuit: if values is None: values = {} _t = type(values) - if isinstance(values, (Sequence, np.ndarray)) or \ - (hasattr(_t, "__module__") and _t.__module__ == 'torch' - and _t.__name__ == 'Tensor'): - # list like type, list/tuple/np.ndarray/torch.Tensor - if len(values) != len(target.parameters_value): - raise ValueError(f"Length of values {len(values)} does not match " - f"the number of parameters {len(self._parameters)}.") - values = dict(zip(self._parameters, values)) + if isinstance(values, ArrayLike): + # Array like type, list/tuple/np.ndarray/torch.Tensor + if lv := len(values) != len(target.parameters_value): + raise ValueError(f"Length of values {lv} does not match " + f"the number of parameters {len(target.parameters_value)}.") + values = dict(zip(target.parameters_value, values)) values.update(kwargs) for param, value in values.items(): if isinstance(value, str): param = Parameter(param) - if param not in self._parameters: + if param not in target.parameters_value: raise KeyError(f"Parameter {param} not found.") # pylint: disable=protected-access diff --git a/cqlib/utils/typing.py b/cqlib/utils/typing.py new file mode 100644 index 0000000000000000000000000000000000000000..7bdf0b4c6ce6077c106dc9d7dec590e08618efba --- /dev/null +++ b/cqlib/utils/typing.py @@ -0,0 +1,80 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Typing utilities. + +This module contains the following classes: +* :class:`ArrayLike`: Returns a ``Union`` of all array-like types, which + includes any scalar or sequence that can be interpreted as a numpy array, + including lists and tuples. +""" +import contextlib +import sys +from typing import Iterator + +import numpy as np + + +class ArrayLikeMETA(type): + """ + ArrayLike metaclass. + """ + _normal_types = list, tuple, np.ndarray + + def __instancecheck__(cls, other): + """ Check if an object is a `ArrayLike` instance. """ + return isinstance(other, cls._normal_types) or _is_torch(other) + + def __subclasscheck__(cls, other): + """ Checks if a class is a subclass of ``ArrayLike``.""" + return issubclass(other, cls._normal_types) or _is_torch(other, subclass=True) + + +# pylint: disable=too-few-public-methods +class ArrayLike(metaclass=ArrayLikeMETA): + """ + Returns a ``Union`` of all array-like types, which includes any scalar or sequence + that can be interpreted as a numpy array, including lists and tuples. + + **Examples** + + >>> from cqlib.utils.typing import ArrayLike + >>> isinstance([2, 6, 8], ArrayLike) + True + >>> isinstance(torch.tensor([1, 2, 3]), ArrayLike) + True + >>> issubclass(list, ArrayLike) + True + >>> isinstance(5, ArrayLike) + False + """ + + def __len__(self) -> int: + """Length of the array-like object.""" + return len(self) + + def __iter__(self) -> Iterator: + """Iterate over array elements.""" + return iter(self) + + +def _is_torch(other, subclass=False): + """ Check whether it is PyTorch tensor type. """ + if "torch" in sys.modules: + with contextlib.suppress(ImportError): + # pylint: disable=import-outside-toplevel + from torch import Tensor as torchTensor + check = issubclass if subclass else isinstance + return check(other, torchTensor) + return False diff --git a/tests/utils/test_typing.py b/tests/utils/test_typing.py new file mode 100644 index 0000000000000000000000000000000000000000..bf05bbcb4356e1880a2b6957459bb9a63749a601 --- /dev/null +++ b/tests/utils/test_typing.py @@ -0,0 +1,30 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Typing testcase. """ +import numpy as np + +from cqlib.utils.typing import ArrayLike + + +def test_typing(): + """ + Test the typing module. + """ + assert isinstance([2, 6, 8], ArrayLike) + assert isinstance((1, 2), ArrayLike) + assert isinstance(np.array([1, 2, 3]), ArrayLike) + assert issubclass(list, ArrayLike) + assert not isinstance(5, ArrayLike) + assert not isinstance({"1": 1}, ArrayLike) + assert not isinstance({1, 2, 3}, ArrayLike)