source: sasview/sansinvariant/src/sans/invariant/invariant.py @ dc9660d

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 dc9660d was 3fc01c5, checked in by Jae Cho <jhjcho@…>, 12 years ago

fixing pylint warnings

  • Property mode set to 100644
File size: 36.1 KB
Line 
1#####################################################################
2#This software was developed by the University of Tennessee as part of the
3#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4#project funded by the US National Science Foundation.
5#See the license text in license.txt
6#copyright 2010, University of Tennessee
7######################################################################
8
9"""
10This module implements invariant and its related computations.
11
12:author: Gervaise B. Alina/UTK
13:author: Mathieu Doucet/UTK
14:author: Jae Cho/UTK
15
16"""
17import math 
18import numpy
19
20from sans.dataloader.data_info import Data1D as LoaderData1D
21
22# The minimum q-value to be used when extrapolating
23Q_MINIMUM  = 1e-5
24
25# The maximum q-value to be used when extrapolating
26Q_MAXIMUM  = 10
27
28# Number of steps in the extrapolation
29INTEGRATION_NSTEPS = 1000
30
31class Transform(object):
32    """
33    Define interface that need to compute a function or an inverse
34    function given some x, y
35    """
36   
37    def linearize_data(self, data):
38        """
39        Linearize data so that a linear fit can be performed.
40        Filter out the data that can't be transformed.
41       
42        :param data: LoadData1D instance
43       
44        """
45        # Check that the vector lengths are equal
46        assert(len(data.x)==len(data.y))
47        if data.dy is not None:
48            assert(len(data.x)==len(data.dy))
49            dy = data.dy
50        else:
51            dy = numpy.ones(len(data.y))
52           
53        # Transform the data
54        data_points = zip(data.x, data.y, dy)
55
56        output_points = [(self.linearize_q_value(p[0]),
57                          math.log(p[1]),
58                          p[2]/p[1]) for p in data_points if p[0]>0 and \
59                          p[1]>0 and p[2]>0]
60       
61        x_out, y_out, dy_out = zip(*output_points)
62       
63        # Create Data1D object
64        x_out = numpy.asarray(x_out)
65        y_out = numpy.asarray(y_out)
66        dy_out = numpy.asarray(dy_out)
67        linear_data = LoaderData1D(x=x_out, y=y_out, dy=dy_out)
68       
69        return linear_data
70   
71    def get_allowed_bins(self, data):
72        """
73        Goes through the data points and returns a list of boolean values
74        to indicate whether each points is allowed by the model or not.
75       
76        :param data: Data1D object
77        """
78        return [p[0]>0 and p[1]>0 and p[2]>0 for p in zip(data.x, data.y,
79                                                           data.dy)]
80       
81    def linearize_q_value(self, value):
82        """
83        Transform the input q-value for linearization
84        """
85        return NotImplemented
86
87    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
88        """
89        set private member
90        """
91        return NotImplemented
92     
93    def evaluate_model(self, x):
94        """
95        Returns an array f(x) values where f is the Transform function.
96        """
97        return NotImplemented
98   
99    def evaluate_model_errors(self, x):
100        """
101        Returns an array of I(q) errors
102        """
103        return NotImplemented
104   
105class Guinier(Transform):
106    """
107    class of type Transform that performs operations related to guinier
108    function
109    """
110    def __init__(self, scale=1, radius=60):
111        Transform.__init__(self)
112        self.scale = scale
113        self.radius = radius
114        ## Uncertainty of scale parameter
115        self.dscale  = 0
116        ## Unvertainty of radius parameter
117        self.dradius = 0
118       
119    def linearize_q_value(self, value):
120        """
121        Transform the input q-value for linearization
122       
123        :param value: q-value
124       
125        :return: q*q
126        """
127        return value * value
128   
129    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
130        """
131            assign new value to the scale and the radius
132        """
133        self.scale = math.exp(constant)
134        self.radius = math.sqrt(-3 * slope)
135       
136        # Errors
137        self.dscale = math.exp(constant)*dconstant
138        self.dradius = -3.0/2.0/math.sqrt(-3 * slope)*dslope
139       
140        return [self.radius, self.scale], [self.dradius, self.dscale]
141       
142    def evaluate_model(self, x):
143        """
144        return F(x)= scale* e-((radius*x)**2/3)
145        """
146        return self._guinier(x)
147             
148    def evaluate_model_errors(self, x):
149        """
150        Returns the error on I(q) for the given array of q-values
151       
152        :param x: array of q-values
153        """
154        p1 = numpy.array([self.dscale * math.exp(-((self.radius * q)**2/3)) \
155                          for q in x])
156        p2 = numpy.array([self.scale * math.exp(-((self.radius * q)**2/3))\
157                     * (-(q**2/3)) * 2 * self.radius * self.dradius for q in x])
158        diq2 = p1*p1 + p2*p2       
159        return numpy.array( [math.sqrt(err) for err in diq2] )
160             
161    def _guinier(self, x):
162        """
163        Retrive the guinier function after apply an inverse guinier function
164        to x
165        Compute a F(x) = scale* e-((radius*x)**2/3).
166       
167        :param x: a vector of q values
168        :param scale: the scale value
169        :param radius: the guinier radius value
170       
171        :return: F(x)
172        """   
173        # transform the radius of coming from the inverse guinier function to a
174        # a radius of a guinier function
175        if self.radius <= 0:
176            msg = "Rg expected positive value, but got %s"%self.radius
177            raise ValueError(msg) 
178        value = numpy.array([math.exp(-((self.radius * i)**2/3)) for i in x ]) 
179        return self.scale * value
180
181class PowerLaw(Transform):
182    """
183    class of type transform that perform operation related to power_law
184    function
185    """
186    def __init__(self, scale=1, power=4):
187        Transform.__init__(self)
188        self.scale = scale
189        self.power = power
190   
191    def linearize_q_value(self, value):
192        """
193        Transform the input q-value for linearization
194       
195        :param value: q-value
196       
197        :return: log(q)
198        """
199        return math.log(value)
200   
201    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
202        """
203        Assign new value to the scale and the power
204        """
205        self.power = -slope
206        self.scale = math.exp(constant)
207       
208        # Errors
209        self.dscale = math.exp(constant)*dconstant
210        self.dpower = -dslope
211       
212        return [self.power, self.scale], [self.dpower, self.dscale]
213       
214    def evaluate_model(self, x):
215        """
216        given a scale and a radius transform x, y using a power_law
217        function
218        """
219        return self._power_law(x)
220   
221    def evaluate_model_errors(self, x):
222        """
223        Returns the error on I(q) for the given array of q-values
224        :param x: array of q-values
225        """
226        p1 = numpy.array([self.dscale * math.pow(q, -self.power) for q in x])
227        p2 = numpy.array([self.scale * self.power * math.pow(q, -self.power-1)\
228                           * self.dpower for q in x])
229        diq2 = p1*p1 + p2*p2       
230        return numpy.array( [math.sqrt(err) for err in diq2] )
231       
232    def _power_law(self, x):
233        """
234        F(x) = scale* (x)^(-power)
235            when power= 4. the model is porod
236            else power_law
237        The model has three parameters: ::
238            1. x: a vector of q values
239            2. power: power of the function
240            3. scale : scale factor value
241       
242        :param x: array
243        :return: F(x)
244        """
245        if self.power <= 0:
246            msg = "Power_law function expected positive power,"
247            msg += " but got %s"%self.power
248            raise ValueError(msg)
249        if self.scale <= 0:
250            msg = "scale expected positive value, but got %s"%self.scale
251            raise ValueError(msg) 
252       
253        value = numpy.array([ math.pow(i, -self.power) for i in x ]) 
254        return self.scale * value
255
256class Extrapolator:
257    """
258    Extrapolate I(q) distribution using a given model
259    """
260    def __init__(self, data, model=None):
261        """
262        Determine a and b given a linear equation y = ax + b
263       
264        If a model is given, it will be used to linearize the data before
265        the extrapolation is performed. If None,
266        a simple linear fit will be done.
267       
268        :param data: data containing x and y  such as  y = ax + b
269        :param model: optional Transform object
270        """
271        self.data  = data
272        self.model = model
273       
274        # Set qmin as the lowest non-zero value
275        self.qmin = Q_MINIMUM
276        for q_value in self.data.x:
277            if q_value > 0: 
278                self.qmin = q_value
279                break
280        self.qmax = max(self.data.x)
281             
282    def fit(self, power=None, qmin=None, qmax=None):
283        """
284        Fit data for y = ax + b  return a and b
285       
286        :param power: a fixed, otherwise None
287        :param qmin: Minimum Q-value
288        :param qmax: Maximum Q-value
289        """
290        if qmin is None:
291            qmin = self.qmin
292        if qmax is None:
293            qmax = self.qmax
294           
295        # Identify the bin range for the fit
296        idx = (self.data.x >= qmin) & (self.data.x <= qmax)
297       
298        fx = numpy.zeros(len(self.data.x))
299
300        # Uncertainty
301        if type(self.data.dy)==numpy.ndarray and \
302            len(self.data.dy)==len(self.data.x) and \
303            numpy.all(self.data.dy>0):
304            sigma = self.data.dy
305        else:
306            sigma = numpy.ones(len(self.data.x))
307
308        # Compute theory data f(x)
309        fx[idx] = self.data.y[idx]
310       
311        # Linearize the data
312        if self.model is not None:
313            linearized_data = self.model.linearize_data(\
314                                            LoaderData1D(self.data.x[idx],
315                                                                fx[idx],
316                                                            dy = sigma[idx]))
317        else:
318            linearized_data = LoaderData1D(self.data.x[idx],
319                                           fx[idx],
320                                           dy = sigma[idx])
321       
322        ##power is given only for function = power_law   
323        if power != None:
324            sigma2 = linearized_data.dy * linearized_data.dy
325            a = -(power)
326            b = (numpy.sum(linearized_data.y/sigma2) \
327                 - a*numpy.sum(linearized_data.x/sigma2))/numpy.sum(1.0/sigma2)
328           
329           
330            deltas = linearized_data.x*a + \
331                    numpy.ones(len(linearized_data.x))*b-linearized_data.y
332            residuals = numpy.sum(deltas*deltas/sigma2)
333
334            err = math.fabs(residuals) / numpy.sum(1.0/sigma2)
335            return [a, b], [0, math.sqrt(err)]
336        else:
337            A = numpy.vstack([ linearized_data.x/linearized_data.dy,
338                               1.0/linearized_data.dy]).T       
339            (p, residuals, rank, s) = numpy.linalg.lstsq(A,
340                                        linearized_data.y/linearized_data.dy)
341           
342            # Get the covariance matrix, defined as inv_cov = a_transposed * a
343            err = numpy.zeros(2)
344            try:
345                inv_cov = numpy.dot(A.transpose(), A)
346                cov = numpy.linalg.pinv(inv_cov)
347                err_matrix = math.fabs(residuals) * cov
348                err = [math.sqrt(err_matrix[0][0]), math.sqrt(err_matrix[1][1])]
349            except:
350                err = [-1.0, -1.0]
351               
352            return p, err
353       
354
355class InvariantCalculator(object):
356    """
357    Compute invariant if data is given.
358    Can provide volume fraction and surface area if the user provides
359    Porod constant  and contrast values.
360   
361    :precondition:  the user must send a data of type DataLoader.Data1D
362                    the user provide background and scale values.
363                   
364    :note: Some computations depends on each others.
365    """
366    def __init__(self, data, background=0, scale=1 ):
367        """
368        Initialize variables.
369       
370        :param data: data must be of type DataLoader.Data1D
371        :param background: Background value. The data will be corrected
372            before processing
373        :param scale: Scaling factor for I(q). The data will be corrected
374            before processing
375        """
376        # Background and scale should be private data member if the only way to
377        # change them are by instantiating a new object.
378        self._background = background
379        self._scale = scale
380        # slit height for smeared data
381        self._smeared = None
382        # The data should be private
383        self._data = self._get_data(data)
384        # get the dxl if the data is smeared: This is done only once on init.
385        if self._data.dxl != None and self._data.dxl.all() >0:
386            # assumes constant dxl
387            self._smeared = self._data.dxl[0]
388     
389        # Since there are multiple variants of Q*, you should force the
390        # user to use the get method and keep Q* a private data member
391        self._qstar = None
392       
393        # You should keep the error on Q* so you can reuse it without
394        # recomputing the whole thing.
395        self._qstar_err = 0
396       
397        # Extrapolation parameters
398        self._low_extrapolation_npts = 4
399        self._low_extrapolation_function = Guinier()
400        self._low_extrapolation_power = None
401        self._low_extrapolation_power_fitted = None
402   
403        self._high_extrapolation_npts = 4
404        self._high_extrapolation_function = PowerLaw()
405        self._high_extrapolation_power = None
406        self._high_extrapolation_power_fitted = None
407       
408        # Extrapolation range
409        self._low_q_limit = Q_MINIMUM
410       
411    def _get_data(self, data):
412        """
413        :note: this function must be call before computing any type
414         of invariant
415         
416        :return: new data = self._scale *data - self._background
417        """
418        if not issubclass(data.__class__, LoaderData1D):
419            #Process only data that inherited from DataLoader.Data_info.Data1D
420            raise ValueError,"Data must be of type DataLoader.Data1D"
421        #from copy import deepcopy
422        new_data = (self._scale * data) - self._background   
423       
424        # Check that the vector lengths are equal
425        assert(len(new_data.x)==len(new_data.y))
426       
427        # Verify that the errors are set correctly
428        if new_data.dy is None or len(new_data.x) != len(new_data.dy) or \
429            (min(new_data.dy)==0 and max(new_data.dy)==0):
430            new_data.dy = numpy.ones(len(new_data.x)) 
431        return  new_data
432     
433    def _fit(self, model, qmin=Q_MINIMUM, qmax=Q_MAXIMUM, power=None):
434        """
435        fit data with function using
436        data = self._get_data()
437        fx = Functor(data , function)
438        y = data.y
439        slope, constant = linalg.lstsq(y,fx)
440       
441        :param qmin: data first q value to consider during the fit
442        :param qmax: data last q value to consider during the fit
443        :param power : power value to consider for power-law
444        :param function: the function to use during the fit
445       
446        :return a: the scale of the function
447        :return b: the other parameter of the function for guinier will be radius
448                for power_law will be the power value
449        """
450        extrapolator = Extrapolator(data=self._data, model=model)
451        p, dp = extrapolator.fit(power=power, qmin=qmin, qmax=qmax) 
452       
453        return model.extract_model_parameters(constant=p[1], slope=p[0],
454                                              dconstant=dp[1], dslope=dp[0])
455   
456    def _get_qstar(self, data):
457        """
458        Compute invariant for pinhole data.
459        This invariant is given by: ::
460   
461            q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1
462                        + ..+ xn**2 *yn *dxn    for non smeared data
463                       
464            q_star = dxl0 *x0 *y0 *dx0 +dxl1 *x1 *y1 *dx1
465                        + ..+ dlxn *xn *yn *dxn    for smeared data
466                       
467            where n >= len(data.x)-1
468            dxl = slit height dQl
469            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
470            dx0 = (x1 - x0)/2
471            dxn = (xn - xn-1)/2
472           
473        :param data: the data to use to compute invariant.
474       
475        :return q_star: invariant value for pinhole data. q_star > 0
476        """
477        if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x)!= len(data.y):
478            msg =  "Length x and y must be equal"
479            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x),
480                                                          len(data.y))
481            raise ValueError, msg
482        else:
483            # Take care of smeared data
484            if self._smeared is None:
485                gx = data.x * data.x
486            # assumes that len(x) == len(dxl).
487            else:               
488                gx = data.dxl * data.x
489               
490            n = len(data.x)- 1
491            #compute the first delta q
492            dx0 = (data.x[1] - data.x[0])/2
493            #compute the last delta q
494            dxn = (data.x[n] - data.x[n-1])/2
495            sum = 0
496            sum += gx[0] * data.y[0] * dx0
497            sum += gx[n] * data.y[n] * dxn
498           
499            if len(data.x) == 2:
500                return sum
501            else:
502                #iterate between for element different
503                #from the first and the last
504                for i in xrange(1, n-1):
505                    dxi = (data.x[i+1] - data.x[i-1])/2
506                    sum += gx[i] * data.y[i] * dxi
507                return sum
508           
509    def _get_qstar_uncertainty(self, data):
510        """
511        Compute invariant uncertainty with with pinhole data.
512        This uncertainty is given as follow: ::
513       
514           dq_star = math.sqrt[(x0**2*(dy0)*dx0)**2 +
515                (x1**2 *(dy1)*dx1)**2 + ..+ (xn**2 *(dyn)*dxn)**2 ]
516        where n >= len(data.x)-1
517        dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
518        dx0 = (x1 - x0)/2
519        dxn = (xn - xn-1)/2
520        dyn: error on dy
521       
522        :param data:
523        :note: if data doesn't contain dy assume dy= math.sqrt(data.y)
524        """         
525        if len(data.x) <= 1 or len(data.y) <= 1 or \
526            len(data.x) != len(data.y) or \
527            (data.dy is not None and (len(data.dy) != len(data.y))):
528            msg = "Length of data.x and data.y must be equal"
529            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x),
530                                                         len(data.y))
531            raise ValueError, msg
532        else:
533            #Create error for data without dy error
534            if data.dy is None:
535                dy = math.sqrt(data.y) 
536            else:
537                dy = data.dy
538            # Take care of smeared data
539            if self._smeared is None:
540                gx = data.x * data.x
541            # assumes that len(x) == len(dxl).
542            else:
543                gx = data.dxl * data.x
544 
545            n = len(data.x) - 1
546            #compute the first delta
547            dx0 = (data.x[1] - data.x[0])/2
548            #compute the last delta
549            dxn= (data.x[n] - data.x[n-1])/2
550            sum = 0
551            sum += (gx[0] * dy[0] * dx0)**2
552            sum += (gx[n] * dy[n] * dxn)**2
553            if len(data.x) == 2:
554                return math.sqrt(sum)
555            else:
556                #iterate between for element different
557                #from the first and the last
558                for i in xrange(1, n-1):
559                    dxi = (data.x[i+1] - data.x[i-1])/2
560                    sum += (gx[i] * dy[i] * dxi)**2
561                return math.sqrt(sum)
562       
563    def _get_extrapolated_data(self, model, npts=INTEGRATION_NSTEPS,
564                              q_start=Q_MINIMUM, q_end=Q_MAXIMUM):
565        """
566        :return: extrapolate data create from data
567        """
568        #create new Data1D to compute the invariant
569        q = numpy.linspace(start=q_start,
570                           stop=q_end,
571                           num=npts,
572                           endpoint=True)
573        iq = model.evaluate_model(q)
574        diq = model.evaluate_model_errors(q)
575         
576        result_data = LoaderData1D(x=q, y=iq, dy=diq)
577        if self._smeared != None:
578            result_data.dxl = self._smeared * numpy.ones(len(q))
579        return result_data
580   
581    def get_data(self):
582        """
583        :return: self._data
584        """
585        return self._data
586   
587    def get_extrapolation_power(self, range='high'):
588        """
589        :return: the fitted power for power law function for a given
590            extrapolation range
591        """
592        if range == 'low':
593            return self._low_extrapolation_power_fitted
594        return self._high_extrapolation_power_fitted
595   
596    def get_qstar_low(self):
597        """
598        Compute the invariant for extrapolated data at low q range.
599       
600        Implementation:
601            data = self._get_extra_data_low()
602            return self._get_qstar()
603           
604        :return q_star: the invariant for data extrapolated at low q.
605        """
606        # Data boundaries for fitting
607        qmin = self._data.x[0]
608        qmax = self._data.x[self._low_extrapolation_npts - 1]
609       
610        # Extrapolate the low-Q data
611        p, dp = self._fit(model=self._low_extrapolation_function,
612                              qmin=qmin,
613                          qmax=qmax,
614                          power=self._low_extrapolation_power)
615        self._low_extrapolation_power_fitted = p[0]
616       
617        # Distribution starting point
618        self._low_q_limit = Q_MINIMUM
619        if Q_MINIMUM >= qmin:
620            self._low_q_limit = qmin/10
621       
622        data = self._get_extrapolated_data(\
623                                    model=self._low_extrapolation_function,
624                                            npts=INTEGRATION_NSTEPS,
625                                        q_start=self._low_q_limit, q_end=qmin)
626       
627        # Systematic error
628        # If we have smearing, the shape of the I(q) distribution at low Q will
629        # may not be a Guinier or simple power law. The following is
630        # a conservative estimation for the systematic error.
631        err = qmin*qmin*math.fabs((qmin-self._low_q_limit)*\
632                                  (data.y[0] - data.y[INTEGRATION_NSTEPS-1]))
633        return self._get_qstar(data), self._get_qstar_uncertainty(data)+err
634       
635    def get_qstar_high(self):
636        """
637        Compute the invariant for extrapolated data at high q range.
638       
639        Implementation:
640            data = self._get_extra_data_high()
641            return self._get_qstar()
642           
643        :return q_star: the invariant for data extrapolated at high q.
644        """
645        # Data boundaries for fitting
646        x_len = len(self._data.x) - 1
647        qmin = self._data.x[x_len - (self._high_extrapolation_npts - 1)]
648        qmax = self._data.x[x_len]
649       
650        # fit the data with a model to get the appropriate parameters
651        p, dp = self._fit(model=self._high_extrapolation_function,
652                          qmin=qmin,
653                          qmax=qmax,
654                          power=self._high_extrapolation_power)
655        self._high_extrapolation_power_fitted = p[0]
656       
657        #create new Data1D to compute the invariant
658        data = self._get_extrapolated_data(\
659                                    model=self._high_extrapolation_function,
660                                           npts=INTEGRATION_NSTEPS,
661                                           q_start=qmax, q_end=Q_MAXIMUM)       
662       
663        return self._get_qstar(data), self._get_qstar_uncertainty(data)
664   
665    def get_extra_data_low(self, npts_in=None, q_start=None, npts=20):
666        """
667        Returns the extrapolated data used for the loew-Q invariant calculation.
668        By default, the distribution will cover the data points used for the
669        extrapolation. The number of overlap points is a parameter (npts_in).
670        By default, the maximum q-value of the distribution will be 
671        the minimum q-value used when extrapolating for the purpose of the
672        invariant calculation.
673       
674        :param npts_in: number of data points for which
675            the extrapolated data overlap
676        :param q_start: is the minimum value to uses for extrapolated data
677        :param npts: the number of points in the extrapolated distribution
678           
679        """
680        # Get extrapolation range
681        if q_start is None:
682            q_start = self._low_q_limit
683           
684        if npts_in is None:
685            npts_in = self._low_extrapolation_npts
686        q_end = self._data.x[max(0, npts_in-1)]
687       
688        if q_start >= q_end:
689            return numpy.zeros(0), numpy.zeros(0)
690
691        return self._get_extrapolated_data(\
692                                    model=self._low_extrapolation_function,
693                                           npts=npts,
694                                           q_start=q_start, q_end=q_end)
695         
696    def get_extra_data_high(self, npts_in=None, q_end=Q_MAXIMUM, npts=20):
697        """
698        Returns the extrapolated data used for the high-Q invariant calculation.
699        By default, the distribution will cover the data points used for the
700        extrapolation. The number of overlap points is a parameter (npts_in).
701        By default, the maximum q-value of the distribution will be Q_MAXIMUM,
702        the maximum q-value used when extrapolating for the purpose of the
703        invariant calculation.
704       
705        :param npts_in: number of data points for which the
706            extrapolated data overlap
707        :param q_end: is the maximum value to uses for extrapolated data
708        :param npts: the number of points in the extrapolated distribution
709        """
710        # Get extrapolation range
711        if npts_in is None:
712            npts_in = self._high_extrapolation_npts
713        _npts = len(self._data.x)
714        q_start = self._data.x[min(_npts, _npts-npts_in)]
715       
716        if q_start >= q_end:
717            return numpy.zeros(0), numpy.zeros(0)
718       
719        return self._get_extrapolated_data(\
720                                model=self._high_extrapolation_function,
721                                           npts=npts,
722                                           q_start=q_start, q_end=q_end)
723     
724    def set_extrapolation(self, range, npts=4, function=None, power=None):
725        """
726        Set the extrapolation parameters for the high or low Q-range.
727        Note that this does not turn extrapolation on or off.
728       
729        :param range: a keyword set the type of extrapolation . type string
730        :param npts: the numbers of q points of data to consider
731            for extrapolation
732        :param function: a keyword to select the function to use
733            for extrapolation.
734            of type string.
735        :param power: an power to apply power_low function
736               
737        """
738        range = range.lower()
739        if range not in ['high', 'low']:
740            raise ValueError, "Extrapolation range should be 'high' or 'low'"
741        function = function.lower()
742        if function not in ['power_law', 'guinier']:
743            msg = "Extrapolation function should be 'guinier' or 'power_law'"
744            raise ValueError, msg
745       
746        if range == 'high':
747            if function != 'power_law':
748                msg = "Extrapolation only allows a power law at high Q"
749                raise ValueError, msg
750            self._high_extrapolation_npts  = npts
751            self._high_extrapolation_power = power
752            self._high_extrapolation_power_fitted = power
753        else:
754            if function == 'power_law':
755                self._low_extrapolation_function = PowerLaw()
756            else:
757                self._low_extrapolation_function = Guinier()
758            self._low_extrapolation_npts  = npts
759            self._low_extrapolation_power = power
760            self._low_extrapolation_power_fitted = power
761       
762    def get_qstar(self, extrapolation=None):
763        """
764        Compute the invariant of the local copy of data.
765       
766        :param extrapolation: string to apply optional extrapolation
767           
768        :return q_star: invariant of the data within data's q range
769       
770        :warning: When using setting data to Data1D ,
771            the user is responsible of
772            checking that the scale and the background are
773            properly apply to the data
774       
775        """
776        self._qstar = self._get_qstar(self._data)
777        self._qstar_err = self._get_qstar_uncertainty(self._data)
778       
779        if extrapolation is None:
780            return self._qstar
781       
782        # Compute invariant plus invariant of extrapolated data
783        extrapolation = extrapolation.lower()   
784        if extrapolation == "low":
785            qs_low, dqs_low = self.get_qstar_low()
786            qs_hi, dqs_hi   = 0, 0
787           
788        elif extrapolation == "high":
789            qs_low, dqs_low = 0, 0
790            qs_hi, dqs_hi   = self.get_qstar_high()
791           
792        elif extrapolation == "both":
793            qs_low, dqs_low = self.get_qstar_low()
794            qs_hi, dqs_hi   = self.get_qstar_high()
795           
796        self._qstar     += qs_low + qs_hi
797        self._qstar_err = math.sqrt(self._qstar_err*self._qstar_err \
798                                    + dqs_low*dqs_low + dqs_hi*dqs_hi)
799       
800        return self._qstar
801       
802    def get_surface(self, contrast, porod_const, extrapolation=None):
803        """
804        Compute the specific surface from the data.
805       
806        Implementation::
807       
808          V =  self.get_volume_fraction(contrast, extrapolation)
809   
810          Compute the surface given by:
811            surface = (2*pi *V(1- V)*porod_const)/ q_star
812           
813        :param contrast: contrast value to compute the volume
814        :param porod_const: Porod constant to compute the surface
815        :param extrapolation: string to apply optional extrapolation
816       
817        :return: specific surface
818        """
819        # Compute the volume
820        volume = self.get_volume_fraction(contrast, extrapolation)
821        return 2 * math.pi * volume *(1 - volume) * \
822            float(porod_const)/self._qstar
823       
824    def get_volume_fraction(self, contrast, extrapolation=None):
825        """
826        Compute volume fraction is deduced as follow: ::
827       
828            q_star = 2*(pi*contrast)**2* volume( 1- volume)
829            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
830            we get 2 values of volume:
831                 with   1 - 4 * k >= 0
832                 volume1 = (1- sqrt(1- 4*k))/2
833                 volume2 = (1+ sqrt(1- 4*k))/2
834           
835            q_star: the invariant value included extrapolation is applied
836                         unit  1/A^(3)*1/cm
837                    q_star = self.get_qstar()
838                   
839            the result returned will be 0 <= volume <= 1
840       
841        :param contrast: contrast value provides by the user of type float.
842                 contrast unit is 1/A^(2)= 10^(16)cm^(2)
843        :param extrapolation: string to apply optional extrapolation
844       
845        :return: volume fraction
846       
847        :note: volume fraction must have no unit
848        """
849        if contrast <= 0:
850            raise ValueError, "The contrast parameter must be greater than zero" 
851       
852        # Make sure Q star is up to date
853        self.get_qstar(extrapolation)
854       
855        if self._qstar <= 0:
856            msg = "Invalid invariant: Invariant Q* must be greater than zero"
857            raise RuntimeError, msg
858       
859        # Compute intermediate constant
860        k =  1.e-8 * self._qstar/(2 * (math.pi * math.fabs(float(contrast)))**2)
861        # Check discriminant value
862        discrim = 1 - 4 * k
863       
864        # Compute volume fraction
865        if discrim < 0:
866            msg = "Could not compute the volume fraction: negative discriminant"
867            raise RuntimeError, msg
868        elif discrim == 0:
869            return 1/2
870        else:
871            volume1 = 0.5 * (1 - math.sqrt(discrim))
872            volume2 = 0.5 * (1 + math.sqrt(discrim))
873           
874            if 0 <= volume1 and volume1 <= 1:
875                return volume1
876            elif 0 <= volume2 and volume2 <= 1: 
877                return volume2
878            msg = "Could not compute the volume fraction: inconsistent results"
879            raise RuntimeError, msg
880   
881    def get_qstar_with_error(self, extrapolation=None):
882        """
883        Compute the invariant uncertainty.
884        This uncertainty computation depends on whether or not the data is
885        smeared.
886       
887        :param extrapolation: string to apply optional extrapolation
888       
889        :return: invariant, the invariant uncertainty
890        """   
891        self.get_qstar(extrapolation)
892        return self._qstar, self._qstar_err
893   
894    def get_volume_fraction_with_error(self, contrast, extrapolation=None):
895        """
896        Compute uncertainty on volume value as well as the volume fraction
897        This uncertainty is given by the following equation: ::
898       
899            dV = 0.5 * (4*k* dq_star) /(2* math.sqrt(1-k* q_star))
900                                 
901            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
902           
903            q_star: the invariant value including extrapolated value if existing
904            dq_star: the invariant uncertainty
905            dV: the volume uncertainty
906       
907        The uncertainty will be set to -1 if it can't be computed.
908       
909        :param contrast: contrast value
910        :param extrapolation: string to apply optional extrapolation
911       
912        :return: V, dV = volume fraction, error on volume fraction
913        """
914        volume = self.get_volume_fraction(contrast, extrapolation)
915       
916        # Compute error
917        k =  1.e-8 * self._qstar /(2 * (math.pi* math.fabs(float(contrast)))**2)
918        # Check value inside the sqrt function
919        value = 1 - k * self._qstar
920        if (value) <= 0:
921            uncertainty = -1
922        # Compute uncertainty
923        uncertainty = math.fabs((0.5 * 4 * k * \
924                        self._qstar_err)/(2 * math.sqrt(1 - k * self._qstar)))
925       
926        return volume, uncertainty
927   
928    def get_surface_with_error(self, contrast, porod_const, extrapolation=None):
929        """
930        Compute uncertainty of the surface value as well as the surface value.
931        The uncertainty is given as follow: ::
932       
933            dS = porod_const *2*pi[( dV -2*V*dV)/q_star
934                 + dq_star(v-v**2)
935                 
936            q_star: the invariant value
937            dq_star: the invariant uncertainty
938            V: the volume fraction value
939            dV: the volume uncertainty
940       
941        :param contrast: contrast value
942        :param porod_const: porod constant value
943        :param extrapolation: string to apply optional extrapolation
944       
945        :return S, dS: the surface, with its uncertainty
946        """
947        # We get the volume fraction, with error
948        #   get_volume_fraction_with_error calls get_volume_fraction
949        #   get_volume_fraction calls get_qstar
950        #   which computes Qstar and dQstar
951        v, dv = self.get_volume_fraction_with_error(contrast, extrapolation)
952
953        s = self.get_surface(contrast=contrast, porod_const=porod_const, 
954                             extrapolation=extrapolation)
955        ds = porod_const * 2 * math.pi * (( dv - 2 * v * dv)/ self._qstar\
956                 + self._qstar_err * ( v - v**2))
957
958        return s, ds
Note: See TracBrowser for help on using the repository browser.