-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Adding Cliffords to QuantumCircuits as Operations #7966
Changes from 10 commits
068525a
cd8367b
2ab3084
dffc618
ea56a13
f3af0b6
2640f5d
568d8b8
ec342e5
c16e85f
015cbe9
e35a3d5
15ce409
d59dd7e
51aff6d
2c3b908
2a87222
14ddc7b
4949a1d
c1ab860
aeef894
9f019c2
c80efa9
4b1a246
5ae37a5
4a5f06f
34eca8c
de6110d
7a7b0f1
76acd6b
64c8de3
b2f972a
0d583ed
bdf9325
49bc436
7f525d2
d82e2e0
07733d6
ac1c874
ab213f2
fb57c9d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,3 +59,26 @@ def num_qubits(self): | |
def num_clbits(self): | ||
"""Number of classical bits.""" | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def broadcast_arguments(self, qargs, cargs): | ||
"""Expanding (broadcasting) arguments.""" | ||
raise NotImplementedError | ||
|
||
@property | ||
@abstractmethod | ||
def _directive(self): | ||
"""Class attribute to treat like barrier for transpiler, unroller, drawer.""" | ||
raise NotImplementedError | ||
|
||
@property | ||
@abstractmethod | ||
def condition(self): | ||
"""Condition for when the instruction has a conditional if.""" | ||
raise NotImplementedError | ||
|
||
@property | ||
@abstractmethod | ||
def definition(self): | ||
"""Definition of the operation in terms of more basic gates.""" | ||
raise NotImplementedError | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we want to carry this over to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently multiple transpiler passes (
only constructs the definition circuit for a single clifford in |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ | |
from .parametertable import 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 | ||
|
@@ -1160,40 +1161,40 @@ def _resolve_classical_resource(self, specifier): | |
|
||
def append( | ||
self, | ||
instruction: Instruction, | ||
instruction: Operation, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also update the docstring below, and potentially rename the argument from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have updated the docstring. Would it be possible to postpone renaming/deprecation to a small follow-up PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't issue a deprecation warning until the alternative has been in place for a version. I wouldn't worry about it, though. Perhaps a long-term solution is (once we drop Python 3.7 support) to make this first argument positional-only ( |
||
qargs: Optional[Sequence[QubitSpecifier]] = None, | ||
cargs: Optional[Sequence[ClbitSpecifier]] = None, | ||
) -> InstructionSet: | ||
"""Append one or more instructions to the end of the circuit, modifying | ||
"""Append one or more operations to the end of the circuit, modifying | ||
the circuit in place. Expands qargs and cargs. | ||
|
||
Args: | ||
instruction (qiskit.circuit.Instruction): Instruction instance to append | ||
instruction (qiskit.circuit.Operation): Operation instance to append | ||
qargs (list(argument)): qubits to attach instruction to | ||
cargs (list(argument)): clbits to attach instruction to | ||
|
||
Returns: | ||
qiskit.circuit.Instruction: a handle to the instruction that was just added | ||
qiskit.circuit.InstructionSet: a handle to the instruction that was just added | ||
|
||
Raises: | ||
CircuitError: if object passed is a subclass of Instruction | ||
CircuitError: if object passed is neither subclass nor an instance of Instruction | ||
CircuitError: if object passed is a subclass of Operation | ||
CircuitError: if object passed is neither subclass nor an instance of Operation | ||
""" | ||
# Convert input to instruction | ||
if not isinstance(instruction, Instruction) and not hasattr(instruction, "to_instruction"): | ||
if issubclass(instruction, Instruction): | ||
if not isinstance(instruction, Operation) and not hasattr(instruction, "to_instruction"): | ||
if issubclass(instruction, 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(instruction, Instruction) and hasattr(instruction, "to_instruction"): | ||
if not isinstance(instruction, Operation) and hasattr(instruction, "to_instruction"): | ||
instruction = instruction.to_instruction() | ||
if not isinstance(instruction, Instruction): | ||
raise CircuitError("object is not an Instruction.") | ||
if not isinstance(instruction, Operation): | ||
raise CircuitError("object is not an Operation.") | ||
|
||
# Make copy of parameterized gate instances | ||
if hasattr(instruction, "params"): | ||
|
@@ -1218,11 +1219,11 @@ def append( | |
|
||
def _append( | ||
self, | ||
instruction: Instruction, | ||
instruction: Operation, | ||
qargs: Sequence[Qubit], | ||
cargs: Sequence[Clbit], | ||
) -> Instruction: | ||
"""Append an instruction to the end of the circuit, modifying the circuit in place. | ||
) -> Operation: | ||
"""Append an operation to the end of the circuit, modifying the circuit in place. | ||
|
||
.. warning:: | ||
|
||
|
@@ -1242,12 +1243,12 @@ def _append( | |
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: | ||
""" | ||
|
@@ -1261,7 +1262,11 @@ def _append( | |
|
||
return instruction | ||
|
||
def _update_parameter_table(self, instruction: Instruction) -> Instruction: | ||
def _update_parameter_table(self, instruction: Operation) -> Operation: | ||
# A generic Operation object at the moment does not require to have params. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this is the right time to reconsider whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have updated the code to check I don't remember the reason of not including |
||
if not isinstance(instruction, Instruction): | ||
return 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. | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,7 +21,7 @@ | |||||
import numpy as np | ||||||
|
||||||
from qiskit.circuit.quantumcircuit import QuantumCircuit | ||||||
from qiskit.circuit.instruction import Instruction | ||||||
from qiskit.circuit.operation import Operation | ||||||
from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate, HGate, SGate, TGate | ||||||
from qiskit.exceptions import QiskitError | ||||||
from qiskit.quantum_info.operators.predicates import is_unitary_matrix, matrix_equal | ||||||
|
@@ -53,7 +53,7 @@ def __init__(self, data, input_dims=None, output_dims=None): | |||||
|
||||||
Args: | ||||||
data (QuantumCircuit or | ||||||
Instruction or | ||||||
Operation or | ||||||
BaseOperator or | ||||||
matrix): data to initialize operator. | ||||||
input_dims (tuple): the input subsystem dimensions. | ||||||
|
@@ -75,8 +75,8 @@ def __init__(self, data, input_dims=None, output_dims=None): | |||||
if isinstance(data, (list, np.ndarray)): | ||||||
# Default initialization from list or numpy array matrix | ||||||
self._data = np.asarray(data, dtype=complex) | ||||||
elif isinstance(data, (QuantumCircuit, Instruction)): | ||||||
# If the input is a Terra QuantumCircuit or Instruction we | ||||||
elif isinstance(data, (QuantumCircuit, Operation)): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a few other places where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I think I have updated all the places that I could find, but maybe I've still missed a few. |
||||||
# If the input is a Terra QuantumCircuit or Operation we | ||||||
# perform a simulation to construct the unitary operator. | ||||||
# This will only work if the circuit or instruction can be | ||||||
# defined in terms of unitary gate instructions which have a | ||||||
|
@@ -498,7 +498,7 @@ def _einsum_matmul(cls, tensor, mat, indices, shift=0, right_mul=False): | |||||
|
||||||
@classmethod | ||||||
def _init_instruction(cls, instruction): | ||||||
"""Convert a QuantumCircuit or Instruction to an Operator.""" | ||||||
"""Convert a QuantumCircuit or Operation to an Operator.""" | ||||||
# Initialize an identity operator of the correct size of the circuit | ||||||
if hasattr(instruction, "__array__"): | ||||||
return Operator(np.array(instruction, dtype=complex)) | ||||||
|
@@ -514,7 +514,7 @@ def _init_instruction(cls, instruction): | |||||
@classmethod | ||||||
def _instruction_to_matrix(cls, obj): | ||||||
"""Return Operator for instruction if defined or None otherwise.""" | ||||||
if not isinstance(obj, Instruction): | ||||||
if not isinstance(obj, Operation): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this actually correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, this is a very good point. On the one hand, we definitely want to construct an
On the other hand, the current code only works because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about doing:
Suggested change
that way we still work with operations like clifford that have a defined matrix. We probably should include this in the operation class docs too as an optional function if we start making this an implicit part of the class (I do worry about bloating operation and having it just repeat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I see now. It should be ok that we cannot construct an It's also no clear to me if constructing So I am happy to adopt the above suggestion (though, hmm, it should be Agreed, I would also like to avoid bloating |
||||||
raise QiskitError("Input is not an instruction.") | ||||||
mat = None | ||||||
if hasattr(obj, "to_matrix"): | ||||||
|
@@ -544,10 +544,10 @@ def _append_instruction(self, obj, qargs=None): | |||||
# circuit decomposition definition if it exists, otherwise we | ||||||
# cannot compose this gate and raise an error. | ||||||
if obj.definition is None: | ||||||
raise QiskitError(f"Cannot apply Instruction: {obj.name}") | ||||||
raise QiskitError(f"Cannot apply Operation: {obj.name}") | ||||||
if not isinstance(obj.definition, QuantumCircuit): | ||||||
raise QiskitError( | ||||||
'Instruction "{}" ' | ||||||
'Operation "{}" ' | ||||||
"definition is {} but expected QuantumCircuit.".format( | ||||||
obj.name, type(obj.definition) | ||||||
) | ||||||
|
@@ -569,7 +569,7 @@ def _append_instruction(self, obj, qargs=None): | |||||
for instr, qregs, cregs in flat_instr: | ||||||
if cregs: | ||||||
raise QiskitError( | ||||||
f"Cannot apply instruction with classical registers: {instr.name}" | ||||||
f"Cannot apply operation with classical registers: {instr.name}" | ||||||
) | ||||||
# Get the integer position of the flat register | ||||||
if qargs is None: | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,11 +23,12 @@ | |
from qiskit.quantum_info.operators.scalar_op import ScalarOp | ||
from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford | ||
from qiskit.quantum_info.operators.mixins import generate_apidocs, AdjointMixin | ||
from qiskit.circuit.operation import Operation | ||
from .stabilizer_table import StabilizerTable | ||
from .clifford_circuits import _append_circuit | ||
|
||
|
||
class Clifford(BaseOperator, AdjointMixin): | ||
class Clifford(BaseOperator, AdjointMixin, Operation): | ||
"""An N-qubit unitary operator from the Clifford group. | ||
|
||
**Representation** | ||
|
@@ -101,6 +102,10 @@ class Clifford(BaseOperator, AdjointMixin): | |
`arXiv:quant-ph/0406196 <https://arxiv.org/abs/quant-ph/0406196>`_ | ||
""" | ||
|
||
# Fields that are currently required to add an object as an Operation. | ||
condition = None | ||
_directive = False | ||
|
||
def __array__(self, dtype=None): | ||
if dtype: | ||
return np.asarray(self.to_matrix(), dtype=dtype) | ||
|
@@ -134,6 +139,9 @@ def __init__(self, data, validate=True): | |
"Invalid Clifford. Input StabilizerTable is not a valid symplectic matrix." | ||
) | ||
|
||
# When required, we will compute the QuantumCircuit for this Clifford. | ||
self._definition = None | ||
|
||
# Initialize BaseOperator | ||
super().__init__(num_qubits=self._table.num_qubits) | ||
|
||
|
@@ -540,6 +548,34 @@ def _pad_with_identity(self, clifford, qargs): | |
|
||
return padded | ||
|
||
def broadcast_arguments(self, qargs, cargs): | ||
""" | ||
Broadcasting of the arguments. | ||
This code is currently copied from Instruction. | ||
This will be cleaned up when broadcasting is moved | ||
to a separate model. | ||
|
||
Args: | ||
qargs (List): List of quantum bit arguments. | ||
cargs (List): List of classical bit arguments. | ||
|
||
Yields: | ||
Tuple(List, List): A tuple with single arguments. | ||
""" | ||
|
||
# [[q[0], q[1]], [c[0], c[1]]] -> [q[0], c[0]], [q[1], c[1]] | ||
flat_qargs = [qarg for sublist in qargs for qarg in sublist] | ||
flat_cargs = [carg for sublist in cargs for carg in sublist] | ||
yield flat_qargs, flat_cargs | ||
|
||
@property | ||
def definition(self): | ||
"""Computes and returns the circuit for this Clifford.""" | ||
if self._definition is None: | ||
self._definition = self.to_circuit() | ||
|
||
return self._definition | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, how far do we make it through a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I would say quite far, please see the code snippet from my answer to a previous question.
I completely agree. This is also something that @ShellyGarion is interested in, as she wants to be able to choose at transpile time whether to apply a synthesis method that minimizes gate-count vs. depth and whether to apply a method that is better suited for all-to-all connectivity vs. linear-neighbor connectivity.
This is an interesting suggestion (that I have not tried yet). I might be wrong about this, but I am afraid that certain methods (like constructing a unitary |
||
|
||
|
||
# Update docstrings for API docs | ||
generate_apidocs(Clifford) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instruction
already as an instance variable calledcondition
which will override this property. Since the setters and getters are no-ops anyway, we shouldn't need the properties.