-
Notifications
You must be signed in to change notification settings - Fork 3k
Adding Cliffords to QuantumCircuits as Operations #7966
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
Changes from 5 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 | ||
|
Member
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
Member
Author
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,7 +1161,7 @@ def _resolve_classical_resource(self, specifier): | |
|
|
||
| def append( | ||
| self, | ||
| instruction: Instruction, | ||
| instruction: Operation, | ||
|
Member
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
Member
Author
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?
Member
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: | ||
|
|
@@ -1180,20 +1181,20 @@ def append( | |
| CircuitError: if object passed is neither subclass nor an instance of Instruction | ||
| """ | ||
| # 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,7 +1219,7 @@ def append( | |
|
|
||
| def _append( | ||
| self, | ||
| instruction: Instruction, | ||
| instruction: Operation, | ||
| qargs: Sequence[Qubit], | ||
| cargs: Sequence[Clbit], | ||
| ) -> Instruction: | ||
|
|
@@ -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. | ||
|
Member
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
Member
Author
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 hasattr(instruction, "params"): | ||
| 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,7 +75,7 @@ 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)): | ||||||
| elif isinstance(data, (QuantumCircuit, Operation)): | ||||||
|
Member
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
Member
Author
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 Instruction we | ||||||
| # perform a simulation to construct the unitary operator. | ||||||
| # This will only work if the circuit or instruction can be | ||||||
|
|
@@ -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): | ||||||
|
Member
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?
Member
Author
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
Member
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
Member
Author
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"): | ||||||
|
|
||||||
| 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 | ||
|
Member
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
Member
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
Member
Author
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) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,85 @@ | ||||||
| # This code is part of Qiskit. | ||||||
| # | ||||||
| # (C) Copyright IBM 2017, 2021. | ||||||
| # | ||||||
| # This code is licensed under the Apache License, Version 2.0. You may | ||||||
| # obtain a copy of this license in the LICENSE.txt file in the root directory | ||||||
| # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||||||
| # | ||||||
| # Any modifications or derivative works of this code must retain this | ||||||
| # copyright notice, and modified files need to carry a notice indicating | ||||||
| # that they have been altered from the originals. | ||||||
|
|
||||||
| """Combine consecutive Cliffords over the same qubits.""" | ||||||
|
|
||||||
| from qiskit.transpiler.basepasses import TransformationPass | ||||||
| from qiskit.quantum_info.operators import Clifford | ||||||
|
|
||||||
|
|
||||||
| class OptimizeCliffords(TransformationPass): | ||||||
| """Combine consecutive Cliffords over the same qubits. | ||||||
| This serves as an example of extra capabilities enabled by storing | ||||||
| Cliffords natively on the circuit. | ||||||
|
Comment on lines
+21
to
+22
Member
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. This looks good. I think we should look to generalize
Member
Author
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.
Agreed. This seems like some important functionality that comes up over and over. |
||||||
| """ | ||||||
|
|
||||||
| def run(self, dag): | ||||||
| """Run the OptimizeCliffords pass on `dag`. | ||||||
|
|
||||||
| Args: | ||||||
| dag (DAGCircuit): the DAG to be optimized. | ||||||
|
|
||||||
| Returns: | ||||||
| DAGCircuit: the optimized DAG. | ||||||
| """ | ||||||
|
|
||||||
| blocks = [] | ||||||
| prev_node = None | ||||||
| cur_block = [] | ||||||
|
|
||||||
| # Iterate over all nodes and collect consecutive Cliffords over the | ||||||
| # same qubits. In this very first proof-of-concept implementation | ||||||
| # we require the same ordering of qubits, but this restriction will | ||||||
| # be shortly removed. An interesting question is whether we may also | ||||||
| # want to compose Cliffords over different sets of qubits, such as | ||||||
| # cliff1 over qubits [1, 2, 3] and cliff2 over [2, 3, 4]. | ||||||
| for node in dag.op_nodes(): | ||||||
|
mtreinish marked this conversation as resolved.
Outdated
|
||||||
| if isinstance(node.op, Clifford): | ||||||
| if prev_node is None: | ||||||
| blocks.append(cur_block) | ||||||
| cur_block = [node] | ||||||
| else: | ||||||
| if prev_node.qargs == node.qargs: | ||||||
|
Member
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.
Suggested change
Can we match here even if the
Member
Author
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 was also thinking about this, and this should be possible, but we need to suitably permute the rows and the columns of the clifford's stabilizer tableau. |
||||||
| cur_block.append(node) | ||||||
| else: | ||||||
| blocks.append(cur_block) | ||||||
| cur_block = [node] | ||||||
|
|
||||||
| prev_node = node | ||||||
|
|
||||||
| else: | ||||||
| # not a clifford | ||||||
| blocks.append(cur_block) | ||||||
| prev_node = None | ||||||
| cur_block = [] | ||||||
|
Member
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. Minor, but this could be wrapped in an |
||||||
|
|
||||||
| blocks.append(cur_block) | ||||||
|
|
||||||
| # Replace every discovered block of cliffords by a single clifford | ||||||
| # based on the Cliffords' compose function. | ||||||
| for cur_nodes in blocks: | ||||||
| # Create clifford functions only out of blocks with at least 2 gates | ||||||
| if len(cur_nodes) <= 1: | ||||||
| continue | ||||||
|
|
||||||
| wire_pos_map = dict((qb, ix) for ix, qb in enumerate(cur_nodes[0].qargs)) | ||||||
|
|
||||||
| # Construct a linear circuit | ||||||
| cliff = cur_nodes[0].op | ||||||
| for i, node in enumerate(cur_nodes): | ||||||
| if i > 0: | ||||||
| cliff = Clifford.compose(node.op, cliff, front=True) | ||||||
|
|
||||||
| # Replace the block by the composed clifford | ||||||
| dag.replace_block_with_op(cur_nodes, cliff, wire_pos_map, cycle_check=False) | ||||||
|
|
||||||
| return dag | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| --- | ||
|
|
||
| features: | ||
| - | | ||
| A new :class:`.Operation` base class for objects that can be added to a :class:`.QuantumCircuit`. | ||
| These objects include :class:`.Gate`, :class:`.Reset`, :class:`.Barrier`, :class:.Measure`, | ||
| and operators such as :class:`.Clifford`. | ||
| - | | ||
| A Clifford gate is now added to a quantum circuit as an :class:`.Operation`, without first | ||
| synthesizing a subcircuit implementing this Clifford gate. The actual synthesis is postponed | ||
| to a later transpilation pass. | ||
| - | | ||
|
mtreinish marked this conversation as resolved.
|
||
| Added a new transpiler pass :class:`.OptimizeCliffords` that collects blocks of consecutive | ||
| Clifford gates in a circuit, and replaces each block with a single Clifford object. | ||
|
mtreinish marked this conversation as resolved.
Outdated
|
||
|
|
||
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.
Instructionalready as an instance variable calledconditionwhich will override this property. Since the setters and getters are no-ops anyway, we shouldn't need the properties.