Bases: object
Pauli operator is a list of (I, X, Y, Z) with length n, which operates on n qubits.
In this class, we use a list of GateType to represent the operator, where the GateTypes stand
for the gates. Despite the operator, the phase is also recorded.
Construct a PauliOperator with a list of GateType
Parameters:
-
operator(list,
(optional)
–
the list of GateType representing the PauliOperator
-
phase(complex,
(optional)
–
the global phase of the PauliOperator, ±1 or ±i
Source code in QuICT/qcda/utility/pauli_operator.py
| def __init__(self, operator=None, phase=1 + 0j):
"""
Construct a PauliOperator with a list of GateType
Args:
operator(list, optional): the list of GateType representing the PauliOperator
phase(complex, optional): the global phase of the PauliOperator, ±1 or ±i
"""
if operator is None:
self._operator = []
else:
assert isinstance(operator, list), TypeError("operator must be list of int(GateType).")
for gate_type in operator:
assert gate_type == GateType.id or gate_type in PAULI_GATE_SET,\
ValueError("operator must contain Pauli gates only.")
self._operator = operator
assert phase in PauliOperator.phase_list, ValueError("phase must be ±1 or ±i")
self._phase = phase
|
combine
Compute the PauliOperator after combined with a PauliOperator from the right side.
Be aware of the order, which would affect the global phase.
Parameters:
-
other(PauliOperator)
–
the PauliOperator to be combined
Returns:
-
PauliOperator –
the combined PauliOperator
Source code in QuICT/qcda/utility/pauli_operator.py
| def combine(self, other):
"""
Compute the PauliOperator after combined with a PauliOperator from the right side.
Be aware of the order, which would affect the global phase.
Args:
other(PauliOperator): the PauliOperator to be combined
Returns:
PauliOperator: the combined PauliOperator
"""
assert isinstance(other, PauliOperator),\
TypeError("combine() only combines PauliOperators.")
assert self.width == other.width,\
ValueError("PauliOperators to be combined must have the same width.")
self.phase *= other.phase
for qubit, gate_type in enumerate(other.operator):
self.combine_one_gate(gate_type, qubit)
return self
|
combine_one_gate
combine_one_gate(gate_type: GateType, qubit: int)
Compute the PauliOperator after combined with a Pauli gate from the right side.
Be aware of the order, which would affect the global phase.
Parameters:
-
gate_type(GateType)
–
type of the Pauli gate to be combined
-
qubit(int)
–
qubit that the Pauli gate acts on
Source code in QuICT/qcda/utility/pauli_operator.py
| def combine_one_gate(self, gate_type: GateType, qubit: int):
"""
Compute the PauliOperator after combined with a Pauli gate from the right side.
Be aware of the order, which would affect the global phase.
Args:
gate_type(GateType): type of the Pauli gate to be combined
qubit(int): qubit that the Pauli gate acts on
"""
assert gate_type in PAULI_GATE_SET or gate_type == GateType.id,\
ValueError('gate to be combined must be a pauli gate')
assert isinstance(qubit, int) and qubit >= 0 and qubit < self.width,\
ValueError('qubit out of range')
self.operator[qubit], phase = PauliOperator.combine_rules[self.operator[qubit], gate_type]
self.phase *= phase
|
commute
Decide whether two PauliOperators commute or anti-commute
Parameters:
-
other(PauliOperator)
–
PauliOperator to be checked with self
Returns:
-
boolean –
True for commutative or False for anti-commutative
Source code in QuICT/qcda/utility/pauli_operator.py
| def commute(self, other):
"""
Decide whether two PauliOperators commute or anti-commute
Args:
other(PauliOperator): PauliOperator to be checked with self
Returns:
boolean: True for commutative or False for anti-commutative
"""
assert isinstance(other, PauliOperator),\
TypeError("commute() only checks commutativity between PauliOperators.")
assert self.width == other.width,\
ValueError("PauliOperators to be checked must have the same width.")
res = True
for qubit in range(self.width):
if self.operator[qubit] == GateType.id or other.operator[qubit] == GateType.id\
or self.operator[qubit] == other.operator[qubit]:
continue
else:
res = not res
return res
|
conjugate_act
conjugate_act(gate: BasicGate)
Compute the PauliOperator after conjugate action of a clifford gate
Be aware that the conjugate action means U^-1 P U, where U is the clifford gate
and P is the PauliOperator. It is important for S gate.
Parameters:
-
gate(BasicGate)
–
the clifford gate to be acted on the PauliOperator
Source code in QuICT/qcda/utility/pauli_operator.py
| def conjugate_act(self, gate: BasicGate):
"""
Compute the PauliOperator after conjugate action of a clifford gate
Be aware that the conjugate action means U^-1 P U, where U is the clifford gate
and P is the PauliOperator. It is important for S gate.
Args:
gate(BasicGate): the clifford gate to be acted on the PauliOperator
"""
assert gate.is_clifford() or gate.type == GateType.id,\
ValueError("Only conjugate action of Clifford gates here.")
for targ in gate.cargs + gate.targs:
assert targ < self.width, ValueError("target of the gate out of range")
if gate.type == GateType.cx:
self.operator[gate.carg], self.operator[gate.targ], phase\
= PauliOperator.conjugate_rules[gate.type, self.operator[gate.carg], self.operator[gate.targ]]
else:
self.operator[gate.targ], phase = PauliOperator.conjugate_rules[gate.type, self.operator[gate.targ]]
self.phase *= phase
|
disentangler
staticmethod
disentangler(pauli_x, pauli_z, target=0) -> CompositeGate
For anti-commuting n-qubit Pauli operators O and O', there exists a Clifford circuit L∈C_n
such that L^(-1) O L = X_j, L^(-1) O' L = Z_j.
L is referred to as a disentangler for the pair (O, O').
Parameters:
-
pauli_x(PauliOperator)
–
the PauliOperator to be transformed to X_j
-
pauli_z(PauliOperator)
–
the PauliOperator to be transformed to Z_j
-
target(int,
(optional)
–
the j in the X_j, Z_j to be transformed to
Returns:
-
CompositeGate ( CompositeGate
) –
the disentangler for the pair (O, O')
Reference
https://arxiv.org/abs/2105.02291
Source code in QuICT/qcda/utility/pauli_operator.py
| @staticmethod
def disentangler(pauli_x, pauli_z, target=0) -> CompositeGate:
"""
For anti-commuting n-qubit Pauli operators O and O', there exists a Clifford circuit L∈C_n
such that L^(-1) O L = X_j, L^(-1) O' L = Z_j.
L is referred to as a disentangler for the pair (O, O').
Args:
pauli_x(PauliOperator): the PauliOperator to be transformed to X_j
pauli_z(PauliOperator): the PauliOperator to be transformed to Z_j
target(int, optional): the j in the X_j, Z_j to be transformed to
Returns:
CompositeGate: the disentangler for the pair (O, O')
Reference:
https://arxiv.org/abs/2105.02291
"""
assert isinstance(pauli_x, PauliOperator) and isinstance(pauli_z, PauliOperator),\
TypeError("disentangler only defined for PauliOperator pairs")
assert pauli_x.width == pauli_z.width,\
ValueError('two PauliOperators must be of the same width')
assert not pauli_x.commute(pauli_z),\
ValueError('anti-commutative pairs needed for computing disentangler')
assert isinstance(target, int) and 0 <= target and target < pauli_x.width,\
ValueError('the target must be integer in the width of the PauliOperators')
pauli_x = copy.deepcopy(pauli_x)
pauli_z = copy.deepcopy(pauli_z)
# Record the qubits in the 5 cases of the standard forms
XZ = []
XX = []
XI = []
IZ = []
II = []
standard_forms = [XZ, XX, XI, IZ, II]
# Transform the pauli_x and pauli_z to 'standard' forms
standardizer = CompositeGate()
for qubit in range(pauli_x.width):
gates, form = PauliOperator.standardize_rules[pauli_x.operator[qubit], pauli_z.operator[qubit]]
for gate_type in gates:
gate = gate_builder(gate_type) & qubit
pauli_x.conjugate_act(gate)
pauli_z.conjugate_act(gate)
standardizer.append(gate)
standard_forms[form].append(qubit)
# Construct the disentangler of 'standard' pairs with the algorithm given in reference
disentangler = CompositeGate()
with disentangler:
for subset in standard_forms:
if target in subset:
if subset is not XZ:
# Swap & [target, XZ[0]]
CX & [target, XZ[0]]
CX & [XZ[0], target]
CX & [target, XZ[0]]
if XZ[0] != target:
subset.remove(target)
subset.append(XZ[0])
XZ[0] = target
for j in XI:
CX & [target, j]
for j in IZ:
CX & [j, target]
if len(XX) > 0:
i = XX[0]
for j in XX[1:]:
CX & [i, j]
CX & [target, i]
H & i
CX & [i, target]
# Anti-commutation gives that len(XZ) is odd
for k in range((len(XZ) - 1) // 2):
i = 2 * k + 1
j = 2 * k + 2
CX & [XZ[j], XZ[i]]
CX & [XZ[i], target]
CX & [target, XZ[j]]
standardizer.extend(disentangler)
# Add the last Pauli gate for the phase correction
for gate in disentangler:
pauli_x.conjugate_act(gate)
pauli_z.conjugate_act(gate)
if pauli_x.phase == 1 and pauli_z.phase == -1:
gate = gate_builder(GateType.x) & target
standardizer.append(gate)
if pauli_x.phase == -1 and pauli_z.phase == -1:
gate = gate_builder(GateType.y) & target
standardizer.append(gate)
if pauli_x.phase == -1 and pauli_z.phase == 1:
gate = gate_builder(GateType.z) & target
standardizer.append(gate)
return standardizer
|
gates
gates(keep_id=False, keep_phase=False) -> CompositeGate
The CompositeGate corresponding to the PauliOperator
Parameters:
-
keep_id(bool,
(optional)
–
whether to keep the IDgate in the CompositeGate
-
keep_phase(bool,
(optional)
–
whether to keep the global phase in the CompositeGate
Returns:
-
CompositeGate ( CompositeGate
) –
The CompositeGate corresponding to the PauliOperator
Source code in QuICT/qcda/utility/pauli_operator.py
| def gates(self, keep_id=False, keep_phase=False) -> CompositeGate:
"""
The CompositeGate corresponding to the PauliOperator
Args:
keep_id(bool, optional): whether to keep the IDgate in the CompositeGate
keep_phase(bool, optional): whether to keep the global phase in the CompositeGate
Returns:
CompositeGate: The CompositeGate corresponding to the PauliOperator
"""
gates = CompositeGate()
for qubit, gate_type in enumerate(self.operator):
if not keep_id and gate_type == GateType.id:
continue
gate = gate_builder(gate_type) & qubit
gates.append(gate)
if keep_phase and not np.isclose(self.phase, 1 + 0j):
phase = -1j * np.log(self.phase)
gate = gate_builder(GateType.gphase, params=[phase.real]) & 0
gates.append(gate)
return gates
|
iterator
staticmethod
Yield all the PauliOperators with given width
Parameters:
-
width(int)
–
the width of the PauliOperator
Yields:
-
PauliOperator –
PauliOperator with given width
Source code in QuICT/qcda/utility/pauli_operator.py
| @staticmethod
def iterator(width: int):
"""
Yield all the PauliOperators with given width
Args:
width(int): the width of the PauliOperator
Yields:
PauliOperator: PauliOperator with given width
"""
for operator in itertools.product(PauliOperator.pauli_list, repeat=width):
yield PauliOperator(list(operator))
|
random
staticmethod
Give a random PauliOperator with given width
Parameters:
-
width(int)
–
the width of the PauliOperator
Returns:
-
PauliOperator –
a random PauliOperator with given width
Source code in QuICT/qcda/utility/pauli_operator.py
| @staticmethod
def random(width: int):
"""
Give a random PauliOperator with given width
Args:
width(int): the width of the PauliOperator
Returns:
PauliOperator: a random PauliOperator with given width
"""
operator = []
for _ in range(width):
operator.append(random.choice(PauliOperator.pauli_list))
return PauliOperator(operator)
|