Skip to content

Commit

Permalink
Adding Cliffords to QuantumCircuits as Operations (#7966)
Browse files Browse the repository at this point in the history
* Adding Clifford to a QuantumCircuit natively; adding some tests; adding a preliminary Clifford optimization pass

* Making Instruction.condition into a property

* improving tests

* adding release notes

* A few changes based on review

* Removing definition from Operation interface. Instead adding
HighLevelSynthesis transpiler pass, and incorporating it into the preset
pass managers

* minor lint fix

* moving all of broadcast functionality to a separate file; removing broadcast from Operation interface and in particular from Clifford

* moving HighLevelSynthesis pass from Decompose to QuantumCircuit.decompose; slightly changing Decompose pass to skip nodes with definition attribute

* Fixing broadcasting for Cliffords

* making sure that transpile decomposes cliffords

* lint

* As per code review, replacing x._direction by getattr(x, '_direction', False)

* Removing condition from Operation API.

Removing previously added getter and setter to instruction.py.

As per code review, starting to replace condition getters and setters.
For now, only dagcircuit and circuit_to_instruction.

* pass over condition in transpiler code

* more refactoring of condition

* finishing condition pass

* minor fixes

* adding OptimizeClifford pass to __init__

* Improving release notes

* considering DAG nodes in topological order; adding a simple test to see this

* typo

* release notes fixes

* Adding TODO comment to HighLevelSynthesis pass

* another attempt to fix docs build

* Fix based on code review

* Only construction Operator from Instruction (as before) and from Clifford (for backward compatibility)
  • Loading branch information
alexanderivrii authored Aug 10, 2022
1 parent dadacb8 commit 19fdd32
Show file tree
Hide file tree
Showing 43 changed files with 738 additions and 112 deletions.
4 changes: 3 additions & 1 deletion qiskit/assembler/assemble_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ def _assemble_circuit(
# their clbit_index, create a new register slot for every conditional gate
# and add a bfunc to map the creg=val mask onto the gating register bit.

is_conditional_experiment = any(instruction.operation.condition for instruction in circuit.data)
is_conditional_experiment = any(
getattr(instruction.operation, "condition", None) for instruction in circuit.data
)
max_conditional_idx = 0

instructions = []
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
Delay
Instruction
InstructionSet
Operation
EquivalenceLibrary
Control Flow Operations
Expand Down Expand Up @@ -232,6 +233,7 @@
from .controlledgate import ControlledGate
from .instruction import Instruction
from .instructionset import InstructionSet
from .operation import Operation
from .barrier import Barrier
from .delay import Delay
from .measure import Measure
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/controlflow/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def build(
# a register is already present, so we use our own tracking.
self.add_register(register)
out.add_register(register)
if instruction.operation.condition is not None:
if getattr(instruction.operation, "condition", None) is not None:
for register in condition_registers(instruction.operation.condition):
if register not in self.registers:
self.add_register(register)
Expand Down
3 changes: 2 additions & 1 deletion qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
from qiskit.qobj.qasm_qobj import QasmQobjInstruction
from qiskit.circuit.parameter import ParameterExpression
from qiskit.circuit.operation import Operation
from .tools import pi_check

_CUTOFF_PRECISION = 1e-10


class Instruction:
class Instruction(Operation):
"""Generic quantum instruction."""

# Class attribute to treat like barrier for transpiler, unroller, drawer
Expand Down
6 changes: 3 additions & 3 deletions qiskit/circuit/instructionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from typing import Callable, Optional, Tuple, Union

from qiskit.circuit.exceptions import CircuitError
from .instruction import Instruction
from .classicalregister import Clbit, ClassicalRegister
from .operation import Operation
from .quantumcircuitdata import CircuitInstruction


Expand Down Expand Up @@ -150,8 +150,8 @@ def __getitem__(self, i):
def add(self, instruction, qargs=None, cargs=None):
"""Add an instruction and its context (where it is attached)."""
if not isinstance(instruction, CircuitInstruction):
if not isinstance(instruction, Instruction):
raise CircuitError("attempt to add non-Instruction to InstructionSet")
if not isinstance(instruction, Operation):
raise CircuitError("attempt to add non-Operation to InstructionSet")
if qargs is None or cargs is None:
raise CircuitError("missing qargs or cargs in old-style InstructionSet.add")
instruction = CircuitInstruction(instruction, tuple(qargs), tuple(cargs))
Expand Down
80 changes: 53 additions & 27 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from .parametertable import ParameterReferences, ParameterTable, ParameterView
from .parametervector import ParameterVector, ParameterVectorElement
from .instructionset import InstructionSet
from .operation import Operation
from .register import Register
from .bit import Bit
from .quantumcircuitdata import QuantumCircuitData, CircuitInstruction
Expand Down Expand Up @@ -947,7 +948,7 @@ def compose(
n_cargs = [edge_map[carg] for carg in instr.clbits]
n_instr = instr.operation.copy()

if instr.operation.condition is not None:
if getattr(instr.operation, "condition", None) is not None:
from qiskit.dagcircuit import DAGCircuit # pylint: disable=cyclic-import

n_instr.condition = DAGCircuit._map_condition(
Expand Down Expand Up @@ -1216,7 +1217,7 @@ def _resolve_classical_resource(self, specifier):

def append(
self,
instruction: Union[Instruction, CircuitInstruction],
instruction: Union[Operation, CircuitInstruction],
qargs: Optional[Sequence[QubitSpecifier]] = None,
cargs: Optional[Sequence[ClbitSpecifier]] = None,
) -> InstructionSet:
Expand Down Expand Up @@ -1252,20 +1253,20 @@ def append(
else:
operation = instruction
# Convert input to instruction
if not isinstance(operation, Instruction) and not hasattr(operation, "to_instruction"):
if issubclass(operation, Instruction):
if not isinstance(operation, Operation) and not hasattr(operation, "to_instruction"):
if issubclass(operation, Operation):
raise CircuitError(
"Object is a subclass of Instruction, please add () to "
"Object is a subclass of Operation, please add () to "
"pass an instance of this object."
)

raise CircuitError(
"Object to append must be an Instruction or have a to_instruction() method."
"Object to append must be an Operation or have a to_instruction() method."
)
if not isinstance(operation, Instruction) and hasattr(operation, "to_instruction"):
if not isinstance(operation, Operation) and hasattr(operation, "to_instruction"):
operation = operation.to_instruction()
if not isinstance(operation, Instruction):
raise CircuitError("object is not an Instruction.")
if not isinstance(operation, Operation):
raise CircuitError("object is not an Operation.")

# Make copy of parameterized gate instances
if hasattr(operation, "params"):
Expand All @@ -1283,11 +1284,21 @@ def append(
appender = self._append
requester = self._resolve_classical_resource
instructions = InstructionSet(resource_requester=requester)
for qarg, carg in operation.broadcast_arguments(expanded_qargs, expanded_cargs):
self._check_dups(qarg)
instruction = CircuitInstruction(operation, qarg, carg)
appender(instruction)
instructions.add(instruction)
if isinstance(operation, Instruction):
for qarg, carg in operation.broadcast_arguments(expanded_qargs, expanded_cargs):
self._check_dups(qarg)
instruction = CircuitInstruction(operation, qarg, carg)
appender(instruction)
instructions.add(instruction)
else:
# For Operations that are non-Instructions, we use the Instruction's default method
for qarg, carg in Instruction.broadcast_arguments(
operation, expanded_qargs, expanded_cargs
):
self._check_dups(qarg)
instruction = CircuitInstruction(operation, qarg, carg)
appender(instruction)
instructions.add(instruction)
return instructions

# Preferred new style.
Expand All @@ -1301,10 +1312,10 @@ def _append(
@typing.overload
def _append(
self,
operation: Instruction,
operation: Operation,
qargs: Sequence[Qubit],
cargs: Sequence[Clbit],
) -> Instruction:
) -> Operation:
...

def _append(self, instruction, qargs=None, cargs=None):
Expand All @@ -1328,20 +1339,22 @@ def _append(self, instruction, qargs=None, cargs=None):
constructs of the control-flow builder interface.
Args:
instruction: Instruction instance to append
instruction: Operation instance to append
qargs: Qubits to attach the instruction to.
cargs: Clbits to attach the instruction to.
Returns:
Instruction: a handle to the instruction that was just added
Operation: a handle to the instruction that was just added
:meta public:
"""
old_style = not isinstance(instruction, CircuitInstruction)
if old_style:
instruction = CircuitInstruction(instruction, qargs, cargs)
self._data.append(instruction)
self._update_parameter_table(instruction)
if isinstance(instruction.operation, Instruction):
self._update_parameter_table(instruction)

# mark as normal circuit if a new instruction is added
self.duration = None
self.unit = "dt"
Expand Down Expand Up @@ -1567,11 +1580,13 @@ def decompose(
"""
# pylint: disable=cyclic-import
from qiskit.transpiler.passes.basis.decompose import Decompose
from qiskit.transpiler.passes.synthesis import HighLevelSynthesis
from qiskit.converters.circuit_to_dag import circuit_to_dag
from qiskit.converters.dag_to_circuit import dag_to_circuit

pass_ = Decompose(gates_to_decompose)
dag = circuit_to_dag(self)
dag = HighLevelSynthesis().run(dag)
pass_ = Decompose(gates_to_decompose)
for _ in range(reps):
dag = pass_.run(dag)
return dag_to_circuit(dag)
Expand Down Expand Up @@ -1936,7 +1951,10 @@ def draw(
)

def size(
self, filter_function: Optional[callable] = lambda x: not x.operation._directive
self,
filter_function: Optional[callable] = lambda x: not getattr(
x.operation, "_directive", False
),
) -> int:
"""Returns total number of instructions in circuit.
Expand All @@ -1951,7 +1969,10 @@ def size(
return sum(map(filter_function, self._data))

def depth(
self, filter_function: Optional[callable] = lambda x: not x.operation._directive
self,
filter_function: Optional[callable] = lambda x: not getattr(
x.operation, "_directive", False
),
) -> int:
"""Return circuit depth (i.e., length of critical path).
Expand Down Expand Up @@ -2000,7 +2021,7 @@ def depth(
levels.append(op_stack[reg_ints[ind]])
# Assuming here that there is no conditional
# snapshots or barriers ever.
if instruction.operation.condition:
if getattr(instruction.operation, "condition", None):
# Controls operate over all bits of a classical register
# or over a single bit
if isinstance(instruction.operation.condition[0], Clbit):
Expand Down Expand Up @@ -2064,7 +2085,9 @@ def num_nonlocal_gates(self) -> int:
"""
multi_qubit_gates = 0
for instruction in self._data:
if instruction.operation.num_qubits > 1 and not instruction.operation._directive:
if instruction.operation.num_qubits > 1 and not getattr(
instruction.operation, "_directive", False
):
multi_qubit_gates += 1
return multi_qubit_gates

Expand Down Expand Up @@ -2105,9 +2128,11 @@ def num_connected_components(self, unitary_only: bool = False) -> int:
num_qargs = len(args)
else:
args = instruction.qubits + instruction.clbits
num_qargs = len(args) + (1 if instruction.operation.condition else 0)
num_qargs = len(args) + (
1 if getattr(instruction.operation, "condition", None) else 0
)

if num_qargs >= 2 and not instruction.operation._directive:
if num_qargs >= 2 and not getattr(instruction.operation, "_directive", False):
graphs_touched = []
num_touched = 0
# Controls necessarily join all the cbits in the
Expand Down Expand Up @@ -4206,7 +4231,8 @@ def _pop_previous_instruction_in_scope(self) -> CircuitInstruction:
if not self._data:
raise CircuitError("This circuit contains no instructions.")
instruction = self._data.pop()
self._update_parameter_table_on_instruction_removal(instruction)
if isinstance(instruction.operation, Instruction):
self._update_parameter_table_on_instruction_removal(instruction)
return instruction

def _update_parameter_table_on_instruction_removal(self, instruction: CircuitInstruction):
Expand Down
7 changes: 6 additions & 1 deletion qiskit/circuit/quantumcircuitdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ def _resolve_legacy_value(self, operation, qargs, cargs) -> CircuitInstruction:
expanded_qargs = [self._circuit.qbit_argument_conversion(qarg) for qarg in qargs or []]
expanded_cargs = [self._circuit.cbit_argument_conversion(carg) for carg in cargs or []]

broadcast_args = list(operation.broadcast_arguments(expanded_qargs, expanded_cargs))
if isinstance(operation, Instruction):
broadcast_args = list(operation.broadcast_arguments(expanded_qargs, expanded_cargs))
else:
broadcast_args = list(
Instruction.broadcast_arguments(operation, expanded_qargs, expanded_cargs)
)

if len(broadcast_args) > 1:
raise CircuitError(
Expand Down
2 changes: 1 addition & 1 deletion qiskit/converters/circuit_to_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def circuit_to_instruction(circuit, parameter_map=None, equivalence_library=None

# fix condition
for rule in definition:
condition = rule.operation.condition
condition = getattr(rule.operation, "condition", None)
if condition:
reg, val = condition
if isinstance(reg, Clbit):
Expand Down
Loading

0 comments on commit 19fdd32

Please sign in to comment.