Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from numpy import (arange, array, conjugate, where, block, 

2 mean, amax, zeros_like, zeros, var, sum as nsum, linalg, 

3 sqrt, savetxt, einsum, exp, log, log2, diag) 

4from scipy.optimize import fminbound, curve_fit 

5from scipy.interpolate import interp1d 

6from math import pi 

7from os import path 

8from contextlib import suppress 

9 

10from mango.constants import c, nestedDict, asciistring 

11from mango.pp.util import at_least2d_end # debye, 

12import mango.imports as imports 

13 

14from mango.debug import debug 

15 

16# from numpy import seterr 

17 

18# seterr(all='raise') 

19 

20 

21def get_suscep(flg, 

22 skip, dt, sus_data, blocks, 

23 stats, mass, vol, ms): 

24 """ 

25 Calculate the ACF of data for different properties. 

26 

27 Parameters 

28 ---------- 

29 

30 Returns 

31 ------- 

32 atc_data: dict 

33 A dictionary of all the calculated ACF 

34 

35 """ 

36 autocorr_labels = {"mag": {"x": r'$\omega$ [$10^{{{}}}$]', 

37 "y": r"MDOS [$10^{{{}}}$]", 

38 "type": "MDOS"}, 

39 "vel": {"x": r'$\omega$ [$10^{{{}}}$]', 

40 "y": r"VDOS [$10^{{{}}}$]", 

41 "type": "VDOS"}, 

42 "angular": {"x": r'$\omega$ [$10^{{{}}}$]', 

43 "y": r"ADOS [$10^{{{}}}$]", 

44 "type": "ADOS"}, 

45 "inertia": {"x": r'$\omega$ [$10^{{{}}}$]', 

46 "y": r"IDOS [$10^{{{}}}$]", 

47 "type": "IDOS"}, 

48 "rotation": {"x": r'$\omega$ [$10^{{{}}}$]', 

49 "y": r"RDOS [$10^{{{}}}$]", 

50 "type": "RDOS"}, 

51 "mag_ifft": {"x": r"Time [$10^{{{}}}$s]", 

52 "y": r"MACF [$10^{{{}}}$]", 

53 "type": "MACF"}, 

54 "vel_ifft": {"x": r"Time [$10^{{{}}}$s]", 

55 "y": r"VACF [$10^{{{}}}$]", 

56 "type": "VACF"}, 

57 "angular_ifft": {"x": r"Time [$10^{{{}}}$s]", 

58 "y": r"AACF [$10^{{{}}}$]", 

59 "type": "AACF"}, 

60 "inertia_ifft": {"x": r"Time [$10^{{{}}}$s]", 

61 "y": r"IACF [$10^{{{}}}$]", 

62 "type": "IACF"}, 

63 "rotation_ifft": {"x": r"Time [$10^{{{}}}$s]", 

64 "y": r"RACF [$10^{{{}}}$]", 

65 "type": "RACF"}} 

66 

67 if 'vel' in flg.suscep: 

68 sus_data["vel"] = einsum("hijk, j -> hijk", sus_data["mom"], 1 / mass) 

69 

70 atc_data = nestedDict() 

71 

72 autocorr = Autocorr(flg, sus_data, dt, skip, ms * vol) 

73 

74 autocorr.conv(len(stats)) 

75 

76 for bl in blocks: 

77 

78 autocorr.analysis(blocks=bl) 

79 

80 for i in flg.suscep: 

81 

82 atc_data[bl][i] = {"xaxis": autocorr.freq, "yaxis": autocorr.ret[i]['data'], "stddev_y": autocorr.ret[i]['error'], 

83 # "popt": tau, "popt_fitted": ad_tau.popt_fit, 

84 "autocorr_labels": autocorr_labels[i], 'scale': autocorr.ret[i]['scale']} 

85 

86 iatc = i + '_ifft' 

87 atc_data[bl][iatc] = {"xaxis": autocorr.time, "yaxis": autocorr.ret[i]['ifft'], "stddev_y": autocorr.ret[i]['ifft_error'], 

88 # "popt": tau, "popt_fitted": ad_tau.popt_fit, 

89 "autocorr_labels": autocorr_labels[iatc], 'scale': autocorr.ret[i]['scale']} 

90 

91 return atc_data 

92 

93 

94class Autocorr(): 

95 """Calculates the ACF and MDOS of provided data.""" 

96 

97 @debug(['acf']) 

98 def __init__(self, flg, sus_data, dt, skip, mmag): 

99 """ 

100 Store data to be processed. 

101 

102 Parameters 

103 ---------- 

104 flg: instance 

105 flags class instance 

106 sus_data: dict 

107 data dictionary 

108 dt: float 

109 timestep 

110 skip: int 

111 number of iterations skipped 

112 mmag: float 

113 Mmag 

114 

115 """ 

116 self.flg = flg 

117 self.dt = dt 

118 self.skip = skip 

119 self.mmag = mmag 

120 

121 self.fft, self.ffts, self.ifft, self.byte_align = imports.fftw() 

122 self.shape = sus_data[list(sus_data.keys())[0]].shape 

123 

124 self.ret = {} 

125 

126 self.sus_collect = {} 

127 

128 self._shape_data(sus_data) 

129 

130 if hasattr(self, 'DBG'): 

131 self.sus_data = sus_data.copy() 

132 

133 def _calc(self, func, *args): 

134 # Paralisable loop c.processors/numkeys 

135 for autocorr_type in self.sus_collect.keys(): 

136 func(autocorr_type, *args) 

137 

138 if hasattr(self, 'DBG'): 

139 from mango.pp.acf_checker import checker, autoold 

140 ret2, freq2, time2 = autoold(c.Error, self.sus_data, self.dt, self.skip, self.mmag, blocks=self.blocks) 

141 checker(self.ret, ret2, self.freq, freq2, self.time, time2) 

142 

143 def conv(self, no_stats, RMS=True): 

144 """Carry out blocking convergence tests.""" 

145 self._calc(self._blockingconvergence, no_stats, RMS) 

146 

147 def analysis(self, blocks): 

148 """ 

149 Carry out DOS and ACF calculation for provided data and blocks. 

150 

151 Parameters 

152 ---------- 

153 blocks: int 

154 number of blocks 

155 

156 """ 

157 self.sample = self.shape[0] * self.shape[1] // blocks 

158 self.sample_pad = self.ffts.next_fast_len(2 * self.sample - 1) 

159 self.freq = 2.0 * pi * self.ffts.fftshift(self.ffts.fftfreq(self.sample_pad, d=(self.dt * self.skip))) 

160 self.norm_val = self.freq[1] - self.freq[0] 

161 # arange(-self.sample + 1, self.sample) * self.dt * self.skip 

162 self.time = 2. * pi * self.ffts.fftshift(self.ffts.fftfreq(self.sample_pad, d=self.norm_val)) 

163 

164 self.blocks = blocks 

165 self._calc(self._blockinganalysis) 

166 

167 def _shape_data(self, sus_data): 

168 """ 

169 Shape arrays dependent on type. 

170 

171 Parameters 

172 ---------- 

173 sus_data: dict 

174 dict of data arrays 

175 

176 """ 

177 reshape = [] 

178 

179 for autocorr_type in self.flg.suscep: 

180 # if autocorr_type in ['chi']: 

181 # continue 

182 # else: 

183 value = sus_data[autocorr_type] 

184 if autocorr_type in ['mag']: 

185 # Sum over the number of atoms 

186 # Flatten and refold -> Blocking will only trim from 

187 # first trajetory for correct dimensions 

188 # data = 

189 # stddev = self._get_av_err(data, av=False) # - (stddev if self.flg.align else 0) 

190 # - (mean(einsum("il, il ->il", data[:, 0], data[:, 0]), axis=1) if self.flg.align else 0 

191 data = self._remove_av(einsum('ijkl->ijl', value)).reshape(-1, 3) 

192 elif autocorr_type in ["rotation"]: 

193 if self.flg.align: 

194 data = self._remove_av(einsum('ijkl, il -> ijk', value, 

195 mean(einsum("ijkz ->ijz", sus_data["mag"]), axis=1))).reshape(-1, 3) 

196 else: 

197 c.Error("W rotation autocorrelation only works with align=True.") 

198 else: 

199 data = self._remove_av(value).reshape([-1, *value.shape[2:]]) 

200 

201 if autocorr_type in ['inertia', "vel"]: 

202 reshape = [-1, 3] 

203 else: 

204 reshape = [3] 

205 

206 print(autocorr_type, data.shape) 

207 e_string = "{0}, {0} -> ".format(''.join(asciistring(data.ndim))) 

208 

209 self.sus_collect[autocorr_type] = {'data': data, 'scale': 2 * pi * einsum(e_string, data, data) / data.size, 

210 'reshapeto': reshape} 

211 

212 @staticmethod 

213 def _remove_av(value): 

214 """ 

215 Remove the average over the iterations before any modifcation. 

216 

217 Trajectory may not be fully relaxed 

218 Removing the mean after blocking leads to spurious errors 

219 

220 """ 

221 return value - mean(value, axis=1)[:, None] 

222 

223 def _fft_av_err(self, dummy, scale, blocks): 

224 """Scale fft errors.""" 

225 ave, err = self._get_av_err(dummy, blocks) 

226 print(ave[0]) 

227 return scale * self.ffts.fftshift(ave), scale * self.ffts.fftshift(err) 

228 

229 @staticmethod 

230 def _get_av_err(dummy, blocks=1, axis=0, av=True): 

231 """ 

232 Average data over blocks and calculate errors. 

233 

234 Parameters 

235 ---------- 

236 dummy: array 

237 data array 

238 blocks: int 

239 number of blocks 

240 

241 Returns 

242 ------- 

243 ave: array 

244 averaged data array 

245 err: array 

246 standard deviation of averaged data 

247 

248 """ 

249 if dummy.shape[0] == 1: 

250 err = zeros(dummy.shape[1:]) 

251 else: 

252 err = sqrt(var(dummy, ddof=1, axis=axis) / blocks) 

253 

254 return (mean(dummy, axis=axis), err) if av else err 

255 

256 def _blockingconvergence(self, autocorr_type, no_stats, RMS=True): 

257 """ 

258 Convergence test for blocking. 

259 

260 Parameters 

261 ---------- 

262 autocorr_type: str 

263 autocorr name 

264 RMS: bool 

265 Root mean squared or mean 

266 

267 """ 

268 print('# Block averages {}'.format('magnetic moment [emu]' if autocorr_type == 'mag' else autocorr_type)) 

269 print('# Block, sample, average, error') 

270 data = self.sus_collect[autocorr_type]['data'] 

271 

272 if RMS: 

273 def RMSwrap(data, **kw): 

274 e_string = "{0}, {0} -> ab".format(''.join(asciistring(data.ndim))) 

275 return sqrt(mean(einsum(e_string, data, data), **kw)) 

276 else: 

277 def RMSwrap(data, **kw): 

278 e_string = "{} -> ab".format(''.join(asciistring(data.ndim))) 

279 return mean(einsum(e_string, data), **kw) 

280 

281 for local_sample in (no_stats * 2**x for x in range(int(log2(self.shape[0] * self.shape[1]) - log2(self.shape[0])))): 

282 no_blocks = self.shape[0] * self.shape[1] // local_sample 

283 blocked = data[:local_sample * no_blocks].reshape([no_blocks, local_sample, *self.sus_collect[autocorr_type]['reshapeto']]) 

284 reblock = RMSwrap(blocked, axis=1) 

285 ave, err = self._get_av_err(reblock, no_blocks) 

286 print(local_sample, no_blocks, ave, err) # if local_sample > 1 else 0) 

287 

288 @staticmethod 

289 def _get_estring(ndim): 

290 """ 

291 Return the 3 character strings for einsum of DOS. 

292 

293 eg 3 dimensions would return: 

294 

295 dos = 'abc' 

296 cdos = 'abd' 

297 fstr = 'abcd' 

298 

299 Parameters 

300 ---------- 

301 ndim: int 

302 number of dimensions of array 

303 

304 Returns 

305 ------- 

306 dos_str: str 

307 string of char length ndim 

308 cdos_str: str 

309 string of char length ndim 

310 fstr: str 

311 string of char length of ndim + 1 

312 

313 """ 

314 chr_list = asciistring(ndim + 1) 

315 dos_str = ''.join(chr_list[:-1]) 

316 cdos_str = ''.join(chr_list[:-2] + [chr_list[-1]]) 

317 return dos_str, cdos_str, ''.join(chr_list) 

318 

319 @staticmethod 

320 def _pmsum(data): 

321 # TODO per particle correleation 

322 # if shape[1] == no_mol and shape[0] == iter (?) 

323 # if data.ndim == 3: 

324 # return einsum("ijk -> ik", data) 

325 return data 

326 

327 def _blockinganalysis(self, autocorr_type): 

328 """ 

329 DOS and ACF calculation. 

330 

331 creates storage dictionary 'ret' 

332 

333 Parameters 

334 ---------- 

335 autocorr_type: str 

336 autocorr name 

337 

338 """ 

339 scale = self.sus_collect[autocorr_type]['scale'] 

340 reshapeto = [self.blocks, self.sample, *self.sus_collect[autocorr_type]['reshapeto']] 

341 # Scale 

342 # if autocorr_type is 'mag': # 2*pi*<M*M>/3 

343 # # scale = 2. * pi * einsum("i, i -> ", self.mmag, self.mmag) / (3. * self.mmag.size) 

344 # scale = einsum("ijk, ijk -> ", mxyz, mxyz) / mxyz.size # This is equal to <M*M>/3 for independent particles 

345 # else: 

346 # 2*pi*<v*v>/3, 2*pi*<M*M>/3 

347 

348 # Blocking 

349 print(autocorr_type, self.sus_collect[autocorr_type]['data'].shape) 

350 

351 mxyz = self.sus_collect[autocorr_type]['data'][:self.sample * self.blocks].reshape(reshapeto) 

352 

353 self.ret[autocorr_type] = {'scale': scale} 

354 

355 print('DOS') 

356 # DOS 

357 dos = self.fft(self.byte_align(mxyz), n=self.sample_pad, axis=1, threads=c.processors) 

358 

359 print("DOne") 

360 # modulus of the data gives separate real and imaginary parts 

361 dos = self._pmsum(dos) 

362 

363 dos_str, cdos_str, f_str = self._get_estring(dos.ndim) 

364 

365 mod_dos = einsum("{}, {} -> {}".format(dos_str, cdos_str, f_str), dos, conjugate(dos)) 

366 # Calculate eigenvalues 

367 eig_dos = linalg.eigvalsh(mod_dos) 

368 

369 # Normalisation: Integral of DOS equal 1 

370 norm = self.norm_val * einsum('{} -> '.format(dos_str), eig_dos).real / self.blocks 

371 eig_dos /= norm 

372 

373 print('IDOS') 

374 # Normalisation: ACF(0) = 1 

375 ifftx = 2 * pi * self.ifft(self.byte_align(eig_dos), axis=1, threads=c.processors) / (self.dt * self.skip) 

376 print("DOne") 

377 

378 # Error and averaging 

379 (self.ret[autocorr_type]['data'], 

380 self.ret[autocorr_type]['error']) = self._fft_av_err(eig_dos, scale, self.blocks) 

381 (self.ret[autocorr_type]['ifft'], 

382 self.ret[autocorr_type]['ifft_error']) = self._fft_av_err(ifftx, scale, self.blocks) 

383 

384 

385@debug(['save']) 

386def save_suscep(flg, directory, run, no_mol, cblock, xaxis, yaxis, stddev_y, 

387 autocorr_labels, scale, line_d={}, **kwargs): 

388 """ 

389 Save the susceptibility calculation. 

390 

391 Saves autocorrelation data in raw text, 

392 The columns are shown below with yaxis and std_y will always be 3 columns: 

393 

394 xaxis yaxis... std_y... 

395 

396 Parameters 

397 ---------- 

398 directory: str 

399 save location 

400 run: int 

401 run number 

402 no_mol: int 

403 number of particles 

404 cblock: int 

405 current block length 

406 xaxis: ndarray 

407 x data 

408 yaxis: ndarray 

409 y data 

410 stddev_y: ndarray 

411 y error data 

412 autocorr_labels: dict 

413 dictionary of x and y labels 

414 scale: float 

415 scale value for data 

416 

417 """ 

418 

419 # if (path.isfile(figname) or path.isfile("{}{}.dat".format(figname, "ALIGN" if flg.align else ''))) and flg.saveg: 

420 # c.Error('W File {} exists, skipping'.format(figname.rsplit('/', 1)[-1])) 

421 # return None 

422 

423 xaxis, yaxis, stddev_y = at_least2d_end(xaxis, yaxis, stddev_y) 

424 

425 if ('DOS' in autocorr_labels["type"]) or ('ACF' in autocorr_labels["type"]): 

426 header = f'scale factor: {scale}' # Remove 2 pi which is from Fourier transform definintion 

427 

428 data = block([xaxis, yaxis, stddev_y]) 

429 print(data.shape, xaxis.shape, yaxis.shape, stddev_y.shape) 

430 name = "{}S_Run{}_mol-{:g}_autocorr_{}_blk{}".format(directory, run, no_mol, autocorr_labels["type"], cblock) 

431 savetxt("{}{}.dat".format(name, "ALIGN" if flg.align else ''), data, header=header) 

432