Index: Invariant/invariant.py
===================================================================
--- Invariant/invariant.py	(revision 75047cf14ae32fa1ec2ce357545f816b87cf5b33)
+++ Invariant/invariant.py	(revision b6666d4bb0a3ae471e37d8d3358b0bbdf559ba61)
@@ -10,9 +10,18 @@
 from DataLoader.qsmearing import smear_selection
 
-INFINITY = 10
-MINIMUM  = 1e-5
-STEPS = 1000
-
-class Functor:
+
+# PLEASE NEVER USE SUCH A DIRTY TRICK. Change your logic instead.
+#INFINITY = 10
+
+# The minimum q-value to be used when extrapolating
+Q_MINIMUM  = 1e-5
+
+# The maximum q-value to be used when extrapolating
+Q_MAXIMUM  = 10
+
+# Number of steps in the extrapolation
+INTEGRATION_NSTEPS = 1000
+
+class FitFunctor:
     """
         compute f(x)
@@ -103,4 +112,26 @@
         return self._get_residuals(params)
     
+    
+def guinier(x, scale=1, radius=0.1):
+    """
+        Compute a F(x) = scale* e-((radius*x)**2/3).
+        @param x: a vector of q values or one q value
+        @param scale: the scale value
+        @param radius: the guinier radius value
+        @return F(x)
+    """
+    
+def power_law(x, scale=1, power=4):
+    """
+        F(x) = scale* (x)^(-power)
+            when power= 4. the model is porod 
+            else power_law
+        The model has three parameters: 
+        @param power: power of the function
+        @param scale : scale factor value
+        @param F(x)
+    """    
+
+    
 class InvariantCalculator(object):
     """
@@ -120,35 +151,64 @@
             @param pConst: Porod Constant of type float
         """
-        self.data = None
-        self.qstar = None
-        self.background = background
-        self.scale = scale
-        
-    def _get_guinier(self, x, scale=1, radius=0.1):
-        """
-            Compute a F(x) = scale* e-((radius*x)**2/3).
-            @param x: a vector of q values or one q value
-            @param scale: the scale value
-            @param radius: the guinier radius value
-            @return F(x)
-        """
-        
-    def _get_power_law(self, x, scale=1, power=4):
-        """
-            F(x) = scale* (x)^(-power)
-                when power= 4. the model is porod 
-                else power_law
-            The model has three parameters: 
-            @param power: power of the function
-            @param scale : scale factor value
-            @param F()
-        """
-        
-    def _get_data(self):
+        # Background and scale should be private data member if the only way to
+        # change them are by instantiating a new object.
+        self._background = background
+        self._scale = scale
+        
+        # The data should be private
+        self._data = self._get_data(data)
+        
+        # Since there are multiple variants of Q*, you should force the
+        # user to use the get method and keep Q* a private data member
+        self._qstar = None
+        
+        # You should keep the error on Q* so you can reuse it without
+        # recomputing the whole thing.
+        self._qstar_err = 0
+        
+        # Extrapolation parameters
+        self._low_extrapolation_npts = 4
+        self._low_extrapolation_function = guinier
+        self._low_extrapolation_power = 4
+        
+        self._high_extrapolation_npts = 4
+        self._high_extrapolation_function = power_law
+        self._high_extrapolation_power = 4
+        
+    def set_extrapolation(self, range, npts=4, function=None, power=4):
+        """
+            Set the extrapolation parameters for the high or low Q-range.
+            Note that this does not turn extrapolation on or off.
+        """
+        range = range.lower()
+        if range not in ['high', 'low']:
+            raise ValueError, "Extrapolation range should be 'high' or 'low'"
+        function = function.lower()
+        if function not in ['power_law', 'guinier']:
+            raise ValueError, "Extrapolation function should be 'guinier' or 'power_law'"
+        
+        if range=='high':
+            if function != 'power_law':
+                raise ValueError, "Extrapolation only allows a power law at high Q"
+            self._high_extrapolation_npts  = npts
+            self._high_extrapolation_power = power
+        else:
+            if function == 'power_law':
+                self._low_extrapolation_function = power_law
+            else:
+                self._low_extrapolation_function = guinier
+            self._low_extrapolation_npts  = npts
+            self._low_extrapolation_power = power
+        
+    def _get_data(self, data):
         """
             @note this function must be call before computing any type
              of invariant
-            @return data= self.scale *self.data - self.background
-        """
+            @return data= self._scale *data - self._background
+        """
+        if not issubclass(data.__class__, LoaderData1D):
+            #Process only data that inherited from DataLoader.Data_info.Data1D
+            raise ValueError,"Data must be of type DataLoader.Data1D"
+        
         
     def _fit(self, function, params=[]):
@@ -166,16 +226,25 @@
         """
         
-    def _get_qstar(self):
+    def get_qstar(self, extrapolation=None):
         """
             Compute the invariant of the local copy of data.
             Implementation:
-                data = self._get_data()
                 if slit smear:
-                    return self._get_qstar_smear(data)
+                    qstar_0 = self._get_qstar_smear()
                 else:
-                    return self._get_qstar_unsmear(data)
+                    qstar_0 = self._get_qstar_unsmear()
+                    
+                if extrapolation==low:
+                    return qstar_0 + self._get_qstar_low()
+                elif extrapolation==high:
+                    return qstar_0 + self._get_qstar_high()
+                elif extrapolation==both:
+                    return qstar_0 + self._get_qstar_low() + self._get_qstar_high()
+                    
             @return q_star: invariant of the data within data's q range
         """
-    def _get_qstar_unsmear(self, data):
+        
+        
+    def _get_qstar_unsmear(self):
         """
             Compute invariant for pinhole data.
@@ -184,13 +253,14 @@
                 q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1 
                             + ..+ xn**2 *yn *dxn 
-            where n= infinity
+                            
+            where n= SOME GOOD DEFAULT
             dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
             dx0 = (x1 - x0)/2
             dxn = xn - xn-1
-            @param data: data of type Data1D
+
             @return q_star: invariant value for pinhole data.
         """
         
-    def _get_qstar_smear(self, data):
+    def _get_qstar_smear(self):
         """
             Compute invariant for slit-smeared data.
@@ -198,14 +268,14 @@
                 q_star = x0*dxl *y0*dx0 + x1*dxl *y1 *dx1 
                             + ..+ xn*dxl *yn *dxn 
-            where n= infinity
+            where n= SOME GOOD DEFAULT
             dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
             dx0 = x0+ (x1 - x0)/2
             dxn = xn - xn-1
             dxl: slit smear value
-            @param data: data of type Data1D
+            
             @return q_star: invariant value for slit smeared data.
         """
         
-    def _get_qstar_unsmear_uncertainty(self, data):
+    def _get_qstar_unsmear_uncertainty(self):
         """
             Compute invariant uncertainty with with pinhole data.
@@ -213,5 +283,5 @@
                dq_star = math.sqrt[(x0**2*(dy0)*dx0)**2 +
                     (x1**2 *(dy1)*dx1)**2 + ..+ (xn**2 *(dyn)*dxn)**2 ]
-            where n = infinity
+            where n = SOME GOOD DEFAULT
             dxi = 1/2*(xi+1 - xi) + (xi - xi-1)
             dx0 = x0+ (x1 - x0)/2
@@ -222,5 +292,6 @@
             note: if data doesn't contain dy assume dy= math.sqrt(data.y)
         """
-    def _get_qstar_smear_uncertainty(self, data):
+        
+    def _get_qstar_smear_uncertainty(self):
         """
             Compute invariant uncertainty with slit smeared data.
@@ -239,24 +310,4 @@
             note: if data doesn't contain dy assume dy= math.sqrt(data.y)
         """
-    def _get_qstar_total(self):
-        """
-            Compute the total invariant whether or not it is extrapolated.
-            Implementation:
-                qstar = self._get_qstar() + self._get_qstar_min()
-                        + self._get_qstar_max()
-            @return q_star: invariant total value
-        """
-        
-    def set_background(self, background=0):
-        """
-            Set the value of the background
-            @param background : the value uses to set the local background
-        """
-        
-    def set_scale(self, scale=1):
-        """
-            Set the value of the scale
-            @param scale: the value to set the scale.
-        """
         
     def get_surface(self,contrast, porod_const):
@@ -297,69 +348,64 @@
         """
         
-    def get_qstar_min(self, npts=0, power_law=False):
+    def _get_qstar_low(self):
         """
             Compute the invariant for extrapolated data at low q range.
             
             Implementation:
-                data = self.get_extra_data_low( npts, power_law)
+                data = self.get_extra_data_low()
                 return self._get_qstar()
                 
-            @param npts: data the number of points of the local data to consider
-                when fitting and created extrapolated data.
-                
             @return q_star: the invariant for data extrapolated at low q.
         """
         
-    def get_qstar_max(self, npts=0):
+    def _get_qstar_high(self):
         """
             Compute the invariant for extrapolated data at high q range.
             
             Implementation:
-                data = self.get_extra_data_high( npts)
+                data = self.get_extra_data_high()
                 return self._get_qstar()
                 
-            @param npts: data the number of points of the local data to consider
-                when fitting and created extrapolated data.
             @return q_star: the invariant for data extrapolated at high q.
         """
         
-    def get_extra_data_low(self, data, npts, power_law=False):
-        """
-            This method creates a new_data from the invariant calculator  data
-            It takes npts first points of  data , 
-            fits them with a given model
-            then uses
-            the new parameters resulting from the fit to create a new data.
-            the new data first point is MINIMUM .
-            the last point of the new data is the first point of the local data.
-            the number of q points of this data is STEPS.
-            
-            @param power_law: a flag to allow the function used for fitting
-                to switch between a guinier or a power_law model.
-                if set to True: the power_law will be used for fitting
-                else: the guinier will be used.
-            @param data : the data to used to extrapolated
-                        assume data is scale and the background is removed
-            @param npts: the number of last points of data to fit.
-            
-            @return new_data: a new data of type Data1D
-        """
-    def get_extra_data_high(self, data, npts):
-        """
-            This method creates a new_data from the invariant calculator data
-            It takes npts last points of data , 
-            fits them with a given model
+    def _get_extra_data_low(self):
+        """
+            This method creates a new data set from the invariant calculator.
+            
+            It will use the extrapolation parameters kept as private data members.
+            
+            self._low_extrapolation_npts is the number of data points to use in to fit.
+            self._low_extrapolation_function will be used as the fit function.
+            
+            
+            
+            It takes npts first points of data, fits them with a given model
+            then uses the new parameters resulting from the fit to create a new data set.
+            
+            The new data first point is Q_MINIMUM.
+            
+            The last point of the new data is the first point of the original data.
+            the number of q points of this data is INTEGRATION_NSTEPS.
+            
+            @return: a new data of type Data1D
+        """
+        
+    def _get_extra_data_high(self):
+        """
+            This method creates a new data from the invariant calculator.
+            
+            It takes npts last points of data, fits them with a given model
             (for this function only power_law will be use), then uses
-            the new parameters resulting from the fit to create a new_data.
-            the new_data first point is the last point of  data.
-            the last point of the new data is INFINITY.
-            the number of q points of this data is STEPS.
-            @param data : the data to used to extrapolated
-                        assume data is scale and the background is removed
-            @param npts: the number of last points of  data to fit.
-            
-            @return new_data: a new data of type Data1D
-        """
-    def get_qstar_uncertainty(self):
+            the new parameters resulting from the fit to create a new data set.
+            The first point is the last point of data.
+            The last point of the new data is Q_MAXIMUM.
+            The number of q points of this data is INTEGRATION_NSTEPS.
+
+            
+            @return: a new data of type Data1D
+        """
+        
+    def get_qstar_with_error(self):
         """
             Compute the invariant uncertainty.
@@ -367,12 +413,11 @@
             smeared.
             @return dq_star:
-                data = self._get_data()
                 if slit smear:
-                    return self._get_qstar_smear_uncertainty(data)
+                    return self._get_qstar(), self._get_qstar_smear_uncertainty()
                 else:
-                    return self._get_qstar_unsmear_uncertainty(data) 
-        """
-        
-    def get_volume_fraction_uncertainty(self, contrast):
+                    return self._get_qstar(), self._get_qstar_unsmear_uncertainty()
+        """
+        
+    def get_volume_fraction_with_error(self, contrast):
         """
             Compute uncertainty on volume value.
@@ -385,7 +430,8 @@
             dV: the volume uncertainty
             @param contrast: contrast value 
-        """
-        
-    def get_surface_uncertainty(self, contrast, porod_const):
+            @return: v, dv
+        """
+        
+    def get_surface_with_error(self, contrast, porod_const):
         """
             Compute uncertainty of the surface value.
@@ -402,6 +448,6 @@
             @param contrast: contrast value
             @param porod_const: porod constant value 
-            @return dS: the surface uncertainty
-        """
-        
-    
+            @return S, dS: the surface, with its uncertainty
+        """
+        
+    
Index: Invariant/test/utest_use_cases.py
===================================================================
--- Invariant/test/utest_use_cases.py	(revision b6666d4bb0a3ae471e37d8d3358b0bbdf559ba61)
+++ Invariant/test/utest_use_cases.py	(revision b6666d4bb0a3ae471e37d8d3358b0bbdf559ba61)
@@ -0,0 +1,134 @@
+"""
+   Implementation of the use-case from a usage perspective. 
+   TODO: check the return values to perform actual unit testing.
+"""
+import unittest
+from DataLoader.loader import  Loader
+from sans.invariant import invariant
+
+
+class TestInvPolySphere(unittest.TestCase):
+    """
+        Test unsmeared data for invariant computation
+    """
+    def setUp(self):
+        self.data = Loader().load("PolySpheres.txt")
+        
+    def test_use_case_1(self):
+        """
+            Invariant without extrapolation
+        """
+        # Create invariant object. Background and scale left as defaults.
+        inv = invariant.InvariantCalculator(data=self.data)
+        
+        # We have to be able to tell the InvariantCalculator whether we want the
+        # extrapolation or not. By default, when the user doesn't specify, we
+        # should compute Q* without extrapolation. That's what should be done in __init__.
+        
+        # We call get_qstar() with no argument, which signifies that we do NOT
+        # want extrapolation.
+        qstar = inv.get_qstar()
+        
+        # The volume fraction and surface use Q*. That means that the following 
+        # methods should check that Q* has been computed. If not, it should 
+        # compute it by calling get_qstare(), leaving the parameters as default.
+        volume_fraction = inv.get_volume_fraction(contrast=1.0)
+        surface         = inv.get_surface(contrast=1.0, porod_const=1.0)
+        
+    def test_use_case_2(self):
+        """
+            Invariant without extrapolation. Invariant, volume fraction and surface 
+            are given with errors.
+        """
+        # Create invariant object. Background and scale left as defaults.
+        inv = invariant.InvariantCalculator(data=self.data)
+        
+        # Get the invariant with errors
+        qstar, qstar_err = inv.get_qstar_with_error()
+        v, dv            = inv.get_volume_fraction_with_error(contrast=1.0)
+        s, ds            = inv.get_surface_with_error(contrast=1.0, porod_const=1.0)
+        
+    def test_use_case_3(self):
+        """
+            Invariant with low-Q extrapolation
+        """
+        # Create invariant object. Background and scale left as defaults.
+        inv = invariant.InvariantCalculator(data=self.data)
+        
+        # Set the extrapolation parameters for the low-Q range
+        
+        # The npts parameter should have a good default.
+        # The range parameter should be 'high' or 'low'
+        # The function parameter should default to None. If it is None,
+        #    the method should pick a good default (Guinier at low-Q and 1/q^4 at high-Q).
+        #    The method should also check for consistency of the extrapolate and function
+        #    parameters. For instance, you might not want to allow 'high' and 'guinier'.
+        # The power parameter (not shown below) should default to 4.
+        inv.set_extraplotation(range='low', npts=5, function='guinier')
+        
+        # The version of the call without error
+        # At this point, we could still compute Q* without extrapolation by calling
+        # get_qstar with arguments, or with extrapolate=None.
+        qstar = inv.get_qstar(extrapolate='low')
+        
+        # The version of the call with error
+        qstar, qstar_err = inv.get_qstar_with_error(extrapolate='low')
+
+        # Get the volume fraction and surface
+        v, dv            = inv.get_volume_fraction_with_error(contrast=1.0)
+        s, ds            = inv.get_surface_with_error(contrast=1.0, porod_const=1.0)
+        
+    def test_use_case_4(self):
+        """
+            Invariant with high-Q extrapolation
+        """
+        # Create invariant object. Background and scale left as defaults.
+        inv = invariant.InvariantCalculator(data=self.data)
+        
+
+        # Set the extrapolation parameters for the high-Q range
+        
+        # The npts parameter should have a good default.
+        # The range parameter should be 'high' or 'low'. Those should be stored separately.
+        # The function parameter should default to None. If it is None,
+        #    the method should pick a good default (Guinier at low-Q and 1/q^4 at high-Q).
+        #    The method should also check for consistency of the extrapolate and function
+        #    parameters. For instance, you might not want to allow 'high' and 'guinier'.
+        # The power parameter should default to 4.
+        inv.set_extraplotation(range='high', npts=5, function='power_law', power=4)
+        
+        
+        # The version of the call without error
+        # The function parameter defaults to None, then is picked to be 'power_law' for extrapolate='high'
+        qstar = inv.get_qstar(extrapolate='high')
+        
+        # The version of the call with error
+        qstar, qstar_err = inv.get_qstar_with_error(extrapolate='high')
+
+        # Get the volume fraction and surface
+        v, dv            = inv.get_volume_fraction_with_error(contrast=1.0)
+        s, ds            = inv.get_surface_with_error(contrast=1.0, porod_const=1.0)
+        
+    def test_use_case_5(self):
+        """
+            Invariant with both high- and low-Q extrapolation
+        """
+        # Create invariant object. Background and scale left as defaults.
+        inv = invariant.InvariantCalculator(data=self.data)
+        
+        # Set the extrapolation parameters for the low- and high-Q ranges
+        inv.set_extraplotation(range='low', npts=5, function='guinier')
+        inv.set_extraplotation(range='high', npts=5, function='power_law', power=4)
+        
+        # The version of the call without error
+        # The function parameter defaults to None, then is picked to be 'power_law' for extrapolate='high'
+        qstar = inv.get_qstar(extrapolate='both')
+        
+        # The version of the call with error
+        qstar, qstar_err = inv.get_qstar_with_error(extrapolate='both')
+
+        # Get the volume fraction and surface
+        v, dv            = inv.get_volume_fraction_with_error(contrast=1.0)
+        s, ds            = inv.get_surface_with_error(contrast=1.0, porod_const=1.0)
+        
+                
