spin-1/2 Heisenberg model in 1d

This example makes use of the user_basis class of basis_general to show how to construct spin-1/2 systems with symmetries.

Please consult this post: user_basis tutorial, for more detailed explanations for using the user_basis class.

Script

download script

  1#
  2import sys, os
  3
  4os.environ["KMP_DUPLICATE_LIB_OK"] = (
  5    "True"  # uncomment this line if omp error occurs on OSX for python 3
  6)
  7os.environ["OMP_NUM_THREADS"] = "1"  # set number of OpenMP threads to run in parallel
  8os.environ["MKL_NUM_THREADS"] = "1"  # set number of MKL threads to run in parallel
  9#
 10
 11#
 12from quspin.operators import hamiltonian  # Hamiltonians and operators
 13from quspin.basis import spin_basis_1d  # Hilbert space spin basis_1d
 14from quspin.basis.user import user_basis  # Hilbert space user basis
 15from quspin.basis.user import (
 16    next_state_sig_32,
 17    op_sig_32,
 18    map_sig_32,
 19    count_particles_sig_32,
 20)  # user basis data types signatures
 21from numba import carray, cfunc  # numba helper functions
 22from numba import uint32, int32  # numba data types
 23import numpy as np
 24from scipy.special import comb
 25
 26#
 27N = 6  # lattice sites
 28Np = N // 2  # total number of spin ups
 29
 30
 31#
 32############   create spin-1/2 user basis object   #############
 33#
 34######  function to call when applying operators
 35@cfunc(
 36    op_sig_32,
 37    locals=dict(s=int32, n=int32, b=uint32),
 38)
 39def op(op_struct_ptr, op_str, site_ind, N, args):
 40    # using struct pointer to pass op_struct_ptr back to C++ see numba Records
 41    op_struct = carray(op_struct_ptr, 1)[0]
 42    err = 0
 43    #
 44    site_ind = N - site_ind - 1  # convention for QuSpin for mapping from bits to sites.
 45    n = (op_struct.state >> site_ind) & 1  # either 0 or 1
 46    s = (((op_struct.state >> site_ind) & 1) << 1) - 1  # either -1 or 1
 47    b = 1 << site_ind
 48    #
 49    if op_str == 120:  # "x" is integer value 120 = ord("x")
 50        op_struct.state ^= b
 51
 52    elif op_str == 121:  # "y" is integer value 120 = ord("y")
 53        op_struct.state ^= b
 54        op_struct.matrix_ele *= 1.0j * s
 55
 56    elif op_str == 43:  # "+" is integer value 43 = ord("+")
 57        if n:
 58            op_struct.matrix_ele = 0
 59        else:
 60            op_struct.state ^= b  # create spin
 61
 62    elif op_str == 45:  # "-" is integer value 45 = ord("-")
 63        if n:
 64            op_struct.state ^= b  # destroy spin
 65        else:
 66            op_struct.matrix_ele = 0
 67
 68    elif op_str == 122:  # "z" is integer value 120 = ord("z")
 69        op_struct.matrix_ele *= s
 70
 71    elif op_str == 110:  # "n" is integer value 110 = ord("n")
 72        op_struct.matrix_ele *= n
 73
 74    elif op_str == 73:  # "I" is integer value 73 = ord("I")
 75        pass
 76
 77    else:
 78        op_struct.matrix_ele = 0
 79        err = -1
 80    #
 81    return err
 82
 83
 84op_args = np.array([], dtype=np.uint32)
 85
 86
 87#
 88######  function to implement magnetization/particle conservation
 89#
 90@cfunc(
 91    next_state_sig_32,
 92    locals=dict(t=uint32),
 93)
 94def next_state(s, counter, N, args):
 95    """implements magnetization conservation."""
 96    if s == 0:
 97        return s
 98    #
 99    t = (s | (s - 1)) + 1
100    return t | ((((t & (0 - t)) // (s & (0 - s))) >> 1) - 1)
101
102
103next_state_args = np.array([], dtype=np.uint32)  # compulsory, even if empty
104
105
106# python function to calculate the starting state to generate the particle conserving basis
107def get_s0_pcon(N, Np):
108    return sum(1 << i for i in range(Np))
109
110
111# python function to calculate the size of the particle-conserved basis,
112# i.e. BEFORE applying pre_check_state and symmetry maps
113def get_Ns_pcon(N, Np):
114    return comb(N, Np, exact=True)
115
116
117#
118######  define symmetry maps
119#
120@cfunc(
121    map_sig_32,
122    locals=dict(
123        shift=uint32,
124        xmax=uint32,
125        x1=uint32,
126        x2=uint32,
127        period=int32,
128        l=int32,
129    ),
130)
131def translation(x, N, sign_ptr, args):
132    """works for all system sizes N."""
133    shift = args[0]  # translate state by shift sites
134    period = N  # periodicity/cyclicity of translation
135    xmax = args[1]
136    #
137    l = (shift + period) % period
138    x1 = x >> (period - l)
139    x2 = (x << l) & xmax
140    #
141    return x2 | x1
142
143
144T_args = np.array([1, (1 << N) - 1], dtype=np.uint32)
145
146
147#
148@cfunc(
149    map_sig_32,
150    locals=dict(
151        out=uint32,
152        s=int32,
153    ),
154)
155def parity(x, N, sign_ptr, args):
156    """works for all system sizes N."""
157    out = 0
158    s = args[0]  # N-1
159    #
160    out ^= x & 1
161    x >>= 1
162    while x:
163        out <<= 1
164        out ^= x & 1
165        x >>= 1
166        s -= 1
167    #
168    out <<= s
169    return out
170
171
172P_args = np.array([N - 1], dtype=np.uint32)
173
174
175#
176@cfunc(
177    map_sig_32,
178    locals=dict(
179        xmax=uint32,
180    ),
181)
182def spin_inversion(x, N, sign_ptr, args):
183    """works for all system sizes N."""
184    xmax = args[0]  # maximum integer
185    return x ^ xmax
186
187
188Z_args = np.array([(1 << N) - 1], dtype=np.uint32)
189
190
191#
192######  define function to count particles in bit representation
193#
194@cfunc(count_particles_sig_32, locals=dict(s_count=uint32))
195def count_particles(x, p_number_ptr, args):
196    """Counts number of particles/spin-ups in a state stored in integer representation for up to N=32 sites"""
197    #
198    s_count = x & ((0x7FFFFFFF) >> (31 - args[0]))
199    s_count = s_count - ((s_count >> 1) & 0x55555555)
200    s_count = (s_count & 0x33333333) + ((s_count >> 2) & 0x33333333)
201    s_count = (((s_count + (s_count >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24
202    #
203    p_number_ptr[0] = s_count
204
205
206n_sectors = 1  # number of particle sectors
207count_particles_args = np.array([N], dtype=np.int32)
208#
209######  construct user_basis
210# define maps dict
211maps = dict(
212    T_block=(translation, N, 0, T_args),
213    P_block=(parity, 2, 0, P_args),
214    Z_block=(spin_inversion, 2, 0, Z_args),
215)
216# define particle conservation and op dicts
217pcon_dict = dict(
218    Np=Np,
219    next_state=next_state,
220    next_state_args=next_state_args,
221    get_Ns_pcon=get_Ns_pcon,
222    get_s0_pcon=get_s0_pcon,
223    count_particles=count_particles,
224    count_particles_args=count_particles_args,
225    n_sectors=n_sectors,
226)
227op_dict = dict(op=op, op_args=op_args)
228# create user basis
229basis = user_basis(
230    np.uint32,
231    N,
232    op_dict,
233    allowed_ops=set("+-xyznI"),
234    sps=2,
235    pcon_dict=pcon_dict,
236    **maps,
237)
238#
239#
240#
241############   create same spin-1/2 basis_1d object   #############
242basis_1d = spin_basis_1d(N, Nup=Np, pauli=True, kblock=0, pblock=1, zblock=1)  #
243#
244#
245print(basis)
246print(basis_1d)
247#
248############   create and compare Hamiltonians   #############
249#
250J = 1.0
251spin_spin = [[J, j, (j + 1) % N] for j in range(N)]
252static = [["xx", spin_spin], ["yy", spin_spin], ["zz", spin_spin]]
253dynamic = []
254#
255no_checks = dict(check_symm=False, check_pcon=False, check_herm=False)
256H = hamiltonian(static, [], basis=basis, dtype=np.float64, **no_checks)
257H_1d = hamiltonian(static, [], basis=basis_1d, dtype=np.float64)
258print(H.toarray())
259print(H_1d.toarray())
260print(np.linalg.norm((H - H_1d).toarray()))