diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e07422f1..21b17e9d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,8 +19,12 @@ jobs: - name: Update pip run: pip install --upgrade pip - name: Install black and pylint - run: pip install black pylint + run: pip install black pylint ruff - name: Check files are formatted with black - run: black --check . + run: | + black --check . + - name: Run ruff + run: | + ruff check . - name: Run pylint run: pylint */ diff --git a/.pylintrc b/.pylintrc index 7b251179..ca202983 100644 --- a/.pylintrc +++ b/.pylintrc @@ -47,7 +47,6 @@ enable= unused-variable, unused-wildcard-import, wildcard-import, - wrong-import-order, wrong-import-position, yield-outside-function diff --git a/pytket/extensions/qiskit/__init__.py b/pytket/extensions/qiskit/__init__.py index 86aef51b..106699ec 100644 --- a/pytket/extensions/qiskit/__init__.py +++ b/pytket/extensions/qiskit/__init__.py @@ -14,17 +14,17 @@ """Module for conversion between IBM Qiskit and tket primitives.""" # _metadata.py is copied to the folder after installation. -from ._metadata import __extension_version__, __extension_name__ +from ._metadata import __extension_name__, __extension_version__ from .backends import ( - IBMQBackend, - NoIBMQCredentialsError, AerBackend, + AerDensityMatrixBackend, AerStateBackend, AerUnitaryBackend, - AerDensityMatrixBackend, + IBMQBackend, IBMQEmulatorBackend, + NoIBMQCredentialsError, ) from .backends.config import set_ibmq_config -from .qiskit_convert import qiskit_to_tk, tk_to_qiskit, process_characterisation +from .qiskit_convert import process_characterisation, qiskit_to_tk, tk_to_qiskit # from .tket_pass import TketPass diff --git a/pytket/extensions/qiskit/backends/__init__.py b/pytket/extensions/qiskit/backends/__init__.py index c87e3bed..6b221855 100644 --- a/pytket/extensions/qiskit/backends/__init__.py +++ b/pytket/extensions/qiskit/backends/__init__.py @@ -13,12 +13,12 @@ # limitations under the License. """Backends for connecting to IBM devices and simulators directly from pytket""" -from .ibm import IBMQBackend, NoIBMQCredentialsError from .aer import ( AerBackend, + AerDensityMatrixBackend, AerStateBackend, AerUnitaryBackend, - AerDensityMatrixBackend, qiskit_aer_backend, ) +from .ibm import IBMQBackend, NoIBMQCredentialsError from .ibmq_emulator import IBMQEmulatorBackend diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 2eaa0aa7..09535720 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -13,20 +13,18 @@ # limitations under the License. import itertools +import json +import warnings from collections import defaultdict +from collections.abc import Sequence from dataclasses import dataclass -import json from logging import warning -from typing import Optional, Sequence, Any, cast, TYPE_CHECKING -import warnings +from typing import TYPE_CHECKING, Any, Optional, cast import numpy as np -from qiskit import transpile # type: ignore -from qiskit_aer.noise import NoiseModel # type: ignore -from qiskit.quantum_info.operators import Pauli as qk_Pauli # type: ignore -from qiskit.quantum_info.operators.symplectic.sparse_pauli_op import SparsePauliOp # type: ignore from qiskit_aer import Aer # type: ignore -from qiskit_aer.library import save_expectation_value # type: ignore # pylint: disable=unused-import +from qiskit_aer.noise import NoiseModel # type: ignore + from pytket.architecture import Architecture, FullyConnected from pytket.backends import Backend, CircuitNotRunError, CircuitStatus, ResultHandle from pytket.backends.backendinfo import BackendInfo @@ -34,47 +32,54 @@ from pytket.backends.resulthandle import _ResultIdTuple from pytket.circuit import Circuit, Node, OpType, Qubit from pytket.passes import ( + AutoRebase, BasePass, CliffordSimp, CXMappingPass, DecomposeBoxes, FullPeepholeOptimise, + NaivePlacementPass, SequencePass, SynthesiseTket, - AutoRebase, - NaivePlacementPass, ) from pytket.pauli import Pauli, QubitPauliString from pytket.placement import NoiseAwarePlacement from pytket.predicates import ( ConnectivityPredicate, + DefaultRegisterPredicate, GateSetPredicate, - NoClassicalControlPredicate, NoBarriersPredicate, + NoClassicalControlPredicate, NoFastFeedforwardPredicate, NoSymbolsPredicate, - DefaultRegisterPredicate, Predicate, ) +from pytket.utils import prepare_circuit from pytket.utils.operators import QubitPauliOperator from pytket.utils.results import KwargTypes -from pytket.utils import prepare_circuit +from qiskit import transpile # type: ignore +from qiskit.quantum_info.operators import Pauli as qk_Pauli # type: ignore +from qiskit.quantum_info.operators.symplectic.sparse_pauli_op import ( # type: ignore + SparsePauliOp, +) -from .ibm_utils import _STATUS_MAP, _batch_circuits from .._metadata import __extension_version__ from ..qiskit_convert import ( - tk_to_qiskit, _gate_str_2_optype, + tk_to_qiskit, ) from ..result_convert import qiskit_result_to_backendresult from .crosstalk_model import ( - NoisyCircuitBuilder, CrosstalkParams, + NoisyCircuitBuilder, ) +from .ibm_utils import _STATUS_MAP, _batch_circuits if TYPE_CHECKING: from qiskit_aer import AerJob - from qiskit_aer.backends.aerbackend import AerBackend as QiskitAerBackend # type: ignore + from qiskit_aer.backends.aerbackend import ( # type: ignore + AerBackend as QiskitAerBackend, + ) def _default_q_index(q: Qubit) -> int: @@ -326,14 +331,14 @@ def process_circuits( return cast(list[ResultHandle], handle_list) def cancel(self, handle: ResultHandle) -> None: - job: "AerJob" = self._cache[handle]["job"] + job: AerJob = self._cache[handle]["job"] cancelled = job.cancel() if not cancelled: warning(f"Unable to cancel job {cast(str, handle[0])}") def circuit_status(self, handle: ResultHandle) -> CircuitStatus: self._check_handle_type(handle) - job: "AerJob" = self._cache[handle]["job"] + job: AerJob = self._cache[handle]["job"] ibmstatus = job.status() return CircuitStatus(_STATUS_MAP[ibmstatus], ibmstatus.value) @@ -343,7 +348,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul except CircuitNotRunError: jobid, _, qubit_n, ppc = handle try: - job: "AerJob" = self._cache[handle]["job"] + job: AerJob = self._cache[handle]["job"] except KeyError: raise CircuitNotRunError(handle) @@ -406,10 +411,8 @@ def get_pauli_expectation_value( """ if self._noise_model: raise RuntimeError( - ( - "Snapshot based expectation value not supported with noise model. " - "Use shots." - ) + "Snapshot based expectation value not supported with noise model. " + "Use shots." ) if not self._supports_expectation: raise NotImplementedError("Cannot get expectation value from this backend") @@ -436,10 +439,8 @@ def get_operator_expectation_value( """ if self._noise_model: raise RuntimeError( - ( - "Snapshot based expectation value not supported with noise model. " - "Use shots." - ) + "Snapshot based expectation value not supported with noise model. " + "Use shots." ) if not self._supports_expectation: raise NotImplementedError("Cannot get expectation value from this backend") @@ -754,11 +755,9 @@ def _process_noise_model( raise RuntimeWarning("Error applies to multiple gates.") if "gate_qubits" not in error: raise RuntimeWarning( - ( - "Please define NoiseModel without using the" - " add_all_qubit_quantum_error()" - " or add_all_qubit_readout_error() method." - ) + "Please define NoiseModel without using the" + " add_all_qubit_quantum_error()" + " or add_all_qubit_readout_error() method." ) name = name[0] diff --git a/pytket/extensions/qiskit/backends/config.py b/pytket/extensions/qiskit/backends/config.py index 5ecedf72..04abd0f9 100644 --- a/pytket/extensions/qiskit/backends/config.py +++ b/pytket/extensions/qiskit/backends/config.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, ClassVar, Optional, Type from dataclasses import dataclass +from typing import Any, ClassVar, Optional + from pytket.config import PytketExtConfig @@ -28,11 +29,11 @@ class QiskitConfig(PytketExtConfig): @classmethod def from_extension_dict( - cls: Type["QiskitConfig"], ext_dict: dict[str, Any] + cls: type["QiskitConfig"], ext_dict: dict[str, Any] ) -> "QiskitConfig": return cls( - ext_dict.get("instance", None), - ext_dict.get("ibmq_api_token", None), + ext_dict.get("instance"), + ext_dict.get("ibmq_api_token"), ) diff --git a/pytket/extensions/qiskit/backends/crosstalk_model.py b/pytket/extensions/qiskit/backends/crosstalk_model.py index fe678968..1d6eaf6e 100644 --- a/pytket/extensions/qiskit/backends/crosstalk_model.py +++ b/pytket/extensions/qiskit/backends/crosstalk_model.py @@ -13,27 +13,29 @@ # limitations under the License. -from typing import Optional from dataclasses import dataclass - -from qiskit_aer.noise import NoiseModel # type: ignore -from qiskit_aer.noise.errors.standard_errors import amplitude_damping_error, phase_damping_error # type: ignore +from typing import Optional import numpy as np +from qiskit_aer.noise import NoiseModel # type: ignore +from qiskit_aer.noise.errors.standard_errors import ( # type: ignore + amplitude_damping_error, + phase_damping_error, +) from scipy.linalg import fractional_matrix_power # type: ignore +from pytket.backends.backendinfo import BackendInfo from pytket.circuit import ( Circuit, - Qubit, - Node, Command, - OpType, + Node, Op, + OpType, + Qubit, Unitary1qBox, Unitary2qBox, Unitary3qBox, ) -from pytket.backends.backendinfo import BackendInfo from pytket.extensions.qiskit.qiskit_convert import _gate_str_2_optype diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 8669ac32..e0b9384d 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -13,83 +13,85 @@ # limitations under the License. import itertools +import json from ast import literal_eval from collections import Counter -import json +from collections.abc import Sequence from time import sleep from typing import ( - cast, - Optional, - Sequence, TYPE_CHECKING, Any, + Optional, + cast, ) from warnings import warn import numpy as np - -from qiskit.primitives import PrimitiveResult, SamplerResult # type: ignore - - -# RuntimeJob has no queue_position attribute, which is referenced -# via job_monitor see-> https://github.com/CQCL/pytket-qiskit/issues/48 -# therefore we can't use job_monitor until fixed -# from qiskit.tools.monitor import job_monitor # type: ignore -from qiskit.result.distributions import QuasiDistribution # type: ignore from qiskit_ibm_runtime import ( # type: ignore QiskitRuntimeService, - Session, + RuntimeJob, SamplerOptions, SamplerV2, - RuntimeJob, + Session, +) +from qiskit_ibm_runtime.models.backend_configuration import ( # type: ignore + PulseBackendConfiguration, +) +from qiskit_ibm_runtime.models.backend_properties import ( # type: ignore + BackendProperties, ) -from qiskit_ibm_runtime.models.backend_configuration import PulseBackendConfiguration # type: ignore -from qiskit_ibm_runtime.models.backend_properties import BackendProperties # type: ignore -from pytket.circuit import Bit, Circuit, OpType +from pytket.architecture import Architecture, FullyConnected from pytket.backends import Backend, CircuitNotRunError, CircuitStatus, ResultHandle from pytket.backends.backendinfo import BackendInfo from pytket.backends.backendresult import BackendResult from pytket.backends.resulthandle import _ResultIdTuple +from pytket.circuit import Bit, Circuit, OpType from pytket.passes import ( - BasePass, AutoRebase, - KAKDecomposition, - RemoveRedundancies, - SequencePass, - SynthesiseTket, + BasePass, + CliffordSimp, CXMappingPass, DecomposeBoxes, FullPeepholeOptimise, - CliffordSimp, - SimplifyInitial, + KAKDecomposition, NaivePlacementPass, + RemoveRedundancies, + SequencePass, + SimplifyInitial, + SynthesiseTket, ) +from pytket.placement import NoiseAwarePlacement from pytket.predicates import ( - NoMidMeasurePredicate, - NoSymbolsPredicate, + DirectednessPredicate, GateSetPredicate, + MaxNQubitsPredicate, NoClassicalControlPredicate, NoFastFeedforwardPredicate, - MaxNQubitsPredicate, + NoMidMeasurePredicate, + NoSymbolsPredicate, Predicate, - DirectednessPredicate, ) - -from pytket.architecture import FullyConnected, Architecture -from pytket.placement import NoiseAwarePlacement from pytket.utils import prepare_circuit from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import KwargTypes -from .ibm_utils import _STATUS_MAP, _batch_circuits -from .config import QiskitConfig -from ..qiskit_convert import tk_to_qiskit, _tk_gate_set +from qiskit.primitives import PrimitiveResult, SamplerResult # type: ignore + +# RuntimeJob has no queue_position attribute, which is referenced +# via job_monitor see-> https://github.com/CQCL/pytket-qiskit/issues/48 +# therefore we can't use job_monitor until fixed +# from qiskit.tools.monitor import job_monitor # type: ignore +from qiskit.result.distributions import QuasiDistribution # type: ignore + +from .._metadata import __extension_version__ from ..qiskit_convert import ( + _tk_gate_set, get_avg_characterisation, process_characterisation_from_config, + tk_to_qiskit, ) - -from .._metadata import __extension_version__ +from .config import QiskitConfig +from .ibm_utils import _STATUS_MAP, _batch_circuits if TYPE_CHECKING: from qiskit_ibm_runtime.ibm_backend import IBMBackend # type: ignore @@ -183,7 +185,7 @@ def __init__( if service is None else service ) - self._backend: "IBMBackend" = self._service.backend(backend_name) + self._backend: IBMBackend = self._service.backend(backend_name) config: PulseBackendConfiguration = self._backend.configuration() self._max_per_job = getattr(config, "max_experiments", 1) @@ -549,7 +551,7 @@ def process_circuits( handle_list[ind] = ResultHandle( job_id, i, qcs[i].count_ops()["measure"], ppcirc_strs[i] ) - batch_id += 1 + batch_id += 1 # noqa: SIM113 for handle in handle_list: assert handle is not None self._cache[handle] = dict() @@ -607,10 +609,9 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul status = job.status() while status not in ["DONE", "CANCELLED", "ERROR"]: status = job.status() - print("Job status is", status) sleep(10) - res = job.result(timeout=kwargs.get("timeout", None)) + res = job.result(timeout=kwargs.get("timeout")) if isinstance(res, SamplerResult): # TODO Is this code still reachable? for circ_index, (r, d) in enumerate(zip(res.quasi_dists, res.metadata)): diff --git a/pytket/extensions/qiskit/backends/ibm_utils.py b/pytket/extensions/qiskit/backends/ibm_utils.py index 19292c76..08055c42 100644 --- a/pytket/extensions/qiskit/backends/ibm_utils.py +++ b/pytket/extensions/qiskit/backends/ibm_utils.py @@ -16,13 +16,13 @@ """ import itertools -from typing import Collection, Optional, Sequence, TYPE_CHECKING +from collections.abc import Collection, Sequence +from typing import TYPE_CHECKING, Optional import numpy as np -from qiskit.providers import JobStatus # type: ignore - from pytket.backends.status import StatusEnum +from qiskit.providers import JobStatus # type: ignore if TYPE_CHECKING: from pytket.circuit import Circuit @@ -61,7 +61,7 @@ def _batch_circuits( n_shots_int = list(map(lambda x: x if x is not None else -1, n_shots)) order: Collection[int] = np.argsort(n_shots_int) - batches: list[tuple[Optional[int], list["Circuit"]]] = [ + batches: list[tuple[Optional[int], list[Circuit]]] = [ (n, [circuits[i] for i in indices]) for n, indices in itertools.groupby(order, key=lambda i: n_shots[i]) ] diff --git a/pytket/extensions/qiskit/backends/ibmq_emulator.py b/pytket/extensions/qiskit/backends/ibmq_emulator.py index 77b25e61..ed33ab25 100644 --- a/pytket/extensions/qiskit/backends/ibmq_emulator.py +++ b/pytket/extensions/qiskit/backends/ibmq_emulator.py @@ -12,22 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import Counter +from collections.abc import Sequence from typing import ( - Optional, - Sequence, + TYPE_CHECKING, Any, + Optional, ) from qiskit_aer.noise.noise_model import NoiseModel # type: ignore -from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore from pytket.backends.backend import Backend -from pytket.backends.status import CircuitStatus - from pytket.backends.backendinfo import BackendInfo from pytket.backends.backendresult import BackendResult -from pytket.backends.resulthandle import _ResultIdTuple, ResultHandle +from pytket.backends.resulthandle import ResultHandle, _ResultIdTuple +from pytket.backends.status import CircuitStatus from pytket.circuit import Circuit from pytket.passes import BasePass from pytket.predicates import Predicate @@ -36,6 +34,11 @@ from .aer import AerBackend from .ibm import IBMQBackend +if TYPE_CHECKING: + from collections import Counter + + from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore + class IBMQEmulatorBackend(Backend): """A backend which uses the AerBackend to loaclly emulate the behaviour of diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py index a5654dc9..49dcb584 100644 --- a/pytket/extensions/qiskit/qiskit_convert.py +++ b/pytket/extensions/qiskit/qiskit_convert.py @@ -16,25 +16,55 @@ """Methods to allow conversion between Qiskit and pytket circuit classes """ from collections import defaultdict +from collections.abc import Iterable +from inspect import signature from typing import ( + TYPE_CHECKING, + Any, Callable, Optional, - Any, - Iterable, - cast, TypeVar, - TYPE_CHECKING, + cast, ) -from inspect import signature from uuid import UUID import numpy as np +import sympy from numpy.typing import NDArray +from qiskit_ibm_runtime.models.backend_configuration import ( # type: ignore + PulseBackendConfiguration, +) +from qiskit_ibm_runtime.models.backend_properties import ( # type: ignore + BackendProperties, +) from symengine import sympify # type: ignore from symengine.lib import symengine_wrapper # type: ignore -import sympy import qiskit.circuit.library.standard_gates as qiskit_gates # type: ignore +from pytket.architecture import Architecture, FullyConnected +from pytket.circuit import ( + Bit, + CircBox, + Circuit, + Node, + Op, + OpType, + QControlBox, + Qubit, + StatePreparationBox, + Unitary1qBox, + Unitary2qBox, + Unitary3qBox, + UnitType, +) +from pytket.passes import AutoRebase +from pytket.pauli import Pauli, QubitPauliString +from pytket.unit_id import _TEMP_BIT_NAME +from pytket.utils import ( + QubitPauliOperator, + gen_term_sequence_circuit, + permute_rows_cols_in_unitary, +) from qiskit import ( ClassicalRegister, QuantumCircuit, @@ -42,57 +72,31 @@ ) from qiskit.circuit import ( Barrier, + Clbit, + ControlledGate, + Gate, Instruction, InstructionSet, - Gate, - ControlledGate, Measure, Parameter, ParameterExpression, Reset, - Clbit, ) from qiskit.circuit.library import ( CRYGate, - RYGate, + Initialize, PauliEvolutionGate, + RYGate, StatePreparation, UnitaryGate, - Initialize, -) -from qiskit_ibm_runtime.models.backend_configuration import PulseBackendConfiguration # type: ignore -from qiskit_ibm_runtime.models.backend_properties import BackendProperties # type: ignore - -from pytket.circuit import ( - CircBox, - Circuit, - Node, - Op, - OpType, - Unitary1qBox, - Unitary2qBox, - Unitary3qBox, - UnitType, - Bit, - Qubit, - QControlBox, - StatePreparationBox, ) -from pytket.unit_id import _TEMP_BIT_NAME -from pytket.pauli import Pauli, QubitPauliString -from pytket.architecture import Architecture, FullyConnected -from pytket.utils import ( - QubitPauliOperator, - gen_term_sequence_circuit, - permute_rows_cols_in_unitary, -) -from pytket.passes import AutoRebase if TYPE_CHECKING: from qiskit_ibm_runtime.ibm_backend import IBMBackend # type: ignore from qiskit_ibm_runtime.models.backend_properties import Nduv + + from pytket.circuit import UnitID from qiskit.circuit.quantumcircuitdata import QuantumCircuitData # type: ignore - from pytket.circuit import Op, UnitID _qiskit_gates_1q = { # Exact equivalents (same signature except for factor of pi in each parameter): @@ -233,7 +237,7 @@ def _qpo_from_peg(peg: PauliEvolutionGate, qubits: list[Qubit]) -> QubitPauliOpe qpodict = {} for p, c in zip(op.paulis, op.coeffs): if np.iscomplex(c): - raise ValueError("Coefficient for Pauli {} is non-real.".format(p)) + raise ValueError(f"Coefficient for Pauli {p} is non-real.") coeff = param_to_tk(t) * c qpslist = [] pstr = p.to_label() @@ -891,7 +895,7 @@ def tk_to_qiskit( cregmap.update({c_reg.name: qis_reg}) qcirc.add_register(qis_reg) symb_map = {Parameter(str(s)): s for s in tkc.free_symbols()} - range_preds: dict[Bit, tuple[list["UnitID"], int]] = dict() + range_preds: dict[Bit, tuple[list[UnitID], int]] = dict() # Apply a rebase to the set of pytket gates which have replacements in qiskit supported_gate_rebase.apply(tkc) @@ -1009,8 +1013,12 @@ def return_value_if_found(iterator: Iterable["Nduv"], name: str) -> Optional[Any K2 = TypeVar("K2") V = TypeVar("V") convert_keys_t = Callable[[Callable[[K1], K2], dict[K1, V]], dict[K2, V]] + # convert qubits to architecture Nodes - convert_keys: convert_keys_t = lambda f, d: {f(k): v for k, v in d.items()} + convert_keys: convert_keys_t = lambda f, d: { # noqa: E731 + f(k): v for k, v in d.items() + } + node_errors = convert_keys(lambda q: Node(q), node_errors) link_errors = convert_keys(lambda p: (Node(p[0]), Node(p[1])), link_errors) readout_errors = convert_keys(lambda q: Node(q), readout_errors) @@ -1042,7 +1050,9 @@ def get_avg_characterisation( V1 = TypeVar("V1") V2 = TypeVar("V2") map_values_t = Callable[[Callable[[V1], V2], dict[K, V1]], dict[K, V2]] - map_values: map_values_t = lambda f, d: {k: f(v) for k, v in d.items()} + map_values: map_values_t = lambda f, d: { # noqa: E731 + k: f(v) for k, v in d.items() + } node_errors = cast(dict[Node, dict[OpType, float]], characterisation["NodeErrors"]) link_errors = cast( @@ -1052,10 +1062,13 @@ def get_avg_characterisation( dict[Node, list[list[float]]], characterisation["ReadoutErrors"] ) - avg: Callable[[dict[Any, float]], float] = lambda xs: sum(xs.values()) / len(xs) - avg_mat: Callable[[list[list[float]]], float] = ( + avg: Callable[[dict[Any, float]], float] = lambda xs: sum( # noqa: E731 + xs.values() + ) / len(xs) + avg_mat: Callable[[list[list[float]]], float] = ( # noqa: E731 lambda xs: (xs[0][1] + xs[1][0]) / 2.0 ) + avg_readout_errors = map_values(avg_mat, readout_errors) avg_node_errors = map_values(avg, node_errors) avg_link_errors = map_values(avg, link_errors) diff --git a/pytket/extensions/qiskit/result_convert.py b/pytket/extensions/qiskit/result_convert.py index 42b38347..39c562d1 100644 --- a/pytket/extensions/qiskit/result_convert.py +++ b/pytket/extensions/qiskit/result_convert.py @@ -13,23 +13,20 @@ # limitations under the License. +from collections import Counter, defaultdict +from collections.abc import Iterator, Sequence from typing import ( - Iterator, - Sequence, - Optional, Any, + Optional, ) -from collections import Counter, defaultdict import numpy as np -from qiskit.result import Result # type: ignore -from qiskit.result.models import ExperimentResult # type: ignore - -from pytket.circuit import Bit, Qubit, UnitID - from pytket.backends.backendresult import BackendResult +from pytket.circuit import Bit, Qubit, UnitID from pytket.utils.outcomearray import OutcomeArray +from qiskit.result import Result # type: ignore +from qiskit.result.models import ExperimentResult # type: ignore def _get_registers_from_uids(uids: list[UnitID]) -> dict[str, set[UnitID]]: @@ -71,14 +68,13 @@ def _result_is_empty_shots(result: ExperimentResult) -> bool: return False datadict = result.data.to_dict() - if len(datadict) == 0: - return True - elif "memory" in datadict and len(datadict["memory"]) == 0: - return True - elif "counts" in datadict and len(datadict["counts"]) == 0: - return True - else: - return False + return bool( + len(datadict) == 0 + or "memory" in datadict + and len(datadict["memory"]) == 0 + or "counts" in datadict + and len(datadict["counts"]) == 0 + ) # In some cases, Qiskit returns a result with fields we don't expect - diff --git a/pytket/extensions/qiskit/tket_backend.py b/pytket/extensions/qiskit/tket_backend.py index 47e598bd..a9b46302 100644 --- a/pytket/extensions/qiskit/tket_backend.py +++ b/pytket/extensions/qiskit/tket_backend.py @@ -13,19 +13,22 @@ # limitations under the License. import inspect -from typing import Optional, Any -from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping # type: ignore +from typing import Any, Optional + +from pytket.architecture import FullyConnected +from pytket.backends import Backend +from pytket.extensions.qiskit import AerStateBackend, AerUnitaryBackend +from pytket.extensions.qiskit.qiskit_convert import _gate_str_2_optype_rev, qiskit_to_tk +from pytket.extensions.qiskit.tket_job import JobInfo, TketJob +from pytket.passes import BasePass +from pytket.predicates import CompilationUnit, GateSetPredicate +from qiskit.circuit.library.standard_gates import ( # type: ignore + get_standard_gate_name_mapping, +) from qiskit.circuit.quantumcircuit import QuantumCircuit # type: ignore -from qiskit.providers.backend import BackendV2 # type: ignore from qiskit.providers import Options # type: ignore +from qiskit.providers.backend import BackendV2 # type: ignore from qiskit.transpiler import CouplingMap, Target # type: ignore -from pytket.extensions.qiskit import AerStateBackend, AerUnitaryBackend -from pytket.extensions.qiskit.qiskit_convert import qiskit_to_tk, _gate_str_2_optype_rev -from pytket.extensions.qiskit.tket_job import TketJob, JobInfo -from pytket.backends import Backend -from pytket.passes import BasePass -from pytket.predicates import GateSetPredicate, CompilationUnit -from pytket.architecture import FullyConnected def _extract_basis_gates(backend: Backend) -> list[str]: @@ -36,7 +39,7 @@ def _extract_basis_gates(backend: Backend) -> list[str]: # Restrict to the gate set accepted by the backend, the converters, # and the Target.from_configuration() method. for optype in pred.gate_set: - if optype in _gate_str_2_optype_rev.keys(): + if optype in _gate_str_2_optype_rev: gate_name = _gate_str_2_optype_rev[optype] if gate_name in standard_gate_mapping: gate_obj = standard_gate_mapping[gate_name] @@ -123,7 +126,7 @@ def run( ) -> TketJob: if isinstance(run_input, QuantumCircuit): run_input = [run_input] - n_shots = options.get("shots", None) + n_shots = options.get("shots") circ_list = [] jobinfos = [] for qc in run_input: diff --git a/pytket/extensions/qiskit/tket_job.py b/pytket/extensions/qiskit/tket_job.py index 2e2dbece..72971832 100644 --- a/pytket/extensions/qiskit/tket_job.py +++ b/pytket/extensions/qiskit/tket_job.py @@ -13,16 +13,17 @@ # limitations under the License. from dataclasses import dataclass -from typing import Optional, Any, TYPE_CHECKING, cast -from qiskit.providers import JobStatus, JobV1 # type: ignore -from qiskit.result import Result # type: ignore +from typing import TYPE_CHECKING, Any, Optional, cast + from pytket.backends import ResultHandle, StatusEnum from pytket.backends.backend import Backend, KwargTypes -from pytket.circuit import UnitID, Qubit, Bit +from pytket.circuit import Bit, Qubit, UnitID from pytket.extensions.qiskit.result_convert import ( - backendresult_to_qiskit_resultdata, _get_header_info, + backendresult_to_qiskit_resultdata, ) +from qiskit.providers import JobStatus, JobV1 # type: ignore +from qiskit.result import Result # type: ignore if TYPE_CHECKING: from pytket.extensions.qiskit.tket_backend import TketBackend @@ -108,13 +109,13 @@ def cancel(self) -> None: def status(self) -> Any: status_list = [self._pytket_backend.circuit_status(h) for h in self._handles] - if any((s.status == StatusEnum.RUNNING for s in status_list)): + if any(s.status == StatusEnum.RUNNING for s in status_list): return JobStatus.RUNNING - elif any((s.status == StatusEnum.ERROR for s in status_list)): + elif any(s.status == StatusEnum.ERROR for s in status_list): return JobStatus.ERROR - elif any((s.status == StatusEnum.CANCELLED for s in status_list)): + elif any(s.status == StatusEnum.CANCELLED for s in status_list): return JobStatus.CANCELLED - elif all((s.status == StatusEnum.COMPLETED for s in status_list)): + elif all(s.status == StatusEnum.COMPLETED for s in status_list): return JobStatus.DONE else: return JobStatus.INITIALIZING diff --git a/pytket/extensions/qiskit/tket_pass.py b/pytket/extensions/qiskit/tket_pass.py index 73071af4..1668fa50 100644 --- a/pytket/extensions/qiskit/tket_pass.py +++ b/pytket/extensions/qiskit/tket_pass.py @@ -13,20 +13,22 @@ # limitations under the License. from typing import Optional -from qiskit.dagcircuit import DAGCircuit # type: ignore -from qiskit.providers import BackendV2 # type: ignore -from qiskit.transpiler.basepasses import TransformationPass, BasePass as qBasePass # type: ignore -from qiskit.converters import circuit_to_dag, dag_to_circuit # type: ignore -from qiskit_aer.backends import AerSimulator # type: ignore +from qiskit_aer.backends import AerSimulator # type: ignore -from pytket.passes import BasePass from pytket.extensions.qiskit import ( - IBMQBackend, AerBackend, AerStateBackend, AerUnitaryBackend, + IBMQBackend, ) +from pytket.passes import BasePass +from qiskit.converters import circuit_to_dag, dag_to_circuit # type: ignore +from qiskit.dagcircuit import DAGCircuit # type: ignore +from qiskit.providers import BackendV2 # type: ignore +from qiskit.transpiler.basepasses import BasePass as qBasePass # type: ignore +from qiskit.transpiler.basepasses import TransformationPass + from .qiskit_convert import qiskit_to_tk, tk_to_qiskit diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..2c68d62c --- /dev/null +++ b/ruff.toml @@ -0,0 +1,44 @@ +target-version = "py39" + +line-length = 88 + +extend-exclude = ["examples"] + +lint.select = [ + "E", # pycodestyle Errors + "W", # pycodestyle Warnings + + # "A", # flake8-builtins + # "B", # flake8-Bugbear + # "C4", # flake8-comprehensions + # "COM", # flake8-commas + # "EXE", # flake8-executable + "F", # pyFlakes + # "FA", # flake8-future-annotations + # "FIX", # flake8-fixme + # "FLY", # flynt + "I", # isort + # "INP", # flake8-no-pep420 + # "ISC", # flake8-implicit-str-concat + # "N", # pep8-Naming + # "NPY", # NumPy-specific + # "PERF", # Perflint + # "PGH", # pygrep-hooks + # "PIE", # flake8-pie + # "PL", # pylint + # "PT", # flake8-pytest-style + # "RSE", # flake8-raise + # "RUF", # Ruff-specific + # "S", # flake8-bandit (Security) + "SIM", # flake8-simplify + # "SLF", # flake8-self + "T20", # flake8-print + "TCH", # flake8-type-checking + # "TRY", # tryceratops + "UP", # pyupgrade + # "YTT", # flake8-2020 +] + +[lint.per-file-ignores] +".github/workflows/docs/conf.py" = ["E402"] +"__init__.py" = ["F401"] # module imported but unused (6) diff --git a/setup.py b/setup.py index 7caed631..949376c0 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import shutil import os -from setuptools import setup, find_namespace_packages # type: ignore +import shutil +from pathlib import Path + +from setuptools import find_namespace_packages, setup # type: ignore metadata: dict = {} with open("_metadata.py") as fp: @@ -38,7 +40,7 @@ }, description="Extension for pytket, providing translation to and from the Qiskit " "framework", - long_description=open("README.md").read(), + long_description=(Path(__file__).parent / "README.md").read_text(), long_description_content_type="text/markdown", license="Apache 2", packages=find_namespace_packages(include=["pytket.*"]), diff --git a/tests/backend_test.py b/tests/backend_test.py index ed2617a9..7c165329 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -11,73 +11,68 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import cmath import json +import math import os from collections import Counter from typing import cast -import math -import cmath -from hypothesis import given, strategies -import numpy as np +import numpy as np import pytest - +from hypothesis import given, strategies from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister # type: ignore from qiskit.circuit import Parameter # type: ignore -from qiskit_aer.noise.noise_model import NoiseModel # type: ignore +from qiskit_aer import Aer # type: ignore from qiskit_aer.noise import ReadoutError # type: ignore from qiskit_aer.noise.errors import depolarizing_error, pauli_error # type: ignore +from qiskit_aer.noise.noise_model import NoiseModel # type: ignore from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore -from qiskit_aer import Aer # type: ignore - -from pytket.circuit import ( - Circuit, - OpType, - BasisOrder, - Qubit, - reg_eq, - Unitary2qBox, - QControlBox, - CircBox, -) -from pytket.passes import CliffordSimp, FlattenRelabelRegistersPass -from pytket.pauli import Pauli, QubitPauliString -from pytket.predicates import CompilationUnit, NoMidMeasurePredicate -from pytket.architecture import Architecture -from pytket.mapping import MappingManager, LexiLabellingMethod, LexiRouteRoutingMethod -from pytket.transform import Transform +from pytket.architecture import Architecture, FullyConnected from pytket.backends import ( - ResultHandle, CircuitNotRunError, CircuitNotValidError, CircuitStatus, + ResultHandle, StatusEnum, ) from pytket.backends.backend import ResultHandleTypeError +from pytket.circuit import ( + BasisOrder, + CircBox, + Circuit, + OpType, + QControlBox, + Qubit, + Unitary2qBox, + reg_eq, +) from pytket.extensions.qiskit import ( - IBMQBackend, AerBackend, + AerDensityMatrixBackend, AerStateBackend, AerUnitaryBackend, + IBMQBackend, IBMQEmulatorBackend, - AerDensityMatrixBackend, -) -from pytket.extensions.qiskit import ( + process_characterisation, qiskit_to_tk, tk_to_qiskit, - process_characterisation, ) from pytket.extensions.qiskit.backends.crosstalk_model import ( CrosstalkParams, - NoisyCircuitBuilder, FractionalUnitary, + NoisyCircuitBuilder, ) +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.passes import CliffordSimp, FlattenRelabelRegistersPass +from pytket.pauli import Pauli, QubitPauliString +from pytket.predicates import CompilationUnit, NoMidMeasurePredicate +from pytket.transform import Transform from pytket.utils.expectations import ( - get_pauli_expectation_value, get_operator_expectation_value, + get_pauli_expectation_value, ) -from pytket.architecture import FullyConnected from pytket.utils.operators import QubitPauliOperator from pytket.utils.results import compare_statevectors, compare_unitaries @@ -147,8 +142,7 @@ def test_statevector_sim_with_permutation() -> None: def test_sim() -> None: c = circuit_gen(True) b = AerBackend() - shots = b.run_circuit(c, n_shots=1024).get_shots() - print(shots) + b.run_circuit(c, n_shots=1024).get_shots() def test_measures() -> None: @@ -395,7 +389,6 @@ def test_cancellation_aer() -> None: c = b.get_compiled_circuit(c) h = b.process_circuit(c, 10) b.cancel(h) - print(b.circuit_status(h)) @pytest.mark.skipif(skip_remote_tests, reason=REASON) @@ -405,7 +398,6 @@ def test_cancellation_ibmq(brisbane_backend: IBMQBackend) -> None: c = b.get_compiled_circuit(c) h = b.process_circuit(c, 10) b.cancel(h) - print(b.circuit_status(h)) @pytest.mark.skipif(skip_remote_tests, reason=REASON) @@ -699,7 +691,7 @@ def test_expectation_bug() -> None: backend = AerStateBackend() # backend.compile_circuit(circuit) circuit = Circuit(16) - with open("big_hamiltonian.json", "r") as f: + with open("big_hamiltonian.json") as f: hamiltonian = QubitPauliOperator.from_list(json.load(f)) exp = backend.get_operator_expectation_value(circuit, hamiltonian) assert np.isclose(exp, 1.4325392) @@ -729,9 +721,11 @@ def test_aer_result_handle() -> None: with pytest.raises(CircuitNotRunError) as errorinfoCirc: _ = b.get_result(wronghandle) - assert "Circuit corresponding to {0!r} ".format( - wronghandle - ) + "has not been run by this backend instance." in str(errorinfoCirc.value) + assert ( + f"Circuit corresponding to {wronghandle!r} " + + "has not been run by this backend instance." + in str(errorinfoCirc.value) + ) def test_aerstate_result_handle() -> None: @@ -780,7 +774,7 @@ def test_mixed_circuit() -> None: backend = AerBackend() c = backend.get_compiled_circuit(c) counts = backend.run_circuit(c, n_shots=1024).get_counts() - for key in counts.keys(): + for key in counts: assert key in {(0, 1), (1, 0)} diff --git a/tests/conftest.py b/tests/conftest.py index e8aa9a90..b9067fb6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,9 +13,10 @@ # limitations under the License. import os -import pytest +import pytest from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore + from pytket.extensions.qiskit import ( IBMQBackend, IBMQEmulatorBackend, @@ -24,18 +25,20 @@ @pytest.fixture(autouse=True, scope="session") def setup_qiskit_account() -> None: - if os.getenv("PYTKET_RUN_REMOTE_TESTS") is not None: - # The remote tests require an active IBMQ account - # We check if an IBMQ account is already saved, otherwise we try - # to enable one using the token in the env variable: - # PYTKET_REMOTE_QISKIT_TOKEN - # Note: The IBMQ account will only be enabled for the current session - if not QiskitRuntimeService.saved_accounts(): - token = os.getenv("PYTKET_REMOTE_QISKIT_TOKEN") - if token: - QiskitRuntimeService.save_account( - channel="ibm_quantum", token=token, overwrite=True - ) + # The remote tests require an active IBMQ account + # We check if an IBMQ account is already saved, otherwise we try + # to enable one using the token in the env variable: + # PYTKET_REMOTE_QISKIT_TOKEN + # Note: The IBMQ account will only be enabled for the current session + if ( + os.getenv("PYTKET_RUN_REMOTE_TESTS") is not None + and not QiskitRuntimeService.saved_accounts() + ): + token = os.getenv("PYTKET_REMOTE_QISKIT_TOKEN") + if token: + QiskitRuntimeService.save_account( + channel="ibm_quantum", token=token, overwrite=True + ) @pytest.fixture(scope="module") @@ -62,7 +65,7 @@ def qiskit_runtime_service() -> QiskitRuntimeService: try: return QiskitRuntimeService(channel="ibm_quantum", instance="ibm-q/open/main") - except: + except: # noqa: E722 token = os.getenv("PYTKET_REMOTE_QISKIT_TOKEN") return QiskitRuntimeService( channel="ibm_quantum", token=token, instance="ibm-q/open/main" diff --git a/tests/mock_pytket_backend.py b/tests/mock_pytket_backend.py index 8afdbd17..13ac0251 100644 --- a/tests/mock_pytket_backend.py +++ b/tests/mock_pytket_backend.py @@ -13,18 +13,19 @@ # limitations under the License. -from typing import Optional, Sequence, cast import json +from collections.abc import Sequence +from typing import Optional, cast -from pytket.circuit import Circuit, OpType +from pytket.architecture import Architecture, FullyConnected from pytket.backends import Backend, CircuitStatus, ResultHandle, StatusEnum +from pytket.backends.backend import KwargTypes, ResultCache from pytket.backends.backendinfo import BackendInfo -from pytket.architecture import Architecture, FullyConnected -from pytket.predicates import Predicate, GateSetPredicate -from pytket.passes import BasePass, CustomPass -from pytket.backends.resulthandle import _ResultIdTuple from pytket.backends.backendresult import BackendResult -from pytket.backends.backend import KwargTypes, ResultCache +from pytket.backends.resulthandle import _ResultIdTuple +from pytket.circuit import Circuit, OpType +from pytket.passes import BasePass, CustomPass +from pytket.predicates import GateSetPredicate, Predicate from pytket.utils.outcomearray import OutcomeArray diff --git a/tests/qiskit_backend_test.py b/tests/qiskit_backend_test.py index fe5fb9b3..0fe745e3 100644 --- a/tests/qiskit_backend_test.py +++ b/tests/qiskit_backend_test.py @@ -16,12 +16,12 @@ import numpy as np import pytest - from qiskit import QuantumCircuit # type: ignore from qiskit.primitives import BackendSamplerV2 # type: ignore from qiskit.providers import JobStatus # type: ignore from qiskit_aer import Aer # type: ignore +from pytket.architecture import Architecture, FullyConnected from pytket.extensions.qiskit import ( AerBackend, AerStateBackend, @@ -29,7 +29,6 @@ IBMQEmulatorBackend, ) from pytket.extensions.qiskit.tket_backend import TketBackend -from pytket.architecture import Architecture, FullyConnected from .mock_pytket_backend import MockShotBackend @@ -55,9 +54,9 @@ def test_samples() -> None: tb = TketBackend(b, comp) job = tb.run(qc, shots=100, memory=True) shots = job.result().get_memory() - assert all(((r[0] == "1" and r[1] == r[2]) for r in shots)) + assert all((r[0] == "1" and r[1] == r[2]) for r in shots) counts = job.result().get_counts() - assert all(((r[0] == "1" and r[1] == r[2]) for r in counts.keys())) + assert all((r[0] == "1" and r[1] == r[2]) for r in counts) def test_state() -> None: @@ -133,6 +132,6 @@ def test_architectures() -> None: tb = TketBackend(b, b.default_compilation_pass()) job = tb.run(qc, shots=100, memory=True) shots = job.result().get_memory() - assert all(((r[0] == "1" and r[1] == r[2]) for r in shots)) + assert all((r[0] == "1" and r[1] == r[2]) for r in shots) counts = job.result().get_counts() - assert all(((r[0] == "1" and r[1] == r[2]) for r in counts.keys())) + assert all((r[0] == "1" and r[1] == r[2]) for r in counts) diff --git a/tests/qiskit_convert_test.py b/tests/qiskit_convert_test.py index e592a6c0..cdde50d3 100644 --- a/tests/qiskit_convert_test.py +++ b/tests/qiskit_convert_test.py @@ -14,61 +14,69 @@ import os from collections import Counter from math import pi -import pytest -from sympy import Symbol + import numpy as np -from qiskit import ( # type: ignore +import pytest +import qiskit.circuit.library.standard_gates as qiskit_gates # type: ignore +from qiskit import ( + ClassicalRegister, QuantumCircuit, QuantumRegister, - ClassicalRegister, transpile, ) -from qiskit.quantum_info import SparsePauliOp, Statevector, Operator # type: ignore -from qiskit.transpiler import PassManager # type: ignore -from qiskit.circuit.library import RYGate, MCMT, XXPlusYYGate, PauliEvolutionGate, UnitaryGate, RealAmplitudes # type: ignore -import qiskit.circuit.library.standard_gates as qiskit_gates # type: ignore from qiskit.circuit import Parameter +from qiskit.circuit.equivalence_library import ( # type: ignore + StandardEquivalenceLibrary, +) +from qiskit.circuit.library import ( + MCMT, + PauliEvolutionGate, + RealAmplitudes, + RYGate, + TwoLocal, + UnitaryGate, + XXPlusYYGate, +) +from qiskit.circuit.parameterexpression import ParameterExpression # type: ignore +from qiskit.quantum_info import Operator, SparsePauliOp, Statevector # type: ignore from qiskit.synthesis import SuzukiTrotter # type: ignore -from qiskit_aer import Aer # type: ignore +from qiskit.transpiler import PassManager # type: ignore from qiskit.transpiler.passes import BasisTranslator # type: ignore -from qiskit.circuit.equivalence_library import StandardEquivalenceLibrary # type: ignore +from qiskit_aer import Aer # type: ignore from qiskit_ibm_runtime.fake_provider import FakeGuadalupeV2 # type: ignore -from qiskit.circuit.parameterexpression import ParameterExpression # type: ignore -from qiskit.circuit.library import TwoLocal -from qiskit import transpile +from sympy import Symbol from pytket.circuit import ( - Circuit, + Bit, CircBox, + Circuit, + CustomGateDef, + Op, + OpType, + QControlBox, + Qubit, + StatePreparationBox, Unitary1qBox, Unitary2qBox, Unitary3qBox, - OpType, - Op, - Qubit, - Bit, - CustomGateDef, reg_eq, - StatePreparationBox, - QControlBox, ) -from pytket.extensions.qiskit import tk_to_qiskit, qiskit_to_tk, IBMQBackend +from pytket.extensions.qiskit import IBMQBackend, qiskit_to_tk, tk_to_qiskit from pytket.extensions.qiskit.backends import qiskit_aer_backend from pytket.extensions.qiskit.qiskit_convert import _gate_str_2_optype -from pytket.extensions.qiskit.tket_pass import TketPass, TketAutoPass from pytket.extensions.qiskit.result_convert import qiskit_result_to_backendresult +from pytket.extensions.qiskit.tket_pass import TketAutoPass, TketPass from pytket.passes import ( - RebaseTket, + CliffordSimp, DecomposeBoxes, FullPeepholeOptimise, + RebaseTket, SequencePass, - CliffordSimp, ) - from pytket.utils.results import ( compare_statevectors, - permute_rows_cols_in_unitary, compare_unitaries, + permute_rows_cols_in_unitary, ) skip_remote_tests: bool = os.getenv("PYTKET_RUN_REMOTE_TESTS") is None @@ -105,7 +113,7 @@ def test_parameterised_circuit_global_phase() -> None: qc_2 = tk_to_qiskit(tket_qc) - assert type(qc_2.global_phase) == ParameterExpression + assert type(qc_2.global_phase) is ParameterExpression def test_classical_barrier_error() -> None: @@ -498,7 +506,6 @@ def test_gate_str_2_optype() -> None: "mcx": OpType.CnX, "x": OpType.X, } - print([(_gate_str_2_optype[key], val) for key, val in samples.items()]) assert all(_gate_str_2_optype[key] == val for key, val in samples.items()) @@ -515,7 +522,6 @@ def test_customgate() -> None: qc1 = tk_to_qiskit(circ) newcirc = qiskit_to_tk(qc1) - print(repr(newcirc)) qc2 = tk_to_qiskit(newcirc) correct_circ = Circuit(3).Rx(0.1, 0).Rx(0.4, 2).CZ(0, 1).Rx(0.2, 1) @@ -618,7 +624,7 @@ def assert_tket_circuits_identical(circuits: list[Circuit]) -> None: circ_copies = [] for nn in range(len(circuits)): - assert type(circuits[nn]) == Circuit + assert type(circuits[nn]) is Circuit circ = circuits[nn].copy() circ.name = "tk_circ_must_be_same_name" qbs = circ.qubits