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

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 b9958b3 was 946b452, checked in by Gervaise Alina <gervyh@…>, 13 years ago

improve pickling smearer

  • Property mode set to 100644
File size: 24.4 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        Restore the state of the object by reconstruction the (smearer) object
407        """
408        self.__dict__, self.model,  self.data = state
409        import sans_extension.smearer as smearer 
410        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
411                                                    self.height, self.qvalues)
412        self.__dict__['_smearer'] = self._smearer
413       
414    def __reduce_ex__(self, proto):
415        """
416        Overwrite the __reduce_ex__to avoid pickle PyCobject(smearer)
417        """
418        model = copy.deepcopy(self.model)
419        data = copy.deepcopy(self.data)
420        dict = {}
421        for k , v in self.__dict__.iteritems():
422            if k != "_smearer":
423                dict[k] = v
424        state = (dict, model, data)
425        return (SlitSmearer, (data, model), state, None, None)
426       
427
428class _QSmearer(_BaseSmearer):
429    """
430    Perform Gaussian Q smearing
431    """
432       
433    def __init__(self, nbins=None, width=None, min=None, max=None):
434        """
435        Initialization
436       
437        :param nbins: number of Q bins
438        :param width: array standard deviation in Q [A-1]
439        :param min: Q_min [A-1]
440        :param max: Q_max [A-1]
441        """
442        _BaseSmearer.__init__(self)
443        ## Standard deviation in Q [A-1]
444        self.width = width
445        ## Q_min (Min Q-value for I(q))
446        self.min = min
447        ## Q_max (Max Q_value for I(q))
448        self.max = max
449        ## Number of Q bins
450        self.nbins = nbins
451        ## Smearing matrix
452        self._weights = None
453        self.qvalues  = None
454     
455       
456    def _initialize_smearer(self):
457        """
458        Initialize the C++ smearer object.
459        This method HAS to be called before smearing
460        """
461        #self._smearer = smearer.new_q_smearer(numpy.asarray(self.width),
462        # self.min, self.max, self.nbins)
463        self._smearer = smearer.new_q_smearer_with_q(numpy.asarray(self.width),
464                                                      self.qvalues)
465        self._init_complete = True
466       
467    def get_unsmeared_range(self, q_min, q_max):
468        """
469        Determine the range needed in unsmeared-Q to cover
470        the smeared Q range
471        Take 3 sigmas as the offset between smeared and unsmeared space
472        """
473        # Range used for input to smearing
474        _qmin_unsmeared = q_min
475        _qmax_unsmeared = q_max
476        try:
477            offset = 3.0 * max(self.width)
478            _qmin_unsmeared = self.min#max([self.min, q_min - offset])
479            _qmax_unsmeared = self.max#min([self.max, q_max + offset])
480        except:
481            logging.error("_QSmearer.get_bin_range: %s" % sys.exc_value)
482        return _qmin_unsmeared, _qmax_unsmeared
483       
484   
485class QSmearer(_QSmearer):
486    """
487    Adaptor for Gaussian Q smearing class and SANS data
488    """
489    def __init__(self, data1D, model = None):
490        """
491        Assumption: equally spaced bins of increasing q-values.
492       
493        :param data1D: data used to set the smearing parameters
494        """
495        # Initialization from parent class
496        super(QSmearer, self).__init__()
497        data1d_x = []
498        self.data =  copy.deepcopy(data1D)
499        self.nbins_low = 0
500        self.nbins_high = 0
501        self.model = copy.deepcopy(model)
502        ## Resolution
503        #self.width = numpy.zeros(len(data1D.x))
504        if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
505            self.width = data1D.dx
506       
507        if self.model == None:
508            data1d_x = data1D.x
509        else:
510            self.nbins_low, self.nbins_high, self.width, data1d_x = \
511                                get_qextrapolate(self.width, data1D.x)
512        ## Number of Q bins
513        self.nbins = len(data1d_x)
514        ## Minimum Q
515        self.min = min(data1d_x)
516        ## Maximum
517        self.max = max(data1d_x)
518        ## Q-values
519        self.qvalues = data1d_x
520       
521       
522    def __deepcopy__(self, memo={}):
523        """
524        Return a valid copy of self.
525        Avoid copying the _smearer C object and force a matrix recompute
526        when the copy is used. 
527        """
528        result = QSmearer(self.data, self.model)
529        result.nbins = self.nbins
530        result.min = self.min
531        result.max = self.max
532        result.nbins_low = self.nbins_low
533        result.nbins_high = self.nbins_high
534        result.width = copy.deepcopy(self.width)
535        result._weights = copy.deepcopy(self._weights)
536        result.qvalues = copy.deepcopy(self.qvalues)
537        ## Internal flag to keep track of C++ smearer initialization
538        result._init_complete = self._init_complete
539        import sans_extension.smearer as smearer 
540        result._smearer =  smearer.new_q_smearer_with_q(numpy.asarray(result.width),
541                                                      result.qvalues)
542        return result
543
544       
545    def __setstate__(self, state):
546        """
547        Restore the state of the object by reconstruction the (smearer) object
548        """
549        self.__dict__, self.model,  self.data = state
550        import sans_extension.smearer as smearer 
551        self._smearer =  smearer.new_q_smearer_with_q(numpy.asarray(self.width),
552                                                      self.qvalues)
553        self.__dict__['_smearer'] = self._smearer
554 
555    def __reduce_ex__(self, proto):
556        """
557        Overwrite the __reduce_ex__to avoid pickle PyCobject(smearer)
558        """
559        model = copy.deepcopy(self.model)
560        data = copy.deepcopy(self.data)
561        dict = {}
562        for k , v in self.__dict__.iteritems():
563            if k != "_smearer":
564                dict[k] = v
565        state = (dict, model, data)
566        return (QSmearer, (data, model), state, None, None)
567   
568
569       
570def get_qextrapolate(width, data_x):
571    """
572    Make fake data_x points extrapolated outside of the data_x points
573   
574    : param width: array of std of q resolution
575    : param Data1D.x: Data1D.x array
576   
577    : return new_width, data_x_ext: extrapolated width array and x array
578   
579    : assumption1: data_x is ordered from lower q to higher q
580    : assumption2: len(data) = len(width)
581    : assumption3: the distance between the data points is more compact
582            than the size of width
583    : Todo1: Make sure that the assumptions are correct for Data1D
584    : Todo2: This fixes the edge problem in Qsmearer but still needs to make
585            smearer interface
586    """
587    # Length of the width
588    length = len(width)
589    width_low = math.fabs(width[0])
590    width_high = math.fabs(width[length -1])
591   
592    # Compare width(dQ) to the data bin size and take smaller one as the bin
593    # size of the extrapolation; this will correct some weird behavior
594    # at the edge: This method was out (commented)
595    # because it becomes very expansive when
596    # bin size is very small comparing to the width.
597    # Now on, we will just give the bin size of the extrapolated points
598    # based on the width.
599    # Find bin sizes
600    #bin_size_low = math.fabs(data_x[1] - data_x[0])
601    #bin_size_high = math.fabs(data_x[length - 1] - data_x[length - 2])
602    # Let's set the bin size 1/3 of the width(sigma), it is good as long as
603    # the scattering is monotonous.
604    #if width_low < (bin_size_low):
605    bin_size_low = width_low / 10.0
606    #if width_high < (bin_size_high):
607    bin_size_high = width_high / 10.0
608       
609    # Number of q points required below the 1st data point in order to extend
610    # them 3 times of the width (std)
611    nbins_low = math.ceil(3.0 * width_low / bin_size_low)
612    # Number of q points required above the last data point
613    nbins_high = math.ceil(3.0 * width_high / (bin_size_high))
614    # Make null q points       
615    extra_low = numpy.zeros(nbins_low)
616    extra_high = numpy.zeros(nbins_high)
617    # Give extrapolated values
618    ind = 0
619    qvalue = data_x[0] - bin_size_low
620    #if qvalue > 0:
621    while(ind < nbins_low):
622        extra_low[nbins_low - (ind + 1)] = qvalue
623        qvalue -= bin_size_low
624        ind += 1
625        #if qvalue <= 0:
626        #    break
627    # Redefine nbins_low
628    nbins_low = ind
629    # Reset ind for another extrapolation
630    ind = 0
631    qvalue = data_x[length -1] + bin_size_high
632    while(ind < nbins_high):
633        extra_high[ind] = qvalue
634        qvalue += bin_size_high
635        ind += 1
636    # Make a new qx array
637    if nbins_low > 0: 
638        data_x_ext = numpy.append(extra_low, data_x)
639    else:
640        data_x_ext = data_x
641    data_x_ext = numpy.append(data_x_ext, extra_high)
642   
643    # Redefine extra_low and high based on corrected nbins 
644    # And note that it is not necessary for extra_width to be a non-zero
645    if nbins_low > 0:     
646        extra_low = numpy.zeros(nbins_low)
647    extra_high = numpy.zeros(nbins_high) 
648    # Make new width array
649    new_width = numpy.append(extra_low, width)
650    new_width = numpy.append(new_width, extra_high)
651   
652    # nbins corrections due to the negative q value
653    nbins_low = nbins_low - len(data_x_ext[data_x_ext<=0])
654    return  nbins_low, nbins_high, \
655             new_width[data_x_ext>0], data_x_ext[data_x_ext>0]
656   
657if __name__ == '__main__':
658    x = 0.001 * numpy.arange(1, 11)
659    y = 12.0 - numpy.arange(1, 11)
660    print x
661    #for i in range(10): print i, 0.001 + i*0.008/9.0
662    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) ))
663    s = _SlitSmearer(nbins=10, width=0.0, height=0.005, min=0.001, max=0.010)
664    #s = _QSmearer(nbins=10, width=0.001, min=0.001, max=0.010)
665    s._compute_matrix()
666
667    sy = s(y)
668    print sy
669   
670    if True:
671        for i in range(10):
672            print x[i], y[i], sy[i]
673            #print q, ' : ', s.weight(q), s._compute_iq(q)
674            #print q, ' : ', s(q), s._compute_iq(q)
675            #s._compute_iq(q)
676
677
678
679
Note: See TracBrowser for help on using the repository browser.