source: sasmodels/sasmodels/resolution.py @ 49d1d42f

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 49d1d42f was 49d1d42f, checked in by Paul Kienzle <pkienzle@…>, 9 years ago

working demo of 1D resolution calculator (still incomplete and incorrect)

  • Property mode set to 100644
File size: 4.9 KB
Line 
1from scipy.special import erf
2from numpy import sqrt
3import numpy as np
4
5SLIT_SMEAR_POINTS = 500
6
7class MatrixResolution:
8    def apply(self, Iq):
9        return np.dot(Iq, self.resolution_matrix)
10
11class 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
18class 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
29def 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
47def pinhole_extend_q(q, q_width):
48    return q
49
50
51def 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
86def slit_extend_q(q, qx_width, qy_width):
87    return q
88
89
90def 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
105def _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
124def 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
130def 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
137def 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
146if __name__ == "__main__":
147    demo()
148
149
Note: See TracBrowser for help on using the repository browser.