Coverage for gamdpy/simulation_boxes/lees_edwards.py: 15%

143 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3""" 

4Created on Thu Jan 30 13:49:08 2025 

5 

6@author: nbailey 

7""" 

8 

9import numpy as np 

10import numba 

11from numba import cuda 

12import math 

13from .simulationbox import SimulationBox 

14 

15 

16class LeesEdwards(SimulationBox): 

17 """ Simulation box class with LeesEdwards bondary conditions 

18 

19 Example 

20 ------- 

21 

22 >>> import gamdpy as gp 

23 >>> import numpy as np 

24 >>> simbox = gp.LeesEdwards(D=3, lengths=np.array([3,4,5]), box_shift=1.0) 

25 

26 """ 

27 def __init__(self, D, lengths, box_shift=0., box_shift_image=0): 

28 if D < 2: 

29 raise ValueError("Cannot use LeesEdwards with dimension smaller than 2") 

30 self.D = D 

31 self.box_shift = box_shift 

32 self.box_shift_image = np.float32(box_shift_image) 

33 self.data_array = np.zeros(D+2, dtype=np.float32) 

34 self.data_array[:D] = np.array(lengths, dtype=np.float32) 

35 self.data_array[D] = self.box_shift 

36 self.data_array[D+1] = self.box_shift_image 

37 self.len_sim_box_data = D+2 

38 

39 return 

40 

41 def get_name(self): 

42 return "LeesEdwards" 

43 

44 def copy_to_device(self): 

45 self.d_data = cuda.to_device(self.data_array) 

46 

47 

48 def copy_to_host(self): 

49 D = self.D 

50 self.data_array = self.d_data.copy_to_host() 

51 self.box_shift = self.data_array[D] 

52 self.boxshift_image = self.data_array[D+1] 

53 

54 def get_volume_function(self): 

55 D = self.D 

56 def volume(sim_box): 

57 ''' Returns volume of the rectangular box ''' 

58 vol = sim_box[0] 

59 for i in range(1,D): 

60 vol *= sim_box[i] 

61 return vol 

62 return volume 

63 

64 def get_lengths(self): 

65 return self.data_array[:self.D].copy() 

66 

67 def get_volume(self): 

68 #self.copy_to_host() 

69 return self.get_volume_function()(self.data_array[:self.D]) 

70 

71 def scale(self, scale_factor): 

72 self.data_array[:self.D] *= scale_factor 

73 

74 

75 def get_dist_sq_dr_function(self): 

76 """Generates function dist_sq_dr which computes displacement and distance for one neighbor """ 

77 D = self.D 

78 

79 def dist_sq_dr_function(ri, rj, sim_box, dr): 

80 ''' Returns the squared distance between ri and rj applying MIC and saves ri-rj in dr ''' 

81 box_shift = sim_box[D] 

82 for k in range(D): 

83 dr[k] = ri[k] - rj[k] 

84 

85 dist_sq = numba.float32(0.0) 

86 box_1 = sim_box[1] 

87 dr[0] += (-box_shift if numba.float32(2.0) * dr[1] > +box_1 else 

88 (+box_shift if numba.float32(2.0) * dr[1] < -box_1 else 

89 numba.float32(0.0))) 

90 

91 for k in range(D): 

92 box_k = sim_box[k] 

93 dr[k] += (-box_k if numba.float32(2.0) * dr[k] > +box_k else 

94 (+box_k if numba.float32(2.0) * dr[k] < -box_k else numba.float32(0.0))) # MIC 

95 dist_sq = dist_sq + dr[k] * dr[k] 

96 return dist_sq 

97 

98 return dist_sq_dr_function 

99 

100 def get_dist_sq_function(self): 

101 

102 D = self.D 

103 def dist_sq_function(ri, rj, sim_box): 

104 ''' Returns the squared distance between ri and rj applying MIC''' 

105 box_shift = sim_box[D] 

106 dist_sq = numba.float32(0.0) 

107 

108 # first shift the x-component depending on whether the y-component is wrapped 

109 dr1 = ri[1] - rj[1] 

110 box_1 = sim_box[1] 

111 x_shift = (-box_shift if numba.float32(2.0) * dr1 > box_1 else 

112 (+box_shift if numba.float32(2.0) * dr1 < -box_1 else 

113 numba.float32(0.0))) 

114 # then wrap as usual for all components 

115 for k in range(D): 

116 dr_k = ri[k] - rj[k] 

117 if k == 0: 

118 dr_k += x_shift 

119 box_k = sim_box[k] 

120 dr_k += (-box_k if numba.float32(2.0) * dr_k > +box_k else 

121 (+box_k if numba.float32(2.0) * dr_k < -box_k else numba.float32(0.0))) # MIC 

122 dist_sq = dist_sq + dr_k * dr_k 

123 return dist_sq 

124 return dist_sq_function 

125 

126 def get_apply_PBC(self): 

127 D = self.D 

128 def apply_PBC(r, image, sim_box): 

129 

130 # first shift the x-component depending on whether the y-component is outside the box 

131 # note: assumes at most one box length needs to be added/subtracted. 

132 box_shift, bs_image = sim_box[D], int(sim_box[D+1]) 

133 box1_half = sim_box[1] * numba.float32(0.5) 

134 if r[1] > + box1_half: 

135 r[0] -= box_shift 

136 image[0] -= bs_image 

137 if r[1] < -box1_half: 

138 r[0] += box_shift 

139 image[0] += bs_image 

140 # then put everything back in the box as usual 

141 for k in range(D): 

142 if r[k] * numba.float32(2.0) > +sim_box[k]: 

143 r[k] -= sim_box[k] 

144 image[k] += 1 

145 if r[k] * numba.float32(2.0) < -sim_box[k]: 

146 r[k] += sim_box[k] 

147 image[k] -= 1 

148 return 

149 return apply_PBC 

150 

151 def get_update_box_shift(self): 

152 D = self.D 

153 def update_box_shift(sim_box, shift): 

154 # carry out the addition in double precision 

155 sim_box[D] = numba.float32(sim_box[D] + numba.float64(shift)) 

156 Lx = sim_box[0] 

157 Lx_half = Lx*numba.float32(0.5) 

158 if sim_box[D] > +Lx_half: 

159 sim_box[D] -= Lx 

160 sim_box[D+1] += 1 

161 if sim_box[D] < -Lx_half: 

162 sim_box[D] += Lx 

163 sim_box[D+1] -= 1 

164 return 

165 return update_box_shift 

166 

167 #def get_dist_moved_sq_function(self): 

168 # D = self.D 

169 # def dist_moved_sq_function(r_current, r_last, sim_box, sim_box_last): 

170 # zero = numba.float32(0.) 

171 # half = numba.float32(0.5) 

172 # one = numba.float32(1.0) 

173 # box_shift = sim_box[D] 

174 # dist_moved_sq = zero 

175 

176 

177 # strain_change = sim_box[D] - sim_box_last[D] # change in box-shift 

178 # strain_change += (sim_box[D+1] - sim_box_last[D+1]) * sim_box[0] # add contribution from box_shift_image 

179 # strain_change /= sim_box[1] # convert to (xy) strain 

180 

181 

182 # # we will shift the x-component when the y-component is 'wrapped' 

183 # dr1 = r_current[1] - r_last[1] 

184 # box_1 = sim_box[1] 

185 # y_wrap = (one if dr1 > half*box_1 else 

186 # -one if dr1 < -half*box_1 else zero) 

187 

188 # x_shift = y_wrap * box_shift + (r_current[1] - 

189 # y_wrap*box_1) * strain_change 

190 # # see the expression in Chatoraj Ph.D. thesis. Adjusted here to 

191 # # take into account BC wrapping (otherwise would use the images 

192 # # ie unwrapped positions) 

193 

194 # for k in range(D): 

195 # dr_k = r_current[k] - r_last[k] 

196 # if k == 0: 

197 # dr_k -= x_shift 

198 # box_k = sim_box[k] 

199 # dr_k += (-box_k if numba.float32(2.0) * dr_k > +box_k else 

200 # (+box_k if numba.float32(2.0) * dr_k < -box_k else numba.float32(0.0))) 

201 # dist_moved_sq = dist_moved_sq + dr_k * dr_k 

202 

203 

204 # return dist_moved_sq 

205 # return dist_moved_sq_function 

206 

207 def get_dist_moved_exceeds_limit_function(self): 

208 D = self.D 

209 

210 def dist_moved_exceeds_limit_function(r_current, r_last, sim_box, sim_box_last, skin, cut): 

211 """ 

212 Returns true if the distance moved since last neighbor-list update exceeds half the skin, taking the change in box-shift into account 

213 See Chattoraj PhD thesis for criterion for neighbor list checking under shear https://pastel.hal.science/pastel-00664392/ 

214 """ 

215 zero = numba.float32(0.) 

216 half = numba.float32(0.5) 

217 one = numba.float32(1.0) 

218 box_shift = sim_box[D] 

219 dist_moved_sq = zero 

220 

221 

222 strain_change = sim_box[D] - sim_box_last[D] # change in box-shift 

223 strain_change += (sim_box[D+1] - sim_box_last[D+1]) * sim_box[0] # add contribution from box_shift_image 

224 strain_change /= sim_box[1] # convert to (xy) strain 

225 

226 # we will shift the x-component when the y-component is 'wrapped' 

227 dr1 = r_current[1] - r_last[1] 

228 box_1 = sim_box[1] 

229 y_wrap = (one if dr1 > half*box_1 else 

230 -one if dr1 < -half*box_1 else zero) 

231 

232 x_shift = y_wrap * box_shift + (r_current[1] - 

233 y_wrap*box_1) * strain_change 

234 # see the expression in Chatoraj Ph.D. thesis. Adjusted here to 

235 # take into account BC wrapping (otherwise would use the images 

236 # ie unwrapped positions) 

237 

238 for k in range(D): 

239 dr_k = r_current[k] - r_last[k] 

240 if k == 0: 

241 dr_k -= x_shift 

242 box_k = sim_box[k] 

243 dr_k += (-box_k if numba.float32(2.0) * dr_k > +box_k else 

244 (+box_k if numba.float32(2.0) * dr_k < -box_k else numba.float32(0.0))) 

245 dist_moved_sq = dist_moved_sq + dr_k * dr_k 

246 

247 skin_corrected = skin - abs(strain_change)*cut 

248 if skin_corrected < zero: 

249 skin_corrected = zero 

250 

251 return dist_moved_sq > skin_corrected*skin_corrected*numba.float32(0.25) 

252 

253 return dist_moved_exceeds_limit_function 

254 

255 def get_loop_x_addition(self): 

256 return 1 

257 

258 def get_loop_x_shift_function(self): 

259 D = self.D 

260 def loop_x_shift_function(sim_box, cell_length_x): 

261 box_shift = sim_box[D] 

262 return -int(math.ceil(box_shift/cell_length_x)) 

263 

264 return loop_x_shift_function