source: sasview/DataLoader/qsmearing.py @ 13913ec

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 13913ec was d5751ac, checked in by Jae Cho <jhjcho@…>, 14 years ago

slit_smear: fixed a bug : improper q-range for 1D fit panel

  • Property mode set to 100644
File size: 20.8 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        """
[a3f8d58]159        # If this is the first time we call for smearing,
160        # initialize the C++ smearer object first
161        if not self._init_complete:
162            self._initialize_smearer()
[f867cd9]163
[a7a5886]164        if last_bin is None or last_bin >= len(iq_in):
165            last_bin = len(iq_in) - 1
[a3f8d58]166        # Check that the first bin is positive
[a7a5886]167        if first_bin < 0:
[a3f8d58]168            first_bin = 0
[535752b]169
[f867cd9]170        # With a model given, compute I for the extrapolated points and append
171        # to the iq_in
[535752b]172        iq_in_temp = iq_in
[f867cd9]173        if self.model != None:
174            temp_first, temp_last = self._get_extrapolated_bin( \
175                                                        first_bin, last_bin)
[cd2ced80]176            if self.nbins_low > 0:
[535752b]177                iq_in_low = self.model.evalDistribution( \
[7241d56]178                                    numpy.fabs(self.qvalues[0:self.nbins_low]))
[f867cd9]179            iq_in_high = self.model.evalDistribution( \
180                                            self.qvalues[(len(self.qvalues) - \
[7241d56]181                                            self.nbins_high - 1):])
182            # Todo: find out who is sending iq[last_poin] = 0.
183            if iq_in[len(iq_in) - 1] == 0:
184                iq_in[len(iq_in) - 1] = iq_in_high[0]
185            # Append the extrapolated points to the data points
[f867cd9]186            if self.nbins_low > 0:                             
187                iq_in_temp = numpy.append(iq_in_low, iq_in)
188            if self.nbins_high > 0:
[7241d56]189                iq_in_temp = numpy.append(iq_in_temp, iq_in_high[1:])
[f867cd9]190        else:
191            temp_first = first_bin
192            temp_last = last_bin
[535752b]193            #iq_in_temp = iq_in
[7241d56]194       
[a3f8d58]195        # Sanity check
[f867cd9]196        if len(iq_in_temp) != self.nbins:
[a7a5886]197            msg = "Invalid I(q) vector: inconsistent array "
[f867cd9]198            msg += " length %d != %s" % (len(iq_in_temp), str(self.nbins))
[a7a5886]199            raise RuntimeError, msg
[f867cd9]200
[a3f8d58]201        # Storage for smeared I(q)   
202        iq_out = numpy.zeros(self.nbins)
[f867cd9]203
204        smear_output = smearer.smear(self._smearer, iq_in_temp, iq_out,
205                                      #0, self.nbins - 1)
206                                      temp_first, temp_last)
207                                      #first_bin, last_bin)
[65883cf]208        if smear_output < 0:
[a7a5886]209            msg = "_BaseSmearer: could not smear, code = %g" % smear_output
210            raise RuntimeError, msg
[f867cd9]211
[d5751ac]212        temp_first = first_bin + self.nbins_low
[7241d56]213        temp_last = self.nbins - self.nbins_high
[535752b]214        out = iq_out[temp_first: temp_last]
[1b02da1d]215
[535752b]216        return out
[d00f8ff]217   
[a7a5886]218    def _initialize_smearer(self):
219        """
220        """
221        return NotImplemented
[f867cd9]222           
223   
224    def _get_unextrapolated_bin(self, first_bin = 0, last_bin = 0):
225        """
226        Get unextrapolated first bin and the last bin
227       
228        : param first_bin: extrapolated first_bin
229        : param last_bin: extrapolated last_bin
230       
231        : return fist_bin, last_bin: unextrapolated first and last bin
232        """
233        #  For first bin
234        if first_bin <= self.nbins_low:
235            first_bin = 0
236        else:
237            first_bin = first_bin - self.nbins_low
238        # For last bin
239        if last_bin >= (self.nbins - self.nbins_high):
240            last_bin  = self.nbins - (self.nbins_high + self.nbins_low + 1)
241        elif last_bin >= self.nbins_low:
242            last_bin = last_bin - self.nbins_low
243        else:
244            last_bin = 0
245        return first_bin, last_bin
246   
247    def _get_extrapolated_bin(self, first_bin = 0, last_bin = 0):
248        """
249        Get extrapolated first bin and the last bin
250       
251        : param first_bin: unextrapolated first_bin
252        : param last_bin: unextrapolated last_bin
253       
254        : return first_bin, last_bin: extrapolated first and last bin
255        """
256        #  For the first bin
[7241d56]257        # In the case that needs low extrapolation data
258        first_bin = 0
[f867cd9]259        # For last bin
260        if last_bin >= self.nbins - (self.nbins_high + self.nbins_low + 1):
261            # In the case that needs higher q extrapolation data
262            last_bin = self.nbins - 1
263        else:
264            # In the case that doesn't need higher q extrapolation data
265             last_bin += self.nbins_low
266
267        return first_bin, last_bin
268       
[d00f8ff]269class _SlitSmearer(_BaseSmearer):
270    """
[0997158f]271    Slit smearing for I(q) array
[d00f8ff]272    """
273   
274    def __init__(self, nbins=None, width=None, height=None, min=None, max=None):
275        """
[0997158f]276        Initialization
[d00f8ff]277           
[0997158f]278        :param iq: I(q) array [cm-1]
279        :param width: slit width [A-1]
280        :param height: slit height [A-1]
281        :param min: Q_min [A-1]
282        :param max: Q_max [A-1]
283       
[d00f8ff]284        """
[a3f8d58]285        _BaseSmearer.__init__(self)
[d00f8ff]286        ## Slit width in Q units
287        self.width  = width
288        ## Slit height in Q units
289        self.height = height
290        ## Q_min (Min Q-value for I(q))
291        self.min    = min
292        ## Q_max (Max Q_value for I(q))
293        self.max    = max
294        ## Number of Q bins
295        self.nbins  = nbins
296        ## Number of points used in the smearing computation
[2ca00c4]297        self.npts   = 3000
[d00f8ff]298        ## Smearing matrix
299        self._weights = None
[65883cf]300        self.qvalues  = None
[d00f8ff]301       
[a3f8d58]302    def _initialize_smearer(self):
[d00f8ff]303        """
[0997158f]304        Initialize the C++ smearer object.
305        This method HAS to be called before smearing
[d00f8ff]306        """
[a7a5886]307        #self._smearer = smearer.new_slit_smearer(self.width,
308        # self.height, self.min, self.max, self.nbins)
309        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
310                                                    self.height, self.qvalues)
[a3f8d58]311        self._init_complete = True
[fe2ade9]312
[5859862]313    def get_unsmeared_range(self, q_min, q_max):
314        """
[0997158f]315        Determine the range needed in unsmeared-Q to cover
316        the smeared Q range
[5859862]317        """
318        # Range used for input to smearing
319        _qmin_unsmeared = q_min
320        _qmax_unsmeared = q_max
321        try:
322            _qmin_unsmeared = self.min
323            _qmax_unsmeared = self.max
324        except:
325            logging.error("_SlitSmearer.get_bin_range: %s" % sys.exc_value)
326        return _qmin_unsmeared, _qmax_unsmeared
[d00f8ff]327
328class SlitSmearer(_SlitSmearer):
329    """
[0997158f]330    Adaptor for slit smearing class and SANS data
[d00f8ff]331    """
[535752b]332    def __init__(self, data1D, model = None):
[d00f8ff]333        """
[0997158f]334        Assumption: equally spaced bins of increasing q-values.
335       
336        :param data1D: data used to set the smearing parameters
[d00f8ff]337        """
338        # Initialization from parent class
339        super(SlitSmearer, self).__init__()
340       
341        ## Slit width
342        self.width = 0
[f867cd9]343        self.nbins_low = 0
344        self.nbins_high = 0
[535752b]345        self.model = model
[a7a5886]346        if data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
[d00f8ff]347            self.width = data1D.dxw[0]
348            # Sanity check
349            for value in data1D.dxw:
350                if value != self.width:
[a7a5886]351                    msg = "Slit smearing parameters must "
352                    msg += " be the same for all data"
353                    raise RuntimeError, msg
[d00f8ff]354        ## Slit height
355        self.height = 0
[a7a5886]356        if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x):
[d00f8ff]357            self.height = data1D.dxl[0]
358            # Sanity check
359            for value in data1D.dxl:
360                if value != self.height:
[a7a5886]361                    msg = "Slit smearing parameters must be"
362                    msg += " the same for all data"
363                    raise RuntimeError, msg
[535752b]364        # If a model is given, get the q extrapolation
365        if self.model == None:
366            data1d_x = data1D.x
367        else:
368            # Take larger sigma
369            if self.height > self.width:
370                # The denominator (2.0) covers all the possible w^2 + h^2 range
371                sigma_in = data1D.dxl / 2.0
372            elif self.width > 0:
373                sigma_in = data1D.dxw / 2.0
374            else:
375                sigma_in = []
376
377            self.nbins_low, self.nbins_high, _, data1d_x = \
378                                get_qextrapolate(sigma_in, data1D.x)
379
[d00f8ff]380        ## Number of Q bins
[535752b]381        self.nbins = len(data1d_x)
[d00f8ff]382        ## Minimum Q
[535752b]383        self.min = min(data1d_x)
[d00f8ff]384        ## Maximum
[535752b]385        self.max = max(data1d_x)
[5859862]386        ## Q-values
[535752b]387        self.qvalues = data1d_x
[5859862]388       
[d00f8ff]389
390class _QSmearer(_BaseSmearer):
391    """
[0997158f]392    Perform Gaussian Q smearing
[d00f8ff]393    """
394       
395    def __init__(self, nbins=None, width=None, min=None, max=None):
396        """
[0997158f]397        Initialization
398       
399        :param nbins: number of Q bins
400        :param width: array standard deviation in Q [A-1]
401        :param min: Q_min [A-1]
402        :param max: Q_max [A-1]
[d00f8ff]403        """
[a3f8d58]404        _BaseSmearer.__init__(self)
[d00f8ff]405        ## Standard deviation in Q [A-1]
[a7a5886]406        self.width = width
[d00f8ff]407        ## Q_min (Min Q-value for I(q))
[a7a5886]408        self.min = min
[d00f8ff]409        ## Q_max (Max Q_value for I(q))
[a7a5886]410        self.max = max
[d00f8ff]411        ## Number of Q bins
[a7a5886]412        self.nbins = nbins
[d00f8ff]413        ## Smearing matrix
414        self._weights = None
[65883cf]415        self.qvalues  = None
[d00f8ff]416       
[a3f8d58]417    def _initialize_smearer(self):
[d00f8ff]418        """
[0997158f]419        Initialize the C++ smearer object.
420        This method HAS to be called before smearing
[d00f8ff]421        """
[a7a5886]422        #self._smearer = smearer.new_q_smearer(numpy.asarray(self.width),
423        # self.min, self.max, self.nbins)
424        self._smearer = smearer.new_q_smearer_with_q(numpy.asarray(self.width),
425                                                      self.qvalues)
[a3f8d58]426        self._init_complete = True
[d00f8ff]427       
[5859862]428    def get_unsmeared_range(self, q_min, q_max):
429        """
[0997158f]430        Determine the range needed in unsmeared-Q to cover
431        the smeared Q range
432        Take 3 sigmas as the offset between smeared and unsmeared space
[5859862]433        """
434        # Range used for input to smearing
435        _qmin_unsmeared = q_min
436        _qmax_unsmeared = q_max
437        try:
[a7a5886]438            offset = 3.0 * max(self.width)
439            _qmin_unsmeared = max([self.min, q_min - offset])
440            _qmax_unsmeared = min([self.max, q_max + offset])
[5859862]441        except:
442            logging.error("_QSmearer.get_bin_range: %s" % sys.exc_value)
443        return _qmin_unsmeared, _qmax_unsmeared
444       
[0997158f]445   
[d00f8ff]446class QSmearer(_QSmearer):
447    """
[0997158f]448    Adaptor for Gaussian Q smearing class and SANS data
[d00f8ff]449    """
[f867cd9]450    def __init__(self, data1D, model = None):
[d00f8ff]451        """
[0997158f]452        Assumption: equally spaced bins of increasing q-values.
453       
454        :param data1D: data used to set the smearing parameters
[d00f8ff]455        """
456        # Initialization from parent class
457        super(QSmearer, self).__init__()
[f867cd9]458        data1d_x = []
459        self.nbins_low = 0
460        self.nbins_high = 0
461        self.model = model
[c0d9981]462        ## Resolution
[f867cd9]463        #self.width = numpy.zeros(len(data1D.x))
[a7a5886]464        if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
[4fe4394]465            self.width = data1D.dx
[d00f8ff]466       
[f867cd9]467        if self.model == None:
468            data1d_x = data1D.x
469        else:
470            self.nbins_low, self.nbins_high, self.width, data1d_x = \
471                                get_qextrapolate(self.width, data1D.x)
472
[d00f8ff]473        ## Number of Q bins
[f867cd9]474        self.nbins = len(data1d_x)
[d00f8ff]475        ## Minimum Q
[f867cd9]476        self.min = min(data1d_x)
[d00f8ff]477        ## Maximum
[f867cd9]478        self.max = max(data1d_x)
[5859862]479        ## Q-values
[f867cd9]480        self.qvalues = data1d_x
[d00f8ff]481
[f867cd9]482       
483def get_qextrapolate(width, data_x):
484    """
485    Make fake data_x points extrapolated outside of the data_x points
486   
487    : param width: array of std of q resolution
488    : param Data1D.x: Data1D.x array
489   
490    : return new_width, data_x_ext: extrapolated width array and x array
491   
492    : assumption1: data_x is ordered from lower q to higher q
493    : assumption2: len(data) = len(width)
494    : assumption3: the distance between the data points is more compact
495            than the size of width
496    : Todo1: Make sure that the assumptions are correct for Data1D
497    : Todo2: This fixes the edge problem in Qsmearer but still needs to make
498            smearer interface
499    """
500    # Length of the width
501    length = len(width)
[7241d56]502    width_low = math.fabs(width[0])
503    width_high = math.fabs(width[length -1])
[535752b]504   
[7241d56]505    # Compare width(dQ) to the data bin size and take smaller one as the bin
506    # size of the extrapolation; this will correct some weird behavior
[535752b]507    # at the edge: This method was out (commented)
508    # because it becomes very expansive when
509    # bin size is very small comparing to the width.
510    # Now on, we will just give the bin size of the extrapolated points
511    # based on the width.
512    # Find bin sizes
513    #bin_size_low = math.fabs(data_x[1] - data_x[0])
514    #bin_size_high = math.fabs(data_x[length - 1] - data_x[length - 2])
515    # Let's set the bin size 1/3 of the width(sigma), it is good as long as
516    # the scattering is monotonous.
517    #if width_low < (bin_size_low):
518    bin_size_low = width_low / 10.0
519    #if width_high < (bin_size_high):
520    bin_size_high = width_high / 10.0
[7241d56]521       
[f867cd9]522    # Number of q points required below the 1st data point in order to extend
523    # them 3 times of the width (std)
[535752b]524    nbins_low = math.ceil(3.0 * width_low / bin_size_low)
[f867cd9]525    # Number of q points required above the last data point
[535752b]526    nbins_high = math.ceil(3.0 * width_high / (bin_size_high))
[f867cd9]527    # Make null q points       
528    extra_low = numpy.zeros(nbins_low)
529    extra_high = numpy.zeros(nbins_high)
530    # Give extrapolated values
531    ind = 0
532    qvalue = data_x[0] - bin_size_low
[535752b]533    #if qvalue > 0:
[f867cd9]534    while(ind < nbins_low):
535        extra_low[nbins_low - (ind + 1)] = qvalue
536        qvalue -= bin_size_low
537        ind += 1
[535752b]538        #if qvalue <= 0:
539        #    break
540    # Redefine nbins_low
541    nbins_low = ind
[f867cd9]542    # Reset ind for another extrapolation
543    ind = 0
544    qvalue = data_x[length -1] + bin_size_high
545    while(ind < nbins_high):
546        extra_high[ind] = qvalue
547        qvalue += bin_size_high
548        ind += 1
549    # Make a new qx array
[535752b]550    if nbins_low > 0: 
[cd2ced80]551        data_x_ext = numpy.append(extra_low, data_x)
[535752b]552    else:
[cd2ced80]553        data_x_ext = data_x
[f867cd9]554    data_x_ext = numpy.append(data_x_ext, extra_high)
[7241d56]555   
[f867cd9]556    # Redefine extra_low and high based on corrected nbins 
[535752b]557    # And note that it is not necessary for extra_width to be a non-zero
558    if nbins_low > 0:     
559        extra_low = numpy.zeros(nbins_low)
[f867cd9]560    extra_high = numpy.zeros(nbins_high) 
561    # Make new width array
562    new_width = numpy.append(extra_low, width)
563    new_width = numpy.append(new_width, extra_high)
[535752b]564   
565    # nbins corrections due to the negative q value
566    nbins_low = nbins_low - len(data_x_ext[data_x_ext<0])
567    return  nbins_low, nbins_high, \
568             new_width[data_x_ext>0], data_x_ext[data_x_ext>0]
[f867cd9]569   
[d00f8ff]570if __name__ == '__main__':
[a7a5886]571    x = 0.001 * numpy.arange(1, 11)
572    y = 12.0 - numpy.arange(1, 11)
[d00f8ff]573    print x
574    #for i in range(10): print i, 0.001 + i*0.008/9.0
575    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) ))
576    s = _SlitSmearer(nbins=10, width=0.0, height=0.005, min=0.001, max=0.010)
577    #s = _QSmearer(nbins=10, width=0.001, min=0.001, max=0.010)
578    s._compute_matrix()
579
580    sy = s(y)
581    print sy
582   
583    if True:
584        for i in range(10):
[a7a5886]585            print x[i], y[i], sy[i]
[d00f8ff]586            #print q, ' : ', s.weight(q), s._compute_iq(q)
587            #print q, ' : ', s(q), s._compute_iq(q)
588            #s._compute_iq(q)
589
590
591
592
Note: See TracBrowser for help on using the repository browser.