diff --git a/Cargo.lock b/Cargo.lock index 03f74d03aedb..cdd703e02316 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", diff --git a/README.md b/README.md index 21fdc1439bf2..9bbebfe0db4a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Qiskit + [![License](https://img.shields.io/github/license/Qiskit/qiskit.svg?)](https://opensource.org/licenses/Apache-2.0) -[![Release](https://img.shields.io/github/release/Qiskit/qiskit.svg)](https://github.com/Qiskit/qiskit/releases) +[![Current Release](https://img.shields.io/github/release/Qiskit/qiskit.svg?logo=Qiskit)](https://github.com/Qiskit/qiskit/releases) +[![Extended Support Release](https://img.shields.io/github/v/release/Qiskit/qiskit?sort=semver&filter=0.*&logo=Qiskit&label=extended%20support)](https://github.com/Qiskit/qiskit/releases?q=tag%3A0) [![Downloads](https://img.shields.io/pypi/dm/qiskit.svg)](https://pypi.org/project/qiskit/) [![Coverage Status](https://coveralls.io/repos/github/Qiskit/qiskit/badge.svg?branch=main)](https://coveralls.io/github/Qiskit/qiskit?branch=main) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/qiskit) diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index 12e3c589192f..dca8fda1f9e2 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -20,7 +20,7 @@ numpy = "0.21.0" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" -ahash = "0.8.6" +ahash = "0.8.11" num-traits = "0.2" num-complex = "0.4" num-bigint = "0.4" diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index 31ffae66f0b6..f6c3404e38c4 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -127,10 +127,12 @@ def _append(self, instruction, _qargs=None, _cargs=None): self._build() return super()._append(instruction, _qargs, _cargs) - def compose(self, other, qubits=None, clbits=None, front=False, inplace=False, wrap=False): + def compose( + self, other, qubits=None, clbits=None, front=False, inplace=False, wrap=False, *, copy=True + ): if not self._is_built: self._build() - return super().compose(other, qubits, clbits, front, inplace, wrap) + return super().compose(other, qubits, clbits, front, inplace, wrap, copy=copy) def inverse(self, annotated: bool = False): if not self._is_built: diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py index c065abbe7cd1..618041142227 100644 --- a/qiskit/circuit/library/generalized_gates/unitary.py +++ b/qiskit/circuit/library/generalized_gates/unitary.py @@ -70,6 +70,8 @@ def __init__( data: numpy.ndarray | Gate | BaseOperator, label: str | None = None, check_input: bool = True, + *, + num_qubits: int | None = None, ) -> None: """Create a gate from a numeric unitary matrix. @@ -81,6 +83,7 @@ def __init__( be skipped. This should only ever be used if you know the input is unitary, setting this to ``False`` and passing in a non-unitary matrix will result unexpected behavior and errors. + num_qubits: If given, the number of qubits in the matrix. If not given, it is inferred. Raises: ValueError: If input data is not an N-qubit unitary operator. @@ -97,7 +100,7 @@ def __init__( # Convert to numpy array in case not already an array data = numpy.asarray(data, dtype=complex) input_dim, output_dim = data.shape - num_qubits = int(math.log2(input_dim)) + num_qubits = num_qubits if num_qubits is not None else int(math.log2(input_dim)) if check_input: # Check input is unitary if not is_unitary_matrix(data): diff --git a/qiskit/circuit/library/phase_oracle.py b/qiskit/circuit/library/phase_oracle.py index f00a8d56255a..355c0a69f85c 100644 --- a/qiskit/circuit/library/phase_oracle.py +++ b/qiskit/circuit/library/phase_oracle.py @@ -87,7 +87,7 @@ def synthesizer(boolean_expression): super().__init__(oracle.num_qubits, name="Phase Oracle") - self.compose(oracle, inplace=True) + self.compose(oracle, inplace=True, copy=False) def evaluate_bitstring(self, bitstring: str) -> bool: """Evaluate the oracle on a bitstring. diff --git a/qiskit/circuit/library/quantum_volume.py b/qiskit/circuit/library/quantum_volume.py index 54a1b30dbec7..1c952808fa5a 100644 --- a/qiskit/circuit/library/quantum_volume.py +++ b/qiskit/circuit/library/quantum_volume.py @@ -15,9 +15,8 @@ from typing import Optional, Union import numpy as np -from qiskit.quantum_info.random import random_unitary -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library.generalized_gates.permutation import Permutation +from qiskit.circuit import QuantumCircuit, CircuitInstruction +from qiskit.circuit.library.generalized_gates import PermutationGate, UnitaryGate class QuantumVolume(QuantumCircuit): @@ -60,6 +59,8 @@ def __init__( depth: Optional[int] = None, seed: Optional[Union[int, np.random.Generator]] = None, classical_permutation: bool = True, + *, + flatten: bool = False, ) -> None: """Create quantum volume model circuit of size num_qubits x depth. @@ -69,46 +70,46 @@ def __init__( seed: Random number generator or generator seed. classical_permutation: use classical permutations at every layer, rather than quantum. + flatten: If ``False`` (the default), construct a circuit that contains a single + instruction, which in turn has the actual volume structure. If ``True``, construct + the volume structure directly. """ - # Initialize RNG - if seed is None: - rng_set = np.random.default_rng() - seed = rng_set.integers(low=1, high=1000) - if isinstance(seed, np.random.Generator): - rng = seed - else: - rng = np.random.default_rng(seed) + import scipy.stats # Parameters depth = depth or num_qubits # how many layers of SU(4) - width = int(np.floor(num_qubits / 2)) # how many SU(4)s fit in each layer - name = "quantum_volume_" + str([num_qubits, depth, seed]).replace(" ", "") + width = num_qubits // 2 # how many SU(4)s fit in each layer + rng = seed if isinstance(seed, np.random.Generator) else np.random.default_rng(seed) + if seed is None: + # Get the internal entropy used to seed the default RNG, if no seed was given. This + # stays in the output name, so effectively stores a way of regenerating the circuit. + # This is just best-effort only, for backwards compatibility, and isn't critical (if + # someone needs full reproducibility, they should be manually controlling the seeding). + seed = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None) - # Generator random unitary seeds in advance. - # Note that this means we are constructing multiple new generator - # objects from low-entropy integer seeds rather than pass the shared - # generator object to the random_unitary function. This is done so - # that we can use the integer seed as a label for the generated gates. - unitary_seeds = rng.integers(low=1, high=1000, size=[depth, width]) + super().__init__( + num_qubits, name="quantum_volume_" + str([num_qubits, depth, seed]).replace(" ", "") + ) + base = self if flatten else QuantumCircuit(num_qubits, name=self.name) # For each layer, generate a permutation of qubits # Then generate and apply a Haar-random SU(4) to each pair - circuit = QuantumCircuit(num_qubits, name=name) - perm_0 = list(range(num_qubits)) - for d in range(depth): - perm = rng.permutation(perm_0) - if not classical_permutation: - layer_perm = Permutation(num_qubits, perm) - circuit.compose(layer_perm, inplace=True) - for w in range(width): - seed_u = unitary_seeds[d][w] - su4 = random_unitary(4, seed=seed_u).to_instruction() - su4.label = "su4_" + str(seed_u) - if classical_permutation: - physical_qubits = int(perm[2 * w]), int(perm[2 * w + 1]) - circuit.compose(su4, [physical_qubits[0], physical_qubits[1]], inplace=True) - else: - circuit.compose(su4, [2 * w, 2 * w + 1], inplace=True) - - super().__init__(*circuit.qregs, name=circuit.name) - self.compose(circuit.to_instruction(), qubits=self.qubits, inplace=True) + unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape(depth, width, 4, 4) + qubits = tuple(base.qubits) + for row in unitaries: + perm = rng.permutation(num_qubits) + if classical_permutation: + for w, unitary in enumerate(row): + gate = UnitaryGate(unitary, check_input=False, num_qubits=2) + qubit = 2 * w + base._append( + CircuitInstruction(gate, (qubits[perm[qubit]], qubits[perm[qubit + 1]])) + ) + else: + base._append(CircuitInstruction(PermutationGate(perm), qubits)) + for w, unitary in enumerate(row): + gate = UnitaryGate(unitary, check_input=False, num_qubits=2) + qubit = 2 * w + base._append(CircuitInstruction(gate, qubits[qubit : qubit + 2])) + if not flatten: + self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits))) diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ed170dde6cb2..56a283652f01 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -15,7 +15,7 @@ """Quantum circuit object.""" from __future__ import annotations -import copy +import copy as _copy import itertools import multiprocessing as mp import typing @@ -425,10 +425,10 @@ def data(self, data_input: Iterable): return if isinstance(data_input[0], CircuitInstruction): for instruction in data_input: - self.append(instruction) + self.append(instruction, copy=False) else: for instruction, qargs, cargs in data_input: - self.append(instruction, qargs, cargs) + self.append(instruction, qargs, cargs, copy=False) @property def op_start_times(self) -> list[int]: @@ -535,7 +535,7 @@ def __deepcopy__(self, memo=None): cls = self.__class__ result = cls.__new__(cls) for k in self.__dict__.keys() - {"_data", "_builder_api"}: - setattr(result, k, copy.deepcopy(self.__dict__[k], memo)) + setattr(result, k, _copy.deepcopy(self.__dict__[k], memo)) result._builder_api = _OuterCircuitScopeInterface(result) @@ -543,10 +543,10 @@ def __deepcopy__(self, memo=None): # like we would when pickling. result._data = self._data.copy() result._data.replace_bits( - qubits=copy.deepcopy(self._data.qubits, memo), - clbits=copy.deepcopy(self._data.clbits, memo), + qubits=_copy.deepcopy(self._data.qubits, memo), + clbits=_copy.deepcopy(self._data.clbits, memo), ) - result._data.map_ops(lambda op: copy.deepcopy(op, memo)) + result._data.map_ops(lambda op: _copy.deepcopy(op, memo)) return result @classmethod @@ -869,6 +869,8 @@ def compose( front: bool = False, inplace: bool = False, wrap: bool = False, + *, + copy: bool = True, ) -> Optional["QuantumCircuit"]: """Compose circuit with ``other`` circuit or instruction, optionally permuting wires. @@ -885,6 +887,12 @@ def compose( inplace (bool): If True, modify the object. Otherwise return composed circuit. wrap (bool): If True, wraps the other circuit into a gate (or instruction, depending on whether it contains only unitary instructions) before composing it onto self. + copy (bool): If ``True`` (the default), then the input is treated as shared, and any + contained instructions will be copied, if they might need to be mutated in the + future. You can set this to ``False`` if the input should be considered owned by + the base circuit, in order to avoid unnecessary copies; in this case, it is not + valid to use ``other`` afterwards, and some instructions may have been mutated in + place. Returns: QuantumCircuit: the composed circuit (returns None if inplace==True). @@ -969,11 +977,11 @@ def compose( # Need to keep a reference to the data for use after we've emptied it. old_data = dest._data.copy() dest.clear() - dest.append(other, qubits, clbits) + dest.append(other, qubits, clbits, copy=copy) for instruction in old_data: dest._append(instruction) else: - dest.append(other, qargs=qubits, cargs=clbits) + dest.append(other, qargs=qubits, cargs=clbits, copy=copy) return None if inplace else dest if other.num_qubits > dest.num_qubits or other.num_clbits > dest.num_clbits: @@ -1035,10 +1043,11 @@ def compose( ) def map_vars(op): - n_op = op.copy() + n_op = op.copy() if copy else op if (condition := getattr(n_op, "condition", None)) is not None: n_op.condition = variable_mapper.map_condition(condition) if isinstance(n_op, SwitchCaseOp): + n_op = n_op.copy() if n_op is op else n_op n_op.target = variable_mapper.map_target(n_op.target) return n_op @@ -1312,6 +1321,8 @@ def append( instruction: Operation | CircuitInstruction, qargs: Sequence[QubitSpecifier] | None = None, cargs: Sequence[ClbitSpecifier] | None = None, + *, + copy: bool = True, ) -> InstructionSet: """Append one or more instructions to the end of the circuit, modifying the circuit in place. @@ -1329,6 +1340,11 @@ def append( :class:`.CircuitInstruction` with all its context. qargs: specifiers of the :class:`~.circuit.Qubit`\\ s to attach instruction to. cargs: specifiers of the :class:`.Clbit`\\ s to attach instruction to. + copy: if ``True`` (the default), then the incoming ``instruction`` is copied before + adding it to the circuit if it contains symbolic parameters, so it can be safely + mutated without affecting other circuits the same instruction might be in. If you + are sure this instruction will not be in other circuits, you can set this ``False`` + for a small speedup. Returns: qiskit.circuit.InstructionSet: a handle to the :class:`.CircuitInstruction`\\ s that @@ -1367,11 +1383,11 @@ def append( if params := getattr(operation, "params", ()): is_parameter = False for param in params: - is_parameter = is_parameter or isinstance(param, Parameter) + is_parameter = is_parameter or isinstance(param, ParameterExpression) if isinstance(param, expr.Expr): param = _validate_expr(circuit_scope, param) - if is_parameter: - operation = copy.deepcopy(operation) + if copy and is_parameter: + operation = _copy.deepcopy(operation) if isinstance(operation, ControlFlowOp): # Verify that any variable bindings are valid. Control-flow ops are already enforced # by the class not to contain 'input' variables. @@ -2543,7 +2559,7 @@ def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit": raise TypeError( f"invalid name for a circuit: '{name}'. The name must be a string or 'None'." ) - cpy = copy.copy(self) + cpy = _copy.copy(self) # copy registers correctly, in copy.copy they are only copied via reference cpy.qregs = self.qregs.copy() cpy.cregs = self.cregs.copy() @@ -2566,8 +2582,8 @@ def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit": ) cpy._data = CircuitData(self._data.qubits, self._data.clbits) - cpy._calibrations = copy.deepcopy(self._calibrations) - cpy._metadata = copy.deepcopy(self._metadata) + cpy._calibrations = _copy.deepcopy(self._calibrations) + cpy._metadata = _copy.deepcopy(self._metadata) if name: cpy.name = name @@ -2616,7 +2632,7 @@ def reset(self, qubit: QubitSpecifier) -> InstructionSet: """ from .reset import Reset - return self.append(Reset(), [qubit], []) + return self.append(Reset(), [qubit], [], copy=False) def store(self, lvalue: typing.Any, rvalue: typing.Any, /) -> InstructionSet: """Store the result of the given real-time classical expression ``rvalue`` in the memory @@ -2641,7 +2657,7 @@ def store(self, lvalue: typing.Any, rvalue: typing.Any, /) -> InstructionSet: :meth:`add_var` Create a new variable in the circuit that can be written to with this method. """ - return self.append(Store(expr.lift(lvalue), expr.lift(rvalue)), (), ()) + return self.append(Store(expr.lift(lvalue), expr.lift(rvalue)), (), (), copy=False) def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet: r"""Measure a quantum bit (``qubit``) in the Z basis into a classical bit (``cbit``). @@ -2718,7 +2734,7 @@ def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet """ from .measure import Measure - return self.append(Measure(), [qubit], [cbit]) + return self.append(Measure(), [qubit], [cbit], copy=False) def measure_active(self, inplace: bool = True) -> Optional["QuantumCircuit"]: """Adds measurement to all non-idle qubits. Creates a new ClassicalRegister with @@ -3294,7 +3310,9 @@ def barrier(self, *qargs: QubitSpecifier, label=None) -> InstructionSet: if qargs: # This uses a `dict` not a `set` to guarantee a deterministic order to the arguments. qubits = tuple({q: None for qarg in qargs for q in self.qbit_argument_conversion(qarg)}) - return self.append(CircuitInstruction(Barrier(len(qubits), label=label), qubits, ())) + return self.append( + CircuitInstruction(Barrier(len(qubits), label=label), qubits, ()), copy=False + ) else: qubits = self.qubits.copy() return self._current_scope().append( @@ -3325,7 +3343,7 @@ def delay( """ if qarg is None: qarg = self.qubits - return self.append(Delay(duration, unit=unit), [qarg], []) + return self.append(Delay(duration, unit=unit), [qarg], [], copy=False) def h(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.HGate`. @@ -3340,7 +3358,7 @@ def h(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.h import HGate - return self.append(HGate(), [qubit], []) + return self.append(HGate(), [qubit], [], copy=False) def ch( self, @@ -3367,7 +3385,10 @@ def ch( from .library.standard_gates.h import CHGate return self.append( - CHGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CHGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def id(self, qubit: QubitSpecifier) -> InstructionSet: # pylint: disable=invalid-name @@ -3383,7 +3404,7 @@ def id(self, qubit: QubitSpecifier) -> InstructionSet: # pylint: disable=invali """ from .library.standard_gates.i import IGate - return self.append(IGate(), [qubit], []) + return self.append(IGate(), [qubit], [], copy=False) def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.MSGate`. @@ -3400,7 +3421,7 @@ def ms(self, theta: ParameterValueType, qubits: Sequence[QubitSpecifier]) -> Ins # pylint: disable=cyclic-import from .library.generalized_gates.gms import MSGate - return self.append(MSGate(len(qubits), theta), qubits) + return self.append(MSGate(len(qubits), theta), qubits, copy=False) def p(self, theta: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.PhaseGate`. @@ -3416,7 +3437,7 @@ def p(self, theta: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.p import PhaseGate - return self.append(PhaseGate(theta), [qubit], []) + return self.append(PhaseGate(theta), [qubit], [], copy=False) def cp( self, @@ -3445,7 +3466,10 @@ def cp( from .library.standard_gates.p import CPhaseGate return self.append( - CPhaseGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CPhaseGate(theta, label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def mcp( @@ -3477,6 +3501,7 @@ def mcp( MCPhaseGate(lam, num_ctrl_qubits, ctrl_state=ctrl_state), control_qubits[:] + [target_qubit], [], + copy=False, ) def r( @@ -3496,7 +3521,7 @@ def r( """ from .library.standard_gates.r import RGate - return self.append(RGate(theta, phi), [qubit], []) + return self.append(RGate(theta, phi), [qubit], [], copy=False) def rv( self, @@ -3523,7 +3548,7 @@ def rv( """ from .library.generalized_gates.rv import RVGate - return self.append(RVGate(vx, vy, vz), [qubit], []) + return self.append(RVGate(vx, vy, vz), [qubit], [], copy=False) def rccx( self, @@ -3545,7 +3570,9 @@ def rccx( """ from .library.standard_gates.x import RCCXGate - return self.append(RCCXGate(), [control_qubit1, control_qubit2, target_qubit], []) + return self.append( + RCCXGate(), [control_qubit1, control_qubit2, target_qubit], [], copy=False + ) def rcccx( self, @@ -3570,7 +3597,10 @@ def rcccx( from .library.standard_gates.x import RC3XGate return self.append( - RC3XGate(), [control_qubit1, control_qubit2, control_qubit3, target_qubit], [] + RC3XGate(), + [control_qubit1, control_qubit2, control_qubit3, target_qubit], + [], + copy=False, ) def rx( @@ -3590,7 +3620,7 @@ def rx( """ from .library.standard_gates.rx import RXGate - return self.append(RXGate(theta, label=label), [qubit], []) + return self.append(RXGate(theta, label=label), [qubit], [], copy=False) def crx( self, @@ -3619,7 +3649,10 @@ def crx( from .library.standard_gates.rx import CRXGate return self.append( - CRXGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CRXGate(theta, label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def rxx( @@ -3639,7 +3672,7 @@ def rxx( """ from .library.standard_gates.rxx import RXXGate - return self.append(RXXGate(theta), [qubit1, qubit2], []) + return self.append(RXXGate(theta), [qubit1, qubit2], [], copy=False) def ry( self, theta: ParameterValueType, qubit: QubitSpecifier, label: str | None = None @@ -3658,7 +3691,7 @@ def ry( """ from .library.standard_gates.ry import RYGate - return self.append(RYGate(theta, label=label), [qubit], []) + return self.append(RYGate(theta, label=label), [qubit], [], copy=False) def cry( self, @@ -3687,7 +3720,10 @@ def cry( from .library.standard_gates.ry import CRYGate return self.append( - CRYGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CRYGate(theta, label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def ryy( @@ -3707,7 +3743,7 @@ def ryy( """ from .library.standard_gates.ryy import RYYGate - return self.append(RYYGate(theta), [qubit1, qubit2], []) + return self.append(RYYGate(theta), [qubit1, qubit2], [], copy=False) def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.RZGate`. @@ -3723,7 +3759,7 @@ def rz(self, phi: ParameterValueType, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.rz import RZGate - return self.append(RZGate(phi), [qubit], []) + return self.append(RZGate(phi), [qubit], [], copy=False) def crz( self, @@ -3752,7 +3788,10 @@ def crz( from .library.standard_gates.rz import CRZGate return self.append( - CRZGate(theta, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CRZGate(theta, label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def rzx( @@ -3772,7 +3811,7 @@ def rzx( """ from .library.standard_gates.rzx import RZXGate - return self.append(RZXGate(theta), [qubit1, qubit2], []) + return self.append(RZXGate(theta), [qubit1, qubit2], [], copy=False) def rzz( self, theta: ParameterValueType, qubit1: QubitSpecifier, qubit2: QubitSpecifier @@ -3791,7 +3830,7 @@ def rzz( """ from .library.standard_gates.rzz import RZZGate - return self.append(RZZGate(theta), [qubit1, qubit2], []) + return self.append(RZZGate(theta), [qubit1, qubit2], [], copy=False) def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.ECRGate`. @@ -3806,7 +3845,7 @@ def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.ecr import ECRGate - return self.append(ECRGate(), [qubit1, qubit2], []) + return self.append(ECRGate(), [qubit1, qubit2], [], copy=False) def s(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SGate`. @@ -3821,7 +3860,7 @@ def s(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.s import SGate - return self.append(SGate(), [qubit], []) + return self.append(SGate(), [qubit], [], copy=False) def sdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SdgGate`. @@ -3836,7 +3875,7 @@ def sdg(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.s import SdgGate - return self.append(SdgGate(), [qubit], []) + return self.append(SdgGate(), [qubit], [], copy=False) def cs( self, @@ -3866,6 +3905,7 @@ def cs( CSGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [], + copy=False, ) def csdg( @@ -3896,6 +3936,7 @@ def csdg( CSdgGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [], + copy=False, ) def swap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: @@ -3911,7 +3952,7 @@ def swap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet """ from .library.standard_gates.swap import SwapGate - return self.append(SwapGate(), [qubit1, qubit2], []) + return self.append(SwapGate(), [qubit1, qubit2], [], copy=False) def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.iSwapGate`. @@ -3926,7 +3967,7 @@ def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSe """ from .library.standard_gates.iswap import iSwapGate - return self.append(iSwapGate(), [qubit1, qubit2], []) + return self.append(iSwapGate(), [qubit1, qubit2], [], copy=False) def cswap( self, @@ -3958,6 +3999,7 @@ def cswap( CSwapGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit1, target_qubit2], [], + copy=False, ) def sx(self, qubit: QubitSpecifier) -> InstructionSet: @@ -3973,7 +4015,7 @@ def sx(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.sx import SXGate - return self.append(SXGate(), [qubit], []) + return self.append(SXGate(), [qubit], [], copy=False) def sxdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.SXdgGate`. @@ -3988,7 +4030,7 @@ def sxdg(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.sx import SXdgGate - return self.append(SXdgGate(), [qubit], []) + return self.append(SXdgGate(), [qubit], [], copy=False) def csx( self, @@ -4018,6 +4060,7 @@ def csx( CSXGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [], + copy=False, ) def t(self, qubit: QubitSpecifier) -> InstructionSet: @@ -4033,7 +4076,7 @@ def t(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.t import TGate - return self.append(TGate(), [qubit], []) + return self.append(TGate(), [qubit], [], copy=False) def tdg(self, qubit: QubitSpecifier) -> InstructionSet: """Apply :class:`~qiskit.circuit.library.TdgGate`. @@ -4048,7 +4091,7 @@ def tdg(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.t import TdgGate - return self.append(TdgGate(), [qubit], []) + return self.append(TdgGate(), [qubit], [], copy=False) def u( self, @@ -4072,7 +4115,7 @@ def u( """ from .library.standard_gates.u import UGate - return self.append(UGate(theta, phi, lam), [qubit], []) + return self.append(UGate(theta, phi, lam), [qubit], [], copy=False) def cu( self, @@ -4110,6 +4153,7 @@ def cu( CUGate(theta, phi, lam, gamma, label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [], + copy=False, ) def x(self, qubit: QubitSpecifier, label: str | None = None) -> InstructionSet: @@ -4126,7 +4170,7 @@ def x(self, qubit: QubitSpecifier, label: str | None = None) -> InstructionSet: """ from .library.standard_gates.x import XGate - return self.append(XGate(label=label), [qubit], []) + return self.append(XGate(label=label), [qubit], [], copy=False) def cx( self, @@ -4154,7 +4198,10 @@ def cx( from .library.standard_gates.x import CXGate return self.append( - CXGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CXGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: @@ -4171,7 +4218,7 @@ def dcx(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.dcx import DCXGate - return self.append(DCXGate(), [qubit1, qubit2], []) + return self.append(DCXGate(), [qubit1, qubit2], [], copy=False) def ccx( self, @@ -4201,6 +4248,7 @@ def ccx( CCXGate(ctrl_state=ctrl_state), [control_qubit1, control_qubit2, target_qubit], [], + copy=False, ) def mcx( @@ -4300,7 +4348,7 @@ def y(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.y import YGate - return self.append(YGate(), [qubit], []) + return self.append(YGate(), [qubit], [], copy=False) def cy( self, @@ -4327,7 +4375,10 @@ def cy( from .library.standard_gates.y import CYGate return self.append( - CYGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CYGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def z(self, qubit: QubitSpecifier) -> InstructionSet: @@ -4343,7 +4394,7 @@ def z(self, qubit: QubitSpecifier) -> InstructionSet: """ from .library.standard_gates.z import ZGate - return self.append(ZGate(), [qubit], []) + return self.append(ZGate(), [qubit], [], copy=False) def cz( self, @@ -4370,7 +4421,10 @@ def cz( from .library.standard_gates.z import CZGate return self.append( - CZGate(label=label, ctrl_state=ctrl_state), [control_qubit, target_qubit], [] + CZGate(label=label, ctrl_state=ctrl_state), + [control_qubit, target_qubit], + [], + copy=False, ) def ccz( @@ -4403,6 +4457,7 @@ def ccz( CCZGate(label=label, ctrl_state=ctrl_state), [control_qubit1, control_qubit2, target_qubit], [], + copy=False, ) def pauli( @@ -4421,7 +4476,7 @@ def pauli( """ from qiskit.circuit.library.generalized_gates.pauli import PauliGate - return self.append(PauliGate(pauli_string), qubits, []) + return self.append(PauliGate(pauli_string), qubits, [], copy=False) def prepare_state( self, @@ -4532,7 +4587,9 @@ def prepare_state( num_qubits = len(qubits) if isinstance(state, int) else None return self.append( - StatePreparation(state, num_qubits, label=label, normalize=normalize), qubits + StatePreparation(state, num_qubits, label=label, normalize=normalize), + qubits, + copy=False, ) def initialize( @@ -4644,7 +4701,7 @@ class to prepare the qubits in a specified state. num_qubits = len(qubits) if isinstance(params, int) else None - return self.append(Initialize(params, num_qubits, normalize), qubits) + return self.append(Initialize(params, num_qubits, normalize), qubits, copy=False) def unitary( self, @@ -4687,7 +4744,7 @@ def unitary( if isinstance(qubits, (int, Qubit)) or len(qubits) > 1: qubits = [qubits] - return self.append(gate, qubits, []) + return self.append(gate, qubits, [], copy=False) def _current_scope(self) -> CircuitScopeInterface: if self._control_flow_scopes: @@ -4869,7 +4926,7 @@ def while_loop(self, condition, body=None, qubits=None, clbits=None, *, label=No "When using 'while_loop' with a body, you must pass qubits and clbits." ) - return self.append(WhileLoopOp(condition, body, label), qubits, clbits) + return self.append(WhileLoopOp(condition, body, label), qubits, clbits, copy=False) @typing.overload def for_loop( @@ -4958,7 +5015,9 @@ def for_loop( "When using 'for_loop' with a body, you must pass qubits and clbits." ) - return self.append(ForLoopOp(indexset, loop_parameter, body, label), qubits, clbits) + return self.append( + ForLoopOp(indexset, loop_parameter, body, label), qubits, clbits, copy=False + ) @typing.overload def if_test( @@ -5068,7 +5127,7 @@ def if_test( elif qubits is None or clbits is None: raise CircuitError("When using 'if_test' with a body, you must pass qubits and clbits.") - return self.append(IfElseOp(condition, true_body, None, label), qubits, clbits) + return self.append(IfElseOp(condition, true_body, None, label), qubits, clbits, copy=False) def if_else( self, @@ -5123,7 +5182,9 @@ def if_else( else: condition = (circuit_scope.resolve_classical_resource(condition[0]), condition[1]) - return self.append(IfElseOp(condition, true_body, false_body, label), qubits, clbits) + return self.append( + IfElseOp(condition, true_body, false_body, label), qubits, clbits, copy=False + ) @typing.overload def switch( @@ -5214,7 +5275,7 @@ def switch(self, target, cases=None, qubits=None, clbits=None, *, label=None): if qubits is None or clbits is None: raise CircuitError("When using 'switch' with cases, you must pass qubits and clbits.") - return self.append(SwitchCaseOp(target, cases, label=label), qubits, clbits) + return self.append(SwitchCaseOp(target, cases, label=label), qubits, clbits, copy=False) def break_loop(self) -> InstructionSet: """Apply :class:`~qiskit.circuit.BreakLoopOp`. @@ -5240,8 +5301,10 @@ def break_loop(self) -> InstructionSet: if self._control_flow_scopes: operation = BreakLoopPlaceholder() resources = operation.placeholder_resources() - return self.append(operation, resources.qubits, resources.clbits) - return self.append(BreakLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits) + return self.append(operation, resources.qubits, resources.clbits, copy=False) + return self.append( + BreakLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits, copy=False + ) def continue_loop(self) -> InstructionSet: """Apply :class:`~qiskit.circuit.ContinueLoopOp`. @@ -5267,9 +5330,9 @@ def continue_loop(self) -> InstructionSet: if self._control_flow_scopes: operation = ContinueLoopPlaceholder() resources = operation.placeholder_resources() - return self.append(operation, resources.qubits, resources.clbits) + return self.append(operation, resources.qubits, resources.clbits, copy=False) return self.append( - ContinueLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits + ContinueLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits, copy=False ) def add_calibration( diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 8e0ce6385eac..606df60869c9 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -35,6 +35,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.synthesis.one_qubit import one_qubit_decompose +from qiskit.transpiler.passes.optimization.optimize_1q_decomposition import _possible_decomposers from qiskit.synthesis.two_qubit.xx_decompose import XXDecomposer, XXEmbodiments from qiskit.synthesis.two_qubit.two_qubit_decompose import ( TwoQubitBasisDecomposer, @@ -84,23 +85,16 @@ def _choose_kak_gate(basis_gates): def _choose_euler_basis(basis_gates): """Choose the first available 1q basis to use in the Euler decomposition.""" basis_set = set(basis_gates or []) - - for basis, gates in one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES.items(): - - if set(gates).issubset(basis_set): - return basis - + decomposers = _possible_decomposers(basis_set) + if decomposers: + return decomposers[0] return "U" def _find_matching_euler_bases(target, qubit): """Find matching available 1q basis to use in the Euler decomposition.""" - euler_basis_gates = [] basis_set = target.operation_names_for_qargs((qubit,)) - for basis, gates in one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES.items(): - if set(gates).issubset(basis_set): - euler_basis_gates.append(basis) - return euler_basis_gates + return _possible_decomposers(basis_set) def _choose_bases(basis_gates, basis_dict=None): @@ -777,6 +771,13 @@ def is_controlled(gate): ) decomposers.append(decomposer) + # If our 2q basis gates are a subset of cx, ecr, or cz then we know TwoQubitBasisDecomposer + # is an ideal decomposition and there is no need to bother calculating the XX embodiments + # or try the XX decomposer + if {"cx", "cz", "ecr"}.issuperset(available_2q_basis): + self._decomposer_cache[qubits_tuple] = decomposers + return decomposers + # possible controlled decomposers (i.e. XXDecomposer) controlled_basis = {k: v for k, v in available_2q_basis.items() if is_controlled(v)} basis_2q_fidelity = {} diff --git a/releasenotes/notes/1.0/add-annotated-arg-to-control-d9a188fe66f037ad.yaml b/releasenotes/notes/1.0/add-annotated-arg-to-control-d9a188fe66f037ad.yaml index c4bfbac46ca4..c86f1f68d427 100644 --- a/releasenotes/notes/1.0/add-annotated-arg-to-control-d9a188fe66f037ad.yaml +++ b/releasenotes/notes/1.0/add-annotated-arg-to-control-d9a188fe66f037ad.yaml @@ -1,5 +1,5 @@ --- -features_circuit: +features_circuits: - | Added a new argument, ``annotated``, to the methods :meth:`.QuantumCircuit.control`, :meth:`.Gate.control` and ``.control()`` methods of :class:`.Gate` subclasses (such as :class:`.UnitaryGate` or :class:`.SwapGate`) diff --git a/releasenotes/notes/quantumcircuit-append-copy-8a9b71ad4b789490.yaml b/releasenotes/notes/quantumcircuit-append-copy-8a9b71ad4b789490.yaml new file mode 100644 index 000000000000..2f7280215671 --- /dev/null +++ b/releasenotes/notes/quantumcircuit-append-copy-8a9b71ad4b789490.yaml @@ -0,0 +1,21 @@ +--- +features_circuits: + - | + :meth:`.QuantumCircuit.append` now has a ``copy`` keyword argument, which defaults to ``True``. + When an instruction with runtime parameters (:class:`.ParameterExpression`\ s) is appended to + a circuit, by default, the circuit has always created a copy of the instruction so that if + :meth:`.QuantumCircuit.assign_parameters` attempts to mutate the instruction in place, it does + not affect other references to the same instruction. Now, setting ``copy=False`` allows you to + override this, so you can avoid the copy penalty if you know your instructions will not be used + in other locations. + - | + :meth:`.QuantumCircuit.compose` now has a ``copy`` keyword argument, which defaults to ``True``. + By default, :meth:`~.QuantumCircuit.compose` copies all instructions, so that mutations from one + circuit do not affect any other. If ``copy=False``, then instructions from the other circuit + will become directly owned by the new circuit, which may involve mutating them in place. The + other circuit must not be used afterwards, in this case. +fixes: + - | + :meth:`.QuantumCircuit.append` with ``copy=True`` (its default) will now correctly copy + instructions parametrized by :class:`.ParameterExpression` instances, and not just by + :class:`.Parameter` instances. diff --git a/releasenotes/notes/qv-perf-be76290f472e4777.yaml b/releasenotes/notes/qv-perf-be76290f472e4777.yaml new file mode 100644 index 000000000000..f7e65901eae0 --- /dev/null +++ b/releasenotes/notes/qv-perf-be76290f472e4777.yaml @@ -0,0 +1,27 @@ +--- +features_circuits: + - | + Construction time for :class:`.QuantumVolume` circuits has been significantly improved, on the + order of 10x or a bit more. The internal SU4 gates will now also use more bits of randomness + during their generation, leading to more representative volume circuits, especially at large + widths and depths. + - | + :class:`.QuantumVolume` now has a ``flatten`` keyword argument. This defaults to ``False``, + where the constructed circuit contains a single instruction that in turn contains the actual + volume structure. If set ``True``, the circuit will directly have the volumetric SU4 matrices. + - | + :class:`.UnitaryGate` now accepts an optional ``num_qubits`` argument. The only effect of this + is to skip the inference of the qubit count, which can be helpful for performance when many + gates are being constructed. +upgrade_circuits: + - | + The random-number usage of :class:`.QuantumVolume` has changed, so you will get a different + circuit for a fixed seed between older versions of Qiskit and this version. The random-unitary + generation now uses more bits of entropy, so large circuits will be less biased. + - | + The internal :class:`.UnitaryGate` instances in the definition of a :class:`.QuantumVolume` + circuit will no longer have a :attr:`~.Instruction.label` field set. Previously this was set + to the string ``su4_`` where ```` was a three-digit number denoting the seed of an + internal Numpy pRNG instance for that gate. Doing this was a serious performance problem, and + the seed ought not to have been useful; if you need to retrieve the matrix from the gate, simply + use the :meth:`.Gate.to_matrix` method. diff --git a/test/python/circuit/library/test_quantum_volume.py b/test/python/circuit/library/test_quantum_volume.py index f2d809fec399..80c762db2640 100644 --- a/test/python/circuit/library/test_quantum_volume.py +++ b/test/python/circuit/library/test_quantum_volume.py @@ -15,25 +15,25 @@ import unittest from test.utils.base import QiskitTestCase -from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import QuantumVolume -from qiskit.quantum_info import Operator -from qiskit.quantum_info.random import random_unitary class TestQuantumVolumeLibrary(QiskitTestCase): """Test library of quantum volume quantum circuits.""" - def test_qv(self): + def test_qv_seed_reproducibility(self): """Test qv circuit.""" - circuit = QuantumVolume(2, 2, seed=2, classical_permutation=False) - expected = QuantumCircuit(2) - expected.swap(0, 1) - expected.append(random_unitary(4, seed=837), [0, 1]) - expected.append(random_unitary(4, seed=262), [0, 1]) - expected = Operator(expected) - simulated = Operator(circuit) - self.assertTrue(expected.equiv(simulated)) + left = QuantumVolume(4, 4, seed=28, classical_permutation=False) + right = QuantumVolume(4, 4, seed=28, classical_permutation=False) + self.assertEqual(left, right) + + left = QuantumVolume(4, 4, seed=3, classical_permutation=True) + right = QuantumVolume(4, 4, seed=3, classical_permutation=True) + self.assertEqual(left, right) + + left = QuantumVolume(4, 4, seed=2024, flatten=True) + right = QuantumVolume(4, 4, seed=2024, flatten=True) + self.assertEqual(left, right) if __name__ == "__main__": diff --git a/test/python/circuit/library/test_state_preparation.py b/test/python/circuit/library/test_state_preparation.py index f4966ab61a73..adad848a2777 100644 --- a/test/python/circuit/library/test_state_preparation.py +++ b/test/python/circuit/library/test_state_preparation.py @@ -98,10 +98,9 @@ def test_incompatible_state_and_qubit_args(self): def test_incompatible_int_state_and_qubit_args(self): """Test error raised if number of qubits not compatible with integer state arg""" - # pylint: disable=pointless-statement with self.assertRaises(QiskitError): stateprep = StatePreparation(5, num_qubits=2) - stateprep.definition + _ = stateprep.definition def test_int_state_and_no_qubit_args(self): """Test automatic determination of qubit number""" diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py index bce49af689af..481f2fe3ca56 100644 --- a/test/python/circuit/test_circuit_properties.py +++ b/test/python/circuit/test_circuit_properties.py @@ -1240,8 +1240,7 @@ def test_scheduling(self): qc.cx(0, 1) with self.assertRaises(AttributeError): - # pylint: disable=pointless-statement - qc.op_start_times + _ = qc.op_start_times if __name__ == "__main__": diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 7cf2be598d95..03301899a6ae 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -345,6 +345,26 @@ def test_compose_permuted_smaller(self): self.assertEqual(circuit_composed, circuit_expected) + def test_compose_copy(self): + """Test that `compose` copies instructions where appropriate.""" + base = QuantumCircuit(2, 2) + + # If given a parametric instruction, the instruction should be copied in the output unless + # specifically set to take ownership. + parametric = QuantumCircuit(1) + parametric.rz(Parameter("x"), 0) + should_copy = base.compose(parametric, qubits=[0]) + self.assertIsNot(should_copy.data[-1].operation, parametric.data[-1].operation) + self.assertEqual(should_copy.data[-1].operation, parametric.data[-1].operation) + forbid_copy = base.compose(parametric, qubits=[0], copy=False) + self.assertIs(forbid_copy.data[-1].operation, parametric.data[-1].operation) + + conditional = QuantumCircuit(1, 1) + conditional.x(0).c_if(conditional.clbits[0], True) + test = base.compose(conditional, qubits=[0], clbits=[0], copy=False) + self.assertIs(test.data[-1].operation, conditional.data[-1].operation) + self.assertEqual(test.data[-1].operation.condition, (test.clbits[0], True)) + def test_compose_classical(self): """Composing on classical bits. diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 9511ac08654c..7bcc2cd35f3a 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -145,6 +145,29 @@ def test_duplicate_name_on_append(self): qc.rx(param_a, 0) self.assertRaises(CircuitError, qc.rx, param_a_again, 0) + def test_append_copies_parametric(self): + """Test that `QuantumCircuit.append` copies instructions when they contain compile + parameters and expressions.""" + param = Parameter("a") + expr = param * 2 + gate_param = RZGate(param) + gate_expr = RZGate(expr) + + qc = QuantumCircuit(1) + qc.append(gate_param, [0], copy=True) + self.assertIsNot(qc.data[-1].operation, gate_param) + self.assertEqual(qc.data[-1].operation, gate_param) + + qc.append(gate_param, [0], copy=False) + self.assertIs(qc.data[-1].operation, gate_param) + + qc.append(gate_expr, [0], copy=True) + self.assertIsNot(qc.data[-1].operation, gate_expr) + self.assertEqual(qc.data[-1].operation, gate_expr) + + qc.append(gate_expr, [0], copy=False) + self.assertIs(qc.data[-1].operation, gate_expr) + def test_parameters_property(self): """Test instantiating gate with variable parameters""" from qiskit.circuit.library.standard_gates.rx import RXGate diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index daf163a2c32b..9c57579d0bf1 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -727,8 +727,7 @@ def test_get_parameters(self): self.assertEqual(drag_pulse.beta, 3) with self.assertRaises(AttributeError): - # pylint: disable=pointless-statement - drag_pulse.non_existing_parameter + _ = drag_pulse.non_existing_parameter def test_envelope_cache(self): """Test speed up of instantiation with lambdify envelope cache."""