source: sasview/sansmodels/src/sans/models/qsmearing.py @ 5d2e4c1

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 5d2e4c1 was 5d2e4c1, checked in by Gervaise Alina <gervyh@…>, 13 years ago

make sure smear is pickelable

  • Property mode set to 100644
File size: 24.1 KB
Line 
1
2#####################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#See the license text in license.txt
7#copyright 2008, University of Tennessee
8######################################################################
9import numpy
10import math
11import logging
12import sys
13import copy
14import sans_extension.smearer as smearer 
15from sans.models.smearing_2d import Smearer2D
16
17def smear_selection(data1D, model = None):
18    """
19    Creates the right type of smearer according
20    to the data.
21
22    The canSAS format has a rule that either
23    slit smearing data OR resolution smearing data
24    is available.
25   
26    For the present purpose, we choose the one that
27    has none-zero data. If both slit and resolution
28    smearing arrays are filled with good data
29    (which should not happen), then we choose the
30    resolution smearing data.
31   
32    :param data1D: Data1D object
33    :param model: sans.model instance
34    """
35    # Sanity check. If we are not dealing with a SANS Data1D
36    # object, just return None
37    if  data1D.__class__.__name__ not in ['Data1D', 'Theory1D']:
38        if data1D == None:
39            return None
40        elif data1D.dqx_data == None or data1D.dqy_data == None:
41            return None
42        return Smearer2D(data1D)
43   
44    if  not hasattr(data1D, "dx") and not hasattr(data1D, "dxl")\
45         and not hasattr(data1D, "dxw"):
46        return None
47   
48    # Look for resolution smearing data
49    _found_resolution = False
50    if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
51       
52        # Check that we have non-zero data
53        if data1D.dx[0] > 0.0:
54            _found_resolution = True
55            #print "_found_resolution",_found_resolution
56            #print "data1D.dx[0]",data1D.dx[0],data1D.dxl[0]
57    # If we found resolution smearing data, return a QSmearer
58    if _found_resolution == True:
59        return QSmearer(data1D, model)
60
61    # Look for slit smearing data
62    _found_slit = False
63    if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x) \
64        and data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
65       
66        # Check that we have non-zero data
67        if data1D.dxl[0] > 0.0 or data1D.dxw[0] > 0.0:
68            _found_slit = True
69       
70        # Sanity check: all data should be the same as a function of Q
71        for item in data1D.dxl:
72            if data1D.dxl[0] != item:
73                _found_resolution = False
74                break
75           
76        for item in data1D.dxw:
77            if data1D.dxw[0] != item:
78                _found_resolution = False
79                break
80    # If we found slit smearing data, return a slit smearer
81    if _found_slit == True:
82        return SlitSmearer(data1D, model)
83    return None
84           
85
86class _BaseSmearer(object):
87   
88    def __init__(self):
89        self.nbins = 0
90        self.nbins_low = 0
91        self.nbins_high = 0
92        self._weights = None
93        ## Internal flag to keep track of C++ smearer initialization
94        self._init_complete = False
95        self._smearer = None
96        self.model = None
97   
98    def _compute_matrix(self):
99        """
100        """
101        return NotImplemented
102
103    def get_bin_range(self, q_min=None, q_max=None):
104        """
105       
106        :param q_min: minimum q-value to smear
107        :param q_max: maximum q-value to smear
108         
109        """
110        # If this is the first time we call for smearing,
111        # initialize the C++ smearer object first
112        if not self._init_complete:
113            self._initialize_smearer()
114        if q_min == None:
115            q_min = self.min
116        if q_max == None:
117            q_max = self.max
118        _qmin_unsmeared, _qmax_unsmeared = self.get_unsmeared_range(q_min,
119                                                                     q_max)
120        _first_bin = None
121        _last_bin  = None
122
123        #step = (self.max - self.min) / (self.nbins - 1.0)
124        # Find the first and last bin number in all extrapolated and real data
125        try:
126            for i in range(self.nbins):
127                q_i = smearer.get_q(self._smearer, i)
128                if (q_i >= _qmin_unsmeared) and (q_i <= _qmax_unsmeared):
129                    # Identify first and last bin
130                    if _first_bin is None:
131                        _first_bin = i
132                    else:
133                        _last_bin  = i
134        except:
135            msg = "_BaseSmearer.get_bin_range: "
136            msg += " error getting range\n  %s" % sys.exc_value
137            raise RuntimeError, msg
138   
139        #  Find the first and last bin number only in the real data
140        _first_bin, _last_bin = self._get_unextrapolated_bin( \
141                                        _first_bin, _last_bin)
142
143        return _first_bin, _last_bin
144
145    def __call__(self, iq_in, first_bin = 0, last_bin = None):
146        """
147        Perform smearing
148        """
149        # If this is the first time we call for smearing,
150        # initialize the C++ smearer object first
151        if not self._init_complete:
152            self._initialize_smearer()
153
154        if last_bin is None or last_bin >= len(iq_in):
155            last_bin = len(iq_in) - 1
156        # Check that the first bin is positive
157        if first_bin < 0:
158            first_bin = 0
159
160        # With a model given, compute I for the extrapolated points and append
161        # to the iq_in
162        iq_in_temp = iq_in
163        if self.model != None:
164            temp_first, temp_last = self._get_extrapolated_bin(first_bin, 
165                                                               last_bin)
166            if self.nbins_low > 0:
167                iq_in_low = self.model.evalDistribution(
168                                    numpy.fabs(self.qvalues[0:self.nbins_low]))
169            iq_in_high = self.model.evalDistribution(
170                                            self.qvalues[(len(self.qvalues) - \
171                                            self.nbins_high - 1):])
172            # Todo: find out who is sending iq[last_poin] = 0.
173            if iq_in[len(iq_in) - 1] == 0:
174                iq_in[len(iq_in) - 1] = iq_in_high[0]
175            # Append the extrapolated points to the data points
176            if self.nbins_low > 0:                             
177                iq_in_temp = numpy.append(iq_in_low, iq_in)
178            if self.nbins_high > 0:
179                iq_in_temp = numpy.append(iq_in_temp, iq_in_high[1:])
180        else:
181            temp_first = first_bin
182            temp_last = last_bin
183        # Sanity check
184        if len(iq_in_temp) != self.nbins:
185            msg = "Invalid I(q) vector: inconsistent array "
186            msg += " length %d != %s" % (len(iq_in_temp), str(self.nbins))
187            raise RuntimeError, msg
188
189        # Storage for smeared I(q)   
190        iq_out = numpy.zeros(self.nbins)
191
192        smear_output = smearer.smear(self._smearer, iq_in_temp, iq_out,
193                                      #0, self.nbins - 1)
194                                      temp_first, temp_last)
195                                      #first_bin, last_bin)
196        if smear_output < 0:
197            msg = "_BaseSmearer: could not smear, code = %g" % smear_output
198            raise RuntimeError, msg
199
200        temp_first = first_bin + self.nbins_low
201        temp_last = self.nbins - self.nbins_high
202        out = iq_out[temp_first: temp_last]
203
204        return out
205   
206    def _initialize_smearer(self):
207        """
208        """
209        return NotImplemented
210           
211   
212    def _get_unextrapolated_bin(self, first_bin = 0, last_bin = 0):
213        """
214        Get unextrapolated first bin and the last bin
215       
216        : param first_bin: extrapolated first_bin
217        : param last_bin: extrapolated last_bin
218       
219        : return fist_bin, last_bin: unextrapolated first and last bin
220        """
221        #  For first bin
222        if first_bin <= self.nbins_low:
223            first_bin = 0
224        else:
225            first_bin = first_bin - self.nbins_low
226        # For last bin
227        if last_bin >= (self.nbins - self.nbins_high):
228            last_bin  = self.nbins - (self.nbins_high + self.nbins_low + 1)
229        elif last_bin >= self.nbins_low:
230            last_bin = last_bin - self.nbins_low
231        else:
232            last_bin = 0
233        return first_bin, last_bin
234   
235    def _get_extrapolated_bin(self, first_bin = 0, last_bin = 0):
236        """
237        Get extrapolated first bin and the last bin
238       
239        : param first_bin: unextrapolated first_bin
240        : param last_bin: unextrapolated last_bin
241       
242        : return first_bin, last_bin: extrapolated first and last bin
243        """
244        #  For the first bin
245        # In the case that needs low extrapolation data
246        first_bin = 0
247        # For last bin
248        if last_bin >= self.nbins - (self.nbins_high + self.nbins_low + 1):
249            # In the case that needs higher q extrapolation data
250            last_bin = self.nbins - 1
251        else:
252            # In the case that doesn't need higher q extrapolation data
253             last_bin += self.nbins_low
254
255        return first_bin, last_bin
256       
257class _SlitSmearer(_BaseSmearer):
258    """
259    Slit smearing for I(q) array
260    """
261   
262    def __init__(self, nbins=None, width=None, height=None, min=None, max=None):
263        """
264        Initialization
265           
266        :param iq: I(q) array [cm-1]
267        :param width: slit width [A-1]
268        :param height: slit height [A-1]
269        :param min: Q_min [A-1]
270        :param max: Q_max [A-1]
271       
272        """
273        _BaseSmearer.__init__(self)
274        ## Slit width in Q units
275        self.width  = width
276        ## Slit height in Q units
277        self.height = height
278        ## Q_min (Min Q-value for I(q))
279        self.min    = min
280        ## Q_max (Max Q_value for I(q))
281        self.max    = max
282        ## Number of Q bins
283        self.nbins  = nbins
284        ## Number of points used in the smearing computation
285        self.npts   = 3000
286        ## Smearing matrix
287        self._weights = None
288        self.qvalues  = None
289       
290   
291    def _initialize_smearer(self):
292        """
293        Initialize the C++ smearer object.
294        This method HAS to be called before smearing
295        """
296        #self._smearer = smearer.new_slit_smearer(self.width,
297        # self.height, self.min, self.max, self.nbins)
298        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
299                                                    self.height, self.qvalues)
300        self._init_complete = True
301
302    def get_unsmeared_range(self, q_min, q_max):
303        """
304        Determine the range needed in unsmeared-Q to cover
305        the smeared Q range
306        """
307        # Range used for input to smearing
308        _qmin_unsmeared = q_min
309        _qmax_unsmeared = q_max
310        try:
311            _qmin_unsmeared = self.min
312            _qmax_unsmeared = self.max
313        except:
314            logging.error("_SlitSmearer.get_bin_range: %s" % sys.exc_value)
315        return _qmin_unsmeared, _qmax_unsmeared
316
317class SlitSmearer(_SlitSmearer):
318    """
319    Adaptor for slit smearing class and SANS data
320    """
321    def __init__(self, data1D, model = None):
322        """
323        Assumption: equally spaced bins of increasing q-values.
324       
325        :param data1D: data used to set the smearing parameters
326        """
327        # Initialization from parent class
328        super(SlitSmearer, self).__init__()
329       
330        ## Slit width
331        self.width = 0
332        self.nbins_low = 0
333        self.nbins_high = 0
334        self.data = data1D
335        self.model = model
336        if data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
337            self.width = data1D.dxw[0]
338            # Sanity check
339            for value in data1D.dxw:
340                if value != self.width:
341                    msg = "Slit smearing parameters must "
342                    msg += " be the same for all data"
343                    raise RuntimeError, msg
344        ## Slit height
345        self.height = 0
346        if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x):
347            self.height = data1D.dxl[0]
348            # Sanity check
349            for value in data1D.dxl:
350                if value != self.height:
351                    msg = "Slit smearing parameters must be"
352                    msg += " the same for all data"
353                    raise RuntimeError, msg
354        # If a model is given, get the q extrapolation
355        if self.model == None:
356            data1d_x = data1D.x
357        else:
358            # Take larger sigma
359            if self.height > self.width:
360                # The denominator (2.0) covers all the possible w^2 + h^2 range
361                sigma_in = data1D.dxl / 2.0
362            elif self.width > 0:
363                sigma_in = data1D.dxw / 2.0
364            else:
365                sigma_in = []
366
367            self.nbins_low, self.nbins_high, _, data1d_x = \
368                                get_qextrapolate(sigma_in, data1D.x)
369
370        ## Number of Q bins
371        self.nbins = len(data1d_x)
372        ## Minimum Q
373        self.min = min(data1d_x)
374        ## Maximum
375        self.max = max(data1d_x)
376        ## Q-values
377        self.qvalues = data1d_x
378       
379    def __deepcopy__(self, memo={}):
380        """
381        Return a valid copy of self.
382        Avoid copying the _smearer C object and force a matrix recompute
383        when the copy is used. 
384        """
385        result = SlitSmearer(self.data, self.model)
386        result.width = self.width
387        result.height = self.height
388        result.nbins_low = self.nbins_low
389        result.nbins_high = self.nbins_high
390        result.data = copy.deepcopy(self.data)
391        result.model = copy.deepcopy(self.model)
392        result.nbins = self.nbins
393        result.nbins_low = self.nbins_low
394        result.nbins_high = self.nbins_high
395        result._weights = self._weights
396        ## Internal flag to keep track of C++ smearer initialization
397        result._init_complete = self._init_complete
398        import sans_extension.smearer as smearer 
399        result._smearer =  smearer.new_slit_smearer_with_q(self.width, 
400                                                    self.height, self.qvalues)
401        return result
402
403       
404    def __setstate__(self, state):
405        """
406        """
407        self.__dict__, self.model,  self.data = state
408        import sans_extension.smearer as smearer 
409        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
410                                                    self.height, self.qvalues)
411       
412    def __reduce_ex__(self, proto):
413        """
414        Overwrite the __reduce_ex__ of PyTypeObject *type call in the init of
415        c model.
416        """
417        model = copy.deepcopy(self.model)
418        data = copy.deepcopy(self.data)
419        dict = self.__dict__
420        if "_smearer" in dict.keys():
421            del dict["_smearer"]
422        state = (dict, model, data)
423        return (QSmearer, (data, model), state, None, None)
424       
425
426class _QSmearer(_BaseSmearer):
427    """
428    Perform Gaussian Q smearing
429    """
430       
431    def __init__(self, nbins=None, width=None, min=None, max=None):
432        """
433        Initialization
434       
435        :param nbins: number of Q bins
436        :param width: array standard deviation in Q [A-1]
437        :param min: Q_min [A-1]
438        :param max: Q_max [A-1]
439        """
440        _BaseSmearer.__init__(self)
441        ## Standard deviation in Q [A-1]
442        self.width = width
443        ## Q_min (Min Q-value for I(q))
444        self.min = min
445        ## Q_max (Max Q_value for I(q))
446        self.max = max
447        ## Number of Q bins
448        self.nbins = nbins
449        ## Smearing matrix
450        self._weights = None
451        self.qvalues  = None
452       
453    def _initialize_smearer(self):
454        """
455        Initialize the C++ smearer object.
456        This method HAS to be called before smearing
457        """
458        #self._smearer = smearer.new_q_smearer(numpy.asarray(self.width),
459        # self.min, self.max, self.nbins)
460        self._smearer = smearer.new_q_smearer_with_q(numpy.asarray(self.width),
461                                                      self.qvalues)
462        self._init_complete = True
463       
464    def get_unsmeared_range(self, q_min, q_max):
465        """
466        Determine the range needed in unsmeared-Q to cover
467        the smeared Q range
468        Take 3 sigmas as the offset between smeared and unsmeared space
469        """
470        # Range used for input to smearing
471        _qmin_unsmeared = q_min
472        _qmax_unsmeared = q_max
473        try:
474            offset = 3.0 * max(self.width)
475            _qmin_unsmeared = self.min#max([self.min, q_min - offset])
476            _qmax_unsmeared = self.max#min([self.max, q_max + offset])
477        except:
478            logging.error("_QSmearer.get_bin_range: %s" % sys.exc_value)
479        return _qmin_unsmeared, _qmax_unsmeared
480       
481   
482class QSmearer(_QSmearer):
483    """
484    Adaptor for Gaussian Q smearing class and SANS data
485    """
486    def __init__(self, data1D, model = None):
487        """
488        Assumption: equally spaced bins of increasing q-values.
489       
490        :param data1D: data used to set the smearing parameters
491        """
492        # Initialization from parent class
493        super(QSmearer, self).__init__()
494        data1d_x = []
495        self.data =  copy.deepcopy(data1D)
496        self.nbins_low = 0
497        self.nbins_high = 0
498        self.model = copy.deepcopy(model)
499        ## Resolution
500        #self.width = numpy.zeros(len(data1D.x))
501        if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
502            self.width = data1D.dx
503       
504        if self.model == None:
505            data1d_x = data1D.x
506        else:
507            self.nbins_low, self.nbins_high, self.width, data1d_x = \
508                                get_qextrapolate(self.width, data1D.x)
509        ## Number of Q bins
510        self.nbins = len(data1d_x)
511        ## Minimum Q
512        self.min = min(data1d_x)
513        ## Maximum
514        self.max = max(data1d_x)
515        ## Q-values
516        self.qvalues = data1d_x
517       
518    def __deepcopy__(self, memo={}):
519        """
520        Return a valid copy of self.
521        Avoid copying the _smearer C object and force a matrix recompute
522        when the copy is used. 
523        """
524        result = QSmearer(self.data, self.model)
525        result.nbins = self.nbins
526        result.min = self.min
527        result.max = self.max
528        result.nbins_low = self.nbins_low
529        result.nbins_high = self.nbins_high
530        result.width = copy.deepcopy(self.width)
531        result._weights = copy.deepcopy(self._weights)
532        result.qvalues = copy.deepcopy(self.qvalues)
533        ## Internal flag to keep track of C++ smearer initialization
534        result._init_complete = self._init_complete
535        import sans_extension.smearer as smearer 
536        result._smearer =  smearer.new_q_smearer_with_q(numpy.asarray(result.width),
537                                                      result.qvalues)
538        return result
539
540       
541    def __setstate__(self, state):
542        """
543        """
544        self.__dict__, self.model,  self.data = state
545        import sans_extension.smearer as smearer 
546        self._smearer =  smearer.new_q_smearer_with_q(numpy.asarray(self.width),
547                                                      self.qvalues)
548 
549    def __reduce_ex__(self, proto):
550        """
551        Overwrite the __reduce_ex__ of PyTypeObject *type call in the init of
552        c model.
553        """
554        model = copy.deepcopy(self.model)
555        data = copy.deepcopy(self.data)
556        dict = self.__dict__
557        if "_smearer" in dict.keys():
558            del dict["_smearer"]
559        state = (dict, model, data)
560        return (QSmearer, (data, model), state, None, None)
561   
562
563       
564def get_qextrapolate(width, data_x):
565    """
566    Make fake data_x points extrapolated outside of the data_x points
567   
568    : param width: array of std of q resolution
569    : param Data1D.x: Data1D.x array
570   
571    : return new_width, data_x_ext: extrapolated width array and x array
572   
573    : assumption1: data_x is ordered from lower q to higher q
574    : assumption2: len(data) = len(width)
575    : assumption3: the distance between the data points is more compact
576            than the size of width
577    : Todo1: Make sure that the assumptions are correct for Data1D
578    : Todo2: This fixes the edge problem in Qsmearer but still needs to make
579            smearer interface
580    """
581    # Length of the width
582    length = len(width)
583    width_low = math.fabs(width[0])
584    width_high = math.fabs(width[length -1])
585   
586    # Compare width(dQ) to the data bin size and take smaller one as the bin
587    # size of the extrapolation; this will correct some weird behavior
588    # at the edge: This method was out (commented)
589    # because it becomes very expansive when
590    # bin size is very small comparing to the width.
591    # Now on, we will just give the bin size of the extrapolated points
592    # based on the width.
593    # Find bin sizes
594    #bin_size_low = math.fabs(data_x[1] - data_x[0])
595    #bin_size_high = math.fabs(data_x[length - 1] - data_x[length - 2])
596    # Let's set the bin size 1/3 of the width(sigma), it is good as long as
597    # the scattering is monotonous.
598    #if width_low < (bin_size_low):
599    bin_size_low = width_low / 10.0
600    #if width_high < (bin_size_high):
601    bin_size_high = width_high / 10.0
602       
603    # Number of q points required below the 1st data point in order to extend
604    # them 3 times of the width (std)
605    nbins_low = math.ceil(3.0 * width_low / bin_size_low)
606    # Number of q points required above the last data point
607    nbins_high = math.ceil(3.0 * width_high / (bin_size_high))
608    # Make null q points       
609    extra_low = numpy.zeros(nbins_low)
610    extra_high = numpy.zeros(nbins_high)
611    # Give extrapolated values
612    ind = 0
613    qvalue = data_x[0] - bin_size_low
614    #if qvalue > 0:
615    while(ind < nbins_low):
616        extra_low[nbins_low - (ind + 1)] = qvalue
617        qvalue -= bin_size_low
618        ind += 1
619        #if qvalue <= 0:
620        #    break
621    # Redefine nbins_low
622    nbins_low = ind
623    # Reset ind for another extrapolation
624    ind = 0
625    qvalue = data_x[length -1] + bin_size_high
626    while(ind < nbins_high):
627        extra_high[ind] = qvalue
628        qvalue += bin_size_high
629        ind += 1
630    # Make a new qx array
631    if nbins_low > 0: 
632        data_x_ext = numpy.append(extra_low, data_x)
633    else:
634        data_x_ext = data_x
635    data_x_ext = numpy.append(data_x_ext, extra_high)
636   
637    # Redefine extra_low and high based on corrected nbins 
638    # And note that it is not necessary for extra_width to be a non-zero
639    if nbins_low > 0:     
640        extra_low = numpy.zeros(nbins_low)
641    extra_high = numpy.zeros(nbins_high) 
642    # Make new width array
643    new_width = numpy.append(extra_low, width)
644    new_width = numpy.append(new_width, extra_high)
645   
646    # nbins corrections due to the negative q value
647    nbins_low = nbins_low - len(data_x_ext[data_x_ext<=0])
648    return  nbins_low, nbins_high, \
649             new_width[data_x_ext>0], data_x_ext[data_x_ext>0]
650   
651if __name__ == '__main__':
652    x = 0.001 * numpy.arange(1, 11)
653    y = 12.0 - numpy.arange(1, 11)
654    print x
655    #for i in range(10): print i, 0.001 + i*0.008/9.0
656    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) ))
657    s = _SlitSmearer(nbins=10, width=0.0, height=0.005, min=0.001, max=0.010)
658    #s = _QSmearer(nbins=10, width=0.001, min=0.001, max=0.010)
659    s._compute_matrix()
660
661    sy = s(y)
662    print sy
663   
664    if True:
665        for i in range(10):
666            print x[i], y[i], sy[i]
667            #print q, ' : ', s.weight(q), s._compute_iq(q)
668            #print q, ' : ', s(q), s._compute_iq(q)
669            #s._compute_iq(q)
670
671
672
673
Note: See TracBrowser for help on using the repository browser.