source: sasview/Invariant/invariant.py @ 35b556d

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 35b556d was 6d55d81, checked in by Gervaise Alina <gervyh@…>, 15 years ago

make sure the data is updated when the scale change

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