source: sasview/DataLoader/qsmearing.py @ 16d7079

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 16d7079 was cd2ced80, checked in by Jae Cho <jhjcho@…>, 14 years ago

new algorithm for slit smearing and its test

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