跳转至

PauliOperator

QuICT.qcda.utility.PauliOperator

PauliOperator(operator=None, phase=1 + 0j)

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

combine(other)

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

commute(other)

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

iterator(width: int)

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

random(width: int)

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)