source: sasview/Invariant/invariant.py @ 9455d77

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 9455d77 was 1702180, checked in by Gervaise Alina <gervyh@…>, 15 years ago

add comment for 2 methods

  • Property mode set to 100644
File size: 43.0 KB
Line 
1"""
2    This module implements invariant and its related computations.
3    @author: Gervaise B. Alina/UTK
4"""
5#TODO: Need to decide if/how to use smearing for extrapolation
6import math 
7import numpy
8
9from DataLoader.data_info import Data1D as LoaderData1D
10from DataLoader.qsmearing import smear_selection
11
12# The minimum q-value to be used when extrapolating
13Q_MINIMUM  = 1e-5
14
15# The maximum q-value to be used when extrapolating
16Q_MAXIMUM  = 10
17
18# Number of steps in the extrapolation
19INTEGRATION_NSTEPS = 1000
20
21class Transform(object):
22    """
23        Define interface that need to compute a function or an inverse
24        function given some x, y
25    """
26   
27    def linearize_data(self, data):
28        """
29            Linearize data so that a linear fit can be performed.
30            Filter out the data that can't be transformed.
31            @param data : LoadData1D instance
32        """
33        # Check that the vector lengths are equal
34        assert(len(data.x)==len(data.y))
35        if data.dy is not None:
36            assert(len(data.x)==len(data.dy))
37            dy = data.dy
38        else:
39            #dy = numpy.array([math.sqrt(math.fabs(j)) for j in data.y])
40            dy = numpy.array([1 for j in data.y])
41        # Transform smear info
42        dxl_out = None
43        dxw_out = None
44        dx_out = None
45        first_x = data.x#[0]
46        #if first_x == 0:
47        #    first_x = data.x[1]
48           
49           
50        data_points = zip(data.x, data.y, dy)
51       
52        # Transform the data
53        output_points = [(self.linearize_q_value(p[0]),
54                          math.log(p[1]),
55                          p[2]/p[1]) for p in data_points if p[0]>0 and p[1]>0]
56       
57        x_out, y_out, dy_out = zip(*output_points)
58       
59        #Transform smear info   
60        if data.dxl is not None  and len(data.dxl)>0:
61            dxl_out = self.linearize_dq_value(x=x_out, dx=data.dxl[:len(x_out)])
62        if data.dxw is not None and len(data.dxw)>0:
63            dxw_out = self.linearize_dq_value(x=x_out, dx=data.dxw[:len(x_out)])
64        if data.dx is not None  and len(data.dx)>0:
65            dx_out = self.linearize_dq_value(x=x_out, dx=data.dx[:len(x_out)])
66        x_out = numpy.asarray(x_out)
67        y_out = numpy.asarray(y_out)
68        dy_out = numpy.asarray(dy_out)
69        linear_data = LoaderData1D(x=x_out, y=y_out, dx=dx_out, dy=dy_out)
70        linear_data.dxl = dxl_out
71        linear_data.dxw = dxw_out
72       
73        return linear_data
74    def linearize_dq_value(self, x, dx):
75        """
76            Transform the input dq-value for linearization
77        """
78        return NotImplemented
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, a, b):
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   
98class Guinier(Transform):
99    """
100        class of type Transform that performs operations related to guinier
101        function
102    """
103    def __init__(self, scale=1, radius=60):
104        Transform.__init__(self)
105        self.scale = scale
106        self.radius = radius
107       
108    def linearize_dq_value(self, x, dx):
109        """
110            Transform the input dq-value for linearization
111        """
112        return numpy.array([2*x[0]*dx[0] for i in xrange(len(x))]) 
113   
114    def linearize_q_value(self, value):
115        """
116            Transform the input q-value for linearization
117            @param value: q-value
118            @return: q*q
119        """
120        return value * value
121   
122    def extract_model_parameters(self, a, b):
123        """
124           assign new value to the scale and the radius
125        """
126        b = math.sqrt(-3 * b)
127        a = math.exp(a)
128        self.scale = a
129        self.radius = b
130        return a, b
131       
132    def evaluate_model(self, x):
133        """
134            return F(x)= scale* e-((radius*x)**2/3)
135        """
136        return self._guinier(x)
137             
138    def _guinier(self, x):
139        """
140            Retrive the guinier function after apply an inverse guinier function
141            to x
142            Compute a F(x) = scale* e-((radius*x)**2/3).
143            @param x: a vector of q values
144            @param scale: the scale value
145            @param radius: the guinier radius value
146            @return F(x)
147        """   
148        # transform the radius of coming from the inverse guinier function to a
149        # a radius of a guinier function
150        if self.radius <= 0:
151            raise ValueError("Rg expected positive value, but got %s"%self.radius) 
152        value = numpy.array([math.exp(-((self.radius * i)**2/3)) for i in x ]) 
153        return self.scale * value
154
155class PowerLaw(Transform):
156    """
157        class of type transform that perform operation related to power_law
158        function
159    """
160    def __init__(self, scale=1, power=4):
161        Transform.__init__(self)
162        self.scale = scale
163        self.power = power
164   
165    def linearize_dq_value(self, x, dx):
166        """
167            Transform the input dq-value for linearization
168        """
169        return  numpy.array([dx[0]/x[0] for i in xrange(len(x))]) 
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, a, b):
180        """
181            Assign new value to the scale and the power
182        """
183        b = -1 * b
184        a = math.exp(a)
185        self.power = b
186        self.scale = a
187        return a, b
188       
189    def evaluate_model(self, x):
190        """
191            given a scale and a radius transform x, y using a power_law
192            function
193        """
194        return self._power_law(x)
195       
196    def _power_law(self, x):
197        """
198            F(x) = scale* (x)^(-power)
199                when power= 4. the model is porod
200                else power_law
201            The model has three parameters:
202            @param x: a vector of q values
203            @param power: power of the function
204            @param scale : scale factor value
205            @param F(x)
206        """
207        if self.power <= 0:
208            raise ValueError("Power_law function expected positive power, but got %s"%self.power)
209        if self.scale <= 0:
210            raise ValueError("scale expected positive value, but got %s"%self.scale) 
211       
212        value = numpy.array([ math.pow(i, -self.power) for i in x ]) 
213        return self.scale * value
214
215class Extrapolator:
216    """
217        Extrapolate I(q) distribution using a given model
218    """
219    def __init__(self, data):
220        """
221            Determine a and b given a linear equation y = ax + b
222            @param Data: data containing x and y  such as  y = ax + b
223        """
224        self.data  = data
225       
226        # Set qmin as the lowest non-zero value
227        self.qmin = Q_MINIMUM
228        for q_value in self.data.x:
229            if q_value > 0: 
230                self.qmin = q_value
231                break
232        self.qmax = max(self.data.x)
233       
234        #get the smear object of data
235        self.smearer = smear_selection(self.data)
236        # Set the q-range information to allow smearing
237        self.set_fit_range()
238     
239    def set_fit_range(self, qmin=None, qmax=None):
240        """ to set the fit range"""
241        if qmin is not None:
242            self.qmin = qmin
243        if qmax is not None:
244            self.qmax = qmax
245           
246        # Determine the range needed in unsmeared-Q to cover
247        # the smeared Q range
248        self._qmin_unsmeared = self.qmin
249        self._qmax_unsmeared = self.qmax   
250       
251        if self.smearer is not None:
252            self._first_unsmeared_bin, self._last_unsmeared_bin = self.smearer.get_bin_range(self.qmin, self.qmax)
253            self._qmin_unsmeared = self.data.x[self._first_unsmeared_bin]
254            self._qmax_unsmeared = self.data.x[self._last_unsmeared_bin]
255           
256        # Identify the bin range for the unsmeared and smeared spaces
257        self.idx = (self.data.x >= self.qmin) & (self.data.x <= self.qmax)
258        self.idx_unsmeared = (self.data.x >= self._qmin_unsmeared) \
259                                & (self.data.x <= self._qmax_unsmeared)
260 
261    def fit(self, power=None):
262        """
263           Fit data for y = ax + b  return a and b
264           @param power = a fixed, otherwise None
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)and self.data.dy[0] != 0:
270            sigma = self.data.dy
271        else:
272            sigma = numpy.ones(len(self.data.x))
273           
274        # Compute theory data f(x)
275        fx[self.idx_unsmeared] = self.data.y[self.idx_unsmeared]
276        ## Smear theory data
277        if self.smearer is not None:
278            fx = self.smearer(fx, self._first_unsmeared_bin,self._last_unsmeared_bin)
279       
280        fx[self.idx_unsmeared] = fx[self.idx_unsmeared]/sigma[self.idx_unsmeared]
281       
282        ##power is given only for function = power_law   
283        if power != None:
284            sigma2 = sigma * sigma
285            a = -(power)
286            b = (numpy.sum(fx[self.idx]/sigma[self.idx]) - a*numpy.sum(self.data.x[self.idx]/sigma2[self.idx]))/numpy.sum(numpy.ones(len(sigma2[self.idx]))/sigma2[self.idx])
287            return a, b
288        else:
289            A = numpy.vstack([ self.data.x[self.idx]/sigma[self.idx],
290                               numpy.ones(len(self.data.x[self.idx]))/sigma[self.idx]]).T
291           
292            a, b = numpy.linalg.lstsq(A, fx[self.idx])[0]
293           
294            return a, b
295       
296
297class InvariantCalculator(object):
298    """
299        Compute invariant if data is given.
300        Can provide volume fraction and surface area if the user provides
301        Porod constant  and contrast values.
302        @precondition:  the user must send a data of type DataLoader.Data1D
303                        the user provide background and scale values.
304                       
305        @note: Some computations depends on each others.
306    """
307    def __init__(self, data, background=0, scale=1 ):
308        """
309            Initialize variables
310            @param data: data must be of type DataLoader.Data1D
311            @param background: Background value. The data will be corrected before processing
312            @param scale: Scaling factor for I(q). The data will be corrected before processing
313        """
314        # Background and scale should be private data member if the only way to
315        # change them are by instantiating a new object.
316        self._background = background
317        self._scale = scale
318       
319        # The data should be private
320        self._data = self._get_data(data)
321     
322        # Since there are multiple variants of Q*, you should force the
323        # user to use the get method and keep Q* a private data member
324        self._qstar = None
325       
326        # You should keep the error on Q* so you can reuse it without
327        # recomputing the whole thing.
328        self._qstar_err = 0
329       
330        # Extrapolation parameters
331        self._low_extrapolation_npts = 4
332        self._low_extrapolation_function = Guinier()
333        self._low_extrapolation_power = None
334   
335        self._high_extrapolation_npts = 4
336        self._high_extrapolation_function = PowerLaw()
337        self._high_extrapolation_power = None
338       
339    def _get_data(self, data):
340        """
341            @note this function must be call before computing any type
342             of invariant
343            @return data= self._scale *data - self._background
344        """
345        if not issubclass(data.__class__, LoaderData1D):
346            #Process only data that inherited from DataLoader.Data_info.Data1D
347            raise ValueError,"Data must be of type DataLoader.Data1D"
348        new_data = (self._scale * data) - self._background   
349        new_data.dy[new_data.dy==0] = 1
350        new_data.dxl = data.dxl
351        new_data.dxw = data.dxw
352        return  new_data
353     
354    def _fit(self, model, qmin=Q_MINIMUM, qmax=Q_MAXIMUM, power=None):
355        """
356            fit data with function using
357            data= self._get_data()
358            fx= Functor(data , function)
359            y = data.y
360            slope, constant = linalg.lstsq(y,fx)
361            @param qmin: data first q value to consider during the fit
362            @param qmax: data last q value to consider during the fit
363            @param power : power value to consider for power-law
364            @param function: the function to use during the fit
365            @return a: the scale of the function
366            @return b: the other parameter of the function for guinier will be radius
367                    for power_law will be the power value
368        """
369        # Linearize the data in preparation for fitting
370        fit_data = model.linearize_data(self._data)
371        qmin = model.linearize_q_value(qmin)
372        qmax = model.linearize_q_value(qmax)
373        # Get coefficient cmoning out of the  fit
374        extrapolator = Extrapolator(data=fit_data)
375        extrapolator.set_fit_range(qmin=qmin, qmax=qmax)
376        b, a = extrapolator.fit(power=power) 
377       
378        return model.extract_model_parameters(a=a, b=b)
379   
380    def _get_qstar(self, data):
381        """
382            Compute invariant for data
383            @param data: data to use to compute invariant.
384             
385        """
386        if data is None:
387            return 0
388        if data.is_slit_smeared():
389            return self._get_qstar_smear(data)
390        else:
391            return self._get_qstar_unsmear(data)
392       
393    def _get_qstar_unsmear(self, data):
394        """
395            Compute invariant for pinhole data.
396            This invariant is given by:
397       
398                q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1
399                            + ..+ xn**2 *yn *dxn
400                           
401            where n >= len(data.x)-1
402            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
403            dx0 = (x1 - x0)/2
404            dxn = (xn - xn-1)/2
405            @param data: the data to use to compute invariant.
406            @return q_star: invariant value for pinhole data. q_star > 0
407        """
408        if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x)!= len(data.y):
409            msg =  "Length x and y must be equal"
410            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x), len(data.y))
411            raise ValueError, msg
412        else:
413            n = len(data.x)- 1
414            #compute the first delta q
415            dx0 = (data.x[1] - data.x[0])/2
416            #compute the last delta q
417            dxn = (data.x[n] - data.x[n-1])/2
418            sum = 0
419            sum += data.x[0] * data.x[0] * data.y[0] * dx0
420            sum += data.x[n] * data.x[n] * data.y[n] * dxn
421           
422            if len(data.x) == 2:
423                return sum
424            else:
425                #iterate between for element different from the first and the last
426                for i in xrange(1, n-1):
427                    dxi = (data.x[i+1] - data.x[i-1])/2
428                    sum += data.x[i] * data.x[i] * data.y[i] * dxi
429                return sum
430           
431    def _get_qstar_smear(self, data):
432        """
433            Compute invariant for slit-smeared data.
434            This invariant is given by:
435                q_star = x0*dxl *y0*dx0 + x1*dxl *y1 *dx1
436                            + ..+ xn*dxl *yn *dxn
437            where n >= len(data.x)-1
438            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
439            dx0 = (x1 - x0)/2
440            dxn = (xn - xn-1)/2
441            dxl: slit smear value
442           
443            @return q_star: invariant value for slit smeared data.
444        """
445        if not data.is_slit_smeared():
446            msg = "_get_qstar_smear need slit smear data "
447            msg += "Hint :dxl= %s , dxw= %s"%(str(data.dxl), str(data.dxw))
448            raise ValueError, msg
449
450        if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x) != len(data.y)\
451              or len(data.x)!= len(data.dxl):
452            msg = "x, dxl, and y must be have the same length and greater than 1"
453            msg +=" len x=%s, y=%s, dxl=%s"%(len(data.x),len(data.y),len(data.dxl))
454            raise ValueError, msg
455        else:
456            n = len(data.x)-1
457            #compute the first delta
458            dx0 = (data.x[1] - data.x[0])/2
459            #compute the last delta
460            dxn = (data.x[n] - data.x[n-1])/2
461            sum = 0
462            sum += data.x[0] * data.dxl[0] * data.y[0] * dx0
463            sum += data.x[n] * data.dxl[n] * data.y[n] * dxn
464           
465            if len(data.x)==2:
466                return sum
467            else:
468                #iterate between for element different from the first and the last
469                for i in xrange(1, n-1):
470                    dxi = (data.x[i+1] - data.x[i-1])/2
471                    sum += data.x[i] * data.dxl[i] * data.y[i] * dxi
472                return sum
473       
474    def _get_qstar_uncertainty(self, data=None):
475        """
476            Compute uncertainty of invariant value
477            Implementation:
478                if data is None:
479                    data = self.data
480           
481                if data.slit smear:
482                        return self._get_qstar_smear_uncertainty(data)
483                    else:
484                        return self._get_qstar_unsmear_uncertainty(data)
485         
486            @param: data use to compute the invariant which allow uncertainty
487            computation.
488            @return: uncertainty
489        """
490        if data is None:
491            data = self._data
492        if data.is_slit_smeared():
493            return self._get_qstar_smear_uncertainty(data)
494        else:
495            return self._get_qstar_unsmear_uncertainty(data)
496       
497    def _get_qstar_unsmear_uncertainty(self, data):
498        """
499            Compute invariant uncertainty with with pinhole data.
500            This uncertainty is given as follow:
501               dq_star = math.sqrt[(x0**2*(dy0)*dx0)**2 +
502                    (x1**2 *(dy1)*dx1)**2 + ..+ (xn**2 *(dyn)*dxn)**2 ]
503            where n >= len(data.x)-1
504            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
505            dx0 = (x1 - x0)/2
506            dxn = (xn - xn-1)/2
507            dyn: error on dy
508           
509            @param data:
510            note: if data doesn't contain dy assume dy= math.sqrt(data.y)
511        """
512        if len(data.x) <= 1 or len(data.y) <= 1 or \
513            len(self.data.x) != len(self.data.y):
514            msg = "Length of data.x and data.y must be equal"
515            msg += " and greater than 1; got x=%s, y=%s"%(len(data.x),
516                                                         len(data.y))
517            raise ValueError, msg
518        else:
519            #Create error for data without dy error
520            if (data.dy is None) or (not data.dy):
521                dy = math.sqrt(y) 
522            else:
523                dy = data.dy
524               
525            n = len(data.x) - 1
526            #compute the first delta
527            dx0 = (data.x[1] - data.x[0])/2
528            #compute the last delta
529            dxn= (data.x[n] - data.x[n-1])/2
530            sum = 0
531            sum += (data.x[0] * data.x[0] * dy[0] * dx0)**2
532            sum += (data.x[n] * data.x[n] * dy[n] * dxn)**2
533            if len(data.x) == 2:
534                return math.sqrt(sum)
535            else:
536                #iterate between for element different from the first and the last
537                for i in xrange(1, n-1):
538                    dxi = (data.x[i+1] - data.x[i-1])/2
539                    sum += (data.x[i] * data.x[i] * dy[i] * dxi)**2
540                return math.sqrt(sum)
541       
542    def _get_qstar_smear_uncertainty(self, data):
543        """
544            Compute invariant uncertainty with slit smeared data.
545            This uncertainty is given as follow:
546                dq_star = x0*dxl *dy0 *dx0 + x1*dxl *dy1 *dx1
547                            + ..+ xn*dxl *dyn *dxn
548            where n >= len(data.x)-1
549            dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
550            dx0 = (x1 - x0)/2
551            dxn = (xn - xn-1)/2
552            dxl: slit smearing value
553            dyn : error on dy
554            @param data: data of type Data1D where the scale is applied
555                        and the background is subtracted.
556         
557            note: if data doesn't contain dy assume dy= math.sqrt(data.y)
558        """
559        if not data.is_slit_smeared():
560            msg = "_get_qstar_smear_uncertainty need slit smear data "
561            msg += "Hint :dxl= %s , dxw= %s"%(str(data.dxl), str(data.dxw))
562            raise ValueError, msg
563
564        if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x) != len(data.y)\
565                or len(data.x) != len(data.dxl):
566            msg = "x, dxl, and y must be have the same length and greater than 1"
567            raise ValueError, msg
568        else:
569            #Create error for data without dy error
570            if (data.dy is None) or (not data.dy):
571                dy = math.sqrt(y)
572            else:
573                dy = data.dy
574               
575            n = len(data.x) - 1
576            #compute the first delta
577            dx0 = (data.x[1] - data.x[0])/2
578            #compute the last delta
579            dxn = (data.x[n] - data.x[n-1])/2
580            sum = 0
581            sum += (data.x[0] * data.dxl[0] * dy[0] * dx0)**2
582            sum += (data.x[n] * data.dxl[n] * dy[n] * dxn)**2
583           
584            if len(data.x) == 2:
585                return math.sqrt(sum)
586            else:
587                #iterate between for element different from the first and the last
588                for i in xrange(1, n-1):
589                    dxi = (data.x[i+1] - data.x[i-1])/2
590                    sum += (data.x[i] * data.dxl[i] * dy[i] * dxi)**2
591                return math.sqrt(sum)
592       
593    def _get_extrapolated_data(self, model, npts=INTEGRATION_NSTEPS,
594                              q_start=Q_MINIMUM, q_end=Q_MAXIMUM):
595        """
596            @return extrapolate data create from data
597        """
598        #create new Data1D to compute the invariant
599        q = numpy.linspace(start=q_start,
600                               stop=q_end,
601                               num=npts,
602                               endpoint=True)
603        iq = model.evaluate_model(q)
604         
605        # Determine whether we are extrapolating to high or low q-values
606        # If the data is slit smeared, get the index of the slit dimension array entry
607        # that we will use to smear the extrapolated data.
608        dxl = None
609        dxw = None
610
611        if self._data.is_slit_smeared():
612            if q_start<min(self._data.x):
613                smear_index = 0
614            elif q_end>max(self._data.x):
615                smear_index = len(self._data.x)-1
616            else:
617                raise RuntimeError, "Extrapolation can only be evaluated for points outside the data Q range"
618           
619            if self._data.dxl is not None :
620                dxl = numpy.ones(INTEGRATION_NSTEPS)
621                dxl = dxl * self._data.dxl[smear_index]
622               
623            if self._data.dxw is not None :
624                dxw = numpy.ones(INTEGRATION_NSTEPS)
625                dxw = dxw * self._data.dxw[smear_index]
626             
627        result_data = LoaderData1D(x=q, y=iq)
628        result_data.dxl = dxl
629        result_data.dxw = dxw
630        return result_data
631   
632    def _get_extra_data_low(self):
633        """
634            This method creates a new data set from the invariant calculator.
635           
636            It will use the extrapolation parameters kept as private data members.
637           
638            self._low_extrapolation_npts is the number of data points to use in to fit.
639            self._low_extrapolation_function will be used as the fit function.
640           
641           
642           
643            It takes npts first points of data, fits them with a given model
644            then uses the new parameters resulting from the fit to create a new data set.
645           
646            The new data first point is Q_MINIMUM.
647           
648            The last point of the new data is the first point of the original data.
649            the number of q points of this data is INTEGRATION_NSTEPS.
650           
651            @return: a new data of type Data1D
652        """
653       
654        # Data boundaries for fitting
655        qmin = self._data.x[0]
656        qmax = self._data.x[self._low_extrapolation_npts - 1]
657       
658        # Extrapolate the low-Q data
659        a, b = self._fit(model=self._low_extrapolation_function,
660                         qmin=qmin,
661                         qmax=qmax,
662                         power=self._low_extrapolation_power)
663        q_start = Q_MINIMUM
664        #q_start point
665        if Q_MINIMUM >= qmin:
666            q_start = qmin/10
667       
668        data_min = self._get_extrapolated_data(model=self._low_extrapolation_function,
669                                               npts=INTEGRATION_NSTEPS,
670                                               q_start=q_start, q_end=qmin)
671        return data_min
672         
673    def _get_extra_data_high(self):
674        """
675            This method creates a new data from the invariant calculator.
676           
677            It takes npts last points of data, fits them with a given model
678            (for this function only power_law will be use), then uses
679            the new parameters resulting from the fit to create a new data set.
680            The first point is the last point of data.
681            The last point of the new data is Q_MAXIMUM.
682            The number of q points of this data is INTEGRATION_NSTEPS.
683
684           
685            @return: a new data of type Data1D
686        """
687        # Data boundaries for fitting
688        x_len = len(self._data.x) - 1
689        qmin = self._data.x[x_len - (self._high_extrapolation_npts - 1)]
690        qmax = self._data.x[x_len]
691        q_end = Q_MAXIMUM
692       
693        # fit the data with a model to get the appropriate parameters
694        a, b = self._fit(model=self._high_extrapolation_function,
695                         qmin=qmin,
696                         qmax=qmax,
697                         power=self._high_extrapolation_power)
698       
699        #create new Data1D to compute the invariant
700        data_max = self._get_extrapolated_data(model=self._high_extrapolation_function,
701                                               npts=INTEGRATION_NSTEPS,
702                                               q_start=qmax, q_end=q_end)
703        return data_max
704         
705    def get_qstar_low(self):
706        """
707            Compute the invariant for extrapolated data at low q range.
708           
709            Implementation:
710                data = self._get_extra_data_low()
711                return self._get_qstar()
712               
713            @return q_star: the invariant for data extrapolated at low q.
714        """
715        data = self._get_extra_data_low()
716       
717        return self._get_qstar(data=data)
718       
719    def get_qstar_high(self):
720        """
721            Compute the invariant for extrapolated data at high q range.
722           
723            Implementation:
724                data = self._get_extra_data_high()
725                return self._get_qstar()
726               
727            @return q_star: the invariant for data extrapolated at high q.
728        """
729        data = self._get_extra_data_high()
730        return self._get_qstar(data=data)
731   
732    def get_extra_data_low(self, npts_in=None, q_start=Q_MINIMUM, nsteps=INTEGRATION_NSTEPS):
733        """
734           This method generates 2 data sets , the first is a data created during
735           low extrapolation . its y is generated from x in [ Q_MINIMUM - the minimum of
736           data.x] and the outputs of the extrapolator .
737            (data is the data used to compute invariant)
738           the second is also data produced during the fit but the x range considered
739           is within the reel range of data x.
740           x uses is in [minimum of data.x up to npts_in points]
741           @param npts_in: the number of first points of data to consider  for computing
742           y's coming out of the fit.
743           @param q_start: is the minimum value to uses for extrapolated data
744           @param npts: the number of point used to create extrapolated data
745           
746        """
747        #Create a data from result of the fit for a range outside of the data
748        # at low q range
749        data_out_range = self._get_extra_data_low()
750       
751        if  q_start != Q_MINIMUM or nsteps != INTEGRATION_NSTEPS:
752            qmin = min(self._data.x)
753            if q_start < Q_MINIMUM:
754                q_start = Q_MINIMUM
755            elif q_start >= qmin:
756                q_start = qmin/10
757               
758            #compute the new data with the proper result of the fit for different
759            #boundary and step, outside of data
760            data_out_range = self._get_extrapolated_data(model=self._low_extrapolation_function,
761                                               npts=nsteps,
762                                               q_start=q_start, q_end=qmin)
763        #Create data from the result of the fit for a range inside data q range for
764        #low q
765        if npts_in is None :
766            npts_in = self._low_extrapolation_npts
767
768        x = self._data.x[:npts_in]
769        y = self._low_extrapolation_function.evaluate_model(x=x)
770        dy = None
771        dx = None
772        dxl = None
773        dxw = None
774        if self._data.dx is not None:
775            dx = self._data.dx[:npts_in]
776        if self._data.dy is not None:
777            dy = self._data.dy[:npts_in]
778        if self._data.dxl is not None and len(self._data.dxl)>0:
779            dxl = self._data.dxl[:npts_in]
780        if self._data.dxw is not None and len(self._data.dxw)>0:
781            dxw = self._data.dxw[:npts_in]
782        #Crate new data
783        data_in_range = LoaderData1D(x=x, y=y, dx=dx, dy=dy)
784        data_in_range.clone_without_data(clone=self._data)
785        data_in_range.dxl = dxl
786        data_in_range.dxw = dxw
787       
788        return data_out_range, data_in_range
789         
790    def get_extra_data_high(self, npts_in=None, q_end=Q_MAXIMUM, nsteps=INTEGRATION_NSTEPS ):
791        """
792           This method generates 2 data sets , the first is a data created during
793           low extrapolation . its y is generated from x in [ the maximum of
794           data.x to  Q_MAXIMUM] and the outputs of the extrapolator .
795            (data is the data used to compute invariant)
796           the second is also data produced during the fit but the x range considered
797           is within the reel range of data x.
798           x uses is from maximum of data.x up to npts_in points before data.x maximum.
799           @param npts_in: the number of first points of data to consider  for computing
800           y's coming out of the fit.
801           @param q_end: is the maximum value to uses for extrapolated data
802           @param npts: the number of point used to create extrapolated data
803           
804        """
805        #Create a data from result of the fit for a range outside of the data
806        # at low q range
807        data_out_range = self._get_extra_data_high()
808        qmax = max(self._data.x)
809        if  q_end != Q_MAXIMUM or nsteps != INTEGRATION_NSTEPS:
810            if q_end > Q_MAXIMUM:
811               q_end = Q_MAXIMUM
812            elif q_end <= qmax:
813                q_end = qmax * 10
814               
815            #compute the new data with the proper result of the fit for different
816            #boundary and step, outside of data
817            data_out_range = self._get_extrapolated_data(model=self._high_extrapolation_function,
818                                               npts=nsteps,
819                                               q_start=qmax, q_end=q_end)
820        #Create data from the result of the fit for a range inside data q range for
821        #high q
822        if npts_in is None :
823            npts_in = self._high_extrapolation_npts
824           
825        x_len = len(self._data.x)
826        x = self._data.x[(x_len-npts_in):]
827        y = self._high_extrapolation_function.evaluate_model(x=x)
828        dy = None
829        dx = None
830        dxl = None
831        dxw = None
832       
833        if self._data.dx is not None:
834            dx = self._data.dx[(x_len-npts_in):]
835        if self._data.dy is not None:
836            dy = self._data.dy[(x_len-npts_in):]
837        if self._data.dxl is not None and len(self._data.dxl)>0:
838            dxl = self._data.dxl[(x_len-npts_in):]
839        if self._data.dxw is not None and len(self._data.dxw)>0:
840            dxw = self._data.dxw[(x_len-npts_in):]
841        #Crate new data
842        data_in_range = LoaderData1D(x=x, y=y, dx=dx, dy=dy)
843        data_in_range.clone_without_data(clone=self._data)
844        data_in_range.dxl = dxl
845        data_in_range.dxw = dxw
846       
847        return data_out_range, data_in_range
848         
849     
850    def set_extrapolation(self, range, npts=4, function=None, power=None):
851        """
852            Set the extrapolation parameters for the high or low Q-range.
853            Note that this does not turn extrapolation on or off.
854            @param range: a keyword set the type of extrapolation . type string
855            @param npts: the numbers of q points of data to consider for extrapolation
856            @param function: a keyword to select the function to use for extrapolation.
857                of type string.
858            @param power: an power to apply power_low function
859               
860        """
861        range = range.lower()
862        if range not in ['high', 'low']:
863            raise ValueError, "Extrapolation range should be 'high' or 'low'"
864        function = function.lower()
865        if function not in ['power_law', 'guinier']:
866            raise ValueError, "Extrapolation function should be 'guinier' or 'power_law'"
867       
868        if range == 'high':
869            if function != 'power_law':
870                raise ValueError, "Extrapolation only allows a power law at high Q"
871            self._high_extrapolation_npts  = npts
872            self._high_extrapolation_power = power
873        else:
874            if function == 'power_law':
875                self._low_extrapolation_function = PowerLaw()
876            else:
877                self._low_extrapolation_function = Guinier()
878            self._low_extrapolation_npts  = npts
879            self._low_extrapolation_power = power
880       
881    def get_qstar(self, extrapolation=None):
882        """
883            Compute the invariant of the local copy of data.
884            Implementation:
885                if slit smear:
886                    qstar_0 = self._get_qstar_smear()
887                else:
888                    qstar_0 = self._get_qstar_unsmear()
889                if extrapolation is None:
890                    return qstar_0   
891                if extrapolation==low:
892                    return qstar_0 + self.get_qstar_low()
893                elif extrapolation==high:
894                    return qstar_0 + self.get_qstar_high()
895                elif extrapolation==both:
896                    return qstar_0 + self.get_qstar_low() + self.get_qstar_high()
897           
898            @param extrapolation: string to apply optional extrapolation   
899            @return q_star: invariant of the data within data's q range
900           
901            @warning: When using setting data to Data1D , the user is responsible of
902            checking that the scale and the background are properly apply to the data
903           
904            @warning: if error occur self.get_qstar_low() or self.get_qstar_low()
905            their returned value will be ignored
906        """
907        qstar_0 = self._get_qstar(data=self._data)
908       
909        if extrapolation is None:
910            self._qstar = qstar_0
911            return self._qstar
912        # Compute invariant plus invaraint of extrapolated data
913        extrapolation = extrapolation.lower()   
914        if extrapolation == "low":
915            self._qstar = qstar_0 + self.get_qstar_low()
916            return self._qstar
917        elif extrapolation == "high":
918            self._qstar = qstar_0 + self.get_qstar_high()
919            return self._qstar
920        elif extrapolation == "both":
921            self._qstar = qstar_0 + self.get_qstar_low() + self.get_qstar_high()
922            return self._qstar
923       
924    def get_surface(self,contrast, porod_const):
925        """
926            Compute the surface of the data.
927           
928            Implementation:
929                V= self.get_volume_fraction(contrast)
930       
931              Compute the surface given by:
932                surface = (2*pi *V(1- V)*porod_const)/ q_star
933               
934            @param contrast: contrast value to compute the volume
935            @param porod_const: Porod constant to compute the surface
936            @return: specific surface
937        """
938        if contrast is None or porod_const is None:
939            return None
940        #Check whether we have Q star
941        if self._qstar is None:
942            self._qstar = self.get_star()
943        if self._qstar == 0:
944            raise RuntimeError("Cannot compute surface, invariant value is zero")
945        # Compute the volume
946        volume = self.get_volume_fraction(contrast)
947        return 2 * math.pi * volume *(1 - volume) * float(porod_const)/self._qstar
948       
949    def get_volume_fraction(self, contrast):
950        """
951            Compute volume fraction is deduced as follow:
952           
953            q_star = 2*(pi*contrast)**2* volume( 1- volume)
954            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
955            we get 2 values of volume:
956                 with   1 - 4 * k >= 0
957                 volume1 = (1- sqrt(1- 4*k))/2
958                 volume2 = (1+ sqrt(1- 4*k))/2
959           
960            q_star: the invariant value included extrapolation is applied
961                         unit  1/A^(3)*1/cm
962                    q_star = self.get_qstar()
963                   
964            the result returned will be 0<= volume <= 1
965           
966            @param contrast: contrast value provides by the user of type float.
967                     contrast unit is 1/A^(2)= 10^(16)cm^(2)
968            @return: volume fraction
969            @note: volume fraction must have no unit
970        """
971        if contrast is None:
972            return None
973        if contrast < 0:
974            raise ValueError, "contrast must be greater than zero" 
975       
976        if self._qstar is None:
977            self._qstar = self.get_qstar()
978       
979        if self._qstar < 0:
980            raise RuntimeError, "invariant must be greater than zero"
981       
982        # Compute intermediate constant
983        k =  1.e-8 * self._qstar/(2 * (math.pi * math.fabs(float(contrast)))**2)
984        #Check discriminant value
985        discrim = 1 - 4 * k
986       
987        # Compute volume fraction
988        if discrim < 0:
989            raise RuntimeError, "could not compute the volume fraction: negative discriminant"
990        elif discrim == 0:
991            return 1/2
992        else:
993            volume1 = 0.5 * (1 - math.sqrt(discrim))
994            volume2 = 0.5 * (1 + math.sqrt(discrim))
995           
996            if 0 <= volume1 and volume1 <= 1:
997                return volume1
998            elif 0 <= volume2 and volume2 <= 1: 
999                return volume2
1000            raise RuntimeError, "could not compute the volume fraction: inconsistent results"
1001   
1002    def get_qstar_with_error(self, extrapolation=None):
1003        """
1004            Compute the invariant uncertainty.
1005            This uncertainty computation depends on whether or not the data is
1006            smeared.
1007            @return: invariant,  the invariant uncertainty
1008                return self._get_qstar(), self._get_qstar_smear_uncertainty()   
1009        """
1010        if self._qstar is None:
1011            self._qstar = self.get_qstar(extrapolation=extrapolation)
1012        if self._qstar_err is None:
1013            self._qstar_err = self._get_qstar_smear_uncertainty()
1014           
1015        return self._qstar, self._qstar_err
1016   
1017    def get_volume_fraction_with_error(self, contrast):
1018        """
1019            Compute uncertainty on volume value as well as the volume fraction
1020            This uncertainty is given by the following equation:
1021            dV = 0.5 * (4*k* dq_star) /(2* math.sqrt(1-k* q_star))
1022                                 
1023            for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2)
1024           
1025            q_star: the invariant value including extrapolated value if existing
1026            dq_star: the invariant uncertainty
1027            dV: the volume uncertainty
1028            @param contrast: contrast value
1029            @return: V, dV = self.get_volume_fraction_with_error(contrast), dV
1030        """
1031        if contrast is None:
1032            return None, None
1033        self._qstar, self._qstar_err = self.get_qstar_with_error()
1034       
1035        volume = self.get_volume_fraction(contrast)
1036        if self._qstar < 0:
1037            raise ValueError, "invariant must be greater than zero"
1038       
1039        k =  1.e-8 * self._qstar /(2 * (math.pi* math.fabs(float(contrast)))**2)
1040        #check value inside the sqrt function
1041        value = 1 - k * self._qstar
1042        if (value) <= 0:
1043            raise ValueError, "Cannot compute incertainty on volume"
1044        # Compute uncertainty
1045        uncertainty = (0.5 * 4 * k * self._qstar_err)/(2 * math.sqrt(1 - k * self._qstar))
1046       
1047        return volume, math.fabs(uncertainty)
1048   
1049    def get_surface_with_error(self, contrast, porod_const):
1050        """
1051            Compute uncertainty of the surface value as well as thesurface value
1052            this uncertainty is given as follow:
1053           
1054            dS = porod_const *2*pi[( dV -2*V*dV)/q_star
1055                 + dq_star(v-v**2)
1056                 
1057            q_star: the invariant value including extrapolated value if existing
1058            dq_star: the invariant uncertainty
1059            V: the volume fraction value
1060            dV: the volume uncertainty
1061           
1062            @param contrast: contrast value
1063            @param porod_const: porod constant value
1064            @return S, dS: the surface, with its uncertainty
1065        """
1066        if contrast is None or porod_const is None:
1067            return None, None
1068        v, dv = self.get_volume_fraction_with_error(contrast)
1069        self._qstar, self._qstar_err = self.get_qstar_with_error()
1070        if self._qstar <= 0:
1071            raise ValueError, "invariant must be greater than zero"
1072        ds = porod_const * 2 * math.pi * (( dv - 2 * v * dv)/ self._qstar\
1073                 + self._qstar_err * ( v - v**2))
1074        s = self.get_surface(contrast=contrast, porod_const=porod_const)
1075        return s, ds
Note: See TracBrowser for help on using the repository browser.