Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change data type of QuantumCircuit.data elements #8093

Merged
merged 17 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
Clbit
AncillaRegister
AncillaQubit
CircuitInstruction

Gates and Instructions
----------------------
Expand Down Expand Up @@ -238,6 +239,7 @@
from .parameter import Parameter
from .parametervector import ParameterVector
from .parameterexpression import ParameterExpression
from .quantumcircuitdata import CircuitInstruction
from .equivalence import EquivalenceLibrary
from .classicalfunction.types import Int1, Int2
from .classicalfunction import classical_function, BooleanExpression
Expand Down
52 changes: 22 additions & 30 deletions qiskit/circuit/controlflow/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from qiskit.circuit.classicalregister import Clbit, ClassicalRegister
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.quantumcircuitdata import CircuitInstruction
from qiskit.circuit.quantumregister import Qubit, QuantumRegister
from qiskit.circuit.register import Register

Expand Down Expand Up @@ -238,7 +239,7 @@ def __init__(
here, and the documentation of :obj:`.InstructionSet`, which uses this same
callback.
"""
self.instructions: List[Tuple[Instruction, Tuple[Qubit, ...], Tuple[Clbit, ...]]] = []
self.instructions: List[CircuitInstruction] = []
self.qubits = set(qubits)
self.clbits = set(clbits)
self.registers = set(registers)
Expand All @@ -260,12 +261,7 @@ def allow_jumps(self):
"""
return self._allow_jumps

def append(
self,
operation: Instruction,
qubits: Iterable[Qubit],
clbits: Iterable[Clbit],
) -> Instruction:
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 not self._allow_jumps:
Expand All @@ -274,18 +270,16 @@ def append(
from .continue_loop import ContinueLoopOp, ContinueLoopPlaceholder

forbidden = (BreakLoopOp, BreakLoopPlaceholder, ContinueLoopOp, ContinueLoopPlaceholder)
if isinstance(operation, forbidden):
if isinstance(instruction.operation, forbidden):
raise CircuitError(
f"The current builder scope cannot take a '{operation.name}'"
f"The current builder scope cannot take a '{instruction.operation.name}'"
" because it is not in a loop."
)

qubits = tuple(qubits)
clbits = tuple(clbits)
self.instructions.append((operation, qubits, clbits))
self.qubits.update(qubits)
self.clbits.update(clbits)
return operation
self.instructions.append(instruction)
self.qubits.update(instruction.qubits)
self.clbits.update(instruction.clbits)
return instruction

def request_classical_resource(self, specifier):
"""Resolve a single classical resource specifier into a concrete resource, raising an error
Expand Down Expand Up @@ -314,19 +308,18 @@ def request_classical_resource(self, specifier):
self.add_register(resource)
return resource

def peek(self) -> Tuple[Instruction, Tuple[Qubit, ...], Tuple[Clbit, ...]]:
def peek(self) -> CircuitInstruction:
"""Get the value of the most recent instruction tuple in this scope."""
if not self.instructions:
raise CircuitError("This scope contains no instructions.")
return self.instructions[-1]

def pop(self) -> Tuple[Instruction, Tuple[Qubit, ...], Tuple[Clbit, ...]]:
"""Get the value of the most recent instruction tuple in this scope, and remove it from this
def pop(self) -> CircuitInstruction:
"""Get the value of the most recent instruction in this scope, and remove it from this
object."""
if not self.instructions:
raise CircuitError("This scope contains no instructions.")
operation, qubits, clbits = self.instructions.pop()
return (operation, qubits, clbits)
return self.instructions.pop()

def add_bits(self, bits: Iterable[Union[Qubit, Clbit]]):
"""Add extra bits to this scope that are not associated with any concrete instruction yet.
Expand Down Expand Up @@ -407,11 +400,14 @@ def build(
# more later as needed.
out = QuantumCircuit(list(self.qubits), list(self.clbits), *self.registers)

for operation, qubits, clbits in self.instructions:
if isinstance(operation, InstructionPlaceholder):
operation, resources = operation.concrete_instruction(all_qubits, all_clbits)
for instruction in self.instructions:
if isinstance(instruction.operation, InstructionPlaceholder):
operation, resources = instruction.operation.concrete_instruction(
all_qubits, all_clbits
)
qubits = tuple(resources.qubits)
clbits = tuple(resources.clbits)
instruction = CircuitInstruction(operation, qubits, clbits)
# We want to avoid iterating over the tuples unnecessarily if there's no chance
# we'll need to add bits to the circuit.
if potential_qubits and qubits:
Expand All @@ -430,19 +426,15 @@ def build(
# a register is already present, so we use our own tracking.
self.add_register(register)
out.add_register(register)
if operation.condition is not None:
for register in condition_registers(operation.condition):
if instruction.operation.condition is not None:
for register in condition_registers(instruction.operation.condition):
if register not in self.registers:
self.add_register(register)
out.add_register(register)
# We already did the broadcasting and checking when the first call to
# QuantumCircuit.append happened (which the user wrote), and added the instruction into
# this scope. We just need to finish the job now.
#
# We have to convert to lists, because some parts of QuantumCircuit still expect
# exactly this type.
out._append(operation, list(qubits), list(clbits))

out._append(instruction)
return out

def copy(self) -> "ControlFlowBuilderBlock":
Expand Down
52 changes: 23 additions & 29 deletions qiskit/circuit/controlflow/if_else.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,13 @@ class ElseContext:
Terra.
"""

__slots__ = ("_if_block", "_if_clbits", "_if_registers", "_if_context", "_if_qubits", "_used")
__slots__ = ("_if_instruction", "_if_registers", "_if_context", "_used")

def __init__(self, if_context: IfContext):
# We want to avoid doing any processing until we're actually used, because the `if` block
# likely isn't finished yet, and we want to have as small a penalty a possible if you don't
# use an `else` branch.
self._if_block = None
self._if_qubits = None
self._if_clbits = None
self._if_instruction = None
self._if_registers = None
self._if_context = if_context
self._used = False
Expand All @@ -440,26 +438,22 @@ def __enter__(self):
# I'm not even sure how you'd get this to trigger, but just in case...
raise CircuitError("Cannot attach an 'else' to a broadcasted 'if' block.")
appended = appended_instructions[0]
operation, _, _ = circuit._peek_previous_instruction_in_scope()
if appended is not operation:
instruction = circuit._peek_previous_instruction_in_scope()
if appended is not instruction:
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
raise CircuitError(
"The 'if' block is not the most recent instruction in the circuit."
f" Expected to find: {appended!r}, but instead found: {operation!r}."
f" Expected to find: {appended!r}, but instead found: {instruction!r}."
)
(
self._if_block,
self._if_qubits,
self._if_clbits,
) = circuit._pop_previous_instruction_in_scope()
if isinstance(self._if_block, IfElseOp):
self._if_registers = set(self._if_block.blocks[0].cregs).union(
self._if_block.blocks[0].qregs
self._if_instruction = circuit._pop_previous_instruction_in_scope()
if isinstance(self._if_instruction.operation, IfElseOp):
self._if_registers = set(self._if_instruction.operation.blocks[0].cregs).union(
self._if_instruction.operation.blocks[0].qregs
)
else:
self._if_registers = self._if_block.registers()
self._if_registers = self._if_instruction.operation.registers()
circuit._push_scope(
self._if_qubits,
self._if_clbits,
self._if_instruction.qubits,
self._if_instruction.clbits,
registers=self._if_registers,
allow_jumps=self._if_context.in_loop,
)
Expand All @@ -472,32 +466,32 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# manager, assuming nothing else untoward happened to the circuit, but that's checked by
# the __enter__ method.
circuit._pop_scope()
circuit.append(self._if_block, self._if_qubits, self._if_clbits)
circuit._append(self._if_instruction)
self._used = False
return False

false_block = circuit._pop_scope()
# `if_block` is a placeholder if this context is in a loop, and a concrete instruction if it
# is not.
if isinstance(self._if_block, IfElsePlaceholder):
if_block = self._if_block.with_false_block(false_block)
resources = if_block.placeholder_resources()
circuit.append(if_block, resources.qubits, resources.clbits)
if isinstance(self._if_instruction.operation, IfElsePlaceholder):
if_operation = self._if_instruction.operation.with_false_block(false_block)
resources = if_operation.placeholder_resources()
circuit.append(if_operation, resources.qubits, resources.clbits)
else:
# In this case, we need to update both true_body and false_body to have exactly the same
# widths. Passing extra resources to `ControlFlowBuilderBlock.build` doesn't _compel_
# the resulting object to use them (because it tries to be minimal), so it's best to
# pass it nothing extra (allows some fast path constructions), and add all necessary
# bits onto the circuits at the end.
true_body = self._if_block.blocks[0]
true_body = self._if_instruction.operation.blocks[0]
false_body = false_block.build(false_block.qubits, false_block.clbits)
true_body, false_body = _unify_circuit_resources(true_body, false_body)
circuit.append(
IfElseOp(
self._if_context.condition,
true_body,
false_body,
label=self._if_block.label,
label=self._if_instruction.operation.label,
),
tuple(true_body.qubits),
tuple(true_body.clbits),
Expand Down Expand Up @@ -575,11 +569,11 @@ def _unify_circuit_resources_rebuild( # pylint: disable=invalid-name # (it's t
clbits = list(set(true_body.clbits).union(false_body.clbits))
# We use the inner `_append` method because everything is already resolved.
true_out = QuantumCircuit(qubits, clbits, *true_body.qregs, *true_body.cregs)
for data in true_body.data:
true_out._append(*data)
for instruction in true_body.data:
true_out._append(instruction)
false_out = QuantumCircuit(qubits, clbits, *false_body.qregs, *false_body.cregs)
for data in false_body.data:
false_out._append(*data)
for instruction in false_body.data:
false_out._append(instruction)
return _unify_circuit_registers(true_out, false_out)


Expand Down
27 changes: 18 additions & 9 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,22 @@ def reverse_ops(self):
qiskit.circuit.Instruction: a new instruction with
sub-instructions reversed.
"""
from qiskit.circuit import QuantumCircuit # pylint: disable=cyclic-import

if not self._definition:
return self.copy()

reverse_inst = self.copy(name=self.name + "_reverse")
reverse_inst.definition._data = [
(inst.reverse_ops(), qargs, cargs) for inst, qargs, cargs in reversed(self._definition)
]

reversed_definition = QuantumCircuit(
self._definition.qubits,
self._definition.clbits,
*self._definition.qregs,
*self._definition.cregs,
global_phase=self.definition.global_phase,
)
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
for inst in reversed(self._definition):
reversed_definition.append(inst.operation.reverse_ops(), inst.qubits, inst.clbits)
reverse_inst.definition = reversed_definition
return reverse_inst

def inverse(self):
Expand Down Expand Up @@ -381,15 +389,16 @@ def inverse(self):
else:
inverse_gate = Gate(name=name, num_qubits=self.num_qubits, params=self.params.copy())

inverse_gate.definition = QuantumCircuit(
inverse_definition = QuantumCircuit(
self.definition.qubits,
self.definition.clbits,
*self.definition.qregs,
*self.definition.cregs,
global_phase=-self.definition.global_phase,
)
inverse_gate.definition._data = [
(inst.inverse(), qargs, cargs) for inst, qargs, cargs in reversed(self._definition)
]

for inst in reversed(self._definition):
inverse_definition._append(inst.operation.inverse(), inst.qubits, inst.clbits)
inverse_gate.definition = inverse_definition
return inverse_gate

def c_if(self, classical, val):
Expand Down
54 changes: 38 additions & 16 deletions qiskit/circuit/instructionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from qiskit.circuit.exceptions import CircuitError
from .instruction import Instruction
from .classicalregister import Clbit, ClassicalRegister
from .quantumcircuitdata import CircuitInstruction


# ClassicalRegister is hashable, and generally the registers in a circuit are completely fixed after
Expand Down Expand Up @@ -89,7 +90,7 @@ def requester(classical):
class InstructionSet:
"""Instruction collection, and their contexts."""

__slots__ = ("instructions", "qargs", "cargs", "_requester")
__slots__ = ("_instructions", "_requester")

def __init__(self, circuit_cregs=None, *, resource_requester: Optional[Callable] = None):
"""New collection of instructions.
Expand Down Expand Up @@ -123,9 +124,7 @@ def __init__(self, circuit_cregs=None, *, resource_requester: Optional[Callable]
CircuitError: if both ``resource_requester`` and ``circuit_cregs`` are passed. Only one
of these may be passed, and it should be ``resource_requester``.
"""
self.instructions = []
self.qargs = []
self.cargs = []
self._instructions = []
if circuit_cregs is not None:
if resource_requester is not None:
raise CircuitError("Cannot pass both 'circuit_cregs' and 'resource_requester'.")
Expand All @@ -142,24 +141,26 @@ def __init__(self, circuit_cregs=None, *, resource_requester: Optional[Callable]

def __len__(self):
"""Return number of instructions in set"""
return len(self.instructions)
return len(self._instructions)

def __getitem__(self, i):
"""Return instruction at index"""
return self.instructions[i]
return self._instructions[i]

def add(self, gate, qargs, cargs):
def add(self, instruction, qargs=None, cargs=None):
"""Add an instruction and its context (where it is attached)."""
if not isinstance(gate, Instruction):
raise CircuitError("attempt to add non-Instruction" + " to InstructionSet")
self.instructions.append(gate)
self.qargs.append(qargs)
self.cargs.append(cargs)
if not isinstance(instruction, CircuitInstruction):
if not isinstance(instruction, Instruction):
raise CircuitError("attempt to add non-Instruction to InstructionSet")
if qargs is None or cargs is None:
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
raise CircuitError("missing qargs or cargs in old-style InstructionSet.add")
instruction = CircuitInstruction(instruction, tuple(qargs), tuple(cargs))
self._instructions.append(instruction)

def inverse(self):
"""Invert all instructions."""
for index, instruction in enumerate(self.instructions):
self.instructions[index] = instruction.inverse()
for i, instruction in enumerate(self._instructions):
self._instructions[i] = instruction.replace(operation=instruction.operation.inverse())
return self

def c_if(self, classical: Union[Clbit, ClassicalRegister, int], val: int) -> "InstructionSet":
Expand Down Expand Up @@ -193,6 +194,27 @@ def c_if(self, classical: Union[Clbit, ClassicalRegister, int], val: int) -> "In
)
if self._requester is not None:
classical = self._requester(classical)
for gate in self.instructions:
gate.c_if(classical, val)
for instruction in self._instructions:
instruction.operation.c_if(classical, val)
return self

# Legacy support for properties. Added in Terra 0.20 to support the internal switch in
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
# `QuantumCircuit.data` from the 3-tuple to `CircuitInstruction`.

@property
def instructions(self):
"""Legacy getter for the instruction components of an instruction set. This does not
support mutation."""
return [instruction.operation for instruction in self._instructions]

@property
def qargs(self):
"""Legacy getter for the qargs components of an instruction set. This does not support
mutation."""
return [list(instruction.qubits) for instruction in self._instructions]

@property
def cargs(self):
"""Legacy getter for the cargs components of an instruction set. This does not support
mutation."""
return [list(instruction.clbits) for instruction in self._instructions]
Loading