cov.py 12.3 KB
Newer Older
1
#!/usr/bin/env python
2
3
4
5
#
# calculate covariance from neutron events
# @author Tobias Weber <tweber@ill.fr>
# @date 30-mar-2019
Tobias WEBER's avatar
Tobias WEBER committed
6
# @license GPLv3, see 'LICENSE' file
7
8
#

Tobias WEBER's avatar
Tobias WEBER committed
9
import os
10
11
12
import numpy as np
import numpy.linalg as la

13

14
15
16
17
18
verbose = True		# console outputs
plot_results = True	# show plot window
plot_neutrons = True	# also plot neutron events
centre_on_Q = False	# centre plots on Q or zero?
ellipse_points = 128	# number of points to draw ellipses
Tobias WEBER's avatar
Tobias WEBER committed
19
symsize = 0.25
20

21
# column indices in kf,kf file
22
23
24
25
ki_start_idx = 0	# start index of ki 3-vector
kf_start_idx = 3	# start index of kf 3-vector
wi_idx = 9		# start index of ki weight factor
wf_idx = 10		# start index of kf weight factor
26

27
28
29
30
# column indices in Q,E file
Q_start_idx = 0
E_idx = 3
w_idx = 4
31

32

33
34
35
36
# constants
sig2hwhm = np.sqrt(2. * np.log(2.))
sig2fwhm = 2.*sig2hwhm

37
38
39
40
41
#import scipy.constants as co
#hbar_in_meVs = co.Planck/co.elementary_charge*1000./2./np.pi
#E_to_k2 = 2.*co.neutron_mass/hbar_in_meVs**2. / co.elementary_charge*1000. * 1e-20
E_to_k2 = 0.482596406464	# E -> k^2, calculated with scipy, using the code above
k2_to_E = 1./E_to_k2		# k^2 -> E
42
43
44
45
46
47



#
# loads a list of neutron events in the [ ki_vec, kf_vec, pos_vec, wi, wf ] format
#
48
def load_events_kikf(filename):
49
	dat = np.loadtxt(filename)
50
51
52
53
	ki = dat[:, ki_start_idx:ki_start_idx+3]
	kf = dat[:, kf_start_idx:kf_start_idx+3]
	wi = dat[:, wi_idx]
	wf = dat[:, wf_idx]
54
55
56
57
58
59
60
61
62

	w = wi * wf
	Q = ki - kf
	E = k2_to_E * (np.multiply(ki, ki).sum(1) - np.multiply(kf, kf).sum(1))

	return [Q, E, w]



63
64
65
66
67
68
69
70
71
72
73
74
75
#
# loads a list of neutron events in the [ h, k, l, E, w ] format
#
def load_events_QE(filename):
	dat = np.loadtxt(filename)
	Q = dat[:, Q_start_idx:Q_start_idx+3]
	E = dat[:, E_idx]
	w = dat[:, w_idx]

	return [Q, E, w]



76
77
78
#
# calculates the covariance matrix of the (Q, E) 4-vectors
#
Tobias WEBER's avatar
Tobias WEBER committed
79
def calc_covar(Q, E, w, Qpara, Qperp):
80
81
82
	# make a [Q, E] 4-vector
	Q4 = np.insert(Q, 3, E, axis=1)

Tobias WEBER's avatar
Tobias WEBER committed
83
	# calculate the mean Q 4-vector
84
	Qmean = [ np.average(Q4[:,i], weights = w) for i in range(4) ]
85
86
	if verbose:
		print("Mean (Q, E) vector in lab system:\n%s\n" % Qmean)
87

Tobias WEBER's avatar
Tobias WEBER committed
88
89
	# get the weighted covariance matrix
	Qcov = np.cov(Q4, rowvar = False, aweights = w, ddof = 0)
90
91
	if verbose:
		print("Covariance matrix in lab system:\n%s\n" % Qcov)
92

Tobias WEBER's avatar
Tobias WEBER committed
93
	# the resolution is the inverse of the covariance
94
	Qres = la.inv(Qcov)
95
96
	if verbose:
		print("Resolution matrix in lab system:\n%s\n" % Qres)
97
98
99


	# create a matrix to transform into the coordinate system with Q along x
Tobias WEBER's avatar
Tobias WEBER committed
100
101
102
103
104
105
106
107
108
	# choose given coordinate system
	if len(Qpara) == 3 and len(Qperp) == 3:
		Qnorm = Qpara / la.norm(Qpara)
		Qside = Qperp / la.norm(Qperp)
		Qup = np.cross(Qnorm, Qside)
	else:
		Qnorm = Qmean[0:3] / la.norm(Qmean[0:3])
		Qup = np.array([0, 1, 0])
		Qside = np.cross(Qup, Qnorm)
109

Tobias WEBER's avatar
Tobias WEBER committed
110
111
	print("Qpara = %s\nQperp = %s\nQup = %s\n" % (Qnorm, Qside, Qup))

Tobias WEBER's avatar
Tobias WEBER committed
112
	# trafo matrix
113
114
115
116
117
118
	T = np.transpose(np.array([
		np.insert(Qnorm, 3, 0),
		np.insert(Qside, 3, 0),
		np.insert(Qup, 3, 0),
		[0, 0, 0, 1] ]))

119
120
	if verbose:
		print("Transformation into (Qpara, Qperp, Qup, E) system:\n%s\n" % T)
121

Tobias WEBER's avatar
Tobias WEBER committed
122
	# transform mean Q vector
123
	Qmean_Q = np.dot(np.transpose(T), Qmean)
124
125
	if verbose:
		print("Mean (Q, E) vector in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qmean_Q)
126

Tobias WEBER's avatar
Tobias WEBER committed
127
	# transform the covariance matrix
128
	Qcov_Q = np.dot(np.transpose(T), np.dot(Qcov, T))
129
130
	if verbose:
		print("Covariance matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qcov_Q)
131

Tobias WEBER's avatar
Tobias WEBER committed
132
	# the resolution is the inverse of the covariance
133
	Qres_Q = la.inv(Qcov_Q)
134
135
	if verbose:
		print("Resolution matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qres_Q)
136

137
138
139
	#[ evals, evecs ] = la.eig(Qcov_Q)
	#print("Ellipsoid fwhm radii:\n%s\n" % (np.sqrt(evals) * sig2fwhm))

140
	# transform all neutron events
141
	Q4_Q = np.array([])
Tobias WEBER's avatar
Tobias WEBER committed
142
	if plot_neutrons:
143
144
145
		Q4_Q = np.dot(Q4, T)
		if not centre_on_Q:
			Q4_Q -= Qmean_Q
146
147


148
	return [Qres_Q, Q4_Q, Qmean_Q]
149
150
151
152



#
153
# calculates the characteristics of a given ellipse
154
155
156
157
158
159
160
161
162
#
def descr_ellipse(quadric):
	[ evals, evecs ] = la.eig(quadric)
	fwhms = 1./np.sqrt(evals) * sig2fwhm

	angles = np.array([])
	if len(quadric) == 2:
		angles = np.array([ np.arctan2(evecs[1][0], evecs[0][0]) ])

163
	return [fwhms, angles/np.pi*180., evecs]
164

165

166
167

#
168
# describes the ellipsoid by a principal axis trafo and by 2d cuts
169
170
#
def calc_ellipses(Qres_Q):
171
172
173
174
175
176
177
178
	if verbose:
		print("4d resolution ellipsoid diagonal elements fwhm (coherent-elastic scattering) lengths:\n%s\n" \
			% (1./np.sqrt(np.diag(Qres_Q)) * sig2fwhm))

	# 4d ellipsoid
	[fwhms, angles, rot] = descr_ellipse(Qres_Q)
	if verbose:
		print("4d resolution ellipsoid principal axes fwhm lengths:\n%s\n" % fwhms)
179
180
181
182
183


	# 2d sliced ellipses
	Qres_QxE = np.delete(np.delete(Qres_Q, 2, axis=0), 2, axis=1)
	Qres_QxE = np.delete(np.delete(Qres_QxE, 1, axis=0), 1, axis=1)
184
185
186
	[fwhms_QxE, angles_QxE, rot_QxE] = descr_ellipse(Qres_QxE)
	if verbose:
		print("2d Qx/E ellipse fwhm lengths and slope angle:\n%s, %f\n" % (fwhms_QxE, angles_QxE[0]))
187
188
189

	Qres_QyE = np.delete(np.delete(Qres_Q, 2, axis=0), 2, axis=1)
	Qres_QyE = np.delete(np.delete(Qres_QyE, 0, axis=0), 0, axis=1)
190
191
192
	[fwhms_QyE, angles_QyE, rot_QyE] = descr_ellipse(Qres_QyE)
	if verbose:
		print("2d Qy/E ellipse fwhm lengths and slope angle:\n%s, %f\n" % (fwhms_QyE, angles_QyE[0]))
193
194
195

	Qres_QzE = np.delete(np.delete(Qres_Q, 1, axis=0), 1, axis=1)
	Qres_QzE = np.delete(np.delete(Qres_QzE, 0, axis=0), 0, axis=1)
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
	[fwhms_QzE, angles_QzE, rot_QzE] = descr_ellipse(Qres_QzE)
	if verbose:
		print("2d Qz/E ellipse fwhm lengths and slope angle:\n%s, %f\n" % (fwhms_QzE, angles_QzE[0]))

	Qres_QxQy = np.delete(np.delete(Qres_Q, 3, axis=0), 3, axis=1)
	Qres_QxQy = np.delete(np.delete(Qres_QxQy, 2, axis=0), 2, axis=1)
	[fwhms_QxQy, angles_QxQy, rot_QxQy] = descr_ellipse(Qres_QxQy)
	if verbose:
		print("2d Qx/Qy ellipse fwhm lengths and slope angle:\n%s, %f\n" % (fwhms_QxQy, angles_QxQy[0]))

	return [fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy]



#
# shows the 2d ellipses
#
Tobias WEBER's avatar
Tobias WEBER committed
213
def plot_ellipses(file, Q4, w, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy):
Tobias WEBER's avatar
Tobias WEBER committed
214
	import mpl_toolkits.mplot3d as mplot3d
215
216
	import matplotlib.pyplot as plot

217
218
219
220
	ellfkt = lambda rad, rot, phi, Qmean2d : \
		np.dot(rot, np.array([ rad[0]*np.cos(phi), rad[1]*np.sin(phi) ])) + Qmean2d


Tobias WEBER's avatar
Tobias WEBER committed
221
	# centre plots on zero or mean Q vector ?
222
223
224
225
226
227
228
229
230
231
232
	QxE = np.array([[0], [0]])
	QyE = np.array([[0], [0]])
	QzE = np.array([[0], [0]])
	QxQy = np.array([[0], [0]])

	if centre_on_Q:
		QxE = np.array([[Qmean[0]], [Qmean[3]]])
		QyE = np.array([[Qmean[1]], [Qmean[3]]])
		QzE = np.array([[Qmean[2]], [Qmean[3]]])
		QxQy = np.array([[Qmean[0]], [Qmean[1]]])

233
234

	phi = np.linspace(0, 2.*np.pi, ellipse_points)
235
236
237
238
	ell_QxE = ellfkt(fwhms_QxE, rot_QxE, phi, QxE)
	ell_QyE = ellfkt(fwhms_QyE, rot_QyE, phi, QyE)
	ell_QzE = ellfkt(fwhms_QzE, rot_QzE, phi, QzE)
	ell_QxQy = ellfkt(fwhms_QxQy, rot_QxQy, phi, QxQy)
239

Tobias WEBER's avatar
Tobias WEBER committed
240
241
	thesymsize = symsize * w

Tobias WEBER's avatar
Tobias WEBER committed
242
243
	
	# Qpara, E axis
244
245
246
	fig = plot.figure()
	subplot_QxE = fig.add_subplot(221)
	subplot_QxE.set_xlabel("Qpara (1/A)")
Tobias WEBER's avatar
Tobias WEBER committed
247
	subplot_QxE.set_ylabel("E (meV)")
248
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
Tobias WEBER's avatar
Tobias WEBER committed
249
		subplot_QxE.scatter(Q4[:, 0], Q4[:, 3], s=thesymsize)
250
	subplot_QxE.plot(ell_QxE[0], ell_QxE[1], c="black")
251

Tobias WEBER's avatar
Tobias WEBER committed
252
	# Qperp, E axis
253
254
255
	subplot_QyE = fig.add_subplot(222)
	subplot_QyE.set_xlabel("Qperp (1/A)")
	subplot_QyE.set_ylabel("E (meV)")
256
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
Tobias WEBER's avatar
Tobias WEBER committed
257
		subplot_QyE.scatter(Q4[:, 1], Q4[:, 3], s=thesymsize)
258
	subplot_QyE.plot(ell_QyE[0], ell_QyE[1], c="black")
259

Tobias WEBER's avatar
Tobias WEBER committed
260
	# Qup, E axis
261
262
263
	subplot_QzE = fig.add_subplot(223)
	subplot_QzE.set_xlabel("Qup (1/A)")
	subplot_QzE.set_ylabel("E (meV)")
264
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
Tobias WEBER's avatar
Tobias WEBER committed
265
		subplot_QzE.scatter(Q4[:, 2], Q4[:, 3], s=thesymsize)
266
	subplot_QzE.plot(ell_QzE[0], ell_QzE[1], c="black")
267

Tobias WEBER's avatar
Tobias WEBER committed
268
	# Qpara, Qperp axis
269
270
271
	subplot_QxQy = fig.add_subplot(224)
	subplot_QxQy.set_xlabel("Qpara (1/A)")
	subplot_QxQy.set_ylabel("Qperp (meV)")
272
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
Tobias WEBER's avatar
Tobias WEBER committed
273
		subplot_QxQy.scatter(Q4[:, 0], Q4[:, 1], s=thesymsize)
274
	subplot_QxQy.plot(ell_QxQy[0], ell_QxQy[1], c="black")
275
	plot.tight_layout()
Tobias WEBER's avatar
Tobias WEBER committed
276

Tobias WEBER's avatar
Tobias WEBER committed
277
278
279
280
281
282
283
284
285

	fig3d = plot.figure()
	subplot3d = fig3d.add_subplot(111, projection="3d")
	subplot3d.scatter(Q4[:,0], Q4[:,1], Q4[:,3], s=thesymsize)
	subplot3d.set_xlabel("Qpara (1/A)")
	subplot3d.set_ylabel("Qperp (1/A)")
	subplot3d.set_zlabel("E (meV)")


Tobias WEBER's avatar
Tobias WEBER committed
286
	if file != "":
Tobias WEBER's avatar
Tobias WEBER committed
287
288
289
		splitext = os.path.splitext(file)
		file3d = splitext[0] + "_3d" + splitext[1]

Tobias WEBER's avatar
Tobias WEBER committed
290
		if verbose:
Tobias WEBER's avatar
Tobias WEBER committed
291
292
293
294
			print("Saving 2d plot to \"%s\"." % file)
			print("Saving 3d plot to \"%s\"." % file3d)
		fig.savefig(file)
		fig3d.savefig(file3d)
Tobias WEBER's avatar
Tobias WEBER committed
295
296
297

	if plot_results:
		plot.show()
298
299
300



301
302
303
304
305
306
307
308
309
310
311
312
313
#
# checks versions of needed packages
#
def check_versions():
	npver = np.version.version.split(".")
	if int(npver[0]) >= 2:
		return
	if int(npver[0]) < 1 or int(npver[1]) < 10:
		print("Numpy version >= 1.10 is required, but installed version is %s." % np.version.version)
		exit(-1)



314
315
316
#
# main
#
317
if __name__ == "__main__":
Tobias WEBER's avatar
Tobias WEBER committed
318
	print("This is a covariance matrix calculator using neutron events, written by T. Weber <tweber@ill.fr>, 30 March 2019.\n")
319
320
	check_versions()

Tobias WEBER's avatar
Tobias WEBER committed
321
322
323
324
325
326
	import argparse as arg

	args = arg.ArgumentParser(description="Calculates the covariance matrix of neutron scattering events.")
	args.add_argument("file", type=str, help="input file")
	args.add_argument("-s", "--save", default="", type=str, nargs="?", help="save plot to file")
	args.add_argument("--ellipse", default=ellipse_points, type=int, nargs="?", help="number of points to draw ellipses")
327
328
329
330
331
332
333
334
	args.add_argument("--ki", default=ki_start_idx, type=int, nargs="?", help="index of ki vector's first column in kikf file")
	args.add_argument("--kf", default=kf_start_idx, type=int, nargs="?", help="index of kf vector's first column in kikf file")
	args.add_argument("--wi", default=wi_idx, type=int, nargs="?", help="index of ki weight factor column in kikf file")
	args.add_argument("--wf", default=wf_idx, type=int, nargs="?", help="index of kf weight factor column in kikf file")
	args.add_argument("--w", default=w_idx, type=int, nargs="?", help="index of neutron weight factor column in QE file")
	args.add_argument("--Q", default=Q_start_idx, type=int, nargs="?", help="index of Q vector's first column in QE file")
	args.add_argument("--E", default=E_idx, type=int, nargs="?", help="index of E column in QE file")
	args.add_argument("--kikf", action="store_true", help="use the kikf file type")
335
	args.add_argument("--centreonQ", action="store_true", help="centre plots on mean Q")
Tobias WEBER's avatar
Tobias WEBER committed
336
	args.add_argument("--noverbose", action="store_true", help="don't show console logs")
Tobias WEBER's avatar
Tobias WEBER committed
337
338
339
340
341
342
343
344
345
	args.add_argument("--noplot", action="store_true", help="don't show any plot windows")
	args.add_argument("--noneutrons", action="store_true", help="don't show neutron events in plots")
	args.add_argument("--symsize", default=symsize, type=float, nargs="?", help="size of the symbols in plots")
	args.add_argument("--ax", default=None, type=float, nargs="?", help="x component of first orientation vector")
	args.add_argument("--ay", default=None, type=float, nargs="?", help="y component of first orientation vector")
	args.add_argument("--az", default=None, type=float, nargs="?", help="z component of first orientation vector")
	args.add_argument("--bx", default=None, type=float, nargs="?", help="x component of second orientation vector")
	args.add_argument("--by", default=None, type=float, nargs="?", help="y component of second orientation vector")
	args.add_argument("--bz", default=None, type=float, nargs="?", help="z component of second orientation vector")
Tobias WEBER's avatar
Tobias WEBER committed
346
	argv = args.parse_args()
347

Tobias WEBER's avatar
Tobias WEBER committed
348
349
350
	verbose = (argv.noverbose==False)
	plot_results = (argv.noplot==False)
	plot_neutrons = (argv.noneutrons==False)
351
	centre_on_Q = argv.centreonQ
352
	use_kikf_file = argv.kikf
353

Tobias WEBER's avatar
Tobias WEBER committed
354
355
356
357
358
359
360
	infile = argv.file
	outfile = argv.save
	ellipse_points = argv.ellipse
	ki_start_idx = argv.ki
	kf_start_idx = argv.kf
	wi_idx = argv.wi
	wf_idx = argv.wf
Tobias WEBER's avatar
Tobias WEBER committed
361
	symsize = argv.symsize
Tobias WEBER's avatar
Tobias WEBER committed
362
363
	avec = [ argv.az, argv.ay, argv.az ]
	bvec = [ argv.bx, argv.by, argv.bz ]
Tobias WEBER's avatar
Tobias WEBER committed
364

365
366
367
368
369
	if use_kikf_file:
		[Q, E, w] = load_events_kikf(infile)
	else:
		[Q, E, w] = load_events_QE(infile)

Tobias WEBER's avatar
Tobias WEBER committed
370
371
372
373
374
375
	if avec[0]!=None and avec[1]!=None and avec[2]!=None and bvec[0]!=None and bvec[1]!=None and bvec[2]!=None:
		Qpara = np.array(avec)
		Qperp = np.array(bvec)
	else:
		Qpara = np.array([])
		Qperp = np.array([])
376

Tobias WEBER's avatar
Tobias WEBER committed
377
	[Qres, Q4, Qmean] = calc_covar(Q, E, w, Qpara, Qperp)
378
379
	[fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy] = calc_ellipses(Qres)

Tobias WEBER's avatar
Tobias WEBER committed
380
	if plot_results or outfile!="":
Tobias WEBER's avatar
Tobias WEBER committed
381
		plot_ellipses(outfile, Q4, w, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy)
Tobias WEBER's avatar
Tobias WEBER committed
382