Add `copy` keyword argument to `QuantumCircuit.append` (#12098)

We always copy compile-time parametric instructions during
`QuantumCircuit.append` to avoid accidental mutation of other references
in a call to `assign_parameters(inplace=True)`.  However, mostly our
rotation gates are added through things like `QuantumCircuit.rz`, where
the only reference to the `RZGate` is internal to the `QuantumCircuit`,
so creating and immediately copying it is a waste of time.

This commit adds a `copy` argument to `QuantumCircuit.append`, so we can
set it to `False` in places where we know we own the instruction being
added.
This commit is contained in:
Jake Lishman 2024-04-08 12:48:41 +01:00 committed by GitHub
parent 9ed963f1aa
commit 44cbb7cec9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 203 additions and 74 deletions

View File

@ -127,10 +127,12 @@ class BlueprintCircuit(QuantumCircuit, ABC):
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:

View File

@ -87,7 +87,7 @@ class PhaseOracle(QuantumCircuit):
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.

View File

@ -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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
# 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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
# 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 @@ class QuantumCircuit:
)
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
: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 @@ class QuantumCircuit:
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 @@ class 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 @@ class 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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
: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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
# 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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
MCPhaseGate(lam, num_ctrl_qubits, ctrl_state=ctrl_state),
control_qubits[:] + [target_qubit],
[],
copy=False,
)
def r(
@ -3496,7 +3521,7 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
CSGate(label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit],
[],
copy=False,
)
def csdg(
@ -3896,6 +3936,7 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
CSXGate(label=label, ctrl_state=ctrl_state),
[control_qubit, target_qubit],
[],
copy=False,
)
def t(self, qubit: QubitSpecifier) -> InstructionSet:
@ -4033,7 +4076,7 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
CCXGate(ctrl_state=ctrl_state),
[control_qubit1, control_qubit2, target_qubit],
[],
copy=False,
)
def mcx(
@ -4300,7 +4348,7 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
CCZGate(label=label, ctrl_state=ctrl_state),
[control_qubit1, control_qubit2, target_qubit],
[],
copy=False,
)
def pauli(
@ -4421,7 +4476,7 @@ class QuantumCircuit:
"""
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 @@ class QuantumCircuit:
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 QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
"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 @@ class QuantumCircuit:
"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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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 @@ class QuantumCircuit:
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(

View File

@ -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.

View File

@ -345,6 +345,26 @@ class TestCircuitCompose(QiskitTestCase):
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.

View File

@ -145,6 +145,29 @@ class TestParameters(QiskitTestCase):
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