source: sasview/Invariant/invariant.py @ 4ea3600

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 4ea3600 was 56f8401, checked in by Gervaise Alina <gervyh@…>, 15 years ago

add set method in invariant

  • Property mode set to 100644
File size: 35.0 KB
Line 
1"""
2    This module implements invariant and its related computations.
3    @author: Gervaise B. Alina/UTK
4   
5   
6    TODO:
7        - intro / documentation
8        - add unit tests for sufrace/volume computation with and without extrapolation.
9        - replace the get_extra_data_* methods
10"""
11import math 
12import numpy
13
14from DataLoader.data_info import Data1D as LoaderData1D
15
16# The minimum q-value to be used when extrapolating
17Q_MINIMUM  = 1e-5
18
19# The maximum q-value to be used when extrapolating
20Q_MAXIMUM  = 10
21
22# Number of steps in the extrapolation
23INTEGRATION_NSTEPS = 1000
24
25class Transform(object):
26    """
27        Define interface that need to compute a function or an inverse
28        function given some x, y
29    """
30   
31    def linearize_data(self, data):
32        """
33            Linearize data so that a linear fit can be performed.
34            Filter out the data that can't be transformed.
35            @param data : LoadData1D instance
36        """
37        # Check that the vector lengths are equal
38        assert(len(data.x)==len(data.y))
39        if data.dy is not None:
40            assert(len(data.x)==len(data.dy))
41            dy = data.dy
42        else:
43            dy = numpy.ones(len(data.y))
44           
45        # Transform the data
46        data_points = zip(data.x, data.y, dy)
47
48        output_points = [(self.linearize_q_value(p[0]),
49                          math.log(p[1]),
50                          p[2]/p[1]) for p in data_points if p[0]>0 and p[1]>0 and p[2]>0]
51       
52        x_out, y_out, dy_out = zip(*output_points)
53       
54        # Create Data1D object
55        x_out = numpy.asarray(x_out)
56        y_out = numpy.asarray(y_out)
57        dy_out = numpy.asarray(dy_out)
58        linear_data = LoaderData1D(x=x_out, y=y_out, dy=dy_out)
59       
60        return linear_data
61   
62    def get_allowed_bins(self, data):
63        """
64            Goes through the data points and returns a list of boolean values
65            to indicate whether each points is allowed by the model or not.
66           
67            @param data: Data1D object
68        """
69        return [p[0]>0 and p[1]>0 and p[2]>0 for p in zip(data.x, data.y, data.dy)]
70       
71    def linearize_q_value(self, value):
72        """
73            Transform the input q-value for linearization
74        """
75        return NotImplemented
76
77    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
78        """
79            set private member
80        """
81        return NotImplemented
82     
83    def evaluate_model(self, x):
84        """
85            Returns an array f(x) values where f is the Transform function.
86        """
87        return NotImplemented
88   
89    def evaluate_model_errors(self, x):
90        """
91            Returns an array of I(q) errors
92        """
93        return NotImplemented
94   
95class Guinier(Transform):
96    """
97        class of type Transform that performs operations related to guinier
98        function
99    """
100    def __init__(self, scale=1, radius=60):
101        Transform.__init__(self)
102        self.scale = scale
103        self.radius = radius
104        ## Uncertainty of scale parameter
105        self.dscale  = 0
106        ## Unvertainty of radius parameter
107        self.dradius = 0
108       
109    def linearize_q_value(self, value):
110        """
111            Transform the input q-value for linearization
112            @param value: q-value
113            @return: q*q
114        """
115        return value * value
116   
117    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
118        """
119           assign new value to the scale and the radius
120        """
121        self.scale = math.exp(constant)
122        self.radius = math.sqrt(-3 * slope)
123       
124        # Errors
125        self.dscale = math.exp(constant)*dconstant
126        self.dradius = -3.0/2.0/math.sqrt(-3 * slope)*dslope
127       
128    def evaluate_model(self, x):
129        """
130            return F(x)= scale* e-((radius*x)**2/3)
131        """
132        return self._guinier(x)
133             
134    def evaluate_model_errors(self, x):
135        """
136            Returns the error on I(q) for the given array of q-values
137            @param x: array of q-values
138        """
139        p1 = numpy.array([self.dscale * math.exp(-((self.radius * q)**2/3)) for q in x])
140        p2 = numpy.array([self.scale * math.exp(-((self.radius * q)**2/3)) * (-(q**2/3)) * 2 * self.radius * self.dradius for q in x])
141        diq2 = p1*p1 + p2*p2       
142        return numpy.array( [math.sqrt(err) for err in diq2] )
143             
144    def _guinier(self, x):
145        """
146            Retrive the guinier function after apply an inverse guinier function
147            to x
148            Compute a F(x) = scale* e-((radius*x)**2/3).
149            @param x: a vector of q values
150            @param scale: the scale value
151            @param radius: the guinier radius value
152            @return F(x)
153        """   
154        # transform the radius of coming from the inverse guinier function to a
155        # a radius of a guinier function
156        if self.radius <= 0:
157            raise ValueError("Rg expected positive value, but got %s"%self.radius) 
158        value = numpy.array([math.exp(-((self.radius * i)**2/3)) for i in x ]) 
159        return self.scale * value
160
161class PowerLaw(Transform):
162    """
163        class of type transform that perform operation related to power_law
164        function
165    """
166    def __init__(self, scale=1, power=4):
167        Transform.__init__(self)
168        self.scale = scale
169        self.power = power
170   
171    def linearize_q_value(self, value):
172        """
173            Transform the input q-value for linearization
174            @param value: q-value
175            @return: log(q)
176        """
177        return math.log(value)
178   
179    def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0):
180        """
181            Assign new value to the scale and the power
182        """
183        self.power = -slope
184        self.scale = math.exp(constant)
185       
186        # Errors
187        self.dscale = math.exp(constant)*dconstant
188        self.dradius = -dslope
189       
190    def evaluate_model(self, x):
191        """
192            given a scale and a radius transform x, y using a power_law
193            function
194        """
195        return self._power_law(x)
196   
197    def evaluate_model_errors(self, x):
198        """
199            Returns the error on I(q) for the given array of q-values
200            @param x: array of q-values
201        """
202        p1 = numpy.array([self.dscale * math.pow(q, -self.power) for q in x])
203        p2 = numpy.array([self.scale * self.power * math.pow(q, -self.power-1) * self.dradius for q in x])
204        diq2 = p1*p1 + p2*p2       
205        return numpy.array( [math.sqrt(err) for err in diq2] )
206       
207    def _power_law(self, x):
208        """
209            F(x) = scale* (x)^(-power)
210                when power= 4. the model is porod
211                else power_law
212            The model has three parameters:
213            @param x: a vector of q values
214            @param power: power of the function
215            @param scale : scale factor value
216            @param F(x)
217        """
218        if self.power <= 0:
219            raise ValueError("Power_law function expected positive power, but got %s"%self.power)
220        if self.scale <= 0:
221            raise ValueError("scale expected positive value, but got %s"%self.scale) 
222       
223        value = numpy.array([ math.pow(i, -self.power) for i in x ]) 
224        return self.scale * value
225
226class Extrapolator:
227    """
228        Extrapolate I(q) distribution using a given model
229    """
230    def __init__(self, data, model=None):
231        """
232            Determine a and b given a linear equation y = ax + b
233           
234            If a model is given, it will be used to linearize the data before
235            the extrapolation is performed. If None, a simple linear fit will be done.
236           
237            @param data: data containing x and y  such as  y = ax + b
238            @param model: optional Transform object
239        """
240        self.data  = data
241        self.model = model
242       
243        # Set qmin as the lowest non-zero value
244        self.qmin = Q_MINIMUM
245        for q_value in self.data.x:
246            if q_value > 0: 
247                self.qmin = q_value
248                break
249        self.qmax = max(self.data.x)
250             
251    def fit(self, power=None, qmin=None, qmax=None):
252        """
253           Fit data for y = ax + b  return a and b
254           @param power: a fixed, otherwise None
255           @param qmin: Minimum Q-value
256           @param qmax: Maximum Q-value
257        """
258        if qmin is None:
259            qmin = self.qmin
260        if qmax is None:
261            qmax = self.qmax
262           
263        # Identify the bin range for the fit
264        idx = (self.data.x >= qmin) & (self.data.x <= qmax)
265       
266        fx = numpy.zeros(len(self.data.x))
267
268        # Uncertainty
269        if type(self.data.dy)==numpy.ndarray and len(self.data.dy)==len(self.data.x):
270            sigma = self.data.dy
271        else:
272            sigma = numpy.ones(len(self.data.x))
273           
274        # Compute theory data f(x)
275        fx[idx] = self.data.y[idx]
276       
277        # Linearize the data
278        if self.model is not None:
279            linearized_data = self.model.linearize_data(LoaderData1D(self.data.x[idx],
280                                                                fx[idx],
281                                                                dy = sigma[idx]))
282        else:
283            linearized_data = LoaderData1D(self.data.x[idx],
284                                           fx[idx],
285                                           dy = sigma[idx])
286       
287        ##power is given only for function = power_law   
288        if power != None:
289            sigma2 = linearized_data.dy * linearized_data.dy
290            a = -(power)
291            b = (numpy.sum(linearized_data.y/sigma2) \
292                 - a*numpy.sum(linearized_data.x/sigma2))/numpy.sum(1.0/sigma2)
293           
294           
295            deltas = linearized_data.x*a+numpy.ones(len(linearized_data.x))*b-linearized_data.y
296            residuals = numpy.sum(deltas*deltas/sigma2)
297           
298            err = math.fabs(residuals) / numpy.sum(1.0/sigma2)
299            return [a, b], [0, math.sqrt(err)]
300        else:
301            A = numpy.vstack([ linearized_data.x/linearized_data.dy,
302                               1.0/linearized_data.dy]).T       
303            (p, residuals, rank, s) = numpy.linalg.lstsq(A, linearized_data.y/linearized_data.dy)
304           
305            # Get the covariance matrix, defined as inv_cov = a_transposed * a
306            err = numpy.zeros(2)
307            try:
308                inv_cov = numpy.dot(A.transpose(), A)
309                cov = numpy.linalg.pinv(inv_cov)
310                err_matrix = math.fabs(residuals) * cov
311                err = [math.sqrt(err_matrix[0][0]), math.sqrt(err_matrix[1][1])]
312            except:
313                err = [-1.0, -1.0]
314               
315            return p, err
316       
317
318class InvariantCalculator(object):
319    """
320        Compute invariant if data is given.
321        Can provide volume fraction and surface area if the user provides
322        Porod constant  and contrast values.
323        @precondition:  the user must send a data of type DataLoader.Data1D
324                        the user provide background and scale values.
325                       
326        @note: Some computations depends on each others.
327    """
328    def __init__(self, data, background=0, scale=1 ):
329        """
330            Initialize variables
331            @param data: data must be of type DataLoader.Data1D
332            @param background: Background value. The data will be corrected before processing
333            @param scale: Scaling factor for I(q). The data will be corrected before processing
334        """
335        # Background and scale should be private data member if the only way to
336        # change them are by instantiating a new object.
337        self._background = background
338        self._scale = scale
339       
340        # The data should be private
341        self._data = self._get_data(data)
342     
343        # Since there are multiple variants of Q*, you should force the
344        # user to use the get method and keep Q* a private data member
345        self._qstar = None
346       
347        # You should keep the error on Q* so you can reuse it without
348        # recomputing the whole thing.
349        self._qstar_err = 0
350       
351        # Extrapolation parameters
352        self._low_extrapolation_npts = 4
353        self._low_extrapolation_function = Guinier()
354        self._low_extrapolation_power = None
355   
356        self._high_extrapolation_npts = 4
357        self._high_extrapolation_function = PowerLaw()
358        self._high_extrapolation_power = None
359   
360    def set_data(self, data):
361        """
362            the data from where the invariant is computed
363        """
364        self._data = data
365       
366    def set_background(self, background=0):
367        """
368            given a value to the background
369        """
370        self._background = background
371       
372    def set_scale(self, scale=1):
373        """
374            Given a value to the scale
375        """
376        self._scale = scale
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_qstar_low(self):
520        """
521            Compute the invariant for extrapolated data at low q range.
522           
523            Implementation:
524                data = self._get_extra_data_low()
525                return self._get_qstar()
526               
527            @return q_star: the invariant for data extrapolated at low q.
528        """
529        # Data boundaries for fitting
530        qmin = self._data.x[0]
531        qmax = self._data.x[self._low_extrapolation_npts - 1]
532       
533        # Extrapolate the low-Q data
534        self._fit(model=self._low_extrapolation_function,
535                         qmin=qmin,
536                         qmax=qmax,
537                         power=self._low_extrapolation_power)
538       
539        # Distribution starting point
540        q_start = Q_MINIMUM
541        if Q_MINIMUM >= qmin:
542            q_start = qmin/10
543       
544        data = self._get_extrapolated_data(model=self._low_extrapolation_function,
545                                               npts=INTEGRATION_NSTEPS,
546                                               q_start=q_start, q_end=qmin)
547       
548        # Systematic error
549        # If we have smearing, the shape of the I(q) distribution at low Q will
550        # may not be a Guinier or simple power law. The following is a conservative
551        # estimation for the systematic error.
552        err = qmin*qmin*math.fabs((qmin-q_start)*(data.y[0] - data.y[INTEGRATION_NSTEPS-1]))
553        return self._get_qstar(data), self._get_qstar_uncertainty(data)+err
554       
555    def get_qstar_high(self):
556        """
557            Compute the invariant for extrapolated data at high q range.
558           
559            Implementation:
560                data = self._get_extra_data_high()
561                return self._get_qstar()
562               
563            @return q_star: the invariant for data extrapolated at high q.
564        """
565        # Data boundaries for fitting
566        x_len = len(self._data.x) - 1
567        qmin = self._data.x[x_len - (self._high_extrapolation_npts - 1)]
568        qmax = self._data.x[x_len]
569        q_end = Q_MAXIMUM
570       
571        # fit the data with a model to get the appropriate parameters
572        self._fit(model=self._high_extrapolation_function,
573                         qmin=qmin,
574                         qmax=qmax,
575                         power=self._high_extrapolation_power)
576       
577        #create new Data1D to compute the invariant
578        data = self._get_extrapolated_data(model=self._high_extrapolation_function,
579                                           npts=INTEGRATION_NSTEPS,
580                                           q_start=qmax, q_end=q_end)       
581       
582        return self._get_qstar(data), self._get_qstar_uncertainty(data)
583   
584    def get_extra_data_low(self, npts_in=None, q_start=Q_MINIMUM, nsteps=INTEGRATION_NSTEPS):
585        """
586           This method generates 2 data sets , the first is a data created during
587           low extrapolation . its y is generated from x in [ Q_MINIMUM - the minimum of
588           data.x] and the outputs of the extrapolator .
589            (data is the data used to compute invariant)
590           the second is also data produced during the fit but the x range considered
591           is within the reel range of data x.
592           x uses is in [minimum of data.x up to npts_in points]
593           @param npts_in: the number of first points of data to consider  for computing
594           y's coming out of the fit.
595           @param q_start: is the minimum value to uses for extrapolated data
596           @param npts: the number of point used to create extrapolated data
597           
598        """
599        # Create a data from result of the fit for a range outside of the data
600        # at low q range
601        q_start = max(Q_MINIMUM, q_start)
602        qmin = min(self._data.x)
603       
604        if q_start < qmin:
605            data_out_range = self._get_extrapolated_data(model=self._low_extrapolation_function,
606                                                         npts=nsteps,
607                                                         q_start=q_start, q_end=qmin)
608        else:
609            data_out_range = LoaderData1D(x=numpy.zeros(0), y=numpy.zeros(0))
610           
611        # Create data from the result of the fit for a range inside data q range for
612        # low q
613        if npts_in is None :
614            npts_in = self._low_extrapolation_npts
615
616        x = self._data.x[:npts_in]
617        y = self._low_extrapolation_function.evaluate_model(x=x)
618        data_in_range = LoaderData1D(x=x, y=y)
619       
620        return data_out_range, data_in_range
621         
622    def get_extra_data_high(self, npts_in=None, q_end=Q_MAXIMUM, nsteps=INTEGRATION_NSTEPS ):
623        """
624           This method generates 2 data sets , the first is a data created during
625           low extrapolation . its y is generated from x in [ the maximum of
626           data.x to  Q_MAXIMUM] and the outputs of the extrapolator .
627            (data is the data used to compute invariant)
628           the second is also data produced during the fit but the x range considered
629           is within the reel range of data x.
630           x uses is from maximum of data.x up to npts_in points before data.x maximum.
631           @param npts_in: the number of first points of data to consider  for computing
632           y's coming out of the fit.
633           @param q_end: is the maximum value to uses for extrapolated data
634           @param npts: the number of point used to create extrapolated data
635           
636        """
637        #Create a data from result of the fit for a range outside of the data
638        # at low q range
639        qmax = max(self._data.x)
640        if  q_end != Q_MAXIMUM or nsteps != INTEGRATION_NSTEPS:
641            if q_end > Q_MAXIMUM:
642               q_end = Q_MAXIMUM
643            elif q_end <= qmax:
644                q_end = qmax * 10
645               
646            #compute the new data with the proper result of the fit for different
647            #boundary and step, outside of data
648            data_out_range = self._get_extrapolated_data(model=self._high_extrapolation_function,
649                                               npts=nsteps,
650                                               q_start=qmax, q_end=q_end)
651        else:
652            data_out_range = LoaderData1D(x=numpy.zeros(0), y=numpy.zeros(0))
653       
654        #Create data from the result of the fit for a range inside data q range for
655        #high q
656        if npts_in is None :
657            npts_in = self._high_extrapolation_npts
658           
659        x_len = len(self._data.x)
660        x = self._data.x[(x_len-npts_in):]
661        y = self._high_extrapolation_function.evaluate_model(x=x)
662        data_in_range = LoaderData1D(x=x, y=y)
663       
664        return data_out_range, data_in_range
665         
666     
667    def set_extrapolation(self, range, npts=4, function=None, power=None):
668        """
669            Set the extrapolation parameters for the high or low Q-range.
670            Note that this does not turn extrapolation on or off.
671            @param range: a keyword set the type of extrapolation . type string
672            @param npts: the numbers of q points of data to consider for extrapolation
673            @param function: a keyword to select the function to use for extrapolation.
674                of type string.
675            @param power: an power to apply power_low function
676               
677        """
678        range = range.lower()
679        if range not in ['high', 'low']:
680            raise ValueError, "Extrapolation range should be 'high' or 'low'"
681        function = function.lower()
682        if function not in ['power_law', 'guinier']:
683            raise ValueError, "Extrapolation function should be 'guinier' or 'power_law'"
684       
685        if range == 'high':
686            if function != 'power_law':
687                raise ValueError, "Extrapolation only allows a power law at high Q"
688            self._high_extrapolation_npts  = npts
689            self._high_extrapolation_power = power
690        else:
691            if function == 'power_law':
692                self._low_extrapolation_function = PowerLaw()
693            else:
694                self._low_extrapolation_function = Guinier()
695            self._low_extrapolation_npts  = npts
696            self._low_extrapolation_power = power
697       
698    def get_qstar(self, extrapolation=None):
699        """
700            Compute the invariant of the local copy of data.
701           
702            @param extrapolation: string to apply optional extrapolation   
703            @return q_star: invariant of the data within data's q range
704           
705            @warning: When using setting data to Data1D , the user is responsible of
706            checking that the scale and the background are properly apply to the data
707        """
708        self._qstar = self._get_qstar(self._data)
709        self._qstar_err = self._get_qstar_uncertainty(self._data)
710       
711        if extrapolation is None:
712            return self._qstar
713       
714        # Compute invariant plus invariant of extrapolated data
715        extrapolation = extrapolation.lower()   
716        if extrapolation == "low":
717            qs_low, dqs_low = self.get_qstar_low()
718            qs_hi, dqs_hi   = 0, 0
719           
720        elif extrapolation == "high":
721            qs_low, dqs_low = 0, 0
722            qs_hi, dqs_hi   = self.get_qstar_high()
723           
724        elif extrapolation == "both":
725            qs_low, dqs_low = self.get_qstar_low()
726            qs_hi, dqs_hi   = self.get_qstar_high()
727           
728        self._qstar     += qs_low + qs_hi
729        self._qstar_err = math.sqrt(self._qstar_err*self._qstar_err \
730                                    + dqs_low*dqs_low + dqs_hi*dqs_hi)
731       
732        return self._qstar
733       
734    def get_surface(self, contrast, porod_const, extrapolation=None):
735        """
736            Compute the surface of the data.
737           
738            Implementation:
739              V=  self.get_volume_fraction(contrast, extrapolation)
740       
741              Compute the surface given by:
742                surface = (2*pi *V(1- V)*porod_const)/ q_star
743               
744            @param contrast: contrast value to compute the volume
745            @param porod_const: Porod constant to compute the surface
746            @param extrapolation: string to apply optional extrapolation
747            @return: specific surface
748        """
749        # Compute the volume
750        volume = self.get_volume_fraction(contrast, extrapolation)
751        return 2 * math.pi * volume *(1 - volume) * float(porod_const)/self._qstar
752       
753    def get_volume_fraction(self, contrast, extrapolation=None):
754        """
755            Compute volume fraction is deduced as follow:
756           
757            q_star = 2*(pi*contrast)**2* volume( 1- volume)
758            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
759            we get 2 values of volume:
760                 with   1 - 4 * k >= 0
761                 volume1 = (1- sqrt(1- 4*k))/2
762                 volume2 = (1+ sqrt(1- 4*k))/2
763           
764            q_star: the invariant value included extrapolation is applied
765                         unit  1/A^(3)*1/cm
766                    q_star = self.get_qstar()
767                   
768            the result returned will be 0 <= volume <= 1
769           
770            @param contrast: contrast value provides by the user of type float.
771                     contrast unit is 1/A^(2)= 10^(16)cm^(2)
772            @param extrapolation: string to apply optional extrapolation
773            @return: volume fraction
774            @note: volume fraction must have no unit
775        """
776        if contrast <= 0:
777            raise ValueError, "The contrast parameter must be greater than zero" 
778       
779        # Make sure Q star is up to date
780        self.get_qstar(extrapolation)
781       
782        if self._qstar <= 0:
783            raise RuntimeError, "Invalid invariant: Invariant Q* must be greater than zero"
784       
785        # Compute intermediate constant
786        k =  1.e-8 * self._qstar/(2 * (math.pi * math.fabs(float(contrast)))**2)
787        # Check discriminant value
788        discrim = 1 - 4 * k
789       
790        # Compute volume fraction
791        if discrim < 0:
792            raise RuntimeError, "Could not compute the volume fraction: negative discriminant"
793        elif discrim == 0:
794            return 1/2
795        else:
796            volume1 = 0.5 * (1 - math.sqrt(discrim))
797            volume2 = 0.5 * (1 + math.sqrt(discrim))
798           
799            if 0 <= volume1 and volume1 <= 1:
800                return volume1
801            elif 0 <= volume2 and volume2 <= 1: 
802                return volume2
803            raise RuntimeError, "Could not compute the volume fraction: inconsistent results"
804   
805    def get_qstar_with_error(self, extrapolation=None):
806        """
807            Compute the invariant uncertainty.
808            This uncertainty computation depends on whether or not the data is
809            smeared.
810           
811            @param extrapolation: string to apply optional extrapolation
812            @return: invariant, the invariant uncertainty
813        """   
814        self.get_qstar(extrapolation)
815        return self._qstar, self._qstar_err
816   
817    def get_volume_fraction_with_error(self, contrast, extrapolation=None):
818        """
819            Compute uncertainty on volume value as well as the volume fraction
820            This uncertainty is given by the following equation:
821            dV = 0.5 * (4*k* dq_star) /(2* math.sqrt(1-k* q_star))
822                                 
823            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
824           
825            q_star: the invariant value including extrapolated value if existing
826            dq_star: the invariant uncertainty
827            dV: the volume uncertainty
828           
829            The uncertainty will be set to -1 if it can't be computed.
830           
831            @param contrast: contrast value
832            @param extrapolation: string to apply optional extrapolation
833            @return: V, dV = volume fraction, error on volume fraction
834        """
835        volume = self.get_volume_fraction(contrast, extrapolation)
836       
837        # Compute error
838        k =  1.e-8 * self._qstar /(2 * (math.pi* math.fabs(float(contrast)))**2)
839        # Check value inside the sqrt function
840        value = 1 - k * self._qstar
841        if (value) <= 0:
842            uncertainty = -1
843        # Compute uncertainty
844        uncertainty = math.fabs((0.5 * 4 * k * self._qstar_err)/(2 * math.sqrt(1 - k * self._qstar)))
845       
846        return volume, uncertainty
847   
848    def get_surface_with_error(self, contrast, porod_const, extrapolation=None):
849        """
850            Compute uncertainty of the surface value as well as the surface value.
851            The uncertainty is given as follow:
852           
853            dS = porod_const *2*pi[( dV -2*V*dV)/q_star
854                 + dq_star(v-v**2)
855                 
856            q_star: the invariant value
857            dq_star: the invariant uncertainty
858            V: the volume fraction value
859            dV: the volume uncertainty
860           
861            @param contrast: contrast value
862            @param porod_const: porod constant value
863            @param extrapolation: string to apply optional extrapolation
864            @return S, dS: the surface, with its uncertainty
865        """
866        # We get the volume fraction, with error
867        #   get_volume_fraction_with_error calls get_volume_fraction
868        #   get_volume_fraction calls get_qstar
869        #   which computes Qstar and dQstar
870        v, dv = self.get_volume_fraction_with_error(contrast, extrapolation)
871
872        s = self.get_surface(contrast=contrast, porod_const=porod_const)
873        ds = porod_const * 2 * math.pi * (( dv - 2 * v * dv)/ self._qstar\
874                 + self._qstar_err * ( v - v**2))
875
876        return s, ds
Note: See TracBrowser for help on using the repository browser.