cov.py 10.4 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
9
10
11
#

import numpy as np
import numpy.linalg as la

12

13
14
15
16
17
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
18
19


20
# column indices in kf,kf file
21
22
23
24
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
25

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

31

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

36
37
38
39
40
#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
41
42
43
44
45
46



#
# loads a list of neutron events in the [ ki_vec, kf_vec, pos_vec, wi, wf ] format
#
47
def load_events_kikf(filename):
48
	dat = np.loadtxt(filename)
49
50
51
52
	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]
53
54
55
56
57
58
59
60
61

	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]



62
63
64
65
66
67
68
69
70
71
72
73
74
#
# 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]



75
76
77
78
79
80
81
#
# calculates the covariance matrix of the (Q, E) 4-vectors
#
def calc_covar(Q, E, w):
	# make a [Q, E] 4-vector
	Q4 = np.insert(Q, 3, E, axis=1)

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

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

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


	# create a matrix to transform into the coordinate system with Q along x
	Qnorm = Qmean[0:3] / la.norm(Qmean[0:3])
	Qup = np.array([0, 1, 0])
	Qside = np.cross(Qup, Qnorm)

Tobias WEBER's avatar
Tobias WEBER committed
103
	# trafo matrix
104
105
106
107
108
109
	T = np.transpose(np.array([
		np.insert(Qnorm, 3, 0),
		np.insert(Qside, 3, 0),
		np.insert(Qup, 3, 0),
		[0, 0, 0, 1] ]))

110
111
	if verbose:
		print("Transformation into (Qpara, Qperp, Qup, E) system:\n%s\n" % T)
112

Tobias WEBER's avatar
Tobias WEBER committed
113
	# transform mean Q vector
114
	Qmean_Q = np.dot(np.transpose(T), Qmean)
115
116
	if verbose:
		print("Mean (Q, E) vector in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qmean_Q)
117

Tobias WEBER's avatar
Tobias WEBER committed
118
	# transform the covariance matrix
119
	Qcov_Q = np.dot(np.transpose(T), np.dot(Qcov, T))
120
121
	if verbose:
		print("Covariance matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qcov_Q)
122

Tobias WEBER's avatar
Tobias WEBER committed
123
	# the resolution is the inverse of the covariance
124
	Qres_Q = la.inv(Qcov_Q)
125
126
	if verbose:
		print("Resolution matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qres_Q)
127

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

131
	# transform all neutron events
132
	Q4_Q = np.array([])
Tobias WEBER's avatar
Tobias WEBER committed
133
	if plot_neutrons:
134
135
136
		Q4_Q = np.dot(Q4, T)
		if not centre_on_Q:
			Q4_Q -= Qmean_Q
137
138


139
	return [Qres_Q, Q4_Q, Qmean_Q]
140
141
142
143



#
144
# calculates the characteristics of a given ellipse
145
146
147
148
149
150
151
152
153
#
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]) ])

154
	return [fwhms, angles/np.pi*180., evecs]
155

156

157
158

#
159
# describes the ellipsoid by a principal axis trafo and by 2d cuts
160
161
#
def calc_ellipses(Qres_Q):
162
163
164
165
166
167
168
169
	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)
170
171
172
173
174


	# 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)
175
176
177
	[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]))
178
179
180

	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)
181
182
183
	[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]))
184
185
186

	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)
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
	[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
#
204
def plot_ellipses(file, Q4, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy):
205
206
	import matplotlib.pyplot as plot

207
208
209
210
	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
211
	# centre plots on zero or mean Q vector ?
212
213
214
215
216
217
218
219
220
221
222
	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]]])

223
224

	phi = np.linspace(0, 2.*np.pi, ellipse_points)
225
226
227
228
	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)
229
230
231
232

	fig = plot.figure()
	subplot_QxE = fig.add_subplot(221)
	subplot_QxE.set_xlabel("Qpara (1/A)")
Tobias WEBER's avatar
Tobias WEBER committed
233
	subplot_QxE.set_ylabel("E (meV)")
234
235
236
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
		subplot_QxE.scatter(Q4[:, 0], Q4[:, 3], s=0.25)
	subplot_QxE.plot(ell_QxE[0], ell_QxE[1], c="black")
237
238
239
240

	subplot_QyE = fig.add_subplot(222)
	subplot_QyE.set_xlabel("Qperp (1/A)")
	subplot_QyE.set_ylabel("E (meV)")
241
242
243
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
		subplot_QyE.scatter(Q4[:, 1], Q4[:, 3], s=0.25)
	subplot_QyE.plot(ell_QyE[0], ell_QyE[1], c="black")
244
245
246
247

	subplot_QzE = fig.add_subplot(223)
	subplot_QzE.set_xlabel("Qup (1/A)")
	subplot_QzE.set_ylabel("E (meV)")
248
249
250
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
		subplot_QzE.scatter(Q4[:, 2], Q4[:, 3], s=0.25)
	subplot_QzE.plot(ell_QzE[0], ell_QzE[1], c="black")
251
252
253
254

	subplot_QxQy = fig.add_subplot(224)
	subplot_QxQy.set_xlabel("Qpara (1/A)")
	subplot_QxQy.set_ylabel("Qperp (meV)")
255
256
257
	if len(Q4.shape)==2 and len(Q4)>0 and len(Q4[0])==4:
		subplot_QxQy.scatter(Q4[:, 0], Q4[:, 1], s=0.25)
	subplot_QxQy.plot(ell_QxQy[0], ell_QxQy[1], c="black")
258
	plot.tight_layout()
Tobias WEBER's avatar
Tobias WEBER committed
259
260
261
262
263
264
265
266

	if file != "":
		if verbose:
			print("Saving plot to \"%s\"." % file)
		plot.savefig(file)

	if plot_results:
		plot.show()
267
268
269



270
271
272
273
274
275
276
277
278
279
280
281
282
#
# 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)



283
284
285
#
# main
#
286
if __name__ == "__main__":
287
	print("This is a covariance matrix calculator using MC events, written by T. Weber <tweber@ill.fr>, 30 March 2019.\n")
288
289
	check_versions()

Tobias WEBER's avatar
Tobias WEBER committed
290
291
292
293
294
295
	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")
296
297
298
299
300
301
302
303
	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")
304
	args.add_argument("--centreonQ", action="store_true", help="centre plots on mean Q")
Tobias WEBER's avatar
Tobias WEBER committed
305
306
307
308
	args.add_argument("--noverbose", action="store_true", help="don't show console logs")
	args.add_argument("--noplot", action="store_true", help="don't show plot window")
	args.add_argument("--noneutrons", action="store_true", help="don't show mc neutrons in plot")
	argv = args.parse_args()
309

Tobias WEBER's avatar
Tobias WEBER committed
310
311
312
	verbose = (argv.noverbose==False)
	plot_results = (argv.noplot==False)
	plot_neutrons = (argv.noneutrons==False)
313
	centre_on_Q = argv.centreonQ
314
	use_kikf_file = argv.kikf
315

Tobias WEBER's avatar
Tobias WEBER committed
316
317
318
319
320
321
322
323
	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

324
325
326
327
328
	if use_kikf_file:
		[Q, E, w] = load_events_kikf(infile)
	else:
		[Q, E, w] = load_events_QE(infile)

329

330
	[Qres, Q4, Qmean] = calc_covar(Q, E, w)
331
332
	[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
333
	if plot_results or outfile!="":
334
		plot_ellipses(outfile, Q4, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy)