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

Multiple Qubits Clifford Gate #4791

Merged
merged 38 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b2d9269
Fix the typo in clifford_gate::test_commutes_pauli
BichengYing May 29, 2021
0234dda
Revert "Fix the typo in clifford_gate::test_commutes_pauli"
BichengYing May 29, 2021
7596136
Merge remote-tracking branch 'upstream/master' into master
BichengYing Jun 5, 2021
3121748
Merge remote-tracking branch 'upstream/master' into master
BichengYing Jun 20, 2021
566db2f
Merge remote-tracking branch 'upstream/master' into master
BichengYing Jun 24, 2021
3dbc1c3
Merge remote-tracking branch 'upstream/master' into master
BichengYing Jul 17, 2021
207a1b1
Merge remote-tracking branch 'upstream/master' into master
BichengYing Aug 13, 2021
7c2f96c
Merge branch 'quantumlib:master' into master
BichengYing Aug 13, 2021
5c931d1
Merge branch 'master' of https://github.com/ybc1991/Cirq into master
BichengYing Aug 13, 2021
cd948c4
Merge remote-tracking branch 'upstream/master' into master
BichengYing Sep 27, 2021
4b8f3a8
Merge remote-tracking branch 'upstream/master' into master
BichengYing Nov 6, 2021
e51d9a9
Merge remote-tracking branch 'upstream/master' into master
BichengYing Dec 15, 2021
0058683
Init add multi-qubit clifford gates
BichengYing Dec 31, 2021
a157f98
Merge remote-tracking branch 'upstream/master' into master
BichengYing Dec 31, 2021
66945bd
Merge branch 'master' into m_clifford
BichengYing Jan 2, 2022
a93a874
Add more functionality to clifford gate
BichengYing Jan 2, 2022
6701aaf
Add more functionality to clifford gate
BichengYing Jan 2, 2022
b36342d
Merge branch 'm_clifford' of https://github.com/ybc1991/Cirq into m_c…
BichengYing Jan 2, 2022
35203c1
Add json serialization and fix the test
BichengYing Jan 2, 2022
2fcbb13
Add more tests for coverage
BichengYing Jan 2, 2022
72495cb
fix the lint
BichengYing Jan 2, 2022
95dfc7d
Fix the coverage of act_on fail
BichengYing Jan 2, 2022
ac5659a
Update clifford_gate_test.py
BichengYing Jan 2, 2022
46fb176
Merge branch 'master' into m_clifford
BichengYing Jan 5, 2022
5f3a62a
Update decompose_clifford_tableau_to_operations into new path
BichengYing Jan 5, 2022
e6c1c63
Rename the CliffordGate to MultipleQubitCliffordGate
BichengYing Feb 16, 2022
8235795
Merge branch 'master' into m_clifford
BichengYing Feb 16, 2022
876e966
Update the Clifford Args Usage and format
BichengYing Feb 16, 2022
c795cce
Fix the format and json test
BichengYing Feb 16, 2022
be25af0
Add more docstring
BichengYing Feb 16, 2022
bb3fa28
Move class methods to the lazyload class
BichengYing Feb 16, 2022
89d5047
Fix the coverage
BichengYing Feb 16, 2022
c030a89
Use LazyLoader
BichengYing Feb 16, 2022
d9bc903
Merge branch 'master' into m_clifford
BichengYing Feb 17, 2022
51933a3
Fix the missing quote in the lazyloader
BichengYing Feb 17, 2022
f19794c
Rename MultipleCliffordGate Back to CliffordGate
BichengYing Feb 17, 2022
9cc461d
Fix the format
BichengYing Feb 17, 2022
ed5b337
Merge branch 'master' into m_clifford
CirqBot Feb 22, 2022
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
3 changes: 2 additions & 1 deletion cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
CCNOT,
CCNotPowGate,
ClassicallyControlledOperation,
CliffordGate,
CNOT,
CNotPowGate,
ControlledGate,
Expand Down Expand Up @@ -704,7 +705,7 @@
contrib,
)

# deprecate cirq.ops.moment and related attributes
# deprecate cirq.ops and related attributes

from cirq import _compat

Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/json_resolver_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def _parallel_gate_op(gate, qubits):
'CircuitOperation': cirq.CircuitOperation,
'ClassicallyControlledOperation': cirq.ClassicallyControlledOperation,
'ClassicalDataDictionaryStore': cirq.ClassicalDataDictionaryStore,
'CliffordGate': cirq.CliffordGate,
'CliffordState': cirq.CliffordState,
'CliffordTableau': cirq.CliffordTableau,
'CNotPowGate': cirq.CNotPowGate,
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)

from cirq.ops.clifford_gate import (
CliffordGate,
PauliTransform,
SingleQubitCliffordGate,
)
Expand Down
311 changes: 309 additions & 2 deletions cirq-core/cirq/ops/clifford_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,44 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, cast, Dict, NamedTuple, Optional, Sequence, Tuple, TYPE_CHECKING, Union
from typing import (
Any,
cast,
Dict,
List,
NamedTuple,
Optional,
Sequence,
Tuple,
TYPE_CHECKING,
Union,
)

import numpy as np

from cirq import protocols, value, linalg, qis
from cirq._doc import document
from cirq.ops import common_gates, gate_features, named_qubit, pauli_gates, phased_x_z_gate
from cirq._import import LazyLoader
from cirq.ops import (
common_gates,
gate_features,
identity,
named_qubit,
raw_types,
pauli_gates,
phased_x_z_gate,
)
from cirq.ops.pauli_gates import Pauli
from cirq.type_workarounds import NotImplementedType

if TYPE_CHECKING:
import cirq

# Lazy imports to break circular dependencies.
devices = LazyLoader("devices", globals(), "cirq.devices")
sim = LazyLoader("sim", globals(), "cirq.sim")
transformers = LazyLoader("transformers", globals(), "cirq.transformers")

PauliTransform = NamedTuple('PauliTransform', [('to', Pauli), ('flip', bool)])
document(PauliTransform, """+X, -X, +Y, -Y, +Z, or -Z.""")

Expand Down Expand Up @@ -571,3 +596,285 @@ def _circuit_diagram_info_(
SingleQubitCliffordGate.Z: SingleQubitCliffordGate.Z_nsqrt,
},
}


class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf):
"""A metaclass used to lazy initialize several common Clifford Gate as class attributes."""

@property
def I(cls):
if getattr(cls, '_I', None) is None:
cls._I = cls._generate_clifford_from_known_gate(1, identity.I)
return cls._I

@property
def X(cls):
if getattr(cls, '_X', None) is None:
cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.X)
return cls._Z

@property
def Y(cls):
if getattr(cls, '_X', None) is None:
cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.Y)
return cls._Z

@property
def Z(cls):
if getattr(cls, '_X', None) is None:
cls._Z = cls._generate_clifford_from_known_gate(1, pauli_gates.Z)
return cls._Z

@property
def H(cls):
if getattr(cls, '_H', None) is None:
cls._H = cls._generate_clifford_from_known_gate(1, common_gates.H)
return cls._H

@property
def S(cls):
if getattr(cls, '_S', None) is None:
cls._S = cls._generate_clifford_from_known_gate(1, common_gates.S)
return cls._S

@property
def CNOT(cls):
if getattr(cls, '_CNOT', None) is None:
cls._CNOT = cls._generate_clifford_from_known_gate(2, common_gates.CNOT)
return cls._CNOT

@property
def CZ(cls):
if getattr(cls, '_CZ', None) is None:
cls._CZ = cls._generate_clifford_from_known_gate(2, common_gates.CZ)
return cls._CZ

@property
def SWAP(cls):
if getattr(cls, '_SWAP', None) is None:
cls._SWAP = cls._generate_clifford_from_known_gate(2, common_gates.SWAP)
return cls._SWAP


class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass):

# We need to use the lazy initialization of these common gates since they need to use
# cirq.sim, which can not be imported when
@classmethod
def _generate_clifford_from_known_gate(
cls, num_qubits: int, gate: raw_types.Gate
) -> 'CliffordGate':
qubits = devices.LineQubit.range(num_qubits)
t = qis.CliffordTableau(num_qubits=num_qubits)
args = sim.ActOnCliffordTableauArgs(
tableau=t, qubits=qubits, prng=np.random.RandomState(), log_of_measurement_results={}
)

protocols.act_on(gate, args, qubits, allow_decompose=False)
return CliffordGate.from_clifford_tableau(args.tableau)

@classmethod
def from_clifford_tableau(cls, tableau: qis.CliffordTableau) -> 'CliffordGate':
"""Create the CliffordGate instance from Clifford Tableau.

Args:
tableau: A CliffordTableau to define the effect of Clifford Gate applying on
the stabilizer state or Pauli group. The meaning of tableau here is
To X Z sign
from X [ X_x Z_x | r_x ]
from Z [ X_z Z_z | r_z ]
Each row in the Clifford tableau indicates how the transformation of original
Pauli gates to the new gates after applying this Clifford Gate.

Returns:
A CliffordGate instance, which has the transformation defined by
the input tableau.

Raises:
ValueError: When input tableau is wrong type or the tableau does not
satisfy the symplectic property.
"""
if not isinstance(tableau, qis.CliffordTableau):
raise ValueError('Input argument has to be a CliffordTableau instance.')
if not tableau._validate():
raise ValueError('It is not a valid Clifford tableau.')
return CliffordGate(_clifford_tableau=tableau)

@classmethod
def from_op_list(
cls, operations: Sequence[raw_types.Operation], qubit_order: Sequence[raw_types.Qid]
) -> 'CliffordGate':
"""Construct a new Clifford gates from several known operations.

Args:
operations: A list of cirq operations to construct the Clifford gate.
The combination order is the first element in the list applies the transformation
on the stabilizer state first.
qubit_order: Determines how qubits are ordered when decomposite the operations.

Returns:
A CliffordGate instance, which has the transformation on the stabilizer
state equivalent to the composition of operations.

Raises:
ValueError: When one or more operations do not have stabilizer effect.
"""
for op in operations:
if op.gate and op.gate._has_stabilizer_effect_():
continue
raise ValueError(
"Clifford Gate can only be constructed from the "
"operations that has stabilizer effect."
)

base_tableau = qis.CliffordTableau(len(qubit_order))
args = sim.clifford.ActOnCliffordTableauArgs(
tableau=base_tableau,
qubits=qubit_order,
prng=np.random.RandomState(0), # unused
log_of_measurement_results={}, # unused
)
for op in operations:
protocols.act_on(op, args, allow_decompose=True)

return CliffordGate.from_clifford_tableau(args.tableau)

@classmethod
def _from_json_dict_(cls, n, rs, xs, zs, **kwargs):
_clifford_tableau = qis.CliffordTableau._from_json_dict_(
n,
rs,
xs,
zs,
)
return cls(_clifford_tableau=_clifford_tableau)


def _pad_tableau(
clifford_tableau: qis.CliffordTableau, num_qubits_after_padding: int, axes: List[int]
) -> qis.CliffordTableau:
"""Roughly, this function copies self.tabluea into the "identity" matrix."""
# Sanity check
if len(set(axes)) != clifford_tableau.n:
raise ValueError(
"Input axes of padding should match with the number of qubits in the input tableau."
)
if clifford_tableau.n > num_qubits_after_padding:
raise ValueError(
"The number of qubits in the input tableau should not be larger than "
"num_qubits_after_padding."
)

padded_tableau = qis.CliffordTableau(num_qubits_after_padding)
v_index = np.concatenate((np.asarray(axes), num_qubits_after_padding + np.asarray(axes)))

padded_tableau.xs[np.ix_(v_index, axes)] = clifford_tableau.xs
padded_tableau.zs[np.ix_(v_index, axes)] = clifford_tableau.zs
padded_tableau.rs[v_index] = clifford_tableau.rs
return padded_tableau


@value.value_equality
class CliffordGate(raw_types.Gate, CommonCliffordGates):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: We should rename this to something like MultiQubitCliffordGate we already have SingleQubitCliffordGate. This could be confusing because a user might think that CliffordGate is a superclass of SingleQubitCliffordGate which it is not.

Copy link
Collaborator Author

@BichengYing BichengYing Feb 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to MultiQubitCliffordGate for now. But, in my opinion, it is better to have CliffordGate as base class and make SingleQubitCliffordGate as derived class in future. (Need more work, which should not be done in this PR anyway). Because SingleQubitCliffordGate can reuse all CliffordGate functions and create more functions, like related to bloch rotation, that is unique in the Single Qubit case. (And overwrite some function since in one qubit there can be more efficient implementation?) Idea 2: Alternatively, create an abstract CliffordGate superclass and move the common methods in SingleQubit and MultipleQubit into the superclass. Wdyt?

Copy link
Collaborator

@MichaelBroughton MichaelBroughton Feb 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, in my opinion, it is better to have CliffordGate as base class and make SingleQubitCliffordGate as derived class in future

This sounds like a good idea to investigate in a future PR. Should we revert back to CliffordGate to make this follow up PR easier ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as discussed in the Cirq-Cynq, will make a follow-up PR to make the SingleQubitCliffordGate as the derived class of CliffordGate. So rename it back.

"""Clifford rotation for N-qubit."""

def __init__(
self,
*,
_clifford_tableau: qis.CliffordTableau,
) -> None:
# We use the Clifford tableau to represent a Clifford gate.
# It is crucial to note that the meaning of tableau here is different
# from the one used to represent a Clifford state (Of course, they are related).
# A) We have to use the full 2n * (2n + 1) matrix
# B) The meaning of tableau here is
# X Z sign
# from X [ X_x Z_x | r_x ]
# from Z [ X_z Z_z | r_z ]
# Each row in the Clifford tableau means the transformation of original Pauli gates.
# For example, take a 2 * (2+1) tableau as example:
# X Z r
# XI [ 1 0 | 1 0 | 0 ]
# IX [ 0 0 | 1 1 | 0 ]
# ZI [ 0 0 | 1 0 | 1 ]
# IZ [ 1 0 | 1 1 | 0 ]
# Take the third row as example: this means the ZI gate after the this gate,
# more precisely the conjugate transformation of ZI by this gate, becomes -ZI.
# (Note the real clifford tableau has to satify the Symplectic property.
# here is just for illustration)
self._clifford_tableau = _clifford_tableau.copy()

@property
def clifford_tableau(self):
return self._clifford_tableau

def _json_dict_(self) -> Dict[str, Any]:
json_dict = self._clifford_tableau._json_dict_()
return json_dict

def _value_equality_values_(self):
return self.clifford_tableau

def _num_qubits_(self):
return self.clifford_tableau.n

def _has_stabilizer_effect_(self) -> Optional[bool]:
# By definition, Clifford Gate should always return True.
return True

def __pow__(self, exponent) -> 'CliffordGate':
if exponent == -1:
return CliffordGate.from_clifford_tableau(self.clifford_tableau.inverse())
if exponent > 0 and int(exponent) == exponent:
base_tableau = self.clifford_tableau.copy()
for _ in range(int(exponent) - 1):
base_tableau = base_tableau.then(self.clifford_tableau)
return CliffordGate.from_clifford_tableau(base_tableau)
if exponent < 0 and int(exponent) == exponent:
base_tableau = self.clifford_tableau.copy()
for _ in range(int(-exponent) - 1):
base_tableau = base_tableau.then(self.clifford_tableau)
return CliffordGate.from_clifford_tableau(base_tableau.inverse())

return NotImplemented

def __repr__(self) -> str:
return f"Clifford Gate with Tableau:\n {self.clifford_tableau._str_full_()}"

def _commutes_(self, other: Any, atol: float) -> Union[bool, NotImplementedType, None]:
# Note even if we assume two gates define the tabluea based on the same qubit order,
# the following approach cannot judge it:
# self.clifford_tableau.then(other.clifford_tableau) == other.clifford_tableau.then(
# self.clifford_tableau
# )
# For example: X.then(Z) and Z.then(X) both return same tableau
# it is because Clifford tableau ignores the global phase information.
return NotImplemented

def _decompose_(self, qubits: Sequence['cirq.Qid']) -> List[raw_types.Operation]:
return transformers.analytical_decompositions.decompose_clifford_tableau_to_operations(
list(qubits), self.clifford_tableau
)

def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool:

# Note the computation complexity difference between _decompose_ and _act_on_.
# Suppose this Gate has `m` qubits, args has `n` qubits, and the decomposition of
# this operation into `k` operations:
# 1. Direct act_on is O(n^3) -- two matrices multiplication
# 2. Decomposition is O(m^3)+O(k*n^2) -- Decomposition complexity + k * One/two-qubits Ops
# So when m << n, the decomposition is more efficient.
if isinstance(args, sim.clifford.ActOnCliffordTableauArgs):
axes = args.get_axes(qubits)
# This padding is important and cannot be omitted.
padded_tableau = _pad_tableau(self._clifford_tableau, len(args.qubits), axes)
args._state = args.tableau.then(padded_tableau)
return True

if isinstance(args, sim.clifford.ActOnStabilizerCHFormArgs):
# Do we know how to apply CliffordTableau on ActOnStabilizerCHFormArgs?
# It should be unlike because CliffordTableau ignores the global phase but CHForm
# is aimed to fix that.
return NotImplemented

return NotImplemented
Loading