Source code for tequila_code.hamiltonian.paulis

"""
Convenience initialization
of Pauli Operators. Resulting structures can be added and multiplied together.
Currently uses OpenFermion as backend (QubitOperators)
"""
import typing
from tequila.hamiltonian import QubitHamiltonian
from tequila import BitString, TequilaException
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
from tequila.tools import list_assignment
import numpy

[docs] def from_string(string, openfermion_format=False): return QubitHamiltonian.from_string(string=string, openfermion_format=openfermion_format)
[docs] def pauli(qubit, type) -> QubitHamiltonian: """ Parameters ---------- qubit: int or list of ints type: str or int or list of strquaing or int: define if X, Y or Z (0,1,2) Returns ------- QubitHamiltonian """ def assign_axis(axis): if axis in QubitHamiltonian.axis_to_string: return QubitHamiltonian.axis_to_string[axis] elif hasattr(axis, "upper"): return axis.upper() else: raise TequilaException("unknown initialization for pauli operator: {}".format(axis)) if not isinstance(qubit, typing.Iterable): qubit = [qubit] type = [type] type = [assign_axis(x) for x in type] init_string = "".join("{}{} ".format(t, q) for t, q in zip(type, qubit)) return QubitHamiltonian.from_string(string=init_string, openfermion_format=True)
[docs] def X(qubit) -> QubitHamiltonian: """ Initialize a single Pauli X Operator Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) return pauli(qubit=qubit, type=["X"] * len(qubit))
[docs] def Y(qubit) -> QubitHamiltonian: """ Initialize a single Pauli Y Operator Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) return pauli(qubit=qubit, type=["Y"] * len(qubit))
[docs] def Z(qubit) -> QubitHamiltonian: """ Initialize a single Pauli Z Operator Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) return pauli(qubit=qubit, type=["Z"] * len(qubit))
[docs] def I(*args, **kwargs) -> QubitHamiltonian: """ Initialize unit Operator Returns ------- QubitHamiltonian """ return QubitHamiltonian.unit()
[docs] def Zero(*args, **kwargs) -> QubitHamiltonian: """ Initialize 0 Operator Returns ------- QubitHamiltonian """ return QubitHamiltonian.zero()
[docs] def Qp(qubit) -> QubitHamiltonian: """ Notes ---------- Initialize .. math:: \\frac{1}{2} \\left( 1 - \\sigma_z \\right) Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) result = I() for q in qubit: result *= 0.5 * (I(qubit=q) + Z(qubit=q)) return result
[docs] def Qm(qubit) -> QubitHamiltonian: """ Notes ---------- Initialize .. math:: \\frac{1}{2} \\left( 1 + \\sigma_z \\right) Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) result = I() for q in qubit: result *= 0.5 * (I(qubit=q) - Z(qubit=q)) return result
[docs] def Sp(qubit) -> QubitHamiltonian: """ Notes ---------- Initialize .. math:: \\frac{1}{2} \\left( \\sigma_x + i\\sigma_y \\right) Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) result = I() for q in qubit: result *= 0.5 * (X(qubit=q) + 1.j * Y(qubit=q)) return result
[docs] def Sm(qubit) -> QubitHamiltonian: """ Notes ---------- Initialize .. math:: \\frac{1}{2} \\left( \\sigma_x - i \\sigma_y \\right) Parameters ---------- qubit: int or list of ints qubit(s) on which the operator should act Returns ------- QubitHamiltonian """ qubit = list_assignment(qubit) result = I() for q in qubit: result *= 0.5 * (X(qubit=q) - 1.j * Y(qubit=q)) return result
[docs] def Projector(wfn, threshold=0.0, n_qubits=None) -> QubitHamiltonian: """ Notes ---------- Initialize a projector given by .. math:: H = \\lvert \\Psi \\rangle \\langle \\Psi \\rvert Parameters ---------- wfn: QubitWaveFunction or int, or string, or array : The wavefunction onto which the projector projects Needs to be passed down as tequilas QubitWaveFunction type See the documentation on how to initialize a QubitWaveFunction from integer, string or array (can also be passed down diretly as one of those types) threshold: float: (Default value = 0.0) neglect small parts of the operator n_qubits: only needed when an integer is given as wavefunction Returns ------- """ wfn = QubitWaveFunction(state=wfn, n_qubits=n_qubits) H = QubitHamiltonian.zero() for k1, v1 in wfn.items(): for k2, v2 in wfn.items(): c = v1.conjugate() * v2 if not numpy.isclose(c, 0.0, atol=threshold): H += c * decompose_transfer_operator(bra=k1, ket=k2) assert (H.is_hermitian()) return H
[docs] def KetBra(ket: QubitWaveFunction, bra: QubitWaveFunction, hermitian: bool = False, threshold: float = 1.e-6, n_qubits=None): """ Notes ---------- Initialize the general KetBra operator .. math:: H = \\lvert ket \\rangle \\langle bra \\rvert e.g. wfn1 = tq.QubitWaveFunction.from_string("1.0*|00> + 1.0*|11>").normalize() wfn2 = tq.QubitWaveFunction.from_string("1.0*|00>") operator = tq.paulis.KetBra(ket=wfn1, bra=wfn1) initializes the transfer operator from the all-zero state to a Bell state Parameters ---------- ket: QubitWaveFunction: QubitWaveFunction which defines the ket element can also be given as string or array or integer bra: QubitWaveFunction: QubitWaveFunction which defines the bra element can also be given as string or array or integer hermitian: bool: (Default False) if True the hermitian version H + H^\dagger is returned threshold: float: (Default 1.e-6) elements smaller than the threshold will be ignored n_qubits: only needed if ket and/or bra are passed down as integers Returns ------- QubitHamiltonian: a tequila QubitHamiltonian (not necessarily hermitian) representing the KetBra operator desired. """ H = QubitHamiltonian.zero() ket = QubitWaveFunction(state=ket, n_qubits=n_qubits) bra = QubitWaveFunction(state=bra, n_qubits=n_qubits) for k1, v1 in bra.items(): for k2, v2 in ket.items(): c = v1.conjugate() * v2 if not numpy.isclose(c, 0.0, atol=threshold): H += c * decompose_transfer_operator(bra=k1, ket=k2) if hermitian: return H.split()[0] else: return H.simplify(threshold=threshold)
[docs] def decompose_transfer_operator(ket: BitString, bra: BitString, qubits: typing.List[int] = None) -> QubitHamiltonian: """ Notes ---------- Create the operator Note that this is operator is not necessarily hermitian So be careful when using it as a generator for gates e.g. decompose_transfer_operator(ket="01", bra="10", qubits=[2,3]) gives the operator .. math:: \\lvert 01 \\rangle \\langle 10 \\rvert_{2,3} acting on qubits 2 and 3 Parameters ---------- ket: pass an integer, string, or tequila BitString bra: pass an integer, string, or tequila BitString qubits: pass the qubits onto which the operator acts Returns ------- """ opmap = { (0, 0): Qp, (0, 1): Sp, (1, 0): Sm, (1, 1): Qm } nbits = None if qubits is not None: nbits = len(qubits) if isinstance(bra, int): bra = BitString.from_int(integer=bra, nbits=nbits) if isinstance(ket, int): ket = BitString.from_int(integer=ket, nbits=nbits) b_arr = bra.array k_arr = ket.array assert (len(b_arr) == len(k_arr)) n_qubits = len(k_arr) if qubits is None: qubits = range(n_qubits) assert (n_qubits <= len(qubits)) result = QubitHamiltonian.unit() for q, b in enumerate(b_arr): k = k_arr[q] result *= opmap[(k, b)](qubit=qubits[q]) return result