def de_pc_correlator(L, N, i, j): """Build a many-body correlation operator 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) ix_t = binsearch(states, t) C[ix_t, ix_s] += 1 return C
def fcompute_2Pij(basis, state, i, j): """Compute P_ij.""" if i == j: return fcompute_1Di(basis, state, i) out = 0. for ix_s, s in enumerate(basis): if not (s >> i) & 1 and (s >> j) & 1: t = s + (1 << i) - (1 << j) ix_t = binsearch(states, t) out += state[ix_s] * np.conj(state[ix_t]) return out
def fcompute_3Pijk(basis, state, i, j, k): """Compute P^i_jk.""" if i == j or j == k: return 0. elif j == k: return fcompute_2Dij(basis, state, i, j) out = 0. for ix_s, s in enumerate(basis): if (s >> i) & 1 and not (s >> j) & 1 and (s >> k) & 1: t = s + (1 << j) - (1 << k) ix_t = binsearch(states, t) out += state[ix_s] * np.conj(state[ix_t]) return out
def fcompute_4Rijkl(basis, state, i, j, k, l): """Compute P^ij_kl.""" if i == j: return fcompute_3Pijk(basis, state, i, k, l) elif i == k or i == l or j == k or j == l: return 0. elif k == l: return fcompute_2Dijk(basis, state, i, j, k) out = 0. for ix_s, s in enumerate(basis): if (s >> i) & 1 and (s >> j) & 1 and not (s >> k) & 1 and (s >> l) & 1: t = s + (1 << k) - (1 << l) ix_t = binsearch(states, t) out += state[ix_s] * np.conj(state[ix_t]) return out
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
def fcompute_4Pijkl(basis, state, i, j, k, l): """Compute P_ijkl.""" if i == j or k == l: return 0. elif i == k: return fcompute_3Pijk(basis, state, i, j, l) elif i == l: return fcompute_3Pijk(basis, state, i, j, k) elif j == k: return fcompute_3Pijk(basis, state, j, i, l) elif j == l: return fcompute_3Pijk(basis, state, j, i, k) out = 0. for ix_s, s in enumerate(basis): if not (s >> i) & 1 and not (s >> j) & 1 and (s >> k) & 1 and ( s >> l) & 1: t = s + (1 << i) + (1 << j) - (1 << k) - (1 << l) ix_t = binsearch(states, t) out += state[ix_s] * np.conj(state[ix_t]) return out
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
def de_sym_pc_op(L, N, J, D): """Build a many-body symmetric 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): symmetric hopping matrix. D (2darray of floats): interaction matrix. Returns: H (2darray of floats): many-body operator. """ if np.sum(np.abs(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), J.dtype) # 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) ix_t = binsearch(states, t) H[ix_t, ix_s] += J[i, j] H[ix_s, ix_t] += 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
def sp_sym_pc_op(L, N, J, D): """Compute sparse matrix of a symmetric 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) vals[c] += J[i, j] rows[c] += ix_t cols[c] += ix_s c += 1 vals[c] += J[i, j] rows[c] += ix_s cols[c] += ix_t c += 1 return vals, rows, cols, num_states
def test_binary_search(self): """Test generated states for some combinations.""" a = np.arange(0, 18, 2) for i in range(9): ix = binsearch(a, 2*i) self.assertEqual(ix, i)
def fer_sp_pc_op(L, N, J, D): """Compute the sparse CSR data of a mb 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. D (2darray of floats): interaction matrix. Returns: vals, rows, cols: arrays to build the sparse Hamiltonian in CSR format. """ 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(L): if (np.abs(D[i, j]) > 1e-6) and (i != j): if ((s >> i) & np.uint16(1)) and ((s >> j) & np.uint16(1)): vals[c] += 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(L): if (np.abs(J[i, j]) > 1e-6) and (j != i): 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 return vals, rows, cols, num_states