-
Notifications
You must be signed in to change notification settings - Fork 0
/
static_correlation.py
executable file
·365 lines (309 loc) · 12.4 KB
/
static_correlation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#!/usr/bin/env python
import os
import numpy as np
def gofr_snapshot(axes,pos,rmax,rmin=0,nbin=40,gofr_norm=None):
""" calculate the pair correlation function of a snapshot of a crystal structure given the 'axes' of the simulation cell and the positions ('pos') of atoms inside the cell.
'rmax' is the maximum pair distance to histogram. 'rmin','rmax', and 'nbin' are passed to numpy.histogram to generate the results.
return 3 lists: r, g(r), and g(r) normalization.
Note: if a gofr_norm is given, then it will not be recalculated. This can save time if the simulation box does not change.
Args:
axes (np.array): crystal lattice vectors.
pos (np.array): positions of atoms.
rmax (float): maximum distance to calculate g(r) to.
rmin (float,optional): minimum distance to calculate g(r) to, default is 0.
nbin (int,optional): number of bins, default is 40.
gofr_norm (float,optional): normalization factor for g(r), default is to recalculate.
Returns:
(np.array,np.array,float): (r,g(r),normalization) """
from ase.geometry import get_distances
from qharv.inspect.axes_pos import volume
cell_volume = volume(axes)
nptcl = len(pos)
dtable = get_distances(pos, cell=axes, pbc=True)[1]
# upper triagular distance matrix (i.e. unique pair distances)
i_triu = np.triu_indices(nptcl,m=nptcl,k=1)
pair_dists = dtable[i_triu]
counts,ticks = np.histogram(pair_dists,range=(rmin,rmax),bins=nbin)
myx = (ticks[:-1] + ticks[1:])/2.
dx = myx[1] - myx[0]
if gofr_norm is None:
# gofr_norm = 4*np.pi*myx**2.*dx * nptcl*(nptcl-1)/2./struct.volume
gofr_norm = 4*np.pi*myx**2.*dx *nptcl**2./cell_volume/2.
# end if
myy = counts/gofr_norm
return myx,myy,gofr_norm
# end def
def sofk_snapshot(axes,pos,nkmax=5,legal_kvecs=None):
""" calculate the structure factor of a snapshot of a crystal structure given 'axes' and 'pos' of atoms.
'nkmax' is the number of kvectors to include in each of x,y,z directions.
return 2 lists: legal kvectors, and S(k) of each kvector
Args:
axes (np.array): crystal lattice vectors.
pos (np.array): positions of atoms.
nkmax (int,optional): maximum number of kvectors in each spatial dimension, default is 5. Used only if legal_kvecs is not give.
leagal_kvecs (np.array,optional): kvectors upon which S(k) is defined, default is to recalculate up to nkmax.
Returns:
(np.array,np.array): (kvecs,S(k))
"""
from qharv.inspect.axes_pos import raxes
rho = lambda kvec: np.exp(1j* np.dot(pos,kvec) ).sum()
reclat = raxes(axes)
if legal_kvecs is None:
import chiesa_correction as chc
cube_pts = chc.cubic_pos(nkmax)
legal_kvecs = np.dot(cube_pts[1:], reclat)
sk_arr = np.array([(rho(kvec)*rho(-kvec)).real/len(pos)
for kvec in legal_kvecs])
return legal_kvecs, sk_arr
# end def
def kshell_sels(kmags, zoom):
kints = np.round(kmags*zoom).astype(int)
unique_kints = np.unique(kints)
nsh = len(unique_kints)
sels = []
for ish in range(nsh):
kint = unique_kints[ish] # shell integer label
sel = kints == kint # select this shell
sels.append(sel)
return sels
def index_shells(kmags, ndig):
kround = kmags.round(ndig)
uk = np.unique(kround)
idx = np.zeros(len(kmags), dtype=int)
for ish, k1 in enumerate(uk):
sel = kround == k1
idx[sel] = ish
return idx
def shavg(kvecs, dskm, dske, zoom=100.):
""" Shell average S(k), including error bar
hint: if your S(k) data has no error, then pass in dske=np.zeros(nk).
Args:
kvecs (np.array): kvectors, shape (nk, ndim)
sk (np.array): S(k), shape (nk,)
zoom (float, optional): control resolution of kshells,
higher zoom will result in more kshells, default is 100.
Return:
(np.array, np.array, np.array): (uk, uskm, uske), shell-averaged k, S(k)
mean and S(k) error
"""
# determine kshells by rounding kvecs
kmags = np.linalg.norm(kvecs, axis=-1)
sels = kshell_sels(kmags, zoom)
nsh = len(sels)
# loop over each shell and average
uk = np.zeros(nsh)
uskm = np.zeros(nsh)
uske = np.zeros(nsh)
for ish, sel in enumerate(sels):
uk[ish] = np.mean(kmags[sel])
uskm[ish] = np.mean(dskm[sel])
uske[ish] = np.sqrt(np.sum(dske[sel]**2))/len(dske[sel])
return uk, uskm, uske
def shell_average(kmags,vals,smear_fac=100):
""" spherical average value over k vector magnutudes
multiple elements of vals may have the same kmag
Args:
kmags (np.array): magnitude of k vectors
vals (np.array): value of function at each k vector
smear_fac (int, optional): smearing factor, default is 100. e.g. kmags of 3.145, 3.146 will be considered to occupy the same kshell, whereas kmags of 3.145 and 3.134 will occupy different kshells.
Returns:
tuple: (unique_kmags, unique_vals), shell-averaged kvector magnitudes and function values.
"""
kids = list(map(int, map(round, kmags*smear_fac)))
# pick unique kshells according to index
unique_kid = np.unique(kids)
# loop over each shell and average
unique_kmags = np.zeros(len(unique_kid))
unique_vals = np.zeros(len(unique_kid))
for iukmag in range(len(unique_kid)):
kid = unique_kid[iukmag] # shell ID
sel = kids == kid # select this shell
unique_kmags[iukmag] = np.mean(kmags[sel])
unique_vals[iukmag] = np.mean(vals[sel])
# end for iukmag
return unique_kmags,unique_vals
# end def shell_average
def shell_avg_sofk(legal_kvecs,sk_arr,**kwargs):
""" average S(k) if multiple k-vectors have the same magnitude,
legal_kvecs.shape should be (Nk,Ndim)
sk_arr.shape should be (Nk) """
# determine similarity of k-vectors by converting magnitude to int
kmags = np.linalg.norm(legal_kvecs,axis=1)
return shell_average(kmags,sk_arr,**kwargs)
# end def
def gr2sk(k,grx,gry,rho):
""" return spherical S(k) at given 'k' value, assuming spherical g(r)=(grx,gry)
'rho' is density N/Vol. """
integrand = grx**2. * (gry-1.0) * np.sin(k*grx)/(k*grx)
val = 4*np.pi*rho*sum(integrand*(grx[1]-grx[0]))
return 1.+val
# end def
def gr2sk2d(k, myr, grm, rho):
""" 2D Fourier transform of isotropic g(r), need Bessel J """
from scipy.special import jv
dr = myr[1]-myr[0]
integrand = myr*jv(0, k*myr)*(grm-1)
val = 2*np.pi*rho* integrand.sum()*dr
sk = 1+val
return sk
def sk2gr(k,grx,gry,rho):
""" return spherical S(k) at given 'k' value, assuming spherical g(r)=(grx,gry)
'rho' is density N/Vol. """
integrand = grx**2. * (gry-1.0) * np.sin(k*grx)/(k*grx)
val = 4*np.pi*rho*sum(integrand*(grx[1]-grx[0]))
val /= (2.*np.pi)**3.
return 1.+val
# end def
def gr2compressibility(grx,gry,rho):
integrand = grx**2. * (gry-1.0)
val = 4*np.pi*rho*sum(integrand*(grx[1]-grx[0]))
return 1.+val
# end def
def dsk_from_csk(fp, csk_name, nequil, kappa=None):
""" extract fluctuating structure factor dS(k) from charged structure factor
Args:
fp (h5py.File): stat.h5 handle
csk_name (str): name the charged S(k) estimator, likely 'csk'
nequil (int): equilibration length
kappa (float, optional): auto-correlation, default recalculate
Return:
(np.array, np.array, np.array): (kvecs, dskm, dske),
kvectors and S(k) mean and error
"""
from qharv.reel.stat_h5 import me2d
# get data
kpt_path = '%s/kpoints/value' % csk_name
csk_path = '%s/csk/value' % csk_name
crho_path = '%s/crhok/value' % csk_name
kvecs = fp[kpt_path][()]
cska = fp[csk_path][()]
crhoa = fp[crho_path][()]
nblock, nspin, nk = cska.shape
# get dsk using equilibrated data
dska = cska[nequil:] - crhoa[nequil:]**2
dskm, dske = me2d(dska)
return kvecs, dskm, dske
def csk1d(fh5, nequil):
from qharv.reel import stat_h5
fp = stat_h5.read(fh5)
kvecs, dskm, dske = dsk_from_csk(fp, 'csk', nequil)
fp.close()
dskm1 = dskm.sum(axis=0)
dske1 = (dske**2).sum(axis=0)**0.5
uk, uskm, uske = shavg(kvecs, dskm1, dske1)
return uk, uskm, uske
def plot_sofk(ax,legal_kvecs,sk_arr):
ax.set_xlabel('k (1/bohr)')
ax.set_ylabel('S(k)')
# shell average
unique_kmags,unique_sk = shell_avg_sofk(legal_kvecs,sk_arr)
# visualize
line, = ax.plot(unique_kmags[1:],unique_sk[1:])
return line
# end def
def cubic_rws(entry):
axes = entry['axes']
alat = axes[0][0]
if not np.allclose( axes, alat*np.eye(3) ):
raise RuntimeError('non-cubic cell, please provide -rws')
# end if
return alat/2
# end def
def get_dimers(sep_max,axes,pos,sep_min=0.0):
""" get a list of pairs of particle indices, representing dimers with a separation between (sep_min,sep_max)
Args:
sep_max (float): maximum dimer separation.
axes (np.array): simulation cell axes.
pos (np.array): particle coordinates.
sep_min (:obj:`float`, optional): minimum dimer separation.
Returns:
dict: a dictionary having keys ['upair','udist'], which store the pair indices and distances, respectively.
"""
# get distance table
elem = ['H']*len(pos)
struct = mg.Structure(axes,elem,pos,coords_are_cartesian=True)
dtable = struct.distance_matrix
# locate pairs
sel = (dtable < sep_max) & (dtable > sep_min)
pairs = np.argwhere(sel)
# remove permutation
usel = pairs[:,0] < pairs[:,1]
upair = pairs[usel]
udist = dtable[sel][usel]
return {'upair':upair,'udist':udist}
# def get_dimers
if __name__ == '__main__':
import pandas as pd
import argparse
parser = argparse.ArgumentParser(description='calculate g(r) and S(k) from a database of crystal structures')
parser.add_argument('config_json', type=str, help='json file containing axes and pos of atomic configurations')
parser.add_argument('-rws','--r_wigner_seizs', type=float, default=None, required=False, help='Wigner-Seitz radius, used to cutoff g(r)')
parser.add_argument('-is','--istart', type=int, default=0, help='index of first sample')
parser.add_argument('-p','--plot', action='store_true', help='plot data')
parser.add_argument('-s','--save', action='store_true', help='save plot')
parser.add_argument('-f','--force', action='store_true', help='force analysis, ignore gofr.dat and sofk.dat in local directory')
args = parser.parse_args()
# read a list of configurations (axes and pos)
#cdf = pd.read_json('i4-pbe-4800-48-solid_configs.json')
cdf = pd.read_json(args.config_json)
prefix = args.config_json.replace('.json','')
# get the first sample
istart = args.istart
entry = cdf[istart]
axes = entry['axes']
pos = entry['pos']
# calculate Wigner-Seitz radius (only for cubic cell)
if args.r_wigner_seizs is not None:
rws = args.r_wigner_seizs
else:
rws = cubic_rws(entry)
# end if
# initialize gofr_norm with the first sample
myx,myy,gofr_norm = gofr_snapshot(axes,pos,rmax=rws)
legal_kvecs,sk_arr = sofk_snapshot(axes,pos)
if (not (os.path.isfile('gofr.dat') and os.path.isfile('sofk.dat'))) or (args.force):
# analyze the rest of the samples
nsample = len(cdf.columns)
all_gofr = np.zeros([nsample,len(myy)])
all_sofk = np.zeros([nsample,len(legal_kvecs)])
for isample in range(istart,istart+nsample):
entry = cdf[isample]
axes = entry['axes']
pos = entry['pos']
# accumulate gofr
myx,myy,junk = gofr_snapshot(axes,pos,rmax=rws
,gofr_norm=gofr_norm)
all_gofr[isample-istart,:] = myy
# accumulate sofk
junk,sk_arr = sofk_snapshot(axes,pos
,legal_kvecs=legal_kvecs)
all_sofk[isample-istart,:] = sk_arr
# end for isample
# save results
gofr = all_gofr.mean(axis=0)
np.savetxt('gofr.dat',gofr)
sofk = all_sofk.mean(axis=0)
np.savetxt('sofk.dat',sofk)
else:
gofr = np.loadtxt('gofr.dat')
sofk = np.loadtxt('sofk.dat')
# end if
if args.plot:
import matplotlib.pyplot as plt
fig,ax = plt.subplots(1,2)
# plot g(r)
ax[0].set_xlabel('r')
ax[0].set_ylabel('g(r)')
ax[0].plot(myx,gofr)
# plot S(k)
ax[1].set_xlabel('k')
ax[1].set_ylabel('S(k)')
ax[1].get_yaxis().tick_right()
ax[1].get_yaxis().set_label_position('right')
plot_sofk(ax[1],legal_kvecs,sofk)
if args.save:
fig.savefig(prefix + '.png',dpi=200)
# end if
plt.show()
# end if
# end __main__