1 | from scipy.special import erf |
---|
2 | from numpy import sqrt |
---|
3 | import numpy as np |
---|
4 | |
---|
5 | SLIT_SMEAR_POINTS = 500 |
---|
6 | |
---|
7 | class MatrixResolution: |
---|
8 | def apply(self, Iq): |
---|
9 | return np.dot(Iq, self.resolution_matrix) |
---|
10 | |
---|
11 | class Pinhole1D(MatrixResolution): |
---|
12 | def __init__(self, q, q_width): |
---|
13 | self.q, self.q_width = q, q_width |
---|
14 | self.q_calc = pinhole_extend_q(q,q_width) |
---|
15 | self.resolution_matrix = \ |
---|
16 | pinhole_resolution(self.q_calc, self.q, self.q_width) |
---|
17 | |
---|
18 | class Slit1D(MatrixResolution): |
---|
19 | def __init__(self, q, qx_width, qy_width): |
---|
20 | if np.isscalar(qx_width): |
---|
21 | qx_width = qx_width*np.ones(len(q)) |
---|
22 | if np.isscalar(qy_width): |
---|
23 | qy_width = qy_width*np.ones(len(q)) |
---|
24 | self.q, self.qx_width, self.qy_width = q, qx_width, qy_width |
---|
25 | self.q_calc = slit_extend_q(q, qx_width, qy_width) |
---|
26 | self.resolution_matrix = \ |
---|
27 | slit_resolution(self.q_calc, self.q, self.qx_width, self.qy_width) |
---|
28 | |
---|
29 | def pinhole_resolution(q_calc, q, q_width): |
---|
30 | """ |
---|
31 | Compute the convolution matrix *W* for pinhole resolution 1-D data. |
---|
32 | |
---|
33 | Each row *W[i]* determines the normalized weight that the corresponding |
---|
34 | points *q_calc* contribute to the resolution smeared point *q[i]*. Given |
---|
35 | *W*, the resolution smearing can be computed using *dot(W,q)*. |
---|
36 | |
---|
37 | *q_calc* must be increasing. |
---|
38 | """ |
---|
39 | edges = bin_edges(q_calc) |
---|
40 | edges[edges<0.] = 0. # clip edges below zero |
---|
41 | G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] ) |
---|
42 | weights = G[1:] - G[:-1] |
---|
43 | weights /= np.sum(weights, axis=1) |
---|
44 | return weights |
---|
45 | |
---|
46 | |
---|
47 | def pinhole_extend_q(q, q_width): |
---|
48 | return q |
---|
49 | |
---|
50 | |
---|
51 | def slit_resolution(q_calc, q, qx_width, qy_width): |
---|
52 | edges = bin_edges(q_calc) # Note: requires q > 0 |
---|
53 | edges[edges<0.] = 0.0 # clip edges below zero |
---|
54 | qy_min, qy_max = 0.0, edges[-1] |
---|
55 | |
---|
56 | # Make q_calc into a row vector, and q, qx_width, qy_width into columns |
---|
57 | # Make weights match [ q_calc X q ] |
---|
58 | weights = np.zeros((len(q),len(q_calc)),'d') |
---|
59 | q_calc = q_calc[None,:] |
---|
60 | q, qx_width, qy_width, edges = [ |
---|
61 | v[:,None] for v in (q, qx_width, qy_width, edges)] |
---|
62 | |
---|
63 | # Loop for width (height is analytical). |
---|
64 | # Condition: height >>> width, otherwise, below is not accurate enough. |
---|
65 | # Smear weight numerical iteration for width>0 when height>0. |
---|
66 | # When width = 0, the numerical iteration will be skipped. |
---|
67 | # The resolution calculation for the height is done by direct integration, |
---|
68 | # assuming the I(q'=sqrt(q_j^2-(q+shift_w)^2)) is constant within |
---|
69 | # a q' bin, [q_high, q_low]. |
---|
70 | # In general, this weight numerical iteration for width>0 might be a rough |
---|
71 | # approximation, but it must be good enough when height >>> width. |
---|
72 | E_sq = edges**2 |
---|
73 | y_points = SLIT_SMEAR_POINTS if np.any(qy_width>0) else 1 |
---|
74 | qy_step = 0 if y_points == 1 else qy_width/(y_points-1) |
---|
75 | for k in range(-y_points+1,y_points): |
---|
76 | qy = np.clip(q + qy_step*k, qy_min, qy_max) |
---|
77 | qx_low = qy |
---|
78 | qx_high = sqrt(qx_low**2 + qx_width**2) |
---|
79 | in_x = (q_calc>=qx_low)*(q_calc<=qx_high) |
---|
80 | qy_sq = qy**2 |
---|
81 | weights += (sqrt(E_sq[1:]-qy_sq) - sqrt(qy_sq - E_sq[:-1]))*in_x |
---|
82 | weights /= np.sum(weights, axis=1) |
---|
83 | return weights |
---|
84 | |
---|
85 | |
---|
86 | def slit_extend_q(q, qx_width, qy_width): |
---|
87 | return q |
---|
88 | |
---|
89 | |
---|
90 | def bin_edges(x): |
---|
91 | if len(x) < 2 or (np.diff(x)<0).any(): |
---|
92 | raise ValueError("Expected bins to be an increasing set") |
---|
93 | edges = np.hstack([ |
---|
94 | x[0] - 0.5*(x[1] - x[0]), # first point minus half first interval |
---|
95 | 0.5*(x[1:] + x[:-1]), # mid points of all central intervals |
---|
96 | x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval |
---|
97 | ]) |
---|
98 | return edges |
---|
99 | |
---|
100 | |
---|
101 | ############################################################################ |
---|
102 | # usage demo |
---|
103 | ############################################################################ |
---|
104 | |
---|
105 | def _eval_demo_1d(resolution, title): |
---|
106 | from sasmodels import core |
---|
107 | from sasmodels.models import cylinder |
---|
108 | ## or alternatively: |
---|
109 | # cylinder = core.load_model_definition('cylinder') |
---|
110 | model = core.load_model(cylinder) |
---|
111 | |
---|
112 | kernel = core.make_kernel(model, [resolution.q_calc]) |
---|
113 | Iq_calc = core.call_kernel(kernel, {'length':210, 'radius':500}) |
---|
114 | Iq = resolution.apply(Iq_calc) |
---|
115 | |
---|
116 | import matplotlib.pyplot as plt |
---|
117 | plt.loglog(resolution.q_calc, Iq_calc, label='unsmeared') |
---|
118 | plt.loglog(resolution.q, Iq, label='smeared', hold=True) |
---|
119 | plt.legend() |
---|
120 | plt.title(title) |
---|
121 | plt.xlabel("Q (1/Ang)") |
---|
122 | plt.ylabel("I(Q) (1/cm)") |
---|
123 | |
---|
124 | def demo_pinhole_1d(): |
---|
125 | q = np.logspace(-3,-1,400) |
---|
126 | dq = 0.1*q |
---|
127 | resolution = Pinhole1D(q, dq) |
---|
128 | _eval_demo_1d(resolution, title="10% dQ/Q Pinhole Resolution") |
---|
129 | |
---|
130 | def demo_slit_1d(): |
---|
131 | q = np.logspace(-3,-1,400) |
---|
132 | qx_width = 0.005 |
---|
133 | qy_width = 0.0 |
---|
134 | resolution = Slit1D(q, qx_width, qy_width) |
---|
135 | _eval_demo_1d(resolution, title="0.005 Qx Slit Resolution") |
---|
136 | |
---|
137 | def demo(): |
---|
138 | import matplotlib.pyplot as plt |
---|
139 | plt.subplot(121) |
---|
140 | demo_pinhole_1d() |
---|
141 | plt.subplot(122) |
---|
142 | demo_slit_1d() |
---|
143 | plt.show() |
---|
144 | |
---|
145 | |
---|
146 | if __name__ == "__main__": |
---|
147 | demo() |
---|
148 | |
---|
149 | |
---|