def test_some_generated_states(self): """Test generated states for some combinations.""" s = np.array([3, 5, 6, 9, 10, 12]) self.assertTrue(np.allclose(generate_states(4, 2), s)) s = np.array([7, 11, 13, 14]) self.assertTrue(np.allclose(generate_states(4, 3), s)) s = np.array([7, 11, 13, 14, 19, 21, 22, 25, 26, 28]) self.assertTrue(np.allclose(generate_states(5, 3), s))
def compute_entropy_singular_vals(psi, L, N, i): """Compute the singular values of a state decomposition. We divide the state in two parts given by a position and then do return its singular values. The space partition is done between i-1 and i. Args: psi (1darray of floats): state vector. L (int): number of lattice sites. N (int): number of particles. i (int): position where the state is partitioned. Returns: svals (1darray of floats): singular values. """ svals = None # States in the whole lattice with N particles. states = generate_states(L, N) # Get the maximum and minimum number of particles that fit in the # subspace 0 to i-1 (both inclusive). num_min = N - min(L-i, N) num_max = min(i, N) for n in range(num_min, num_max+1): # Generate all states in the interval (0, i-1) with n # particles. a_states = generate_states(i, n) num_a_states = a_states.size # Generate all states in the interval (i, L-1) with N-n # particles. b_states = generate_states(L-i, N-n) num_b_states = b_states.size A = np.zeros((num_a_states, num_b_states), dtype=np.float64) for ia, a in enumerate(a_states): for ib, b in enumerate(b_states): # Tensor multiply a and b to produce a state in (0, L). ab = np.left_shift(a, L-i) + b A[ia, ib] = psi[np.nonzero(states == ab)] if n == num_min: svals = svdvals(A) else: svals = np.concatenate((svals, svdvals(A))) return svals
def de_pc_interaction(L, N, i, j): """Build the many-body operator n_i n_j. Args: L (int): system's length. N (int): particle number. i (int): position of the first number operator. j (int): position of the second number operator. Returns: D (1darray of floats): many-body density operator. """ if i == j: raise ValueError('i and j must be different.') states = generate_states(L, N) num_states = states.size D = np.zeros(num_states, np.float64) for ix_s, s in enumerate(states): if (s>>i)&1 and (s>>j)&1: D[ix_s] += 1 return D
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 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 de_pc_number_op(L, N, i): """Build a many-body number operator n_i. Args: L (int): system's length. N (int): particle number. i (int): position of the number operator. Returns: C (1darray of floats): many-body corelation operator. """ states = generate_states(L, N) num_states = states.size C = np.zeros(num_states, np.float64) for ix_s, s in enumerate(states): if (s>>i)&1: C[ix_s] += 1 return C
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 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