diff --git a/crates/accelerate/src/quantum_circuit/circuit_data.rs b/crates/accelerate/src/quantum_circuit/circuit_data.rs index 9b7011cbc682..982a648b9d18 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_data.rs +++ b/crates/accelerate/src/quantum_circuit/circuit_data.rs @@ -13,7 +13,7 @@ use crate::quantum_circuit::circuit_instruction::CircuitInstruction; use crate::quantum_circuit::intern_context::{BitType, IndexType, InternContext}; use crate::quantum_circuit::py_ext; -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyIterator, PyList, PySlice, PyTuple, PyType}; @@ -251,10 +251,13 @@ impl CircuitData { "The number of qubits in the circuit has exceeded the maximum capacity", ) })?; - self.qubit_indices_native.insert(BitAsKey::new(bit)?, idx); - self.qubits_native.push(bit.into_py(py)); - self.qubits = PyList::new(py, &self.qubits_native).into_py(py); - Ok(()) + self.qubit_indices_native + .try_insert(BitAsKey::new(bit)?, idx) + .map_or(Ok(()), |_| { + self.qubits_native.push(bit.into_py(py)); + self.qubits = PyList::new(py, &self.qubits_native).into_py(py); + Ok(()) + }) } /// Registers a :class:`.Clbit` instance. @@ -267,10 +270,13 @@ impl CircuitData { "The number of clbits in the circuit has exceeded the maximum capacity", ) })?; - self.clbit_indices_native.insert(BitAsKey::new(bit)?, idx); - self.clbits_native.push(bit.into_py(py)); - self.clbits = PyList::new(py, &self.clbits_native).into_py(py); - Ok(()) + self.clbit_indices_native + .try_insert(BitAsKey::new(bit)?, idx) + .map_or(Ok(()), |_| { + self.clbits_native.push(bit.into_py(py)); + self.clbits = PyList::new(py, &self.clbits_native).into_py(py); + Ok(()) + }) } /// Performs a shallow copy. @@ -278,16 +284,16 @@ impl CircuitData { /// Returns: /// CircuitData: The shallow copy. pub fn copy(&self, py: Python<'_>) -> PyResult { - Ok(CircuitData { - data: self.data.clone(), - intern_context: self.intern_context.clone(), - qubits_native: self.qubits_native.clone(), - clbits_native: self.clbits_native.clone(), - qubit_indices_native: self.qubit_indices_native.clone(), - clbit_indices_native: self.clbit_indices_native.clone(), - qubits: PyList::new(py, &self.qubits_native).into_py(py), - clbits: PyList::new(py, &self.clbits_native).into_py(py), - }) + let mut res = CircuitData::new( + py, + Some(self.qubits.as_ref(py)), + Some(self.clbits.as_ref(py)), + None, + 0, + )?; + res.intern_context = self.intern_context.clone(); + res.data = self.data.clone(); + Ok(res) } /// Reserves capacity for at least ``additional`` more @@ -300,6 +306,159 @@ impl CircuitData { self.data.reserve(additional); } + /// Returns a tuple of the sets of :class:`.Qubit` and :class:`.Clbit` instances + /// that appear in at least one instruction's bit lists. + pub fn active_bits(&self, py: Python<'_>) -> Py { + let mut qubits: HashSet = HashSet::new(); + let mut clbits: HashSet = HashSet::new(); + for inst in self.data.iter() { + for b in self.intern_context.lookup(inst.qubits_id).iter() { + qubits.insert(*b); + } + for b in self.intern_context.lookup(inst.clbits_id).iter() { + clbits.insert(*b); + } + } + + ( + PyList::new( + py, + qubits + .into_iter() + .map(|b| self.qubits_native[b as usize].clone_ref(py)), + ), + PyList::new( + py, + clbits + .into_iter() + .map(|b| self.clbits_native[b as usize].clone_ref(py)), + ), + ) + .into_py(py) + } + + /// Invokes callable ``func`` with each instruction's operation. + /// + /// Args: + /// func (Callable[[:class:`~.Operation`], None]): + /// The callable to invoke. + #[pyo3(signature = (func))] + pub fn foreach_op(&self, py: Python<'_>, func: &PyAny) -> PyResult<()> { + for inst in self.data.iter() { + func.call1((inst.op.as_ref(py),))?; + } + Ok(()) + } + + /// Invokes callable ``func`` with each instruction's operation, + /// replacing the operation with the result. + /// + /// Args: + /// func (Callable[[:class:`~.Operation`], :class:`~.Operation`]): + /// A callable used to map original operation to their + /// replacements. + #[pyo3(signature = (func))] + pub fn replace_ops(&mut self, py: Python<'_>, func: &PyAny) -> PyResult<()> { + for inst in self.data.iter_mut() { + inst.op = func.call1((inst.op.as_ref(py),))?.into_py(py); + } + Ok(()) + } + + /// Replaces the bits of this container with the given ``qubits`` + /// and/or ``clbits``. + /// + /// The `:attr:`~.CircuitInstruction.qubits` and + /// :attr:`~.CircuitInstruction.clbits` of existing instructions are + /// reinterpreted using the new bit sequences on access. + /// As such, the primary use-case for this method is to remap a circuit to + /// a different set of bits in O(1) time. + /// + /// The lists :attr:`.CircuitData.qubits` and :attr:`.CircuitData.clbits` + /// are NOT modified in place; they are replaced by reference. The + /// previous lists are not kept in sync after the replacement. + /// + /// Args: + /// qubits (Iterable[:class:`.Qubit] | None): + /// The qubit sequence which should replace the container's + /// existing qubits, or ``None`` to skip replacement. + /// clbits (Iterable[:class:`.Clbit] | None): + /// The clbit sequence which should replace the container's + /// existing qubits, or ``None`` to skip replacement. + /// + /// Raises: + /// ValueError: A replacement sequence is smaller than the bit list + /// its contents would replace. + /// + /// .. note:: + /// + /// Instruction operations themselves are NOT adjusted. + /// To modify bits referenced by an operation, use + /// :meth:`~.CircuitData.foreach_op` or + /// :meth:`~.CircuitData.replace_ops` to adjust the operations manually + /// after calling this method. + /// + /// Examples: + /// + /// The following :class:`.CircuitData` is reinterpreted as if its bits + /// were originally added in reverse. + /// + /// .. code-block:: + /// + /// qr = QuantumRegister(3) + /// data = CircuitData(qubits=qr, data=[ + /// CircuitInstruction(XGate(), [qr[0]], []), + /// CircuitInstruction(XGate(), [qr[1]], []), + /// CircuitInstruction(XGate(), [qr[2]], []), + /// ]) + /// + /// data.replace_bits(qubits=reversed(qr)) + /// assert(data == [ + /// CircuitInstruction(XGate(), [qr[2]], []), + /// CircuitInstruction(XGate(), [qr[1]], []), + /// CircuitInstruction(XGate(), [qr[0]], []), + /// ]) + #[pyo3(signature = (qubits=None, clbits=None))] + pub fn replace_bits( + &mut self, + py: Python<'_>, + qubits: Option<&PyAny>, + clbits: Option<&PyAny>, + ) -> PyResult<()> { + let mut temp = CircuitData::new(py, qubits, clbits, None, 0)?; + if temp.qubits_native.len() < self.qubits_native.len() { + return Err(PyValueError::new_err(format!( + "Replacement 'qubits' of size {:?} must contain at least {:?} bits.", + temp.qubits_native.len(), + self.qubits_native.len(), + ))); + } + if temp.clbits_native.len() < self.clbits_native.len() { + return Err(PyValueError::new_err(format!( + "Replacement 'clbits' of size {:?} must contain at least {:?} bits.", + temp.clbits_native.len(), + self.clbits_native.len(), + ))); + } + if qubits.is_some() { + std::mem::swap(&mut temp.qubits, &mut self.qubits); + std::mem::swap(&mut temp.qubits_native, &mut self.qubits_native); + std::mem::swap( + &mut temp.qubit_indices_native, + &mut self.qubit_indices_native, + ); + } + if clbits.is_some() { + std::mem::swap(&mut temp.clbits, &mut self.clbits); + std::mem::swap(&mut temp.clbits_native, &mut self.clbits_native); + std::mem::swap( + &mut temp.clbit_indices_native, + &mut self.clbit_indices_native, + ); + } + Ok(()) + } + pub fn __len__(&self) -> usize { self.data.len() } @@ -449,10 +608,43 @@ impl CircuitData { } // To prevent the entire iterator from being loaded into memory, - // we create a `GILPool` for each iteration of the loop, which + // this function creates a `GILPool` for each iteration of the loop, which // ensures that the `CircuitInstruction` returned by the call // to `next` is dropped before the next iteration. pub fn extend(&mut self, py: Python<'_>, itr: &PyAny) -> PyResult<()> { + if let Ok(other) = itr.extract::>() { + // Fast path to avoid unnecessary construction of + // CircuitInstruction instances. + self.data.reserve(other.data.len()); + for inst in other.data.iter() { + let qubits = other + .intern_context + .lookup(inst.qubits_id) + .iter() + .map(|b| { + Ok(self.qubit_indices_native + [&BitAsKey::new(other.qubits_native[*b as usize].as_ref(py))?]) + }) + .collect::>>()?; + let clbits = other + .intern_context + .lookup(inst.clbits_id) + .iter() + .map(|b| { + Ok(self.clbit_indices_native + [&BitAsKey::new(other.clbits_native[*b as usize].as_ref(py))?]) + }) + .collect::>>()?; + + self.data.push(PackedInstruction { + op: inst.op.clone_ref(py), + qubits_id: self.intern_context.intern(qubits)?, + clbits_id: self.intern_context.intern(clbits)?, + }); + } + return Ok(()); + } + // To ensure proper lifetime management, we explicitly store // the result of calling `iter(itr)` as a GIL-independent // reference that we access only with the most recent GILPool. diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index c8ada706e5fe..0021ac22699c 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -21,8 +21,9 @@ import abc import itertools import typing -from typing import Callable, Collection, Iterable, List, FrozenSet, Tuple, Union, Optional +from typing import Callable, Collection, Iterable, FrozenSet, Tuple, Union, Optional +from qiskit._accelerate.quantum_circuit import CircuitData from qiskit.circuit.classicalregister import Clbit, ClassicalRegister from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.instruction import Instruction @@ -200,8 +201,6 @@ class ControlFlowBuilderBlock: __slots__ = ( "instructions", - "qubits", - "clbits", "registers", "global_phase", "_allow_jumps", @@ -251,9 +250,7 @@ def __init__( position where no instructions should be accepted, such as when inside a ``switch`` but outside any cases. """ - self.instructions: List[CircuitInstruction] = [] - self.qubits = set(qubits) - self.clbits = set(clbits) + self.instructions: CircuitData = CircuitData(qubits, clbits) self.registers = set(registers) self.global_phase = 0.0 self._allow_jumps = allow_jumps @@ -261,6 +258,16 @@ def __init__( self._built = False self._forbidden_message = forbidden_message + @property + def qubits(self): + """The set of qubits associated with this scope.""" + return set(self.instructions.qubits) + + @property + def clbits(self): + """The set of clbits associated with this scope.""" + return set(self.instructions.clbits) + @property def allow_jumps(self): """Whether this builder scope should allow ``break`` and ``continue`` statements within it. @@ -275,29 +282,47 @@ def allow_jumps(self): """ return self._allow_jumps + @staticmethod + def _raise_on_jump(operation): + # pylint: disable=cyclic-import + from .break_loop import BreakLoopOp, BreakLoopPlaceholder + from .continue_loop import ContinueLoopOp, ContinueLoopPlaceholder + + forbidden = (BreakLoopOp, BreakLoopPlaceholder, ContinueLoopOp, ContinueLoopPlaceholder) + if isinstance(operation, forbidden): + raise CircuitError( + f"The current builder scope cannot take a '{operation.name}'" + " because it is not in a loop." + ) + def append(self, instruction: CircuitInstruction) -> CircuitInstruction: """Add an instruction into the scope, keeping track of the qubits and clbits that have been used in total.""" if self._forbidden_message is not None: raise CircuitError(self._forbidden_message) - if not self._allow_jumps: - # pylint: disable=cyclic-import - from .break_loop import BreakLoopOp, BreakLoopPlaceholder - from .continue_loop import ContinueLoopOp, ContinueLoopPlaceholder - - forbidden = (BreakLoopOp, BreakLoopPlaceholder, ContinueLoopOp, ContinueLoopPlaceholder) - if isinstance(instruction.operation, forbidden): - raise CircuitError( - f"The current builder scope cannot take a '{instruction.operation.name}'" - " because it is not in a loop." - ) - + self._raise_on_jump(instruction.operation) + for b in instruction.qubits: + self.instructions.add_qubit(b) + for b in instruction.clbits: + self.instructions.add_clbit(b) self.instructions.append(instruction) - self.qubits.update(instruction.qubits) - self.clbits.update(instruction.clbits) return instruction + def extend(self, data: CircuitData): + """Appends all instructions from ``data`` to the scope, expanding the scope's + tracked resources with its active bits.""" + if self._forbidden_message is not None: + raise CircuitError(self._forbidden_message) + if not self._allow_jumps: + data.foreach_op(self._raise_on_jump) + qubits, clbits = data.active_bits() + for b in qubits: + self.instructions.add_qubit(b) + for b in clbits: + self.instructions.add_clbit(b) + self.instructions.extend(data) + def request_classical_resource(self, specifier): """Resolve a single classical resource specifier into a concrete resource, raising an error if the specifier is invalid, and track it as now being used in scope. @@ -355,9 +380,9 @@ def add_bits(self, bits: Iterable[Union[Qubit, Clbit]]): """ for bit in bits: if isinstance(bit, Qubit): - self.qubits.add(bit) + self.instructions.add_qubit(bit) elif isinstance(bit, Clbit): - self.clbits.add(bit) + self.instructions.add_clbit(bit) else: raise TypeError(f"Can only add qubits or classical bits, but received '{bit}'.") @@ -420,9 +445,14 @@ def build( # We start off by only giving the QuantumCircuit the qubits we _know_ it will need, and add # more later as needed. out = QuantumCircuit( - list(self.qubits), list(self.clbits), *self.registers, global_phase=self.global_phase + self.instructions.qubits, + self.instructions.clbits, + *self.registers, + global_phase=self.global_phase, ) + # TODO: this can likely be optimized with a CircuitData.foreach_op followed + # by a CircuitData.replace_bits. for instruction in self.instructions: if isinstance(instruction.operation, InstructionPlaceholder): operation, resources = instruction.operation.concrete_instruction( @@ -483,8 +513,6 @@ def copy(self) -> "ControlFlowBuilderBlock": """ out = type(self).__new__(type(self)) out.instructions = self.instructions.copy() - out.qubits = self.qubits.copy() - out.clbits = self.clbits.copy() out.registers = self.registers.copy() out.global_phase = self.global_phase out._allow_jumps = self._allow_jumps diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index d968d3dc1cc4..12a70edef3cb 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -516,12 +516,12 @@ def __deepcopy__(self, memo=None): # Avoids pulling self._data into a Python list # like we would when pickling. - result._data = CircuitData( - copy.deepcopy(self._data.qubits, memo), - copy.deepcopy(self._data.clbits, memo), - (i.replace(operation=copy.deepcopy(i.operation, memo)) for i in self._data), - reserve=len(self._data), + result._data = self._data.copy() + result._data.replace_bits( + qubits=copy.deepcopy(self._data.qubits, memo), + clbits=copy.deepcopy(self._data.clbits, memo), ) + result._data.replace_ops(lambda op: copy.deepcopy(op, memo)) return result @classmethod @@ -905,6 +905,8 @@ def compose( ) dest = self if inplace else self.copy() + dest.duration = None + dest.unit = "dt" # As a special case, allow composing some clbits onto no clbits - normally the destination # has to be strictly larger. This allows composing final measurements onto unitary circuits. @@ -935,18 +937,33 @@ def compose( dest._append(instruction) else: dest.append(other, qargs=qubits, cargs=clbits) - if inplace: - return None - return dest + return None if inplace else dest if other.num_qubits > dest.num_qubits or other.num_clbits > dest.num_clbits: raise CircuitError( "Trying to compose with another QuantumCircuit which has more 'in' edges." ) - # number of qubits and clbits must match number in circuit or None + for gate, cals in other.calibrations.items(): + dest._calibrations[gate].update(cals) + + dest.global_phase += other.global_phase + + if not other.data: + # Nothing left to do. Plus, accessing 'data' here is necessary + # to trigger any lazy building since we now access '_data' + # directly. + return None if inplace else dest + + # The 'qubits' and 'clbits' used for 'dest'. + mapped_qubits: list[Qubit] + mapped_clbits: list[Clbit] + + # Maps bits in 'other' to bits in 'dest'. Used only for + # adjusting bits in variables (e.g. condition and target). edge_map: dict[Qubit | Clbit, Qubit | Clbit] = {} if qubits is None: + mapped_qubits = dest.qubits edge_map.update(zip(other.qubits, dest.qubits)) else: mapped_qubits = dest.qbit_argument_conversion(qubits) @@ -958,6 +975,7 @@ def compose( edge_map.update(zip(other.qubits, mapped_qubits)) if clbits is None: + mapped_clbits = dest.clbits edge_map.update(zip(other.clbits, dest.clbits)) else: mapped_clbits = dest.cbit_argument_conversion(clbits) @@ -971,34 +989,40 @@ def compose( variable_mapper = _classical_resource_map.VariableMapper( dest.cregs, edge_map, dest.add_register ) - mapped_instrs: CircuitData = CircuitData(dest.qubits, dest.clbits, reserve=len(other.data)) - for instr in other.data: - n_qargs: list[Qubit] = [edge_map[qarg] for qarg in instr.qubits] - n_cargs: list[Clbit] = [edge_map[carg] for carg in instr.clbits] - n_op = instr.operation.copy() + + def map_vars(op): + # TODO: do we really need to copy if we're not updating vars? + n_op = op.copy() 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.target = variable_mapper.map_target(n_op.target) - mapped_instrs.append(CircuitInstruction(n_op, n_qargs, n_cargs)) + return n_op - if front: - # adjust new instrs before original ones and update all parameters - mapped_instrs.extend(dest._data) - dest.clear() - append = dest._control_flow_scopes[-1].append if dest._control_flow_scopes else dest._append - for instr in mapped_instrs: - append(instr) + mapped_instrs: CircuitData = other._data.copy() + mapped_instrs.replace_bits(qubits=mapped_qubits, clbits=mapped_clbits) + mapped_instrs.replace_ops(map_vars) - for gate, cals in other.calibrations.items(): - dest._calibrations[gate].update(cals) + if dest._control_flow_scopes: + dest._control_flow_scopes[-1].extend(mapped_instrs) + else: + append_existing = None + if front: + append_existing = dest._data.copy() + dest.clear() - dest.global_phase += other.global_phase + def update_param(op): + if isinstance(op, Instruction): + dest._update_parameter_table(op) - if inplace: - return None + dest._data.extend(mapped_instrs) + mapped_instrs.foreach_op(update_param) - return dest + if append_existing: + dest._data.extend(append_existing) + append_existing.foreach_op(update_param) + + return None if inplace else dest def tensor(self, other: "QuantumCircuit", inplace: bool = False) -> Optional["QuantumCircuit"]: """Tensor ``self`` with ``other``. @@ -1379,15 +1403,15 @@ def _append( instruction = CircuitInstruction(instruction, qargs, cargs) self._data.append(instruction) if isinstance(instruction.operation, Instruction): - self._update_parameter_table(instruction) + self._update_parameter_table(instruction.operation) # mark as normal circuit if a new instruction is added self.duration = None self.unit = "dt" return instruction.operation if old_style else instruction - def _update_parameter_table(self, instruction: CircuitInstruction): - for param_index, param in enumerate(instruction.operation.params): + def _update_parameter_table(self, instruction: Instruction): + for param_index, param in enumerate(instruction.params): if isinstance(param, (ParameterExpression, QuantumCircuit)): # Scoped constructs like the control-flow ops use QuantumCircuit as a parameter. atomic_parameters = set(param.parameters) @@ -1396,12 +1420,12 @@ def _update_parameter_table(self, instruction: CircuitInstruction): for parameter in atomic_parameters: if parameter in self._parameter_table: - self._parameter_table[parameter].add((instruction.operation, param_index)) + self._parameter_table[parameter].add((instruction, param_index)) else: if parameter.name in self._parameter_table.get_names(): raise CircuitError(f"Name conflict on adding parameter: {parameter.name}") self._parameter_table[parameter] = ParameterReferences( - ((instruction.operation, param_index),) + ((instruction, param_index),) ) # clear cache if new parameter is added @@ -2106,10 +2130,10 @@ def copy(self, name: str | None = None) -> "QuantumCircuit": QuantumCircuit: a deepcopy of the current circuit, with the specified name """ cpy = self.copy_empty_like(name) + cpy._data = self._data.copy() - operation_copies = { - id(instruction.operation): instruction.operation.copy() for instruction in self._data - } + operation_copies = {} + self._data.foreach_op(lambda op: operation_copies.update({id(op): op.copy()})) cpy._parameter_table = ParameterTable( { @@ -2121,12 +2145,7 @@ def copy(self, name: str | None = None) -> "QuantumCircuit": } ) - cpy._data.reserve(len(self._data)) - cpy._data.extend( - instruction.replace(operation=operation_copies[id(instruction.operation)]) - for instruction in self._data - ) - + cpy._data.replace_ops(lambda op: operation_copies[id(op)]) return cpy def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit": diff --git a/qiskit/circuit/quantumcircuitdata.py b/qiskit/circuit/quantumcircuitdata.py index 9500cdf9425d..9808f461eea6 100644 --- a/qiskit/circuit/quantumcircuitdata.py +++ b/qiskit/circuit/quantumcircuitdata.py @@ -46,7 +46,7 @@ def __setitem__(self, key, value): value = self._resolve_legacy_value(operation, qargs, cargs) self._circuit._data[key] = value if isinstance(value.operation, Instruction): - self._circuit._update_parameter_table(value) + self._circuit._update_parameter_table(value.operation) def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction: """Resolve the old-style 3-tuple into the new :class:`CircuitInstruction` type.""" diff --git a/qiskit/converters/circuit_to_instruction.py b/qiskit/converters/circuit_to_instruction.py index 58323e07d0f6..4b96c6b6b446 100644 --- a/qiskit/converters/circuit_to_instruction.py +++ b/qiskit/converters/circuit_to_instruction.py @@ -11,7 +11,7 @@ # that they have been altered from the originals. """Helper function for converting a circuit to an instruction.""" - +from qiskit.circuit.parametertable import ParameterTable, ParameterReferences from qiskit.exceptions import QiskitError from qiskit.circuit.instruction import Instruction from qiskit.circuit.quantumregister import QuantumRegister @@ -89,6 +89,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None equivalence_library.add_equivalence(out_instruction, target) regs = [] + q, c = None, None if out_instruction.num_qubits > 0: q = QuantumRegister(out_instruction.num_qubits, "q") regs.append(q) @@ -97,31 +98,41 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None c = ClassicalRegister(out_instruction.num_clbits, "c") regs.append(c) - qubit_map = {bit: q[idx] for idx, bit in enumerate(circuit.qubits)} clbit_map = {bit: c[idx] for idx, bit in enumerate(circuit.clbits)} + operation_map = {} - qc = QuantumCircuit(*regs, name=out_instruction.name) - qc._data.reserve(len(target.data)) - for instruction in target._data: - rule = instruction.replace( - qubits=[qubit_map[y] for y in instruction.qubits], - clbits=[clbit_map[y] for y in instruction.clbits], - ) - - # fix condition - condition = getattr(rule.operation, "condition", None) + def fix_condition(op): + original_id = id(op) + condition = getattr(op, "condition", None) if condition: reg, val = condition if isinstance(reg, Clbit): - rule = rule.replace(operation=rule.operation.c_if(clbit_map[reg], val)) + op = op.c_if(clbit_map[reg], val) elif reg.size == c.size: - rule = rule.replace(operation=rule.operation.c_if(c, val)) + op = op.c_if(c, val) else: raise QiskitError( "Cannot convert condition in circuit with " "multiple classical registers to instruction" ) - qc._append(rule) + operation_map[original_id] = op + return op + + data = target._data.copy() + data.replace_bits(qubits=q, clbits=c) + data.replace_ops(fix_condition) + + qc = QuantumCircuit(*regs, name=out_instruction.name) + qc._data = data + qc._parameter_table = ParameterTable( + { + param: ParameterReferences( + (operation_map[id(operation)], param_index) + for operation, param_index in target._parameter_table[param] + ) + for param in target._parameter_table + } + ) if circuit.global_phase: qc.global_phase = circuit.global_phase diff --git a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py index 44394b874618..2aee4c0bdb9c 100644 --- a/qiskit/opflow/gradients/circuit_gradients/lin_comb.py +++ b/qiskit/opflow/gradients/circuit_gradients/lin_comb.py @@ -590,8 +590,8 @@ def apply_grad_gate( # possible, to avoid introducing further complications, but this whole method # accesses internal attributes of `QuantumCircuit` and needs rewriting. # - Jake Lishman, 2022-03-02. - out._update_parameter_table(CircuitInstruction(rz_plus, (gate_qubits[0],), ())) - out._update_parameter_table(CircuitInstruction(rz_minus, (gate_qubits[0],), ())) + out._update_parameter_table(rz_plus) + out._update_parameter_table(rz_minus) if open_ctrl: replacement.append(CircuitInstruction(XGate(), qr_superpos_qubits, ()))