Changeset 02098e3 in sasview for src/sas/models


Ignore:
Timestamp:
Dec 8, 2015 11:09:13 AM (9 years ago)
Author:
ajj
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
50a77df
Parents:
12e5cc8 (diff), 1c2bf90 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

merging master to integration branch. hopefully successfully this time and with no weird line ending issues

Location:
src/sas/models
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/sas/models/PeakGaussModel.py

    rac7be54 rd430ee8  
    33PeakGaussModel function as a BaseComponent model 
    44""" 
     5from __future__ import division 
    56 
    67from sas.models.BaseComponent import BaseComponent 
  • src/sas/models/resolution.py

    rbe0c318 r80ba1a2  
    1111MINIMUM_RESOLUTION = 1e-8 
    1212 
    13 class Resolution1D(object): 
     13 
     14# When extrapolating to -q, what is the minimum positive q relative to q_min 
     15# that we wish to calculate? 
     16MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION = 0.01 
     17 
     18class Resolution(object): 
    1419    """ 
    1520    Abstract base class defining a 1D resolution function. 
     
    3237 
    3338 
    34 class Perfect1D(Resolution1D): 
     39class Perfect1D(Resolution): 
    3540    """ 
    3641    Resolution function to use when there is no actual resolution smearing 
     
    4550 
    4651 
    47 class Pinhole1D(Resolution1D): 
     52class Pinhole1D(Resolution): 
    4853    r""" 
    4954    Pinhole aperture with q-dependent gaussian resolution. 
     
    7782 
    7883 
    79 class Slit1D(Resolution1D): 
     84class Slit1D(Resolution): 
    8085    """ 
    8186    Slit aperture with a complicated resolution function. 
     
    9297    The *weight_matrix* is computed by :func:`slit1d_resolution` 
    9398    """ 
    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  
     99    def __init__(self, q, width, height=0., q_calc=None): 
    105100        # Remember what width/height was used even though we won't need them 
    106101        # after the weight matrix is constructed 
    107102        self.width, self.height = width, height 
     103 
     104        # Allow independent resolution on each point even though it is not 
     105        # needed in practice. 
     106        if np.isscalar(width): 
     107            width = np.ones(len(q))*width 
     108        else: 
     109            width = np.asarray(width) 
     110        if np.isscalar(height): 
     111            height = np.ones(len(q))*height 
     112        else: 
     113            height = np.asarray(height) 
    108114 
    109115        self.q = q.flatten() 
     
    147153 
    148154 
    149 def slit_resolution(q_calc, q, width, height): 
     155def slit_resolution(q_calc, q, width, height, n_height=30): 
    150156    r""" 
    151157    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. 
     158    $q_\perp$ = *width* and $q_\parallel$ = *height*.  *n_height* is 
     159    is the number of steps to use in the integration over $q_\parallel$ 
     160    when both $q_\perp$ and $q_\parallel$ are non-zero. 
     161 
     162    Each $q$ can have an independent width and height value even though 
     163    current instruments use the same slit setting for all measured points. 
    156164 
    157165    If slit height is large relative to width, use: 
     
    159167    .. math:: 
    160168 
    161         I_s(q_o) = \frac{1}{\Delta q_v} 
    162             \int_0^{\Delta q_v} I(\sqrt{q_o^2 + u^2} du 
     169        I_s(q_i) = \frac{1}{\Delta q_\perp} 
     170            \int_0^{\Delta q_\perp} I(\sqrt{q_i^2 + q_\perp^2} dq_\perp 
    163171 
    164172    If slit width is large relative to height, use: 
     
    166174    .. math:: 
    167175 
    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  
     176        I_s(q_i) = \frac{1}{2 \Delta q_\parallel} 
     177            \int_{-\Delta q_\parallel}^{\Delta q_\parallel} 
     178                I(|q_i + q_\parallel|) dq_\parallel 
     179 
     180    For a mixture of slit width and height use: 
     181 
     182    .. math:: 
     183 
     184        I_s(q_i) = \frac{1}{2 \Delta q_\parallel \Delta q_\perp} 
     185            \int_{-\Delta q_\parallel)^{\Delta q_parallel} 
     186            \int_0^[\Delta q_\perp} 
     187                I(\sqrt{(q_i + q_\parallel)^2 + q_\perp^2}) 
     188                dq_\perp dq_\parallel 
     189 
     190 
     191    Algorithm 
     192    --------- 
     193 
     194    We are using the mid-point integration rule to assign weights to each 
     195    element of a weight matrix $W$ so that 
     196 
     197    .. math:: 
     198 
     199        I_s(q) = W I(q_\text{calc}) 
     200 
     201    If *q_calc* is at the mid-point, we can infer the bin edges from the 
     202    pairwise averages of *q_calc*, adding the missing edges before 
     203    *q_calc[0]* and after *q_calc[-1]*. 
     204 
     205    For $q_\parallel = 0$, the smeared value can be computed numerically 
     206    using the $u$ substitution 
     207 
     208    .. math:: 
     209 
     210        u_j = \sqrt{q_j^2 - q^2} 
     211 
     212    This gives 
     213 
     214    .. math:: 
     215 
     216        I_s(q) \approx \sum_j I(u_j) \Delta u_j 
     217 
     218    where $I(u_j)$ is the value at the mid-point, and $\Delta u_j$ is the 
     219    difference between consecutive edges which have been first converted 
     220    to $u$.  Only $u_j \in [0, \Delta q_\perp]$ are used, which corresponds 
     221    to $q_j \in [q, \sqrt{q^2 + \Delta q_\perp}]$, so 
     222 
     223    .. math:: 
     224 
     225        W_{ij} = \frac{1}{\Delta q_\perp} \Delta u_j 
     226               = \frac{1}{\Delta q_\perp} 
     227                    \sqrt{q_{j+1}^2 - q_i^2} - \sqrt{q_j^2 - q_i^2} 
     228            \text{if} q_j \in [q_i, \sqrt{q_i^2 + q_\perp^2}] 
     229 
     230    where $I_s(q_i)$ is the theory function being computed and $q_j$ are the 
     231    mid-points between the calculated values in *q_calc*.  We tweak the 
     232    edges of the initial and final intervals so that they lie on integration 
     233    limits. 
     234 
     235    (To be precise, the transformed midpoint $u(q_j)$ is not necessarily the 
     236    midpoint of the edges $u((q_{j-1}+q_j)/2)$ and $u((q_j + q_{j+1})/2)$, 
     237    but it is at least in the interval, so the approximation is going to be 
     238    a little better than the left or right Riemann sum, and should be 
     239    good enough for our purposes.) 
     240 
     241    For $q_\perp = 0$, the $u$ substitution is simpler: 
     242 
     243    .. math:: 
     244 
     245        u_j = |q_j - q| 
     246 
     247    so 
     248 
     249    .. math:: 
     250 
     251        W_ij = \frac{1}{2 \Delta q_\parallel} \Delta u_j 
     252            = \frac{1}{2 \Delta q_\parallel} (q_{j+1} - q_j) 
     253            \text{if} q_j \in [q-\Delta q_\parallel, q+\Delta q_\parallel] 
     254 
     255    However, we need to support cases were $u_j < 0$, which means using 
     256    $2 (q_{j+1} - q_j)$ when $q_j \in [0, q_\parallel-q_i]$.  This is not 
     257    an issue for $q_i > q_\parallel$. 
     258 
     259    For bot $q_\perp > 0$ and $q_\parallel > 0$ we perform a 2 dimensional 
     260    integration with 
     261 
     262    .. math:: 
     263 
     264        u_jk = \sqrt{q_j^2 - (q + (k\Delta q_\parallel/L))^2} 
     265            \text{for} k = -L \ldots L 
     266 
     267    for $L$ = *n_height*.  This gives 
     268 
     269    .. math:: 
     270 
     271        W_{ij} = \frac{1}{2 \Delta q_\perp q_\parallel} 
     272            \sum_{k=-L}^L \Delta u_jk (\frac{\Delta q_\parallel}{2 L + 1} 
     273 
     274 
     275    """ 
     276    #np.set_printoptions(precision=6, linewidth=10000) 
     277 
     278    # The current algorithm is a midpoint rectangle rule. 
    175279    q_edges = bin_edges(q_calc) # Note: requires q > 0 
    176280    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  
     281    weights = np.zeros((len(q), len(q_calc)), 'd') 
     282 
     283    #print q_calc 
     284    for i, (qi, w, h) in enumerate(zip(q, width, height)): 
     285        if w == 0. and h == 0.: 
     286            # Perfect resolution, so return the theory value directly. 
     287            # Note: assumes that q is a subset of q_calc.  If qi need not be 
     288            # in q_calc, then we can do a weighted interpolation by looking 
     289            # up qi in q_calc, then weighting the result by the relative 
     290            # distance to the neighbouring points. 
     291            weights[i, :] = (q_calc == qi) 
     292        elif h == 0: 
     293            weights[i, :] = _q_perp_weights(q_edges, qi, w) 
     294        elif w == 0: 
     295            in_x = 1.0 * ((q_calc >= qi-h) & (q_calc <= qi+h)) 
     296            abs_x = 1.0*(q_calc < abs(qi - h)) if qi < h else 0. 
     297            #print qi - h, qi + h 
     298            #print in_x + abs_x 
     299            weights[i,:] = (in_x + abs_x) * np.diff(q_edges) / (2*h) 
     300        else: 
     301            L = n_height 
     302            for k in range(-L, L+1): 
     303                weights[i,:] += _q_perp_weights(q_edges, qi+k*h/L, w) 
     304            weights[i,:] /= 2*L + 1 
     305 
     306    return weights.T 
     307 
     308 
     309def _q_perp_weights(q_edges, qi, w): 
     310    # Convert bin edges from q to u 
     311    u_limit = np.sqrt(qi**2 + w**2) 
     312    u_edges = q_edges**2 - qi**2 
     313    u_edges[q_edges < abs(qi)] = 0. 
     314    u_edges[q_edges > u_limit] = u_limit**2 - qi**2 
     315    weights = np.diff(np.sqrt(u_edges))/w 
     316    #print "i, qi",i,qi,qi+width 
     317    #print q_calc 
     318    #print weights 
    195319    return weights 
    196320 
     
    212336    function. 
    213337    """ 
    214     height # keep lint happy 
    215     q_min, q_max = np.min(q), np.max(np.sqrt(q**2 + width**2)) 
     338    q_min, q_max = np.min(q-height), np.max(np.sqrt((q+height)**2 + width**2)) 
     339 
    216340    return geometric_extrapolation(q, q_min, q_max) 
    217341 
     
    233357        ]) 
    234358    return edges 
    235  
    236  
    237 def 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) 
    245359 
    246360 
     
    275389    q = np.sort(q) 
    276390    if q_min < q[0]: 
    277         if q_min <= 0: q_min = q[0]/10 
     391        if q_min <= 0: q_min = q_min*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION 
    278392        n_low = np.ceil((q[0]-q_min) / (q[1]-q[0])) if q[1]>q[0] else 15 
    279393        q_low = np.linspace(q_min, q[0], n_low+1)[:-1] 
     
    297411    *points_per_decade* sets the ratio between consecutive steps such 
    298412    that there will be $n$ points used for every factor of 10 increase 
    299     in $q$. 
     413    in *q*. 
    300414 
    301415    If *points_per_decade* is not given, it will be estimated as follows. 
     
    316430    Substituting: 
    317431 
    318     .. math:: 
    319  
    320432        n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n) 
    321             / (\log q_n - \log q_1) 
     433            / (\log q_n - log q_1) 
    322434    """ 
    323435    q = np.sort(q) 
     
    327439        log_delta_q = log(10.) / points_per_decade 
    328440    if q_min < q[0]: 
    329         if q_min < 0: q_min = q[0]/10 
     441        if q_min < 0: q_min = q[0]*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION 
    330442        n_low = log_delta_q * (log(q[0])-log(q_min)) 
    331443        q_low  = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1] 
     
    359471 
    360472 
    361 def romberg_slit_1d(q, delta_qv, form, pars): 
     473def romberg_slit_1d(q, width, height, form, pars): 
    362474    """ 
    363475    Romberg integration for slit resolution. 
     
    366478    that make it slow to evaluate but give it good accuracy. 
    367479    """ 
    368     from scipy.integrate import romberg 
     480    from scipy.integrate import romberg, dblquad 
    369481 
    370482    if any(k not in form.info['defaults'] for k in pars.keys()): 
     
    374486                         (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 
    375487 
    376     _fn = lambda u, q0: eval_form(sqrt(q0**2 + u**2), form, pars) 
    377     r = [romberg(_fn, 0, delta_qv[0], args=(qi,), 
    378                  divmax=100, vec_func=True, tol=0, rtol=1e-8) 
    379          for qi in q] 
     488    if np.isscalar(width): 
     489        width = [width]*len(q) 
     490    if np.isscalar(height): 
     491        height = [height]*len(q) 
     492    _int_w = lambda w, qi: eval_form(sqrt(qi**2 + w**2), form, pars) 
     493    _int_h = lambda h, qi: eval_form(abs(qi+h), form, pars) 
     494    # If both width and height are defined, then it is too slow to use dblquad. 
     495    # Instead use trapz on a fixed grid, interpolated into the I(Q) for 
     496    # the extended Q range. 
     497    #_int_wh = lambda w, h, qi: eval_form(sqrt((qi+h)**2 + w**2), form, pars) 
     498    q_calc = slit_extend_q(q, np.asarray(width), np.asarray(height)) 
     499    Iq = eval_form(q_calc, form, pars) 
     500    result = np.empty(len(q)) 
     501    for i, (qi, w, h) in enumerate(zip(q, width, height)): 
     502        if h == 0.: 
     503            r = romberg(_int_w, 0, w, args=(qi,), 
     504                        divmax=100, vec_func=True, tol=0, rtol=1e-8) 
     505            result[i] = r/w 
     506        elif w == 0.: 
     507            r = romberg(_int_h, -h, h, args=(qi,), 
     508                        divmax=100, vec_func=True, tol=0, rtol=1e-8) 
     509            result[i] = r/(2*h) 
     510        else: 
     511            w_grid = np.linspace(0, w, 21)[None,:] 
     512            h_grid = np.linspace(-h, h, 23)[:,None] 
     513            u = sqrt((qi+h_grid)**2 + w_grid**2) 
     514            Iu = np.interp(u, q_calc, Iq) 
     515            #print np.trapz(Iu, w_grid, axis=1) 
     516            Is = np.trapz(np.trapz(Iu, w_grid, axis=1), h_grid[:,0]) 
     517            result[i] = Is / (2*h*w) 
     518            """ 
     519            r, err = dblquad(_int_wh, -h, h, lambda h: 0., lambda h: w, 
     520                             args=(qi,)) 
     521            result[i] = r/(w*2*h) 
     522            """ 
     523 
    380524    # r should be [float, ...], but it is [array([float]), array([float]),...] 
    381     return np.asarray(r).flatten()/delta_qv[0] 
     525    return result 
    382526 
    383527 
     
    520664 
    521665    def compare(self, q, output, answer, tolerance): 
    522         err = (output - answer)/answer 
    523         idx = abs(err) >= tolerance 
    524         problem = zip(q[idx], output[idx], answer[idx], err[idx]) 
    525         print "\n".join(str(v) for v in problem) 
     666        #err = (output - answer)/answer 
     667        #idx = abs(err) >= tolerance 
     668        #problem = zip(q[idx], output[idx], answer[idx], err[idx]) 
     669        #print "\n".join(str(v) for v in problem) 
    526670        np.testing.assert_allclose(output, answer, rtol=tolerance) 
    527671 
     
    609753        data = np.loadtxt(data_string.split('\n')).T 
    610754        q, delta_qv, _, answer = data 
    611         answer = romberg_slit_1d(q, delta_qv, self.model, pars) 
     755        answer = romberg_slit_1d(q, delta_qv, 0., self.model, pars) 
    612756        q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20), 
    613                                delta_qv[0], delta_qv[0]) 
     757                               delta_qv[0], 0.) 
    614758        resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc) 
    615759        output = self.Iq_sphere(pars, resolution) 
     
    629773        form = load_model('ellipsoid', dtype='double') 
    630774        q = np.logspace(log10(4e-5),log10(2.5e-2), 68) 
    631         delta_qv = [0.117] 
    632         resolution = Slit1D(q, width=delta_qv, height=0) 
    633         answer = romberg_slit_1d(q, delta_qv, form, pars) 
     775        width, height = 0.117, 0. 
     776        resolution = Slit1D(q, width=width, height=height) 
     777        answer = romberg_slit_1d(q, width, height, form, pars) 
    634778        output = resolution.apply(eval_form(resolution.q_calc, form, pars)) 
    635779        # TODO: 10% is too much error; use better algorithm 
     780        #print np.max(abs(answer-output)/answer) 
    636781        self.compare(q, output, answer, 0.1) 
    637782 
     
    8601005 
    8611006def _eval_demo_1d(resolution, title): 
     1007    import sys 
    8621008    from sasmodels import core 
    863     from sasmodels.models import cylinder 
    864     ## or alternatively: 
    865     # cylinder = core.load_model_definition('cylinder') 
    866     model = core.load_model(cylinder) 
     1009    name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder' 
     1010 
     1011    if name == 'cylinder': 
     1012        pars = {'length':210, 'radius':500} 
     1013    elif name == 'teubner_strey': 
     1014        pars = {'a2':0.003, 'c1':-1e4, 'c2':1e10, 'background':0.312643} 
     1015    elif name == 'sphere' or name == 'spherepy': 
     1016        pars = TEST_PARS_SLIT_SPHERE 
     1017    elif name == 'ellipsoid': 
     1018        pars = { 
     1019            'scale':0.05, 
     1020            'rpolar':500, 'requatorial':15000, 
     1021            'sld':6, 'solvent_sld': 1, 
     1022            } 
     1023    else: 
     1024        pars = {} 
     1025    defn = core.load_model_definition(name) 
     1026    model = core.load_model(defn) 
    8671027 
    8681028    kernel = core.make_kernel(model, [resolution.q_calc]) 
    869     theory = core.call_kernel(kernel, {'length':210, 'radius':500}) 
     1029    theory = core.call_kernel(kernel, pars) 
    8701030    Iq = resolution.apply(theory) 
     1031 
     1032    if isinstance(resolution, Slit1D): 
     1033        width, height = resolution.width, resolution.height 
     1034        Iq_romb = romberg_slit_1d(resolution.q, width, height, model, pars) 
     1035    else: 
     1036        dq = resolution.q_width 
     1037        Iq_romb = romberg_pinhole_1d(resolution.q, dq, model, pars) 
    8711038 
    8721039    import matplotlib.pyplot as plt 
    8731040    plt.loglog(resolution.q_calc, theory, label='unsmeared') 
    8741041    plt.loglog(resolution.q, Iq, label='smeared', hold=True) 
     1042    plt.loglog(resolution.q, Iq_romb, label='romberg smeared', hold=True) 
    8751043    plt.legend() 
    8761044    plt.title(title) 
     
    8791047 
    8801048def demo_pinhole_1d(): 
    881     q = np.logspace(-3, -1, 400) 
     1049    q = np.logspace(-4, np.log10(0.2), 400) 
    8821050    q_width = 0.1*q 
    8831051    resolution = Pinhole1D(q, q_width) 
     
    8851053 
    8861054def demo_slit_1d(): 
    887     q = np.logspace(-3, -1, 400) 
    888     qx_width = 0.005 
    889     qy_width = 0.0 
    890     resolution = Slit1D(q, qx_width, qy_width) 
    891     _eval_demo_1d(resolution, title="0.005 Qx Slit Resolution") 
     1055    q = np.logspace(-4, np.log10(0.2), 100) 
     1056    w = h = 0. 
     1057    #w = 0.000000277790 
     1058    w = 0.0277790 
     1059    #h = 0.00277790 
     1060    #h = 0.0277790 
     1061    resolution = Slit1D(q, w, h) 
     1062    _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w,h)) 
    8921063 
    8931064def demo(): 
     
    8951066    plt.subplot(121) 
    8961067    demo_pinhole_1d() 
     1068    #plt.yscale('linear') 
    8971069    plt.subplot(122) 
    8981070    demo_slit_1d() 
     1071    #plt.yscale('linear') 
    8991072    plt.show() 
    9001073 
    9011074 
    9021075if __name__ == "__main__": 
    903     #demo() 
    904     main() 
     1076    demo() 
     1077    #main() 
  • src/sas/models/dispersion_models.py

    rfd5ac0d r0e4e554  
    154154        c_models.set_dispersion_weights(self.cdisp, values, weights) 
    155155         
    156 models = {"gaussian":GaussianDispersion,  "rectangula":RectangleDispersion, 
     156models = {"gaussian":GaussianDispersion,  "rectangular":RectangleDispersion, 
    157157          "array":ArrayDispersion, "schulz":SchulzDispersion,  
    158158          "lognormal":LogNormalDispersion}        
Note: See TracChangeset for help on using the changeset viewer.