source: sasmodels/sasmodels/resolution.py @ 7954f5c

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

rewrite slit resolution algorithm

  • Property mode set to 100644
File size: 29.5 KB
Line 
1"""
2Define the resolution functions for the data.
3
4This defines classes for 1D and 2D resolution calculations.
5"""
6from __future__ import division
7from scipy.special import erf
8from numpy import sqrt, log, log10
9import numpy as np
10
11MINIMUM_RESOLUTION = 1e-8
12
13class Resolution1D(object):
14    """
15    Abstract base class defining a 1D resolution function.
16
17    *q* is the set of q values at which the data is measured.
18
19    *q_calc* is the set of q values at which the theory needs to be evaluated.
20    This may extend and interpolate the q values.
21
22    *apply* is the method to call with I(q_calc) to compute the resolution
23    smeared theory I(q).
24    """
25    q = None
26    q_calc = None
27    def apply(self, theory):
28        """
29        Smear *theory* by the resolution function, returning *Iq*.
30        """
31        raise NotImplementedError("Subclass does not define the apply function")
32
33
34class Perfect1D(Resolution1D):
35    """
36    Resolution function to use when there is no actual resolution smearing
37    to be applied.  It has the same interface as the other resolution
38    functions, but returns the identity function.
39    """
40    def __init__(self, q):
41        self.q_calc = self.q = q
42
43    def apply(self, theory):
44        return theory
45
46
47class Pinhole1D(Resolution1D):
48    r"""
49    Pinhole aperture with q-dependent gaussian resolution.
50
51    *q* points at which the data is measured.
52
53    *q_width* gaussian 1-sigma resolution at each data point.
54
55    *q_calc* is the list of points to calculate, or None if this should
56    be estimated from the *q* and *q_width*.
57    """
58    def __init__(self, q, q_width, q_calc=None):
59        #*min_step* is the minimum point spacing to use when computing the
60        #underlying model.  It should be on the order of
61        #$\tfrac{1}{10}\tfrac{2\pi}{d_\text{max}}$ to make sure that fringes
62        #are computed with sufficient density to avoid aliasing effects.
63
64        # Protect against calls with q_width=0.  The extend_q function will
65        # not extend the q if q_width is 0, but q_width must be non-zero when
66        # constructing the weight matrix to avoid division by zero errors.
67        # In practice this should never be needed, since resolution should
68        # default to Perfect1D if the pinhole geometry is not defined.
69        self.q, self.q_width = q, q_width
70        self.q_calc = pinhole_extend_q(q, q_width) \
71            if q_calc is None else np.sort(q_calc)
72        self.weight_matrix = pinhole_resolution(self.q_calc,
73                self.q, np.maximum(q_width, MINIMUM_RESOLUTION))
74
75    def apply(self, theory):
76        return apply_resolution_matrix(self.weight_matrix, theory)
77
78
79class Slit1D(Resolution1D):
80    """
81    Slit aperture with a complicated resolution function.
82
83    *q* points at which the data is measured.
84
85    *qx_width* slit width
86
87    *qy_height* slit height
88
89    *q_calc* is the list of points to calculate, or None if this should
90    be estimated from the *q* and *q_width*.
91
92    The *weight_matrix* is computed by :func:`slit1d_resolution`
93    """
94    def __init__(self, q, width, height, q_calc=None):
95        # TODO: maybe issue warnings rather than raising errors
96        if not np.isscalar(width):
97            if np.any(np.diff(width) > 0.0):
98                raise ValueError("Slit resolution requires fixed width slits")
99            width = width[0]
100        if not np.isscalar(height):
101            if np.any(np.diff(height) > 0.0):
102                raise ValueError("Slit resolution requires fixed height slits")
103            height = height[0]
104
105        # Remember what width/height was used even though we won't need them
106        # after the weight matrix is constructed
107        self.width, self.height = width, height
108
109        self.q = q.flatten()
110        self.q_calc = slit_extend_q(q, width, height) \
111            if q_calc is None else np.sort(q_calc)
112        self.weight_matrix = \
113            slit_resolution(self.q_calc, self.q, width, height)
114
115    def apply(self, theory):
116        return apply_resolution_matrix(self.weight_matrix, theory)
117
118
119def apply_resolution_matrix(weight_matrix, theory):
120    """
121    Apply the resolution weight matrix to the computed theory function.
122    """
123    #print "apply shapes", theory.shape, weight_matrix.shape
124    Iq = np.dot(theory[None,:], weight_matrix)
125    #print "result shape",Iq.shape
126    return Iq.flatten()
127
128
129def pinhole_resolution(q_calc, q, q_width):
130    """
131    Compute the convolution matrix *W* for pinhole resolution 1-D data.
132
133    Each row *W[i]* determines the normalized weight that the corresponding
134    points *q_calc* contribute to the resolution smeared point *q[i]*.  Given
135    *W*, the resolution smearing can be computed using *dot(W,q)*.
136
137    *q_calc* must be increasing.  *q_width* must be greater than zero.
138    """
139    # The current algorithm is a midpoint rectangle rule.  In the test case,
140    # neither trapezoid nor Simpson's rule improved the accuracy.
141    edges = bin_edges(q_calc)
142    edges[edges<0.0] = 0.0 # clip edges below zero
143    G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] )
144    weights = G[1:] - G[:-1]
145    weights /= np.sum(weights, axis=0)[None,:]
146    return weights
147
148
149def slit_resolution(q_calc, q, width, height):
150    r"""
151    Build a weight matrix to compute *I_s(q)* from *I(q_calc)*, given
152    $q_v$ = *width* and $q_h$ = *height*.
153
154    *width* and *height* are scalars since current instruments use the
155    same slit settings for all measurement points.
156
157    If slit height is large relative to width, use:
158
159    .. math::
160
161        I_s(q_o) = \frac{1}{\Delta q_v}
162            \int_0^{\Delta q_v} I(\sqrt{q_o^2 + u^2} du
163
164    If slit width is large relative to height, use:
165
166    .. math::
167
168        I_s(q_o) = \frac{1}{2 \Delta q_v}
169            \int_{-\Delta q_v}^{\Delta q_v} I(u) du
170    """
171    if width == 0.0 and height == 0.0:
172        #print "condition zero"
173        return 1
174
175    q_edges = bin_edges(q_calc) # Note: requires q > 0
176    q_edges[q_edges<0.0] = 0.0 # clip edges below zero
177
178    #np.set_printoptions(linewidth=10000)
179    if width <= 100.0 * height or height == 0:
180        # The current algorithm is a midpoint rectangle rule.  In the test case,
181        # neither trapezoid nor Simpson's rule improved the accuracy.
182        #print "condition h", q_edges.shape, q.shape, q_calc.shape
183        weights = np.zeros((len(q), len(q_calc)), 'd')
184        for i, qi in enumerate(q):
185            weights[i, :] = np.diff(q_to_u(q_edges, qi))
186        weights /= width
187        weights = weights.T
188    else:
189        #print "condition w"
190        # Make q_calc into a row vector, and q into a column vector
191        q, q_calc = q[None,:], q_calc[:,None]
192        in_x = (q_calc >= q-width) * (q_calc <= q+width)
193        weights = np.diff(q_edges)[:,None] * in_x
194
195    return weights
196
197
198def pinhole_extend_q(q, q_width, nsigma=3):
199    """
200    Given *q* and *q_width*, find a set of sampling points *q_calc* so
201    that each point I(q) has sufficient support from the underlying
202    function.
203    """
204    q_min, q_max = np.min(q - nsigma*q_width), np.max(q + nsigma*q_width)
205    return geometric_extrapolation(q, q_min, q_max)
206
207
208def slit_extend_q(q, width, height):
209    """
210    Given *q*, *width* and *height*, find a set of sampling points *q_calc* so
211    that each point I(q) has sufficient support from the underlying
212    function.
213    """
214    height # keep lint happy
215    q_min, q_max = np.min(q), np.max(np.sqrt(q**2 + width**2))
216    return geometric_extrapolation(q, q_min, q_max)
217
218
219def bin_edges(x):
220    """
221    Determine bin edges from bin centers, assuming that edges are centered
222    between the bins.
223
224    Note: this uses the arithmetic mean, which may not be appropriate for
225    log-scaled data.
226    """
227    if len(x) < 2 or (np.diff(x)<0).any():
228        raise ValueError("Expected bins to be an increasing set")
229    edges = np.hstack([
230        x[0]  - 0.5*(x[1]  - x[0]),  # first point minus half first interval
231        0.5*(x[1:] + x[:-1]),        # mid points of all central intervals
232        x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval
233        ])
234    return edges
235
236
237def q_to_u(q, q0):
238    """
239    Convert *q* values to *u* values for the integral computed at *q0*.
240    """
241    # array([value])**2 - value**2 is not always zero
242    qpsq = q**2 - q0**2
243    qpsq[qpsq<0] = 0
244    return sqrt(qpsq)
245
246
247def interpolate(q, max_step):
248    """
249    Returns *q_calc* with points spaced at most max_step apart.
250    """
251    step = np.diff(q)
252    index = step>max_step
253    if np.any(index):
254        inserts = []
255        for q_i,step_i in zip(q[:-1][index],step[index]):
256            n = np.ceil(step_i/max_step)
257            inserts.extend(q_i + np.arange(1,n)*(step_i/n))
258        # Extend a couple of fringes beyond the end of the data
259        inserts.extend(q[-1] + np.arange(1,8)*max_step)
260        q_calc = np.sort(np.hstack((q,inserts)))
261    else:
262        q_calc = q
263    return q_calc
264
265
266def linear_extrapolation(q, q_min, q_max):
267    """
268    Extrapolate *q* out to [*q_min*, *q_max*] using the step size in *q* as
269    a guide.  Extrapolation below uses steps the same size as the first
270    interval.  Extrapolation above uses steps the same size as the final
271    interval.
272
273    if *q_min* is zero or less then *q[0]/10* is used instead.
274    """
275    q = np.sort(q)
276    if q_min < q[0]:
277        if q_min <= 0: q_min = q[0]/10
278        delta = q[1] - q[0]
279        q_low = np.arange(q_min, q[0], delta)
280    else:
281        q_low = []
282    if q_max > q[-1]:
283        delta = q[-1] - q[-2]
284        q_high = np.arange(q[-1]+delta, q_max, delta)
285    else:
286        q_high = []
287    return np.concatenate([q_low, q, q_high])
288
289
290def geometric_extrapolation(q, q_min, q_max):
291    r"""
292    Extrapolate *q* to [*q_min*, *q_max*] using geometric steps, with the
293    average geometric step size in *q* as the step size.
294
295    if *q_min* is zero or less then *q[0]/10* is used instead.
296
297    Starting at $q_1$ and stepping geometrically by $\Delta q$
298    to $q_n$ in $n$ points gives a geometric average of:
299
300    .. math::
301
302         \log \Delta q = (\log q_n - log q_1) / (n - 1)
303
304    From this we can compute the number of steps required to extend $q$
305    from $q_n$ to $q_\text{max}$ by $\Delta q$ as:
306
307    .. math::
308
309         n_\text{extend} = (\log q_\text{max} - \log q_n) / \log \Delta q
310
311    Substituting:
312
313        n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n)
314            / (\log q_n - log q_1)
315    """
316    q = np.sort(q)
317    delta_q = (len(q)-1)/(log(q[-1]) - log(q[0]))
318    if q_min < q[0]:
319        if q_min < 0: q_min = q[0]/10
320        n_low = delta_q * (log(q[0])-log(q_min))
321        q_low  = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1]
322    else:
323        q_low = []
324    if q_max > q[-1]:
325        n_high = delta_q * (log(q_max)-log(q[-1]))
326        q_high = np.logspace(log10(q[-1]), log10(q_max), np.ceil(n_high)+1)[1:]
327    else:
328        q_high = []
329    return np.concatenate([q_low, q, q_high])
330
331
332############################################################################
333# unit tests
334############################################################################
335import unittest
336
337
338def eval_form(q, form, pars):
339    from sasmodels import core
340    kernel = core.make_kernel(form, [q])
341    theory = core.call_kernel(kernel, pars)
342    kernel.release()
343    return theory
344
345
346def gaussian(q, q0, dq):
347    from numpy import exp, pi
348    return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq)
349
350
351def romberg_slit_1d(q, delta_qv, form, pars):
352    """
353    Romberg integration for slit resolution.
354
355    This is an adaptive integration technique.  It is called with settings
356    that make it slow to evaluate but give it good accuracy.
357    """
358    from scipy.integrate import romberg
359    _fn = lambda u, q0: eval_form(sqrt(q0**2 + u**2), form, pars)
360    r = [romberg(_fn, 0, delta_qv[0], args=(qi,),
361                 divmax=100, vec_func=True, tol=0, rtol=1e-8)
362         for qi in q]
363    # r should be [float, ...], but it is [array([float]), array([float]),...]
364    return np.asarray(r).flatten()/delta_qv[0]
365
366
367def romberg_pinhole_1d(q, q_width, form, pars):
368    """
369    Romberg integration for pinhole resolution.
370
371    This is an adaptive integration technique.  It is called with settings
372    that make it slow to evaluate but give it good accuracy.
373    """
374    from scipy.integrate import romberg
375
376    _fn = lambda q, q0, dq: eval_form(q, form, pars)*gaussian(q, q0, dq)
377    r = [romberg(_fn, max(qi-5*dqi,0.01*q[0]), qi+5*dqi, args=(qi, dqi),
378                 divmax=100, vec_func=True, tol=0, rtol=1e-8)
379         for qi,dqi in zip(q,q_width)]
380    return np.asarray(r).flatten()
381
382
383class ResolutionTest(unittest.TestCase):
384
385    def setUp(self):
386        self.x = 0.001*np.arange(1, 11)
387        self.y = self.Iq(self.x)
388
389    def Iq(self, q):
390        "Linear function for resolution unit test"
391        return 12.0 - 1000.0*q
392
393    def test_perfect(self):
394        """
395        Perfect resolution and no smearing.
396        """
397        resolution = Perfect1D(self.x)
398        theory = self.Iq(resolution.q_calc)
399        output = resolution.apply(theory)
400        np.testing.assert_equal(output, self.y)
401
402    def test_slit_zero(self):
403        """
404        Slit smearing with perfect resolution.
405        """
406        resolution = Slit1D(self.x, width=0, height=0, q_calc=self.x)
407        theory = self.Iq(resolution.q_calc)
408        output = resolution.apply(theory)
409        np.testing.assert_equal(output, self.y)
410
411    @unittest.skip("not yet supported")
412    def test_slit_high(self):
413        """
414        Slit smearing with height 0.005
415        """
416        resolution = Slit1D(self.x, width=0, height=0.005, q_calc=self.x)
417        theory = self.Iq(resolution.q_calc)
418        output = resolution.apply(theory)
419        answer = [ 9.0618, 8.6402, 8.1187, 7.1392, 6.1528,
420                   5.5555, 4.5584, 3.5606, 2.5623, 2.0000 ]
421        np.testing.assert_allclose(output, answer, atol=1e-4)
422
423    @unittest.skip("not yet supported")
424    def test_slit_both_high(self):
425        """
426        Slit smearing with width < 100*height.
427        """
428        q = np.logspace(-4, -1, 10)
429        resolution = Slit1D(q, width=0.2, height=np.inf)
430        theory = 1000*self.Iq(resolution.q_calc**4)
431        output = resolution.apply(theory)
432        answer = [ 8.85785, 8.43012, 7.92687, 6.94566, 6.03660,
433                   5.40363, 4.40655, 3.40880, 2.41058, 2.00000 ]
434        np.testing.assert_allclose(output, answer, atol=1e-4)
435
436    @unittest.skip("not yet supported")
437    def test_slit_wide(self):
438        """
439        Slit smearing with width 0.0002
440        """
441        resolution = Slit1D(self.x, width=0.0002, height=0, q_calc=self.x)
442        theory = self.Iq(resolution.q_calc)
443        output = resolution.apply(theory)
444        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
445        np.testing.assert_allclose(output, answer, atol=1e-4)
446
447    @unittest.skip("not yet supported")
448    def test_slit_both_wide(self):
449        """
450        Slit smearing with width > 100*height.
451        """
452        resolution = Slit1D(self.x, width=0.0002, height=0.000001,
453                            q_calc=self.x)
454        theory = self.Iq(resolution.q_calc)
455        output = resolution.apply(theory)
456        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
457        np.testing.assert_allclose(output, answer, atol=1e-4)
458
459    def test_pinhole_zero(self):
460        """
461        Pinhole smearing with perfect resolution
462        """
463        resolution = Pinhole1D(self.x, 0.0*self.x)
464        theory = self.Iq(resolution.q_calc)
465        output = resolution.apply(theory)
466        np.testing.assert_equal(output, self.y)
467
468    def test_pinhole(self):
469        """
470        Pinhole smearing with dQ = 0.001 [Note: not dQ/Q = 0.001]
471        """
472        resolution = Pinhole1D(self.x, 0.001*np.ones_like(self.x),
473                               q_calc=self.x)
474        theory = 12.0-1000.0*resolution.q_calc
475        output = resolution.apply(theory)
476        answer = [ 10.44785079, 9.84991299, 8.98101708,
477                  7.99906585, 6.99998311, 6.00001689,
478                  5.00093415, 4.01898292, 3.15008701, 2.55214921]
479        np.testing.assert_allclose(output, answer, atol=1e-8)
480
481
482class IgorComparisonTest(unittest.TestCase):
483
484    def setUp(self):
485        self.pars = TEST_PARS_PINHOLE_SPHERE
486        from sasmodels import core
487        from sasmodels.models import sphere
488        self.model = core.load_model(sphere, dtype='double')
489
490    def Iq_sphere(self, pars, resolution):
491        from sasmodels import core
492        kernel = core.make_kernel(self.model, [resolution.q_calc])
493        theory = core.call_kernel(kernel, pars)
494        result = resolution.apply(theory)
495        kernel.release()
496        return result
497
498    def compare(self, q, output, answer, tolerance):
499        err = (output - answer)/answer
500        idx = abs(err) >= tolerance
501        problem = zip(q[idx], output[idx], answer[idx], err[idx])
502        print "\n".join(str(v) for v in problem)
503        np.testing.assert_allclose(output, answer, rtol=tolerance)
504
505    def test_perfect(self):
506        """
507        Compare sphere model with NIST Igor SANS, no resolution smearing.
508        """
509        pars = TEST_PARS_SLIT_SPHERE
510        data_string = TEST_DATA_SLIT_SPHERE
511
512        data = np.loadtxt(data_string.split('\n')).T
513        q, width, answer, _ = data
514        resolution = Perfect1D(q)
515        output = self.Iq_sphere(pars, resolution)
516        self.compare(q, output, answer, 1e-6)
517
518    def test_pinhole(self):
519        """
520        Compare pinhole resolution smearing with NIST Igor SANS
521        """
522        pars = TEST_PARS_PINHOLE_SPHERE
523        data_string = TEST_DATA_PINHOLE_SPHERE
524
525        data = np.loadtxt(data_string.split('\n')).T
526        q, q_width, answer = data
527        resolution = Pinhole1D(q, q_width)
528        output = self.Iq_sphere(pars, resolution)
529        # TODO: relative error should be lower
530        self.compare(q, output, answer, 3e-4)
531
532    def test_pinhole_romberg(self):
533        """
534        Compare pinhole resolution smearing with romberg integration result.
535        """
536        pars = TEST_PARS_PINHOLE_SPHERE
537        data_string = TEST_DATA_PINHOLE_SPHERE
538        pars['radius'] *= 5
539        radius = pars['radius']
540
541        data = np.loadtxt(data_string.split('\n')).T
542        q, q_width, answer = data
543        answer = romberg_pinhole_1d(q, q_width, self.model, pars)
544        ## Getting 0.1% requires 5 sigma and 200 points per fringe
545        #q_calc = interpolate(pinhole_extend_q(q, q_width, nsigma=5),
546        #                     2*np.pi/radius/200)
547        #tol = 0.001
548        ## The default 3 sigma and no extra points gets 1%
549        q_calc, tol = None, 0.01
550        resolution = Pinhole1D(q, q_width, q_calc=q_calc)
551        output = self.Iq_sphere(pars, resolution)
552        if 0: # debug plot
553            import matplotlib.pyplot as plt
554            resolution = Perfect1D(q)
555            source = self.Iq_sphere(pars, resolution)
556            plt.loglog(q, source, '.')
557            plt.loglog(q, answer, '-', hold=True)
558            plt.loglog(q, output, '-', hold=True)
559            plt.show()
560        self.compare(q, output, answer, tol)
561
562    def test_slit(self):
563        """
564        Compare slit resolution smearing with NIST Igor SANS
565        """
566        pars = TEST_PARS_SLIT_SPHERE
567        data_string = TEST_DATA_SLIT_SPHERE
568
569        data = np.loadtxt(data_string.split('\n')).T
570        q, delta_qv, _, answer = data
571        resolution = Slit1D(q, width=delta_qv, height=0)
572        output = self.Iq_sphere(pars, resolution)
573        # TODO: eliminate Igor test since it is too inaccurate to be useful.
574        # This means we can eliminate the test data as well, and instead
575        # use a generated q vector.
576        self.compare(q, output, answer, 0.5)
577
578    def test_slit_romberg(self):
579        """
580        Compare slit resolution smearing with romberg integration result.
581        """
582        pars = TEST_PARS_SLIT_SPHERE
583        data_string = TEST_DATA_SLIT_SPHERE
584        radius = pars['radius']
585
586        data = np.loadtxt(data_string.split('\n')).T
587        q, delta_qv, _, answer = data
588        answer = romberg_slit_1d(q, delta_qv, self.model, pars)
589        q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20),
590                               delta_qv[0], delta_qv[0])
591        resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc)
592        output = self.Iq_sphere(pars, resolution)
593        # TODO: relative error should be lower
594        self.compare(q, output, answer, 0.025)
595
596    #TODO: can sas q spacing be too sparse for the resolution calculation?
597    @unittest.skip("suppress sparse data test; not supported by current code")
598    def test_pinhole_sparse(self):
599        """
600        Compare pinhole resolution smearing with NIST Igor SANS on sparse data
601        """
602        pars = TEST_PARS_PINHOLE_SPHERE
603        data_string = TEST_DATA_PINHOLE_SPHERE
604
605        data = np.loadtxt(data_string.split('\n')).T
606        q, q_width, answer = data[:, ::20] # Take every nth point
607        resolution = Pinhole1D(q, q_width)
608        output = self.Iq_sphere(pars, resolution)
609        self.compare(q, output, answer, 1e-6)
610
611
612
613# pinhole sphere parameters
614TEST_PARS_PINHOLE_SPHERE = {
615    'scale': 1.0, 'background': 0.01,
616    'radius': 60.0, 'sld': 1, 'solvent_sld': 6.3,
617    }
618# Q, dQ, I(Q) calculated by NIST Igor SANS package
619TEST_DATA_PINHOLE_SPHERE = """\
6200.001278 0.0002847 2538.41176383
6210.001562 0.0002905 2536.91820405
6220.001846 0.0002956 2535.13182479
6230.002130 0.0003017 2533.06217813
6240.002414 0.0003087 2530.70378586
6250.002698 0.0003165 2528.05024192
6260.002982 0.0003249 2525.10408349
6270.003266 0.0003340 2521.86667499
6280.003550 0.0003437 2518.33907750
6290.003834 0.0003539 2514.52246995
6300.004118 0.0003646 2510.41798319
6310.004402 0.0003757 2506.02690988
6320.004686 0.0003872 2501.35067884
6330.004970 0.0003990 2496.38678318
6340.005253 0.0004112 2491.16237596
6350.005537 0.0004237 2485.63911673
6360.005821 0.0004365 2479.83657083
6370.006105 0.0004495 2473.75676948
6380.006389 0.0004628 2467.40145990
6390.006673 0.0004762 2460.77293372
6400.006957 0.0004899 2453.86724627
6410.007241 0.0005037 2446.69623838
6420.007525 0.0005177 2439.25775219
6430.007809 0.0005318 2431.55421398
6440.008093 0.0005461 2423.58785521
6450.008377 0.0005605 2415.36158137
6460.008661 0.0005750 2406.87009473
6470.008945 0.0005896 2398.12841186
6480.009229 0.0006044 2389.13360806
6490.009513 0.0006192 2379.88958042
6500.009797 0.0006341 2370.39776774
6510.010080 0.0006491 2360.69528793
6520.010360 0.0006641 2350.85169027
6530.010650 0.0006793 2340.42023633
6540.010930 0.0006945 2330.11206013
6550.011220 0.0007097 2319.20109972
6560.011500 0.0007251 2308.43503981
6570.011780 0.0007404 2297.44820179
6580.012070 0.0007558 2285.83853677
6590.012350 0.0007713 2274.41290746
6600.012640 0.0007868 2262.36219581
6610.012920 0.0008024 2250.51169731
6620.013200 0.0008180 2238.45596231
6630.013490 0.0008336 2225.76495666
6640.013770 0.0008493 2213.29618391
6650.014060 0.0008650 2200.19110751
6660.014340 0.0008807 2187.34050325
6670.014620 0.0008965 2174.30529864
6680.014910 0.0009123 2160.61632548
6690.015190 0.0009281 2147.21038112
6700.015470 0.0009440 2133.62023580
6710.015760 0.0009598 2119.37907426
6720.016040 0.0009757 2105.45234903
6730.016330 0.0009916 2090.86319102
6740.016610 0.0010080 2076.60576032
6750.016890 0.0010240 2062.19214565
6760.017180 0.0010390 2047.10550219
6770.017460 0.0010550 2032.38715621
6780.017740 0.0010710 2017.52560123
6790.018030 0.0010880 2001.99124318
6800.018310 0.0011040 1986.84662060
6810.018600 0.0011200 1971.03389745
6820.018880 0.0011360 1955.61395119
6830.019160 0.0011520 1940.08291563
6840.019450 0.0011680 1923.87672225
6850.019730 0.0011840 1908.10656374
6860.020020 0.0012000 1891.66297192
6870.020300 0.0012160 1875.66789021
6880.020580 0.0012320 1859.56357196
6890.020870 0.0012490 1842.79468290
6900.021150 0.0012650 1826.50064489
6910.021430 0.0012810 1810.11533702
6920.021720 0.0012970 1793.06840882
6930.022000 0.0013130 1776.51153580
6940.022280 0.0013290 1759.87201249
6950.022570 0.0013460 1742.57354412
6960.022850 0.0013620 1725.79397319
6970.023140 0.0013780 1708.35831550
6980.023420 0.0013940 1691.45256069
6990.023700 0.0014110 1674.48561783
7000.023990 0.0014270 1656.86525366
7010.024270 0.0014430 1639.79847285
7020.024550 0.0014590 1622.68887088
7030.024840 0.0014760 1604.96421100
7040.025120 0.0014920 1587.85768129
7050.025410 0.0015080 1569.99297335
7060.025690 0.0015240 1552.84580279
7070.025970 0.0015410 1535.54074115
7080.026260 0.0015570 1517.75249337
7090.026540 0.0015730 1500.40115023
7100.026820 0.0015900 1483.03632237
7110.027110 0.0016060 1465.05942429
7120.027390 0.0016220 1447.67682181
7130.027670 0.0016390 1430.46495191
7140.027960 0.0016550 1412.49232282
7150.028240 0.0016710 1395.13182318
7160.028520 0.0016880 1377.93439837
7170.028810 0.0017040 1359.99528971
7180.029090 0.0017200 1342.67274512
7190.029370 0.0017370 1325.55375609
720"""
721
722# Slit sphere parameters
723TEST_PARS_SLIT_SPHERE = {
724    'scale': 0.01, 'background': 0.01,
725    'radius': 60000, 'sld': 1, 'solvent_sld': 4,
726    }
727# Q dQ I(Q) I_smeared(Q)
728TEST_DATA_SLIT_SPHERE = """\
7292.26097e-05 0.117 5.5781372896e+09 1.4626077708e+06
7302.53847e-05 0.117 5.0363141458e+09 1.3117318023e+06
7312.81597e-05 0.117 4.4850108103e+09 1.1594863713e+06
7323.09347e-05 0.117 3.9364658459e+09 1.0094881630e+06
7333.37097e-05 0.117 3.4019975074e+09 8.6518941303e+05
7343.92597e-05 0.117 2.4139519814e+09 6.0232158311e+05
7354.48097e-05 0.117 1.5816877820e+09 3.8739994090e+05
7365.03597e-05 0.117 9.3715407224e+08 2.2745304775e+05
7375.59097e-05 0.117 4.8387917428e+08 1.2101295768e+05
7386.14597e-05 0.117 2.0193586928e+08 6.0055107771e+04
7396.70097e-05 0.117 5.5886110911e+07 3.2749521065e+04
7407.25597e-05 0.117 3.7782348010e+06 2.6350963616e+04
7417.81097e-05 0.117 5.3407817904e+06 2.9624963314e+04
7428.36597e-05 0.117 2.7975485523e+07 3.4403962254e+04
7438.92097e-05 0.117 4.9845448282e+07 3.6130017903e+04
7449.47597e-05 0.117 6.0092588905e+07 3.3495107849e+04
7451.00310e-04 0.117 5.6823430831e+07 2.7475726279e+04
7461.05860e-04 0.117 4.3857024036e+07 2.0144282226e+04
7471.11410e-04 0.117 2.7277144760e+07 1.3647403260e+04
7481.22510e-04 0.117 3.3119334113e+06 6.6519711526e+03
7491.33610e-04 0.117 1.4412859402e+06 6.9726212813e+03
7501.44710e-04 0.117 8.5620162463e+06 8.1441335775e+03
7511.55810e-04 0.117 9.6957429033e+06 6.4559996521e+03
7521.66910e-04 0.117 4.3818341914e+06 3.6252154156e+03
7531.78010e-04 0.117 2.7448997387e+05 2.4006505342e+03
7541.89110e-04 0.117 8.0472009936e+05 2.8187789089e+03
7552.00210e-04 0.117 2.8149552834e+06 3.0915662855e+03
7562.11310e-04 0.117 2.7510907861e+06 2.3722530293e+03
7572.22410e-04 0.117 1.0053133293e+06 1.4473468311e+03
7582.33510e-04 0.117 5.8428305052e+03 1.2048540556e+03
7592.44610e-04 0.117 5.1699305004e+05 1.4625670042e+03
7602.55710e-04 0.117 1.2120227268e+06 1.5010705973e+03
7612.66810e-04 0.117 9.7896842846e+05 1.1336343426e+03
7622.77910e-04 0.117 2.5507264791e+05 8.1848818080e+02
7633.05660e-04 0.117 5.2403101181e+05 7.4913374357e+02
7643.33410e-04 0.117 5.8699343809e+04 4.4669964560e+02
7653.61160e-04 0.117 3.0844327150e+05 4.6774007542e+02
7663.88910e-04 0.117 8.3360142970e+03 2.7169550220e+02
7674.16660e-04 0.117 1.8630080583e+05 3.0710983679e+02
7684.44410e-04 0.117 3.1616804732e-01 1.7959006831e+02
7694.72160e-04 0.117 1.1299016314e+05 2.0763952339e+02
7704.99910e-04 0.117 2.9952522747e+03 1.2536542765e+02
7715.27660e-04 0.117 6.7625695649e+04 1.4013969777e+02
7725.55410e-04 0.117 7.6927460089e+03 8.2145593180e+01
7736.10910e-04 0.117 1.1229057779e+04 8.4519745643e+01
7746.66410e-04 0.117 1.3035567943e+04 8.1554625609e+01
7757.21910e-04 0.117 1.3309931343e+04 7.4437319172e+01
7767.77410e-04 0.117 1.2462626212e+04 6.4697088261e+01
7778.32910e-04 0.117 1.0912927143e+04 5.3773301044e+01
7788.88410e-04 0.117 9.0172597469e+03 4.2843375753e+01
7799.43910e-04 0.117 7.0496495917e+03 3.2771032724e+01
7809.99410e-04 0.117 5.2030483682e+03 2.4113557144e+01
7811.05491e-03 0.117 3.5988976711e+03 1.7160773658e+01
7821.11041e-03 0.117 2.2996060652e+03 1.2016626459e+01
7831.22141e-03 0.117 6.4766590598e+02 6.0373017740e+00
7841.33241e-03 0.117 4.1963483264e+01 4.5215452974e+00
7851.44341e-03 0.117 6.3370708246e+01 5.1054681903e+00
7861.55441e-03 0.117 3.0736750577e+02 5.9176165298e+00
7871.66541e-03 0.117 5.0327682399e+02 5.9815000189e+00
7881.77641e-03 0.117 5.4084331454e+02 5.1634639625e+00
7891.88741e-03 0.117 4.3488671756e+02 3.8535158148e+00
7901.99841e-03 0.117 2.6322287860e+02 2.5824997753e+00
7912.10941e-03 0.117 1.0793633150e+02 1.7315517194e+00
7922.22041e-03 0.117 1.8474448850e+01 1.4077213604e+00
7932.33141e-03 0.117 1.5864062279e+00 1.4771560682e+00
7942.44241e-03 0.117 3.2267213848e+01 1.6916253448e+00
7952.55341e-03 0.117 7.4289116207e+01 1.8274751193e+00
7962.66441e-03 0.117 9.9000521929e+01 1.7706812289e+00
797"""
798
799def main():
800    """
801    Run tests given is sys.argv.
802
803    Returns 0 if success or 1 if any tests fail.
804    """
805    import sys
806    import xmlrunner
807
808    suite = unittest.TestSuite()
809    suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
810
811    runner = xmlrunner.XMLTestRunner(output='logs')
812    result = runner.run(suite)
813    return 1 if result.failures or result.errors else 0
814
815
816############################################################################
817# usage demo
818############################################################################
819
820def _eval_demo_1d(resolution, title):
821    from sasmodels import core
822    from sasmodels.models import cylinder
823    ## or alternatively:
824    # cylinder = core.load_model_definition('cylinder')
825    model = core.load_model(cylinder)
826
827    kernel = core.make_kernel(model, [resolution.q_calc])
828    theory = core.call_kernel(kernel, {'length':210, 'radius':500})
829    Iq = resolution.apply(theory)
830
831    import matplotlib.pyplot as plt
832    plt.loglog(resolution.q_calc, theory, label='unsmeared')
833    plt.loglog(resolution.q, Iq, label='smeared', hold=True)
834    plt.legend()
835    plt.title(title)
836    plt.xlabel("Q (1/Ang)")
837    plt.ylabel("I(Q) (1/cm)")
838
839def demo_pinhole_1d():
840    q = np.logspace(-3, -1, 400)
841    q_width = 0.1*q
842    resolution = Pinhole1D(q, q_width)
843    _eval_demo_1d(resolution, title="10% dQ/Q Pinhole Resolution")
844
845def demo_slit_1d():
846    q = np.logspace(-3, -1, 400)
847    qx_width = 0.005
848    qy_width = 0.0
849    resolution = Slit1D(q, qx_width, qy_width)
850    _eval_demo_1d(resolution, title="0.005 Qx Slit Resolution")
851
852def demo():
853    import matplotlib.pyplot as plt
854    plt.subplot(121)
855    demo_pinhole_1d()
856    plt.subplot(122)
857    demo_slit_1d()
858    plt.show()
859
860
861if __name__ == "__main__":
862    #demo()
863    main()
Note: See TracBrowser for help on using the repository browser.