cov.py 9.55 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 mc 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
30
31
# constants
sig2hwhm = np.sqrt(2. * np.log(2.))
sig2fwhm = 2.*sig2hwhm

32
33
34
35
36
#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
37
38
39
40
41
42
43
44



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

	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]



#
# 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
65
	# calculate the mean Q 4-vector
66
	Qmean = [ np.average(Q4[:,i], weights = w) for i in range(4) ]
67
68
	if verbose:
		print("Mean (Q, E) vector in lab system:\n%s\n" % Qmean)
69

Tobias WEBER's avatar
Tobias WEBER committed
70
71
	# get the weighted covariance matrix
	Qcov = np.cov(Q4, rowvar = False, aweights = w, ddof = 0)
72
73
	if verbose:
		print("Covariance matrix in lab system:\n%s\n" % Qcov)
74

Tobias WEBER's avatar
Tobias WEBER committed
75
	# the resolution is the inverse of the covariance
76
	Qres = la.inv(Qcov)
77
78
	if verbose:
		print("Resolution matrix in lab system:\n%s\n" % Qres)
79
80
81
82
83
84
85


	# 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
86
	# trafo matrix
87
88
89
90
91
92
	T = np.transpose(np.array([
		np.insert(Qnorm, 3, 0),
		np.insert(Qside, 3, 0),
		np.insert(Qup, 3, 0),
		[0, 0, 0, 1] ]))

93
94
	if verbose:
		print("Transformation into (Qpara, Qperp, Qup, E) system:\n%s\n" % T)
95

Tobias WEBER's avatar
Tobias WEBER committed
96
	# transform mean Q vector
97
	Qmean_Q = np.dot(np.transpose(T), Qmean)
98
99
	if verbose:
		print("Mean (Q, E) vector in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qmean_Q)
100

Tobias WEBER's avatar
Tobias WEBER committed
101
	# transform the covariance matrix
102
	Qcov_Q = np.dot(np.transpose(T), np.dot(Qcov, T))
103
104
	if verbose:
		print("Covariance matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qcov_Q)
105

Tobias WEBER's avatar
Tobias WEBER committed
106
	# the resolution is the inverse of the covariance
107
	Qres_Q = la.inv(Qcov_Q)
108
109
	if verbose:
		print("Resolution matrix in (Qpara, Qperp, Qup, E) system:\n%s\n" % Qres_Q)
110

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

114
	# transform all neutron events
115
	Q4_Q = np.array([])
Tobias WEBER's avatar
Tobias WEBER committed
116
	if plot_neutrons:
117
118
119
		Q4_Q = np.dot(Q4, T)
		if not centre_on_Q:
			Q4_Q -= Qmean_Q
120
121


122
	return [Qres_Q, Q4_Q, Qmean_Q]
123
124
125
126



#
127
# calculates the characteristics of a given ellipse
128
129
130
131
132
133
134
135
136
#
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]) ])

137
	return [fwhms, angles/np.pi*180., evecs]
138

139

140
141

#
142
# describes the ellipsoid by a principal axis trafo and by 2d cuts
143
144
#
def calc_ellipses(Qres_Q):
145
146
147
148
149
150
151
152
	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)
153
154
155
156
157


	# 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)
158
159
160
	[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]))
161
162
163

	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)
164
165
166
	[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]))
167
168
169

	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)
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
	[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
#
187
def plot_ellipses(file, Q4, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy):
188
189
	import matplotlib.pyplot as plot

190
191
192
193
	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
194
	# centre plots on zero or mean Q vector ?
195
196
197
198
199
200
201
202
203
204
205
	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]]])

206
207

	phi = np.linspace(0, 2.*np.pi, ellipse_points)
208
209
210
211
	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)
212
213
214
215
216

	fig = plot.figure()
	subplot_QxE = fig.add_subplot(221)
	subplot_QxE.set_xlabel("Qpara (1/A)")
	subplot_QxE.set_ylabel("E (meV")
217
218
219
	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")
220
221
222
223

	subplot_QyE = fig.add_subplot(222)
	subplot_QyE.set_xlabel("Qperp (1/A)")
	subplot_QyE.set_ylabel("E (meV)")
224
225
226
	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")
227
228
229
230

	subplot_QzE = fig.add_subplot(223)
	subplot_QzE.set_xlabel("Qup (1/A)")
	subplot_QzE.set_ylabel("E (meV)")
231
232
233
	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")
234
235
236
237

	subplot_QxQy = fig.add_subplot(224)
	subplot_QxQy.set_xlabel("Qpara (1/A)")
	subplot_QxQy.set_ylabel("Qperp (meV)")
238
239
240
	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")
241
	plot.tight_layout()
Tobias WEBER's avatar
Tobias WEBER committed
242
243
244
245
246
247
248
249

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

	if plot_results:
		plot.show()
250
251
252



253
254
255
256
257
258
259
260
261
262
263
264
265
#
# 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)



266
267
268
#
# main
#
269
if __name__ == "__main__":
270
	print("This is a covariance matrix calculator using MC events, written by T. Weber <tweber@ill.fr>, 30 March 2019.\n")
271
272
	check_versions()

Tobias WEBER's avatar
Tobias WEBER committed
273
274
275
276
277
278
279
280
281
282
	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")
	args.add_argument("--ki", default=ki_start_idx, type=int, nargs="?", help="index of ki vector's first column in file")
	args.add_argument("--kf", default=kf_start_idx, type=int, nargs="?", help="index of kf vector's first column in file")
	args.add_argument("--wi", default=wi_idx, type=int, nargs="?", help="index of ki weight factor column in file")
	args.add_argument("--wf", default=wf_idx, type=int, nargs="?", help="index of kf weight factor column in file")
283
	args.add_argument("--centreonQ", action="store_true", help="centre plots on mean Q")
Tobias WEBER's avatar
Tobias WEBER committed
284
285
286
287
	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()
288

Tobias WEBER's avatar
Tobias WEBER committed
289
290
291
	verbose = (argv.noverbose==False)
	plot_results = (argv.noplot==False)
	plot_neutrons = (argv.noneutrons==False)
292
293
	centre_on_Q = argv.centreonQ

Tobias WEBER's avatar
Tobias WEBER committed
294
295
296
297
298
299
300
301
302
	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

	[Q, E, w] = load_events(infile)
303

304
	[Qres, Q4, Qmean] = calc_covar(Q, E, w)
305
306
	[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
307
	if plot_results or outfile!="":
308
		plot_ellipses(outfile, Q4, Qmean, fwhms_QxE, rot_QxE, fwhms_QyE, rot_QyE, fwhms_QzE, rot_QzE, fwhms_QxQy, rot_QxQy)