예제 #1
0
def fer_sp_npc_correlator(L, i, j):
    """Compute sparse number nonconserving fermionic correlator.

    Args:
        L (int): system's length.
        J (2darray of floats): hopping matrix. Must be symmetric.
        D (2darray of floats): interaction matrix. Must be symmetric.
        r (1darray of floats): raise matrix. Operator: b^dagger_i.
        l (1darray of floats): lower matrix. Operator: b_i.

    Returns:
        vals, rows, cols: arrays to build the sparse Hamiltonian in
            CSR format.

    """
    if i == j:
        raise ValueError('i and j must be different.')

    num_states = 1 << L
    number_nnz_vals = 1 << (L - 2)
    vals = np.zeros(number_nnz_vals, dtype=np.float64)
    rows = np.zeros(number_nnz_vals, dtype=np.int32)
    cols = np.zeros(number_nnz_vals, dtype=np.int32)

    c = 0
    for s in range(num_states):
        if (not (s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)):
            t = s + (1 << i) - (1 << j)
            par = get_parity(s, i, j)
            vals[c] += par
            rows[c] = t
            cols[c] = s
            c += 1

    return vals, rows, cols, num_states
예제 #2
0
def fer_de_npc_correlator(L, i, j):
    """Build a dense number nonconserving fermionic correlation.

    Args:
        L (int): system's length.
        i (int): site of the creation operator.
        j (int): site of the annihilation operator.

    Returns:
        C (2darray of floats): many-body correlation operator.

    """
    if i == j:
        raise ValueError('i and j must be different.')

    num_states = 1 << L

    C = np.zeros((num_states, num_states), np.float64)

    for s in range(1 << L):
        if not (s >> i) & 1 and (s >> j) & 1:
            t = s + (1 << i) - (1 << j)
            par = get_parity(t, i, j)
            C[t, s] += par

    return C
예제 #3
0
def fer_de_pc_correlator(L, N, i, j):
    """Build a many-body fermionic correlation b^dagger_i*b_j, i != j.

    Args:
        L (int): system's length.
        N (int): particle number.
        i (int): position of the creation operator.
        j (int): position of the annihilation operator.

    Returns:
        C (2darray of floats): many-body corelation operator.

    """
    if i == j:
        raise ValueError('i and j must be different.')

    states = generate_states(L, N)
    num_states = states.size

    C = np.zeros((num_states, num_states), np.float64)

    for ix_s, s in enumerate(states):
        if (not (s >> i) & 1) and ((s >> j) & 1):
            t = s + (1 << i) - (1 << j)
            par = get_parity(t, i, j)
            ix_t = binsearch(states, t)
            C[ix_t, ix_s] += par

    return C
예제 #4
0
def fer_de_npc_op(L, J, D, r, l):
    """Build a dense fermionic number nonconserving operator.

    Args:
        L (int): system's length.
        J (2darray of floats): hopping matrix.
        D (2darray of floats): interaction matrix.
        r (1darray of floats): raising operator: b^dagger_i.
        l (1darray of floats): lowering operator: b_i.

    Returns:
        H (2darray of floats): many-body operator.

    """
    num_states = 1 << L

    H = np.zeros((num_states, num_states), np.float64)

    # Notation:
    #     s: initial state.
    #     t: final state.
    for s in range(1 << L):
        for i in range(L):
            # On-site terms: n_i.
            if (s >> i) & 1:
                H[s, s] += J[i, i]

            for j in range(L):
                if i != j:
                    # Hopping terms: b^dagger_i*b_j.
                    if not (s >> i) & 1 and (s >> j) & 1:
                        t = s + (1 << i) - (1 << j)
                        par = get_parity(t, i, j)
                        H[t, s] += par * J[i, j]

                    # Interaction terms: n_i*n_j.
                    if (s >> i) & 1 and (s >> j) & 1:
                        H[s, s] += D[i, j]

            # Raising operator: b^dagger_i.
            if not (s >> i) & 1:
                t = s + (1 << i)
                par = get_parity_at_i(t, i)
                H[t, s] += par * r[i]

            # Lowering operator: b_i.
            if (s >> i) & 1:
                t = s - (1 << i)
                par = get_parity_at_i(t, i)
                H[t, s] += par * l[i]

    return H
예제 #5
0
 def test_parity_between_i_and_j(self):
     """Test parity of states between two sites."""
     self.assertEqual(get_parity(1, 0, 1), 1)
     self.assertEqual(get_parity(3, 2, 0), -1)
     self.assertEqual(get_parity(15, 0, 4), -1)
     self.assertEqual(get_parity(15, 1, 4), 1)
     self.assertEqual(get_parity(15, 2, 4), -1)
     self.assertEqual(get_parity(15, 3, 4), 1)
예제 #6
0
def fer_de_pc_op(L, N, J, D):
    """Build a many-body fermionic operator with 2-particle terms.

    Note: numba parallel does not support the enumerate function, which
    would make the code more readable. Instead we have to make the
    outermost loops with range().

    Args:
        L (int): system's length.
        N (int): particle number.
        J (2darray of floats): hopping matrix.
        D (2darray of floats): interaction matrix.

    Returns:
        H (2darray of floats): many-body operator.

    """
    states = generate_states(L, N)
    num_states = states.size

    H = np.zeros((num_states, num_states), np.float64)

    # Notation:
    #     s: initial state.
    #     t: final state.
    #     ix_#: index of #.
    for ix_s in prange(num_states):
        s = states[ix_s]
        for i in range(L):
            # On-site terms: n_i.
            if (s >> i) & 1:
                H[ix_s, ix_s] += J[i, i]

            for j in range(L):
                if i != j:
                    # Hopping terms: b^dagger_i b_j.
                    if not (s >> i) & 1 and (s >> j) & 1:
                        t = s + (1 << i) - (1 << j)
                        par = get_parity(t, i, j)
                        ix_t = binsearch(states, t)
                        H[ix_t, ix_s] += par * J[i, j]

                    # Interaction terms: n_i n_j.
                    if (s >> i) & 1 and (s >> j) & 1:
                        H[ix_s, ix_s] += D[i, j]

    return H
예제 #7
0
def fer_sp_pc_correlator(L, N, i, j):
    """Build a many-body fermionic correlation c^dagger_i*c_j, i != j.

    Args:
        L (int): system's length.
        N (int): particle number.
        i (int): position of the creation operator.
        j (int): position of the annihilation operator.

    Returns:
        vals, rows, cols (1darray of floats): sparse
            representation of the mb corelation operator.
        num_states (int): number of states.

    """
    if i == j:
        raise ValueError('i and j must be different.')

    states = generate_states(L, N)
    num_states = states.size

    number_nnz_vals = binom(L - 2, N - 1)
    vals = np.zeros(number_nnz_vals, dtype=np.float64)
    rows = np.zeros(number_nnz_vals, dtype=np.int32)
    cols = np.zeros(number_nnz_vals, dtype=np.int32)

    c = 0
    for ix_s, s in enumerate(states):
        if (not (s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)):
            t = s + (1 << i) - (1 << j)
            ix_t = binsearch(states, t)
            par = get_parity(s, i, j)
            vals[c] += par
            rows[c] = ix_t
            cols[c] = ix_s
            c += 1

    return vals, rows, cols, num_states
예제 #8
0
def fer_sp_sym_pc_op(L, N, J, D):
    """Compute sparse matrix of a symmetric fermionic operator.

    Note: if jit is not used, the loops must be done with np.arange
        because the 'right shift' function (>>) needs to operate with
        two uint's as types. Example:
        >>> for i in np.arange(L, dtype=np.uint16):

    Args:
        L (int): system's length.
        N (int): particle number.
        J (2darray of floats): hopping matrix. Must be symmetric.
        D (2darray of floats): interaction matrix.

    Returns:
        vals, rows, cols: arrays to build the sparse Hamiltonian in
            CSR format.

    """
    if (np.sum((J - J.T)**2) > 1e-7) or (np.sum((D - D.T)**2) > 1e-7):
        raise ValueError('J and/or D is not symmetric.')

    states = generate_states(L, N)
    num_states = states.size

    number_nnz_vals = binom(
        L, N) + count_nnz_off_diagonal(J) * binom(L - 2, N - 1)
    vals = np.zeros(number_nnz_vals, dtype=np.float64)
    rows = np.zeros(number_nnz_vals, dtype=np.int32)
    cols = np.zeros(number_nnz_vals, dtype=np.int32)

    # Notation:
    #     s: initial state.
    #     t: final state.
    #     ix_#: index of #.
    c = 0
    for ix_s, s in enumerate(states):
        # On-site terms: n_i.
        for i in range(L):
            if ((s >> i) & np.uint16(1)):
                vals[c] += J[i, i]

        # Interaction terms: n_i*n_j.
        for i in range(L):
            for j in range(i):  # j < i.
                if (np.abs(D[i, j]) > 1e-7):
                    if ((s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)):
                        vals[c] += 2 * D[i, j]

        cols[c] += ix_s
        rows[c] += ix_s
        c += 1

        # Hopping terms: b^dagger_i*b_j.
        for i in range(L):
            for j in range(i):
                if (np.abs(J[i, j]) > 1e-7):
                    if (not (s >> i) & np.uint16(1)) and ((s >> j)
                                                          & np.uint16(1)):
                        t = s + (1 << i) - (1 << j)
                        ix_t = binsearch(states, t)
                        par = get_parity(s, i, j)
                        vals[c] += par * J[i, j]
                        rows[c] += ix_t
                        cols[c] += ix_s
                        c += 1
                        vals[c] += par * J[i, j]
                        rows[c] += ix_s
                        cols[c] += ix_t
                        c += 1

    return vals, rows, cols, num_states
예제 #9
0
def fer_sp_npc_op(L, J, D, r, l):
    """Compute sparse matrix of number nonconserving fermionic operator.

    Args:
        L (int): system's length.
        J (2darray of floats): hopping matrix. Must be symmetric.
        D (2darray of floats): interaction matrix. Must be symmetric.
        r (1darray of floats): raise matrix. Operator: b^dagger_i.
        l (1darray of floats): lower matrix. Operator: b_i.

    Returns:
        vals, rows, cols: arrays to build the sparse Hamiltonian in
            CSR format.

    """
    num_states = 1 << L
    number_nnz_vals = (num_states + count_nnz_off_diagonal(J) * (1 <<
                                                                 (L - 2)) +
                       r.size * (1 << (L - 1)) + l.size * (1 << (L - 1)))
    vals = np.zeros(number_nnz_vals, dtype=np.float64)
    rows = np.zeros(number_nnz_vals, dtype=np.int32)
    cols = np.zeros(number_nnz_vals, dtype=np.int32)

    # Notation:
    #     s: initial state.
    #     t: final state.
    #     ix_#: index of #.
    c = 0
    for s in range(num_states):
        # On-site terms: n_i.
        for i in range(L):
            if ((s >> i) & np.uint16(1)):
                vals[c] += J[i, i]

        # Interaction terms: n_i*n_j.
        for i in range(L):
            for j in range(i):  # j < i.
                if (np.abs(D[i, j]) > 1e-7):
                    if ((s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)):
                        vals[c] += D[i, j]

                if (np.abs(D[j, i]) > 1e-7):
                    if ((s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)):
                        vals[c] += D[j, i]

        cols[c] = s
        rows[c] = s
        c += 1

        # Hopping terms: b^dagger_i*b_j.
        for i in range(L):
            for j in range(i):  # j < i.
                if (np.abs(J[i, j]) > 1e-7):
                    if (not (s >> i) & np.uint16(1)) and ((s >> j)
                                                          & np.uint16(1)):
                        t = s + (1 << i) - (1 << j)
                        par = get_parity(s, i, j)
                        vals[c] += par * J[i, j]
                        rows[c] = t
                        cols[c] = s
                        c += 1

                if (np.abs(J[j, i]) > 1e-7):
                    if (not (s >> j) & np.uint16(1)) and ((s >> i)
                                                          & np.uint16(1)):
                        t = s + (1 << j) - (1 << i)
                        par = get_parity(s, i, j)
                        vals[c] += par * J[j, i]
                        rows[c] = t
                        cols[c] = s
                        c += 1

        # Raising terms: b^dagger_i.
        for i in range(L):
            if (np.abs(r[i]) > 1e-7):
                if not (s >> i) & np.uint16(1):
                    t = s + (1 << i)
                    par = get_parity_at_i(s, i)
                    vals[c] += par * r[i]
                    rows[c] = t
                    cols[c] = s
                    c += 1

        # Lowering terms: b_i.
        for i in range(L):
            if (np.abs(l[i]) > 1e-7):
                if (s >> i) & np.uint16(1):
                    t = s - (1 << i)
                    par = get_parity_at_i(s, i)
                    vals[c] += par * l[i]
                    rows[c] = t
                    cols[c] = s
                    c += 1

    return vals, rows, cols, num_states
예제 #10
0
def fer_de_sym_pc_op(L, N, J, D):
    """Build a many-body symmetric fermionic operator.

    Note: numba parallel does not support the enumerate function, which
    would make the code more readable. Instead we have to make the
    outermost loops with range().

    Args:
        L (int): system's length.
        N (int): particle number.
        J (2darray of floats): symmetric hopping matrix.
        D (2darray of floats): interaction matrix.

    Returns:
        H (2darray of floats): many-body operator.

    """
    if np.sum((J - J.T)**2) > 1e-7:
        raise ValueError('J is not symmetric.')

    # Put all elts of D in the lower triangle. Making a copy of D
    # instead of working with views prevents the function to make
    # changes in D outside of it.
    D = np.copy(D)
    for i in range(L):
        for j in range(i):  # j < i.
            D[i, j] += D[j, i]
            D[j, i] = 0

    states = generate_states(L, N)
    num_states = states.size

    H = np.zeros((num_states, num_states), np.float64)

    # Notation:
    #     s: initial state.
    #     t: final state.
    #     ix_#: index of #.
    for ix_s in prange(num_states):
        s = states[ix_s]
        for i in range(L):
            # On-site terms: n_i.
            if np.abs(J[i, i]) > 1e-7:
                if (s >> i) & 1:
                    H[ix_s, ix_s] += J[i, i]

            for j in range(i):
                # Hopping terms: b^dagger_i b_j.
                if np.abs(J[i, j]) > 1e-7:
                    if not (s >> i) & 1 and (s >> j) & 1:
                        t = s + (1 << i) - (1 << j)
                        par = get_parity(t, i, j)
                        ix_t = binsearch(states, t)
                        H[ix_t, ix_s] += par * J[i, j]
                        H[ix_s, ix_t] += par * J[i, j]

                # Interaction terms: n_i n_j.
                if np.abs(D[i, j]) > 1e-7:
                    if (s >> i) & 1 and (s >> j) & 1:
                        H[ix_s, ix_s] += D[i, j]

    return H