1 | from scipy.special import erf |
---|
2 | from numpy import sqrt |
---|
3 | import numpy as np |
---|
4 | |
---|
5 | SLIT_SMEAR_POINTS = 500 |
---|
6 | |
---|
7 | def pinhole_resolution(q_calc, q, q_width): |
---|
8 | """ |
---|
9 | Compute the convolution matrix *W* for pinhole resolution 1-D data. |
---|
10 | |
---|
11 | Each row *W[i]* determines the normalized weight that the corresponding |
---|
12 | points *q_calc* contribute to the resolution smeared point *q[i]*. Given |
---|
13 | *W*, the resolution smearing can be computed using *dot(W,q)*. |
---|
14 | |
---|
15 | *q_calc* must be increasing. |
---|
16 | """ |
---|
17 | edges = bin_edges(q_calc) |
---|
18 | edges[edges<0.] = 0. # clip edges below zero |
---|
19 | G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] ) |
---|
20 | weights = G[1:] - G[:-1] |
---|
21 | weights /= sum(weights, axis=1) |
---|
22 | return weights |
---|
23 | |
---|
24 | def slit_resolution(q_calc, q, qx_width, qy_width): |
---|
25 | edges = bin_edges(q_calc) # Note: requires q > 0 |
---|
26 | edges[edges<0.] = 0.0 # clip edges below zero |
---|
27 | qy_min, qy_max = 0.0, edges[-1] |
---|
28 | |
---|
29 | weights = np.zeros((len(q),len(q_calc)),'d') |
---|
30 | # Loop for width (height is analytical). |
---|
31 | # Condition: height >>> width, otherwise, below is not accurate enough. |
---|
32 | # Smear weight numerical iteration for width>0 when height>0. |
---|
33 | # When width = 0, the numerical iteration will be skipped. |
---|
34 | # The resolution calculation for the height is done by direct integration, |
---|
35 | # assuming the I(q'=sqrt(q_j^2-(q+shift_w)^2)) is constant within |
---|
36 | # a q' bin, [q_high, q_low]. |
---|
37 | # In general, this weight numerical iteration for width>0 might be a rough |
---|
38 | # approximation, but it must be good enough when height >>> width. |
---|
39 | E_sq = edges**2[:,None] |
---|
40 | y_points = SLIT_SMEAR_POINTS if np.any(qy_width>0) else 1 |
---|
41 | qy_step = 0 if y_points == 1 else qy_width/(y_points-1) |
---|
42 | for k in range(-y_points+1,y_points): |
---|
43 | qy = np.clip(q + qy_step*k, qy_min, qy_max) |
---|
44 | qx_low = qy |
---|
45 | qx_high = sqrt(qx_low**2 + qx_width**2) |
---|
46 | in_x = (q_calc[:,None]>=qx_low[None,:])*(q_calc[:,None]<=qx_high[None,:]) |
---|
47 | qy_sq = qy**2[None,:] |
---|
48 | weights += (sqrt(E_sq[1:]-qy_sq) - sqrt(E_sq[:-1]-qy_sq))*in_x |
---|
49 | weights /= sum(weights, axis=1) |
---|
50 | return weights |
---|
51 | |
---|
52 | def bin_edges(x): |
---|
53 | if len(x) < 2 or (np.diff(x)<0).any(): |
---|
54 | raise ValueError("Expected bins to be an increasing set") |
---|
55 | edges = np.hstack([ |
---|
56 | x[0] - 0.5*(x[1] - x[0]), # first point minus half first interval |
---|
57 | 0.5*(x[1:] + x[:-1]), # mid points of all central intervals |
---|
58 | x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval |
---|
59 | ]) |
---|
60 | return edges |
---|