Applying symmetries to reduce user-imported bases using QuSpin
This example makes use of the user_basis class to take a user-imported basis consisting of ordered integers, and apply QuSpin to reduce it to a desired symmetry sector.
In the example, we manually construct the basis for a two-legged ladder (and handle it as a user-imported array of basis states). Then we use QuSpin to apply translation and parity (reflection) symmetries to reduce the Hilbert space dimension.
Please consult this post – user_basis tutorial – for more detailed explanations on using the user_basis class.
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###########################################################################
12# example 16 #
13# In this script we demonstrate how to apply the user_basis to reduce #
14# user-imported arrays of bases states (in integer representation) #
15# to user-defined symmetry-reduced subspaces. #
16###########################################################################
17from quspin.basis import (
18 spin_basis_1d,
19 spin_basis_general,
20) # Hilbert space spin basis_1d
21from quspin.basis.user import user_basis # Hilbert space user basis
22from quspin.basis.user import (
23 next_state_sig_32,
24 op_sig_32,
25 map_sig_32,
26) # user basis data types
27from numba import carray, cfunc # numba helper functions
28from numba import uint32, int32 # numba data types
29import numpy as np
30from scipy.special import comb
31
32#
33#####
34N_half = 10 # number of sites for each leg of the ladder
35N = 2 * N_half # total number of lattice sites
36
37
38#
39def make_basis(N_half):
40 """Generates a list of integers to represent external, user-imported basis"""
41 old_basis = spin_basis_general(N_half, m=0)
42 #
43 states = old_basis.states
44 shift_states = np.left_shift(states, N_half)
45 #
46 shape = states.shape + states.shape
47 #
48 states_b = np.broadcast_to(states, shape)
49 shift_states_b = np.broadcast_to(shift_states, shape)
50 # this does the kronecker sum in a more memory efficient way.
51 return (states_b + shift_states_b.T).ravel()
52
53
54#
55external_basis = make_basis(N_half)
56#
57Np = () # dummy argument, could be any value (particle conservation should've been
58
59
60# taken into account when constructing the basis object)
61#
62###### function to call when applying operators
63@cfunc(op_sig_32, locals=dict(s=int32, b=uint32))
64def op(op_struct_ptr, op_str, ind, N, args):
65 # using struct pointer to pass op_struct_ptr back to C++ see numba Records
66 op_struct = carray(op_struct_ptr, 1)[0]
67 err = 0
68 ind = N - ind - 1 # convention for QuSpin for mapping from bits to sites.
69 s = (((op_struct.state >> ind) & 1) << 1) - 1
70 b = 1 << ind
71 #
72 if op_str == 120: # "x" is integer value 120 (check with ord("x"))
73 op_struct.state ^= b
74 elif op_str == 121: # "y" is integer value 120 (check with ord("y"))
75 op_struct.state ^= b
76 op_struct.matrix_ele *= 1.0j * s
77 elif op_str == 122: # "z" is integer value 120 (check with ord("z"))
78 op_struct.matrix_ele *= s
79 else:
80 op_struct.matrix_ele = 0
81 err = -1
82 #
83 return err
84
85
86#
87op_args = np.array([], dtype=np.uint32)
88
89
90#
91###### function to read user-imported basis into QuSpin
92#
93# function to call when generating next_state
94@cfunc(next_state_sig_32)
95def next_state(s, counter, N, args):
96 # return pre-calculated basis state.
97 # add one to counter because the first state is already checked.
98 return args[counter + 1] # = basis
99
100
101#
102next_state_args = (
103 external_basis # this has to be an array of same dtype as the user_basis
104)
105
106
107#
108class function_wrapper(object):
109 """
110 This class provides a wrapper for the user-imported basis,
111 as well as the functions required for the `user_basis` functionality.
112 #
113 This is needed to easily pass parameters (defined as class attributes) to the
114 functions `get_so_pcon()` and `get_Ns_pcon`.
115 """
116
117 def __init__(self, basis):
118 self.basis = basis
119
120 #
121 # python function to calculate the starting state to generate the particle conserving basis
122 def get_s0_pcon(self, N, Np):
123 """calculates the starting state to generate the particle conserving basis."""
124 # ignore input arguments as basis is already calculated.
125 return self.basis[0]
126
127 #
128 # python function to calculate the size of the particle-conserved basis,
129 # i.e. BEFORE applying pre_check_state and symmetry maps
130 def get_Ns_pcon(self, N, Np):
131 """calculates the size of the particle conservation basis (ignoring symmetries at this stage)."""
132 # ignore input arguments as basis is already calculated.
133 return self.basis.size
134
135
136#
137###### define symmetry maps
138#
139if N_half != 10:
140 print(
141 "symmetry masks are hard-coded and work only for N=10; \n\
142To do a different system size, it is required to update the masks accordingly.\n\
143exiting..."
144 )
145 exit()
146
147
148#
149@cfunc(map_sig_32)
150def translation(x, N, sign_ptr, args):
151 # bit permutation target bits: 1 2 3 4 5 6 7 8 9 0 11 12 13 14 15 16 17 18 19 10
152 # code generated here: http://programming.sirrida.de/calcperm.php
153 # only works for N_half=10
154 return ((x & 0x0007FDFF) << 1) | ((x & 0x00080200) >> 9)
155
156
157T_args = np.array([], dtype=np.uint32)
158
159
160#
161@cfunc(map_sig_32)
162def parity(x, N, sign_ptr, args):
163 # bit permutation target bits: 9 8 7 6 5 4 3 2 1 0 19 18 17 16 15 14 13 12 11 10
164 # code generated here: http://programming.sirrida.de/calcperm.php
165 # only works for N_half=10
166 return (
167 ((x & 0x00004010) << 1)
168 | ((x & 0x00002008) << 3)
169 | ((x & 0x00001004) << 5)
170 | ((x & 0x00000802) << 7)
171 | ((x & 0x00000401) << 9)
172 | ((x & 0x00080200) >> 9)
173 | ((x & 0x00040100) >> 7)
174 | ((x & 0x00020080) >> 5)
175 | ((x & 0x00010040) >> 3)
176 | ((x & 0x00008020) >> 1)
177 )
178
179
180P_args = np.array([], dtype=np.uint32)
181#
182###### construct user_basis
183# define maps dict
184maps = dict(
185 T_block=(translation, N_half, 0, T_args),
186 P_block=(parity, 2, 0, P_args),
187)
188# define particle conservation and op dicts
189FW = function_wrapper(external_basis)
190pcon_dict = dict(
191 Np=Np,
192 next_state=next_state,
193 next_state_args=next_state_args,
194 get_Ns_pcon=FW.get_Ns_pcon,
195 get_s0_pcon=FW.get_s0_pcon,
196)
197op_dict = dict(op=op, op_args=op_args)
198# create user basis
199basis = user_basis(
200 np.uint32, N, op_dict, allowed_ops=set("xyz"), sps=2, pcon_dict=pcon_dict, **maps
201)
202# print basis
203print(basis)