Exemplo n.º 1
0
 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))
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
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
Exemplo n.º 6
0
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
Exemplo n.º 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
Exemplo n.º 8
0
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
Exemplo n.º 10
0
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