source: sasview/Invariant/invariant.py @ aef2cf2

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 aef2cf2 was c4f52e3, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

invariant: minor clean up

  • Property mode set to 100644
File size: 34.3 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        new_data = (self._scale * data) - self._background   
388       
389        # Check that the vector lengths are equal
390        assert(len(new_data.x)==len(new_data.y))
391       
392        # Verify that the errors are set correctly
393        if new_data.dy is None or len(new_data.x) != len(new_data.dy) or \
394            (min(new_data.dy)==0 and max(new_data.dy)==0):
395            new_data.dy = numpy.ones(len(new_data.x)) 
396       
397        return  new_data
398     
399    def _fit(self, model, qmin=Q_MINIMUM, qmax=Q_MAXIMUM, power=None):
400        """
401            fit data with function using
402            data= self._get_data()
403            fx= Functor(data , function)
404            y = data.y
405            slope, constant = linalg.lstsq(y,fx)
406            @param qmin: data first q value to consider during the fit
407            @param qmax: data last q value to consider during the fit
408            @param power : power value to consider for power-law
409            @param function: the function to use during the fit
410            @return a: the scale of the function
411            @return b: the other parameter of the function for guinier will be radius
412                    for power_law will be the power value
413        """
414        extrapolator = Extrapolator(data=self._data, model=model)
415        p, dp = extrapolator.fit(power=power, qmin=qmin, qmax=qmax) 
416       
417        return model.extract_model_parameters(constant=p[1], slope=p[0], dconstant=dp[1], dslope=dp[0])
418   
419    def _get_qstar(self, data):
420        """
421            Compute invariant for pinhole data.
422            This invariant is given by:
423       
424                q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1
425                            + ..+ xn**2 *yn *dxn
426                           
427            where n >= len(data.x)-1
428            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
429            dx0 = (x1 - x0)/2
430            dxn = (xn - xn-1)/2
431            @param data: the data to use to compute invariant.
432            @return q_star: invariant value for pinhole data. q_star > 0
433        """
434        if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x)!= len(data.y):
435            msg =  "Length x and y must be equal"
436            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x), len(data.y))
437            raise ValueError, msg
438        else:
439            n = len(data.x)- 1
440            #compute the first delta q
441            dx0 = (data.x[1] - data.x[0])/2
442            #compute the last delta q
443            dxn = (data.x[n] - data.x[n-1])/2
444            sum = 0
445            sum += data.x[0] * data.x[0] * data.y[0] * dx0
446            sum += data.x[n] * data.x[n] * data.y[n] * dxn
447           
448            if len(data.x) == 2:
449                return sum
450            else:
451                #iterate between for element different from the first and the last
452                for i in xrange(1, n-1):
453                    dxi = (data.x[i+1] - data.x[i-1])/2
454                    sum += data.x[i] * data.x[i] * data.y[i] * dxi
455                return sum
456           
457    def _get_qstar_uncertainty(self, data):
458        """
459            Compute invariant uncertainty with with pinhole data.
460            This uncertainty is given as follow:
461               dq_star = math.sqrt[(x0**2*(dy0)*dx0)**2 +
462                    (x1**2 *(dy1)*dx1)**2 + ..+ (xn**2 *(dyn)*dxn)**2 ]
463            where n >= len(data.x)-1
464            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
465            dx0 = (x1 - x0)/2
466            dxn = (xn - xn-1)/2
467            dyn: error on dy
468           
469            @param data:
470            note: if data doesn't contain dy assume dy= math.sqrt(data.y)
471        """         
472        if len(data.x) <= 1 or len(data.y) <= 1 or \
473            len(data.x) != len(data.y) or \
474            (data.dy is not None and (len(data.dy) != len(data.y))):
475            msg = "Length of data.x and data.y must be equal"
476            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x),
477                                                         len(data.y))
478            raise ValueError, msg
479        else:
480            #Create error for data without dy error
481            if data.dy is None:
482                dy = math.sqrt(y) 
483            else:
484                dy = data.dy
485               
486            n = len(data.x) - 1
487            #compute the first delta
488            dx0 = (data.x[1] - data.x[0])/2
489            #compute the last delta
490            dxn= (data.x[n] - data.x[n-1])/2
491            sum = 0
492            sum += (data.x[0] * data.x[0] * dy[0] * dx0)**2
493            sum += (data.x[n] * data.x[n] * dy[n] * dxn)**2
494            if len(data.x) == 2:
495                return math.sqrt(sum)
496            else:
497                #iterate between for element different from the first and the last
498                for i in xrange(1, n-1):
499                    dxi = (data.x[i+1] - data.x[i-1])/2
500                    sum += (data.x[i] * data.x[i] * dy[i] * dxi)**2
501                return math.sqrt(sum)
502       
503    def _get_extrapolated_data(self, model, npts=INTEGRATION_NSTEPS,
504                              q_start=Q_MINIMUM, q_end=Q_MAXIMUM):
505        """
506            @return extrapolate data create from data
507        """
508        #create new Data1D to compute the invariant
509        q = numpy.linspace(start=q_start,
510                           stop=q_end,
511                           num=npts,
512                           endpoint=True)
513        iq = model.evaluate_model(q)
514        diq = model.evaluate_model_errors(q)
515         
516        result_data = LoaderData1D(x=q, y=iq, dy=diq)
517        return result_data
518   
519    def get_extrapolation_power(self, range='high'):
520        """
521            return the fitted power for power law function for a given extrapolation range
522        """
523        if range == 'low':
524            return self._low_extrapolation_power_fitted
525        return self._high_extrapolation_power_fitted
526   
527    def get_qstar_low(self):
528        """
529            Compute the invariant for extrapolated data at low q range.
530           
531            Implementation:
532                data = self._get_extra_data_low()
533                return self._get_qstar()
534               
535            @return q_star: the invariant for data extrapolated at low q.
536        """
537        # Data boundaries for fitting
538        qmin = self._data.x[0]
539        qmax = self._data.x[self._low_extrapolation_npts - 1]
540       
541        # Extrapolate the low-Q data
542        p, dp = self._fit(model=self._low_extrapolation_function,
543                              qmin=qmin,
544                          qmax=qmax,
545                          power=self._low_extrapolation_power)
546        self._low_extrapolation_power_fitted = p[0]
547       
548        # Distribution starting point
549        self._low_q_limit = Q_MINIMUM
550        if Q_MINIMUM >= qmin:
551            self._low_q_limit = qmin/10
552       
553        data = self._get_extrapolated_data(model=self._low_extrapolation_function,
554                                               npts=INTEGRATION_NSTEPS,
555                                               q_start=self._low_q_limit, q_end=qmin)
556       
557        # Systematic error
558        # If we have smearing, the shape of the I(q) distribution at low Q will
559        # may not be a Guinier or simple power law. The following is a conservative
560        # estimation for the systematic error.
561        err = qmin*qmin*math.fabs((qmin-self._low_q_limit)*(data.y[0] - data.y[INTEGRATION_NSTEPS-1]))
562        return self._get_qstar(data), self._get_qstar_uncertainty(data)+err
563       
564    def get_qstar_high(self):
565        """
566            Compute the invariant for extrapolated data at high q range.
567           
568            Implementation:
569                data = self._get_extra_data_high()
570                return self._get_qstar()
571               
572            @return q_star: the invariant for data extrapolated at high q.
573        """
574        # Data boundaries for fitting
575        x_len = len(self._data.x) - 1
576        qmin = self._data.x[x_len - (self._high_extrapolation_npts - 1)]
577        qmax = self._data.x[x_len]
578       
579        # fit the data with a model to get the appropriate parameters
580        p, dp = self._fit(model=self._high_extrapolation_function,
581                          qmin=qmin,
582                          qmax=qmax,
583                          power=self._high_extrapolation_power)
584        self._high_extrapolation_power_fitted = p[0]
585       
586        #create new Data1D to compute the invariant
587        data = self._get_extrapolated_data(model=self._high_extrapolation_function,
588                                           npts=INTEGRATION_NSTEPS,
589                                           q_start=qmax, q_end=Q_MAXIMUM)       
590       
591        return self._get_qstar(data), self._get_qstar_uncertainty(data)
592   
593    def get_extra_data_low(self, npts_in=None, q_start=None, npts=20):
594        """
595            Returns the extrapolated data used for the loew-Q invariant calculation.
596            By default, the distribution will cover the data points used for the
597            extrapolation. The number of overlap points is a parameter (npts_in).
598            By default, the maximum q-value of the distribution will be 
599            the minimum q-value used when extrapolating for the purpose of the
600            invariant calculation.
601           
602            @param npts_in: number of data points for which the extrapolated data overlap
603            @param q_start: is the minimum value to uses for extrapolated data
604            @param npts: the number of points in the extrapolated distribution
605           
606        """
607        # Get extrapolation range
608        if q_start is None:
609            q_start = self._low_q_limit
610           
611        if npts_in is None:
612            npts_in = self._low_extrapolation_npts
613        q_end = self._data.x[max(0, npts_in-1)]
614       
615        if q_start >= q_end:
616            return numpy.zeros(0), numpy.zeros(0)
617
618        return self._get_extrapolated_data(model=self._low_extrapolation_function,
619                                           npts=npts,
620                                           q_start=q_start, q_end=q_end)
621         
622    def get_extra_data_high(self, npts_in=None, q_end=Q_MAXIMUM, npts=20):
623        """
624            Returns the extrapolated data used for the high-Q invariant calculation.
625            By default, the distribution will cover the data points used for the
626            extrapolation. The number of overlap points is a parameter (npts_in).
627            By default, the maximum q-value of the distribution will be Q_MAXIMUM,
628            the maximum q-value used when extrapolating for the purpose of the
629            invariant calculation.
630           
631            @param npts_in: number of data points for which the extrapolated data overlap
632            @param q_end: is the maximum value to uses for extrapolated data
633            @param npts: the number of points in the extrapolated distribution
634        """
635        # Get extrapolation range
636        if npts_in is None:
637            npts_in = self._high_extrapolation_npts
638        _npts = len(self._data.x)
639        q_start = self._data.x[min(_npts, _npts-npts_in)]
640       
641        if q_start >= q_end:
642            return numpy.zeros(0), numpy.zeros(0)
643       
644        return self._get_extrapolated_data(model=self._high_extrapolation_function,
645                                           npts=npts,
646                                           q_start=q_start, q_end=q_end)
647     
648    def set_extrapolation(self, range, npts=4, function=None, power=None):
649        """
650            Set the extrapolation parameters for the high or low Q-range.
651            Note that this does not turn extrapolation on or off.
652            @param range: a keyword set the type of extrapolation . type string
653            @param npts: the numbers of q points of data to consider for extrapolation
654            @param function: a keyword to select the function to use for extrapolation.
655                of type string.
656            @param power: an power to apply power_low function
657               
658        """
659        range = range.lower()
660        if range not in ['high', 'low']:
661            raise ValueError, "Extrapolation range should be 'high' or 'low'"
662        function = function.lower()
663        if function not in ['power_law', 'guinier']:
664            raise ValueError, "Extrapolation function should be 'guinier' or 'power_law'"
665       
666        if range == 'high':
667            if function != 'power_law':
668                raise ValueError, "Extrapolation only allows a power law at high Q"
669            self._high_extrapolation_npts  = npts
670            self._high_extrapolation_power = power
671            self._high_extrapolation_power_fitted = power
672        else:
673            if function == 'power_law':
674                self._low_extrapolation_function = PowerLaw()
675            else:
676                self._low_extrapolation_function = Guinier()
677            self._low_extrapolation_npts  = npts
678            self._low_extrapolation_power = power
679            self._low_extrapolation_power_fitted = power
680       
681    def get_qstar(self, extrapolation=None):
682        """
683            Compute the invariant of the local copy of data.
684           
685            @param extrapolation: string to apply optional extrapolation   
686            @return q_star: invariant of the data within data's q range
687           
688            @warning: When using setting data to Data1D , the user is responsible of
689            checking that the scale and the background are properly apply to the data
690        """
691        self._qstar = self._get_qstar(self._data)
692        self._qstar_err = self._get_qstar_uncertainty(self._data)
693       
694        if extrapolation is None:
695            return self._qstar
696       
697        # Compute invariant plus invariant of extrapolated data
698        extrapolation = extrapolation.lower()   
699        if extrapolation == "low":
700            qs_low, dqs_low = self.get_qstar_low()
701            qs_hi, dqs_hi   = 0, 0
702           
703        elif extrapolation == "high":
704            qs_low, dqs_low = 0, 0
705            qs_hi, dqs_hi   = self.get_qstar_high()
706           
707        elif extrapolation == "both":
708            qs_low, dqs_low = self.get_qstar_low()
709            qs_hi, dqs_hi   = self.get_qstar_high()
710           
711        self._qstar     += qs_low + qs_hi
712        self._qstar_err = math.sqrt(self._qstar_err*self._qstar_err \
713                                    + dqs_low*dqs_low + dqs_hi*dqs_hi)
714       
715        return self._qstar
716       
717    def get_surface(self, contrast, porod_const, extrapolation=None):
718        """
719            Compute the specific surface from the data.
720           
721            Implementation:
722              V=  self.get_volume_fraction(contrast, extrapolation)
723       
724              Compute the surface given by:
725                surface = (2*pi *V(1- V)*porod_const)/ q_star
726               
727            @param contrast: contrast value to compute the volume
728            @param porod_const: Porod constant to compute the surface
729            @param extrapolation: string to apply optional extrapolation
730            @return: specific surface
731        """
732        # Compute the volume
733        volume = self.get_volume_fraction(contrast, extrapolation)
734        return 2 * math.pi * volume *(1 - volume) * float(porod_const)/self._qstar
735       
736    def get_volume_fraction(self, contrast, extrapolation=None):
737        """
738            Compute volume fraction is deduced as follow:
739           
740            q_star = 2*(pi*contrast)**2* volume( 1- volume)
741            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
742            we get 2 values of volume:
743                 with   1 - 4 * k >= 0
744                 volume1 = (1- sqrt(1- 4*k))/2
745                 volume2 = (1+ sqrt(1- 4*k))/2
746           
747            q_star: the invariant value included extrapolation is applied
748                         unit  1/A^(3)*1/cm
749                    q_star = self.get_qstar()
750                   
751            the result returned will be 0 <= volume <= 1
752           
753            @param contrast: contrast value provides by the user of type float.
754                     contrast unit is 1/A^(2)= 10^(16)cm^(2)
755            @param extrapolation: string to apply optional extrapolation
756            @return: volume fraction
757            @note: volume fraction must have no unit
758        """
759        if contrast <= 0:
760            raise ValueError, "The contrast parameter must be greater than zero" 
761       
762        # Make sure Q star is up to date
763        self.get_qstar(extrapolation)
764       
765        if self._qstar <= 0:
766            raise RuntimeError, "Invalid invariant: Invariant Q* must be greater than zero"
767       
768        # Compute intermediate constant
769        k =  1.e-8 * self._qstar/(2 * (math.pi * math.fabs(float(contrast)))**2)
770        # Check discriminant value
771        discrim = 1 - 4 * k
772       
773        # Compute volume fraction
774        if discrim < 0:
775            raise RuntimeError, "Could not compute the volume fraction: negative discriminant"
776        elif discrim == 0:
777            return 1/2
778        else:
779            volume1 = 0.5 * (1 - math.sqrt(discrim))
780            volume2 = 0.5 * (1 + math.sqrt(discrim))
781           
782            if 0 <= volume1 and volume1 <= 1:
783                return volume1
784            elif 0 <= volume2 and volume2 <= 1: 
785                return volume2
786            raise RuntimeError, "Could not compute the volume fraction: inconsistent results"
787   
788    def get_qstar_with_error(self, extrapolation=None):
789        """
790            Compute the invariant uncertainty.
791            This uncertainty computation depends on whether or not the data is
792            smeared.
793           
794            @param extrapolation: string to apply optional extrapolation
795            @return: invariant, the invariant uncertainty
796        """   
797        self.get_qstar(extrapolation)
798        return self._qstar, self._qstar_err
799   
800    def get_volume_fraction_with_error(self, contrast, extrapolation=None):
801        """
802            Compute uncertainty on volume value as well as the volume fraction
803            This uncertainty is given by the following equation:
804            dV = 0.5 * (4*k* dq_star) /(2* math.sqrt(1-k* q_star))
805                                 
806            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
807           
808            q_star: the invariant value including extrapolated value if existing
809            dq_star: the invariant uncertainty
810            dV: the volume uncertainty
811           
812            The uncertainty will be set to -1 if it can't be computed.
813           
814            @param contrast: contrast value
815            @param extrapolation: string to apply optional extrapolation
816            @return: V, dV = volume fraction, error on volume fraction
817        """
818        volume = self.get_volume_fraction(contrast, extrapolation)
819       
820        # Compute error
821        k =  1.e-8 * self._qstar /(2 * (math.pi* math.fabs(float(contrast)))**2)
822        # Check value inside the sqrt function
823        value = 1 - k * self._qstar
824        if (value) <= 0:
825            uncertainty = -1
826        # Compute uncertainty
827        uncertainty = math.fabs((0.5 * 4 * k * self._qstar_err)/(2 * math.sqrt(1 - k * self._qstar)))
828       
829        return volume, uncertainty
830   
831    def get_surface_with_error(self, contrast, porod_const, extrapolation=None):
832        """
833            Compute uncertainty of the surface value as well as the surface value.
834            The uncertainty is given as follow:
835           
836            dS = porod_const *2*pi[( dV -2*V*dV)/q_star
837                 + dq_star(v-v**2)
838                 
839            q_star: the invariant value
840            dq_star: the invariant uncertainty
841            V: the volume fraction value
842            dV: the volume uncertainty
843           
844            @param contrast: contrast value
845            @param porod_const: porod constant value
846            @param extrapolation: string to apply optional extrapolation
847            @return S, dS: the surface, with its uncertainty
848        """
849        # We get the volume fraction, with error
850        #   get_volume_fraction_with_error calls get_volume_fraction
851        #   get_volume_fraction calls get_qstar
852        #   which computes Qstar and dQstar
853        v, dv = self.get_volume_fraction_with_error(contrast, extrapolation)
854
855        s = self.get_surface(contrast=contrast, porod_const=porod_const, 
856                             extrapolation=extrapolation)
857        ds = porod_const * 2 * math.pi * (( dv - 2 * v * dv)/ self._qstar\
858                 + self._qstar_err * ( v - v**2))
859
860        return s, ds
Note: See TracBrowser for help on using the repository browser.