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
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
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
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
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)
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 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 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
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
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