Index: build_tools/jenkins_win_build.bat
===================================================================
--- build_tools/jenkins_win_build.bat	(revision 261e075d60d39e9e8ffac2eb555f24abe16d4878)
+++ build_tools/jenkins_win_build.bat	(revision 261e075d60d39e9e8ffac2eb555f24abe16d4878)
@@ -0,0 +1,52 @@
+
+set PYTHON=C:\Python27\python
+set EASY_INSTALL=c:\python27\scripts\easy_install.exe
+set NEXUSDIR="C:\Program Files (x86)\NeXus Data Format\"
+set PATH=C:\Python27;C:\Python27\Scripts;C:\mingw\bin;%PATH%
+set PYLINT=c:\python27\scripts\pylint
+
+
+cd %WORKSPACE%\sasview
+%PYTHON% check_packages.py
+
+
+cd %WORKSPACE%\sasview
+python setup.py build -cmingw32
+
+
+cd %WORKSPACE%\sasview
+python setup.py docs
+
+
+cd %WORKSPACE%\sasview
+python setup.py bdist_egg --skip-build
+
+
+cd %WORKSPACE%\sasview\test
+%PYTHON% utest_sasview.py
+
+
+cd %WORKSPACE%\sasview
+mkdir sasview-install
+set PYTHONPATH=%WORKSPACE%\sasview\sasview-install;%PYTHONPATH%
+
+
+cd %WORKSPACE%\sasview
+cd dist
+%EASY_INSTALL% -d ..\sasview-install sasview-3.1.2-py2.7-win32.egg
+
+
+cd %WORKSPACE%\sasview\sasview
+python setup_exe.py py2exe
+python installer_generator.py
+"C:\Program Files (x86)\Inno Setup 5\ISCC.exe" installer.iss 
+
+
+cd Output
+xcopy setupSasView.exe %WORKSPACE%\sasview\dist
+
+cd %WORKSPACE%\sasview
+%PYLINT% --rcfile "build_tools/pylint.rc" -f parseable sasview-install/sasview*.egg/sas sasview > test/sasview.txt
+
+
+cd  %WORKSPACE%
Index: build_tools/jenkins_win_pylint.bat
===================================================================
--- build_tools/jenkins_win_pylint.bat	(revision 3a7047da7bc30f45445771a105eb4d774163e16a)
+++ build_tools/jenkins_win_pylint.bat	(revision 3a7047da7bc30f45445771a105eb4d774163e16a)
@@ -0,0 +1,13 @@
+
+set PYTHON=c:\Python27\python
+set EASY_INSTALL=c:\python27\scripts\easy_install.exe
+set PATH=c:\python27;c:\mingw\bin;%PATH%
+set PYLINT=c:\python27\scripts\pylint
+
+set PYTHONPATH=%WORKSPACE%\sasview-install;%PYTHONPATH%
+
+
+cd %WORKSPACE%
+%PYLINT% --rcfile "%WORKSPACE%cd /build_tools/pylint.rc" -f parseable sasview-install/sasview*.egg/sas sasview > test/sasview.txt
+
+cd %WORKSPACE%
Index: docs/sphinx-docs/source/conf.py
===================================================================
--- docs/sphinx-docs/source/conf.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ docs/sphinx-docs/source/conf.py	(revision 82bc7a9caadc5f70a931b411321a8c06a9d49340)
@@ -201,7 +201,14 @@
 # -- Options for LaTeX output --------------------------------------------------
 
+LATEX_PREAMBLE=r"""
+\renewcommand{\AA}{\text{\r{A}}} % Allow \AA in math mode
+\usepackage[utf8]{inputenc}      % Allow unicode symbols in text
+\DeclareUnicodeCharacter {00B7} {\ensuremath{\cdot}}   % cdot
+\DeclareUnicodeCharacter {00B0} {\ensuremath{^\circ}}  % degrees
+\DeclareUnicodeCharacter {212B} {\AA}                  % Angstrom
+"""
 latex_elements = {
 # The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
+'papersize': 'a4paper',
 
 # The font size ('10pt', '11pt' or '12pt').
@@ -209,5 +216,6 @@
 
 # Additional stuff for the LaTeX preamble.
-#'preamble': '',
+'preamble': LATEX_PREAMBLE
+
 }
 
Index: docs/sphinx-docs/source/user/analysis.rst
===================================================================
--- docs/sphinx-docs/source/user/analysis.rst	(revision 2dd1dd321ccf28cca91c0e42d618c03001f817c2)
+++ docs/sphinx-docs/source/user/analysis.rst	(revision 2dd1dd321ccf28cca91c0e42d618c03001f817c2)
@@ -0,0 +1,13 @@
+SasView Documentation: Types of Analysis
+========================================
+
+.. note:: In Windows use [Alt]-[Cursor left] to return to the previous page
+
+.. toctree::
+   :maxdepth: 1
+   
+   Model Fitting <sasgui/perspectives/fitting/fitting>
+   
+   P(r) Inversion <sasgui/perspectives/pr/pr_help>
+   
+   Invariant Calculation <sasgui/perspectives/invariant/invariant_help>
Index: docs/sphinx-docs/source/user/tools.rst
===================================================================
--- docs/sphinx-docs/source/user/tools.rst	(revision 2dd1dd321ccf28cca91c0e42d618c03001f817c2)
+++ docs/sphinx-docs/source/user/tools.rst	(revision 2dd1dd321ccf28cca91c0e42d618c03001f817c2)
@@ -0,0 +1,26 @@
+SasView Documentation: Tools
+============================
+
+.. note:: In Windows use [Alt]-[Cursor left] to return to the previous page
+
+.. toctree::
+   :maxdepth: 1
+
+   Data Operations Utility <sasgui/perspectives/calculator/data_operator_help>
+      
+   Density/Volume Calculator <sasgui/perspectives/calculator/density_calculator_help>
+   
+   Generic SANS Calculator <sasgui/perspectives/calculator/sas_calculator_help>
+   
+   Image Viewer <sasgui/perspectives/calculator/image_viewer_help>
+   
+   Kiessig Thickness Calculator <sasgui/perspectives/calculator/kiessig_calculator_help>
+   
+   SLD Calculator <sasgui/perspectives/calculator/sld_calculator_help>
+   
+   Slit Size Calculator <sasgui/perspectives/calculator/slit_calculator_help>
+   
+   Q Resolution Estimator <sasgui/perspectives/calculator/resolution_calculator_help>
+   
+   Python Shell <sasgui/perspectives/calculator/python_shell_help>
+   
Index: docs/sphinx-docs/source/user/user.rst
===================================================================
--- docs/sphinx-docs/source/user/user.rst	(revision e0bbb7c77043bded2a28ecb9af676f5e8c2af3f7)
+++ docs/sphinx-docs/source/user/user.rst	(revision e479a9f1468d6feb654a2e1980d6c9c538c25a52)
@@ -8,4 +8,8 @@
 
    Model Documentation <models/model_functions>
+   
+   Tools <tools>
+   
+   Types of Analysis <analysis>
 
    Data Formats <sasgui/guiframe/data_formats_help>
@@ -14,28 +18,4 @@
    
    Plotting Data/Models <sasgui/guiframe/graph_help>
-   
-   Fitting Perspective <sasgui/perspectives/fitting/fitting>
-   
-   P(r) Inversion Perspective <sasgui/perspectives/pr/pr_help>
-   
-   Invariant Calculation Perspective <sasgui/perspectives/invariant/invariant_help>
-   
-   Data Operations Tool <sasgui/perspectives/calculator/data_operator_help>
       
-   Density/Volume Calculator Tool <sasgui/perspectives/calculator/density_calculator_help>
-   
-   Generic SANS Calculator Tool <sasgui/perspectives/calculator/sas_calculator_help>
-   
-   Image Viewer Tool <sasgui/perspectives/calculator/image_viewer_help>
-   
-   Kiessig Thickness Calculator Tool <sasgui/perspectives/calculator/kiessig_calculator_help>
-   
-   SLD Calculator Tool <sasgui/perspectives/calculator/sld_calculator_help>
-   
-   Slit Size Calculator Tool <sasgui/perspectives/calculator/slit_calculator_help>
-   
-   Q Resolution Estimator Tool <sasgui/perspectives/calculator/resolution_calculator_help>
-   
-   Python Shell Tool <sasgui/perspectives/calculator/python_shell_help>
-
    Test Data <../test/testdata_help.rst>
Index: src/examples/test_chisq_panel.py
===================================================================
--- src/examples/test_chisq_panel.py	(revision d7bb5267934b5676c90b1655346a3793a8022965)
+++ src/examples/test_chisq_panel.py	(revision c10d9d6c8667422d3db329ac32d0528a6cce3fc6)
@@ -86,5 +86,5 @@
         dy = numpy.sqrt(100*numpy.abs(y))/100
 
-    from sas.plottools.plottables import Data1D, Theory1D, Chisq , Graph
+    from sas.sasgui.plottools.plottables import Data1D, Theory1D, Chisq , Graph
     data = Data1D(x,y,dy=dy)
     data.xaxis('distance', 'm')
Index: c/sas/models/qsmearing.py
===================================================================
--- src/sas/models/qsmearing.py	(revision a3f125f0f3c4ff6ed01529da503fbdd5ccb23b14)
+++ 	(revision )
@@ -1,651 +1,0 @@
-"""
-    Handle Q smearing
-"""
-#####################################################################
-#This software was developed by the University of Tennessee as part of the
-#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
-#project funded by the US National Science Foundation. 
-#See the license text in license.txt
-#copyright 2008, University of Tennessee
-######################################################################
-import numpy
-import math
-import logging
-import sys
-import sas.models.sas_extension.smearer as smearer
-from sas.models.smearing_2d import Smearer2D
-
-def smear_selection(data1D, model = None):
-    """
-    Creates the right type of smearer according 
-    to the data.
-
-    The canSAS format has a rule that either
-    slit smearing data OR resolution smearing data
-    is available. 
-    
-    For the present purpose, we choose the one that
-    has none-zero data. If both slit and resolution
-    smearing arrays are filled with good data 
-    (which should not happen), then we choose the
-    resolution smearing data. 
-    
-    :param data1D: Data1D object
-    :param model: sas.model instance
-    """
-    # Sanity check. If we are not dealing with a SAS Data1D
-    # object, just return None
-    if  data1D.__class__.__name__ not in ['Data1D', 'Theory1D']:
-        if data1D == None:
-            return None
-        elif data1D.dqx_data == None or data1D.dqy_data == None:
-            return None
-        return Smearer2D(data1D)
-    
-    if  not hasattr(data1D, "dx") and not hasattr(data1D, "dxl")\
-         and not hasattr(data1D, "dxw"):
-        return None
-    
-    # Look for resolution smearing data
-    _found_resolution = False
-    if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
-        
-        # Check that we have non-zero data
-        if data1D.dx[0] > 0.0:
-            _found_resolution = True
-            #print "_found_resolution",_found_resolution
-            #print "data1D.dx[0]",data1D.dx[0],data1D.dxl[0]
-    # If we found resolution smearing data, return a QSmearer
-    if _found_resolution == True:
-        return QSmearer(data1D, model)
-        #return pinhole_smear(data1D, model)
-
-    # Look for slit smearing data
-    _found_slit = False
-    if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x) \
-        and data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
-        
-        # Check that we have non-zero data
-        if data1D.dxl[0] > 0.0 or data1D.dxw[0] > 0.0:
-            _found_slit = True
-        
-        # Sanity check: all data should be the same as a function of Q
-        for item in data1D.dxl:
-            if data1D.dxl[0] != item:
-                _found_resolution = False
-                break
-            
-        for item in data1D.dxw:
-            if data1D.dxw[0] != item:
-                _found_resolution = False
-                break
-    # If we found slit smearing data, return a slit smearer
-    if _found_slit == True:
-        #return SlitSmearer(data1D, model)
-        return slit_smear(data1D, model)
-    return None
-            
-
-class _BaseSmearer(object):
-    """
-        Base class for smearers
-    """
-    def __init__(self):
-        self.nbins = 0
-        self.nbins_low = 0
-        self.nbins_high = 0
-        self._weights = None
-        ## Internal flag to keep track of C++ smearer initialization
-        self._init_complete = False
-        self._smearer = None
-        self.model = None
-        self.min = None
-        self.max = None
-        self.qvalues = []
-        
-    def __deepcopy__(self, memo=None):
-        """
-        Return a valid copy of self.
-        Avoid copying the _smearer C object and force a matrix recompute
-        when the copy is used.  
-        """
-        result = _BaseSmearer()
-        result.nbins = self.nbins
-        return result
-
-    def _compute_matrix(self):
-        """
-            Place holder for matrix computation 
-        """
-        return NotImplemented
-
-    def get_unsmeared_range(self, q_min=None, q_max=None):
-        """
-            Place holder for method returning unsmeared range
-        """
-        return NotImplemented
-    
-    def get_bin_range(self, q_min=None, q_max=None):
-        """
-        
-        :param q_min: minimum q-value to smear
-        :param q_max: maximum q-value to smear
-         
-        """
-        # If this is the first time we call for smearing,
-        # initialize the C++ smearer object first
-        if not self._init_complete:
-            self._initialize_smearer()
-        if q_min == None:
-            q_min = self.min
-        if q_max == None:
-            q_max = self.max
-
-        _qmin_unsmeared, _qmax_unsmeared = self.get_unsmeared_range(q_min,
-                                                                     q_max)
-        _first_bin = None
-        _last_bin  = None
-
-        #step = (self.max - self.min) / (self.nbins - 1.0)
-        # Find the first and last bin number in all extrapolated and real data
-        try:
-            for i in range(self.nbins):
-                q_i = smearer.get_q(self._smearer, i)
-                if (q_i >= _qmin_unsmeared) and (q_i <= _qmax_unsmeared):
-                    # Identify first and last bin
-                    if _first_bin is None:
-                        _first_bin = i
-                    else:
-                        _last_bin  = i
-        except:
-            msg = "_BaseSmearer.get_bin_range: "
-            msg += " error getting range\n  %s" % sys.exc_value
-            raise RuntimeError, msg
-   
-        #  Find the first and last bin number only in the real data
-        _first_bin, _last_bin = self._get_unextrapolated_bin( \
-                                        _first_bin, _last_bin)
-
-        return _first_bin, _last_bin
-
-    def __call__(self, iq_in, first_bin = 0, last_bin = None):
-        """
-        Perform smearing
-        """
-        # If this is the first time we call for smearing,
-        # initialize the C++ smearer object first
-        if not self._init_complete:
-            self._initialize_smearer()
-
-        if last_bin is None or last_bin >= len(iq_in):
-            last_bin = len(iq_in) - 1
-        # Check that the first bin is positive
-        if first_bin < 0:
-            first_bin = 0
-
-        # With a model given, compute I for the extrapolated points and append
-        # to the iq_in
-        iq_in_temp = iq_in
-        if self.model != None:
-            temp_first, temp_last = self._get_extrapolated_bin( \
-                                                        first_bin, last_bin)
-            if self.nbins_low > 0:
-                iq_in_low = self.model.evalDistribution( \
-                                    numpy.fabs(self.qvalues[0:self.nbins_low]))
-            iq_in_high = self.model.evalDistribution( \
-                                            self.qvalues[(len(self.qvalues) - \
-                                            self.nbins_high - 1):])
-            # Todo: find out who is sending iq[last_poin] = 0.
-            if iq_in[len(iq_in) - 1] == 0:
-                iq_in[len(iq_in) - 1] = iq_in_high[0]
-            # Append the extrapolated points to the data points
-            if self.nbins_low > 0:
-                iq_in_temp = numpy.append(iq_in_low, iq_in)
-            if self.nbins_high > 0:
-                iq_in_temp = numpy.append(iq_in_temp, iq_in_high[1:])
-        else:
-            temp_first = first_bin
-            temp_last = last_bin
-            #iq_in_temp = iq_in
-
-        # Sanity check
-        if len(iq_in_temp) != self.nbins:
-            msg = "Invalid I(q) vector: inconsistent array "
-            msg += " length %d != %s" % (len(iq_in_temp), str(self.nbins))
-            raise RuntimeError, msg
-
-        # Storage for smeared I(q)   
-        iq_out = numpy.zeros(self.nbins)
-
-        smear_output = smearer.smear(self._smearer, iq_in_temp, iq_out,
-                                      #0, self.nbins - 1)
-                                      temp_first, temp_last)
-                                      #first_bin, last_bin)
-        if smear_output < 0:
-            msg = "_BaseSmearer: could not smear, code = %g" % smear_output
-            raise RuntimeError, msg
-
-        temp_first = first_bin + self.nbins_low
-        temp_last = self.nbins - self.nbins_high
-        out = iq_out[temp_first: temp_last]
-
-        return out
-    
-    def _initialize_smearer(self):
-        """
-            Place holder for initializing data smearer
-        """
-        return NotImplemented
-            
-    
-    def _get_unextrapolated_bin(self, first_bin = 0, last_bin = 0):
-        """
-        Get unextrapolated first bin and the last bin
-        
-        : param first_bin: extrapolated first_bin
-        : param last_bin: extrapolated last_bin
-        
-        : return fist_bin, last_bin: unextrapolated first and last bin
-        """
-        #  For first bin
-        if first_bin <= self.nbins_low:
-            first_bin = 0
-        else:
-            first_bin = first_bin - self.nbins_low
-        # For last bin
-        if last_bin >= (self.nbins - self.nbins_high):
-            last_bin  = self.nbins - (self.nbins_high + self.nbins_low + 1)
-        elif last_bin >= self.nbins_low:
-            last_bin = last_bin - self.nbins_low
-        else:
-            last_bin = 0
-        return first_bin, last_bin
-    
-    def _get_extrapolated_bin(self, first_bin = 0, last_bin = 0):
-        """
-        Get extrapolated first bin and the last bin
-        
-        : param first_bin: unextrapolated first_bin
-        : param last_bin: unextrapolated last_bin
-        
-        : return first_bin, last_bin: extrapolated first and last bin
-        """
-        #  For the first bin
-        # In the case that needs low extrapolation data
-        first_bin = 0
-        # For last bin
-        if last_bin >= self.nbins - (self.nbins_high + self.nbins_low + 1):
-            # In the case that needs higher q extrapolation data 
-            last_bin = self.nbins - 1
-        else:
-            # In the case that doesn't need higher q extrapolation data 
-            last_bin += self.nbins_low
-
-        return first_bin, last_bin
-        
-class _SlitSmearer(_BaseSmearer):
-    """
-    Slit smearing for I(q) array
-    """
-    
-    def __init__(self, nbins=None, width=None, height=None, min=None, max=None):
-        """
-        Initialization
-            
-        :param iq: I(q) array [cm-1]
-        :param width: slit width [A-1]
-        :param height: slit height [A-1]
-        :param min: Q_min [A-1]
-        :param max: Q_max [A-1]
-        
-        """
-        _BaseSmearer.__init__(self)
-        ## Slit width in Q units
-        self.width  = width
-        ## Slit height in Q units
-        self.height = height
-        ## Q_min (Min Q-value for I(q))
-        self.min    = min
-        ## Q_max (Max Q_value for I(q))
-        self.max    = max
-        ## Number of Q bins 
-        self.nbins  = nbins
-        ## Number of points used in the smearing computation
-        self.npts   = 3000
-        ## Smearing matrix
-        self._weights = None
-        self.qvalues  = None
-        
-    def _initialize_smearer(self):
-        """
-        Initialize the C++ smearer object.
-        This method HAS to be called before smearing
-        """
-        #self._smearer = smearer.new_slit_smearer(self.width,
-        # self.height, self.min, self.max, self.nbins)
-        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
-                                                    self.height, self.qvalues)
-        self._init_complete = True
-
-    def get_unsmeared_range(self, q_min, q_max):
-        """
-        Determine the range needed in unsmeared-Q to cover
-        the smeared Q range
-        """
-        # Range used for input to smearing
-        _qmin_unsmeared = q_min
-        _qmax_unsmeared = q_max 
-        try:
-            _qmin_unsmeared = self.min
-            _qmax_unsmeared = self.max
-        except:
-            logging.error("_SlitSmearer.get_bin_range: %s" % sys.exc_value)
-        return _qmin_unsmeared, _qmax_unsmeared
-
-class SlitSmearer(_SlitSmearer):
-    """
-    Adaptor for slit smearing class and SAS data
-    """
-    def __init__(self, data1D, model = None):
-        """
-        Assumption: equally spaced bins of increasing q-values.
-        
-        :param data1D: data used to set the smearing parameters
-        """
-        # Initialization from parent class
-        super(SlitSmearer, self).__init__()
-        
-        ## Slit width
-        self.width = 0
-        self.nbins_low = 0
-        self.nbins_high = 0
-        self.model = model
-        if data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
-            self.width = data1D.dxw[0]
-            # Sanity check
-            for value in data1D.dxw:
-                if value != self.width:
-                    msg = "Slit smearing parameters must "
-                    msg += " be the same for all data"
-                    raise RuntimeError, msg
-        ## Slit height
-        self.height = 0
-        if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x):
-            self.height = data1D.dxl[0]
-            # Sanity check
-            for value in data1D.dxl:
-                if value != self.height:
-                    msg = "Slit smearing parameters must be"
-                    msg += " the same for all data"
-                    raise RuntimeError, msg
-        # If a model is given, get the q extrapolation
-        if self.model == None:
-            data1d_x = data1D.x
-        else:
-            # Take larger sigma
-            if self.height > self.width:
-                # The denominator (2.0) covers all the possible w^2 + h^2 range
-                sigma_in = data1D.dxl / 2.0
-            elif self.width > 0:
-                sigma_in = data1D.dxw / 2.0
-            else:
-                sigma_in = []
-
-            self.nbins_low, self.nbins_high, _, data1d_x = \
-                                get_qextrapolate(sigma_in, data1D.x)
-
-        ## Number of Q bins
-        self.nbins = len(data1d_x)
-        ## Minimum Q 
-        self.min = min(data1d_x)
-        ## Maximum
-        self.max = max(data1d_x)
-        ## Q-values
-        self.qvalues = data1d_x
-        
-
-class _QSmearer(_BaseSmearer):
-    """
-    Perform Gaussian Q smearing
-    """
-        
-    def __init__(self, nbins=None, width=None, min=None, max=None):
-        """
-        Initialization
-        
-        :param nbins: number of Q bins
-        :param width: array standard deviation in Q [A-1]
-        :param min: Q_min [A-1]
-        :param max: Q_max [A-1]
-        """
-        _BaseSmearer.__init__(self)
-        ## Standard deviation in Q [A-1]
-        self.width = width
-        ## Q_min (Min Q-value for I(q))
-        self.min = min
-        ## Q_max (Max Q_value for I(q))
-        self.max = max
-        ## Number of Q bins 
-        self.nbins = nbins
-        ## Smearing matrix
-        self._weights = None
-        self.qvalues  = None
-        
-    def _initialize_smearer(self):
-        """
-        Initialize the C++ smearer object.
-        This method HAS to be called before smearing
-        """
-        #self._smearer = smearer.new_q_smearer(numpy.asarray(self.width),
-        # self.min, self.max, self.nbins)
-        self._smearer = smearer.new_q_smearer_with_q(numpy.asarray(self.width),
-                                                      self.qvalues)
-        self._init_complete = True
-        
-    def get_unsmeared_range(self, q_min, q_max):
-        """
-        Determine the range needed in unsmeared-Q to cover
-        the smeared Q range
-        Take 3 sigmas as the offset between smeared and unsmeared space
-        """
-        # Range used for input to smearing
-        _qmin_unsmeared = q_min
-        _qmax_unsmeared = q_max 
-        try:
-            offset = 3.0 * max(self.width)
-            _qmin_unsmeared = self.min#max([self.min, q_min - offset])
-            _qmax_unsmeared = self.max#min([self.max, q_max + offset])
-        except:
-            logging.error("_QSmearer.get_bin_range: %s" % sys.exc_value)
-        return _qmin_unsmeared, _qmax_unsmeared
-        
-    
-class QSmearer(_QSmearer):
-    """
-    Adaptor for Gaussian Q smearing class and SAS data
-    """
-    def __init__(self, data1D, model = None):
-        """
-        Assumption: equally spaced bins of increasing q-values.
-        
-        :param data1D: data used to set the smearing parameters
-        """
-        # Initialization from parent class
-        super(QSmearer, self).__init__()
-        data1d_x = []
-        self.nbins_low = 0
-        self.nbins_high = 0
-        self.model = model
-        ## Resolution
-        #self.width = numpy.zeros(len(data1D.x))
-        if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
-            self.width = data1D.dx
-        
-        if self.model == None:
-            data1d_x = data1D.x
-        else:
-            self.nbins_low, self.nbins_high, self.width, data1d_x = \
-                                get_qextrapolate(self.width, data1D.x)
-
-        ## Number of Q bins
-        self.nbins = len(data1d_x)
-        ## Minimum Q 
-        self.min = min(data1d_x)
-        ## Maximum
-        self.max = max(data1d_x)
-        ## Q-values
-        self.qvalues = data1d_x
-
-        
-def get_qextrapolate(width, data_x):
-    """
-    Make fake data_x points extrapolated outside of the data_x points
-    
-    :param width: array of std of q resolution
-    :param Data1D.x: Data1D.x array
-    
-    :return new_width, data_x_ext: extrapolated width array and x array
-    
-    :assumption1: data_x is ordered from lower q to higher q
-    :assumption2: len(data) = len(width)
-    :assumption3: the distance between the data points is more compact than the size of width 
-    :Todo1: Make sure that the assumptions are correct for Data1D
-    :Todo2: This fixes the edge problem in Qsmearer but still needs to make smearer interface 
-    """
-    # Length of the width
-    length = len(width)
-    width_low = math.fabs(width[0])   
-    width_high = math.fabs(width[length -1])
-    nbins_low = 0.0 
-    nbins_high = 0.0
-    # Compare width(dQ) to the data bin size and take smaller one as the bin 
-    # size of the extrapolation; this will correct some weird behavior 
-    # at the edge: This method was out (commented) 
-    # because it becomes very expansive when
-    # bin size is very small comparing to the width.
-    # Now on, we will just give the bin size of the extrapolated points 
-    # based on the width.
-    # Find bin sizes
-    #bin_size_low = math.fabs(data_x[1] - data_x[0])
-    #bin_size_high = math.fabs(data_x[length - 1] - data_x[length - 2])
-    # Let's set the bin size 1/3 of the width(sigma), it is good as long as
-    # the scattering is monotonous.
-    #if width_low < (bin_size_low):
-    bin_size_low = width_low / 10.0
-    #if width_high < (bin_size_high):
-    bin_size_high = width_high / 10.0
-        
-    # Number of q points required below the 1st data point in order to extend
-    # them 3 times of the width (std)
-    if width_low > 0.0:
-        nbins_low = math.ceil(3.0 * width_low / bin_size_low)
-    # Number of q points required above the last data point
-    if width_high > 0.0:
-        nbins_high = math.ceil(3.0 * width_high / bin_size_high)
-    # Make null q points        
-    extra_low = numpy.zeros(nbins_low)
-    extra_high = numpy.zeros(nbins_high)
-    # Give extrapolated values
-    ind = 0
-    qvalue = data_x[0] - bin_size_low
-    #if qvalue > 0:
-    while(ind < nbins_low):
-        extra_low[nbins_low - (ind + 1)] = qvalue
-        qvalue -= bin_size_low
-        ind += 1
-        #if qvalue <= 0:
-        #    break
-    # Redefine nbins_low
-    nbins_low = ind
-    # Reset ind for another extrapolation
-    ind = 0
-    qvalue = data_x[length -1] + bin_size_high
-    while(ind < nbins_high):
-        extra_high[ind] = qvalue
-        qvalue += bin_size_high
-        ind += 1
-    # Make a new qx array
-    if nbins_low > 0:  
-        data_x_ext = numpy.append(extra_low, data_x)
-    else:
-        data_x_ext = data_x
-    data_x_ext = numpy.append(data_x_ext, extra_high)
-    
-    # Redefine extra_low and high based on corrected nbins  
-    # And note that it is not necessary for extra_width to be a non-zero 
-    if nbins_low > 0:     
-        extra_low = numpy.zeros(nbins_low)
-    extra_high = numpy.zeros(nbins_high) 
-    # Make new width array
-    new_width = numpy.append(extra_low, width)
-    new_width = numpy.append(new_width, extra_high)
-    
-    # nbins corrections due to the negative q value
-    nbins_low = nbins_low - len(data_x_ext[data_x_ext <= 0])
-    return nbins_low, nbins_high, \
-             new_width[data_x_ext > 0], data_x_ext[data_x_ext > 0]
-
-
-
-from .resolution import Slit1D, Pinhole1D
-class PySmear(object):
-    """
-    Wrapper for pure python sasmodels resolution functions.
-    """
-    def __init__(self, resolution, model):
-        self.model = model
-        self.resolution = resolution
-        self.offset = numpy.searchsorted(self.resolution.q_calc, self.resolution.q[0])
-
-    def apply(self, iq_in, first_bin=0, last_bin=None):
-        """
-        Apply the resolution function to the data.
-
-        Note that this is called with iq_in matching data.x, but with
-        iq_in[first_bin:last_bin] set to theory values for these bins,
-        and the remainder left undefined.  The first_bin, last_bin values
-        should be those returned from get_bin_range.
-
-        The returned value is of the same length as iq_in, with the range
-        first_bin:last_bin set to the resolution smeared values.
-        """
-        if last_bin is None: last_bin = len(iq_in)
-        start, end = first_bin + self.offset, last_bin + self.offset
-        q_calc = self.resolution.q_calc
-        iq_calc = numpy.empty_like(q_calc)
-        if start > 0:
-            iq_calc[:start] = self.model.evalDistribution(q_calc[:start])
-        if end+1 < len(q_calc):
-            iq_calc[end+1:] = self.model.evalDistribution(q_calc[end+1:])
-        iq_calc[start:end+1] = iq_in[first_bin:last_bin+1]
-        smeared = self.resolution.apply(iq_calc)
-        return smeared
-    __call__ = apply
-
-    def get_bin_range(self, q_min=None, q_max=None):
-        """
-        For a given q_min, q_max, find the corresponding indices in the data.
-
-        Returns first, last.
-
-        Note that these are indexes into q from the data, not the q_calc
-        needed by the resolution function.  Note also that these are the
-        indices, not the range limits.  That is, the complete range will be
-        q[first:last+1].
-        """
-        q = self.resolution.q
-        first = numpy.searchsorted(q, q_min)
-        last = numpy.searchsorted(q, q_max)
-        return first, min(last,len(q)-1)
-
-def slit_smear(data, model=None):
-    q = data.x
-    width = data.dxw if data.dxw is not None else 0
-    height = data.dxl if data.dxl is not None else 0
-    # TODO: width and height seem to be reversed
-    return PySmear(Slit1D(q, height, width), model)
-
-def pinhole_smear(data, model=None):
-    q = data.x
-    width = data.dx if data.dx is not None else 0
-    return PySmear(Pinhole1D(q, width), model)
Index: c/sas/models/resolution.py
===================================================================
--- src/sas/models/resolution.py	(revision 80ba1a2137352c44778c86431353df4ba1f0b6a7)
+++ 	(revision )
@@ -1,1077 +1,0 @@
-"""
-Define the resolution functions for the data.
-
-This defines classes for 1D and 2D resolution calculations.
-"""
-from __future__ import division
-from scipy.special import erf
-from numpy import sqrt, log, log10
-import numpy as np
-
-MINIMUM_RESOLUTION = 1e-8
-
-
-# When extrapolating to -q, what is the minimum positive q relative to q_min
-# that we wish to calculate?
-MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION = 0.01
-
-class Resolution(object):
-    """
-    Abstract base class defining a 1D resolution function.
-
-    *q* is the set of q values at which the data is measured.
-
-    *q_calc* is the set of q values at which the theory needs to be evaluated.
-    This may extend and interpolate the q values.
-
-    *apply* is the method to call with I(q_calc) to compute the resolution
-    smeared theory I(q).
-    """
-    q = None
-    q_calc = None
-    def apply(self, theory):
-        """
-        Smear *theory* by the resolution function, returning *Iq*.
-        """
-        raise NotImplementedError("Subclass does not define the apply function")
-
-
-class Perfect1D(Resolution):
-    """
-    Resolution function to use when there is no actual resolution smearing
-    to be applied.  It has the same interface as the other resolution
-    functions, but returns the identity function.
-    """
-    def __init__(self, q):
-        self.q_calc = self.q = q
-
-    def apply(self, theory):
-        return theory
-
-
-class Pinhole1D(Resolution):
-    r"""
-    Pinhole aperture with q-dependent gaussian resolution.
-
-    *q* points at which the data is measured.
-
-    *q_width* gaussian 1-sigma resolution at each data point.
-
-    *q_calc* is the list of points to calculate, or None if this should
-    be estimated from the *q* and *q_width*.
-    """
-    def __init__(self, q, q_width, q_calc=None, nsigma=3):
-        #*min_step* is the minimum point spacing to use when computing the
-        #underlying model.  It should be on the order of
-        #$\tfrac{1}{10}\tfrac{2\pi}{d_\text{max}}$ to make sure that fringes
-        #are computed with sufficient density to avoid aliasing effects.
-
-        # Protect against calls with q_width=0.  The extend_q function will
-        # not extend the q if q_width is 0, but q_width must be non-zero when
-        # constructing the weight matrix to avoid division by zero errors.
-        # In practice this should never be needed, since resolution should
-        # default to Perfect1D if the pinhole geometry is not defined.
-        self.q, self.q_width = q, q_width
-        self.q_calc = pinhole_extend_q(q, q_width, nsigma=nsigma) \
-            if q_calc is None else np.sort(q_calc)
-        self.weight_matrix = pinhole_resolution(self.q_calc,
-                self.q, np.maximum(q_width, MINIMUM_RESOLUTION))
-
-    def apply(self, theory):
-        return apply_resolution_matrix(self.weight_matrix, theory)
-
-
-class Slit1D(Resolution):
-    """
-    Slit aperture with a complicated resolution function.
-
-    *q* points at which the data is measured.
-
-    *qx_width* slit width
-
-    *qy_height* slit height
-
-    *q_calc* is the list of points to calculate, or None if this should
-    be estimated from the *q* and *q_width*.
-
-    The *weight_matrix* is computed by :func:`slit1d_resolution`
-    """
-    def __init__(self, q, width, height=0., q_calc=None):
-        # Remember what width/height was used even though we won't need them
-        # after the weight matrix is constructed
-        self.width, self.height = width, height
-
-        # Allow independent resolution on each point even though it is not
-        # needed in practice.
-        if np.isscalar(width):
-            width = np.ones(len(q))*width
-        else:
-            width = np.asarray(width)
-        if np.isscalar(height):
-            height = np.ones(len(q))*height
-        else:
-            height = np.asarray(height)
-
-        self.q = q.flatten()
-        self.q_calc = slit_extend_q(q, width, height) \
-            if q_calc is None else np.sort(q_calc)
-        self.weight_matrix = \
-            slit_resolution(self.q_calc, self.q, width, height)
-
-    def apply(self, theory):
-        return apply_resolution_matrix(self.weight_matrix, theory)
-
-
-def apply_resolution_matrix(weight_matrix, theory):
-    """
-    Apply the resolution weight matrix to the computed theory function.
-    """
-    #print "apply shapes", theory.shape, weight_matrix.shape
-    Iq = np.dot(theory[None,:], weight_matrix)
-    #print "result shape",Iq.shape
-    return Iq.flatten()
-
-
-def pinhole_resolution(q_calc, q, q_width):
-    """
-    Compute the convolution matrix *W* for pinhole resolution 1-D data.
-
-    Each row *W[i]* determines the normalized weight that the corresponding
-    points *q_calc* contribute to the resolution smeared point *q[i]*.  Given
-    *W*, the resolution smearing can be computed using *dot(W,q)*.
-
-    *q_calc* must be increasing.  *q_width* must be greater than zero.
-    """
-    # The current algorithm is a midpoint rectangle rule.  In the test case,
-    # neither trapezoid nor Simpson's rule improved the accuracy.
-    edges = bin_edges(q_calc)
-    edges[edges<0.0] = 0.0 # clip edges below zero
-    G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] )
-    weights = G[1:] - G[:-1]
-    weights /= np.sum(weights, axis=0)[None,:]
-    return weights
-
-
-def slit_resolution(q_calc, q, width, height, n_height=30):
-    r"""
-    Build a weight matrix to compute *I_s(q)* from *I(q_calc)*, given
-    $q_\perp$ = *width* and $q_\parallel$ = *height*.  *n_height* is
-    is the number of steps to use in the integration over $q_\parallel$
-    when both $q_\perp$ and $q_\parallel$ are non-zero.
-
-    Each $q$ can have an independent width and height value even though
-    current instruments use the same slit setting for all measured points.
-
-    If slit height is large relative to width, use:
-
-    .. math::
-
-        I_s(q_i) = \frac{1}{\Delta q_\perp}
-            \int_0^{\Delta q_\perp} I(\sqrt{q_i^2 + q_\perp^2} dq_\perp
-
-    If slit width is large relative to height, use:
-
-    .. math::
-
-        I_s(q_i) = \frac{1}{2 \Delta q_\parallel}
-            \int_{-\Delta q_\parallel}^{\Delta q_\parallel}
-                I(|q_i + q_\parallel|) dq_\parallel
-
-    For a mixture of slit width and height use:
-
-    .. math::
-
-        I_s(q_i) = \frac{1}{2 \Delta q_\parallel \Delta q_\perp}
-            \int_{-\Delta q_\parallel)^{\Delta q_parallel}
-            \int_0^[\Delta q_\perp}
-                I(\sqrt{(q_i + q_\parallel)^2 + q_\perp^2})
-                dq_\perp dq_\parallel
-
-
-    Algorithm
-    ---------
-
-    We are using the mid-point integration rule to assign weights to each
-    element of a weight matrix $W$ so that
-
-    .. math::
-
-        I_s(q) = W I(q_\text{calc})
-
-    If *q_calc* is at the mid-point, we can infer the bin edges from the
-    pairwise averages of *q_calc*, adding the missing edges before
-    *q_calc[0]* and after *q_calc[-1]*.
-
-    For $q_\parallel = 0$, the smeared value can be computed numerically
-    using the $u$ substitution
-
-    .. math::
-
-        u_j = \sqrt{q_j^2 - q^2}
-
-    This gives
-
-    .. math::
-
-        I_s(q) \approx \sum_j I(u_j) \Delta u_j
-
-    where $I(u_j)$ is the value at the mid-point, and $\Delta u_j$ is the
-    difference between consecutive edges which have been first converted
-    to $u$.  Only $u_j \in [0, \Delta q_\perp]$ are used, which corresponds
-    to $q_j \in [q, \sqrt{q^2 + \Delta q_\perp}]$, so
-
-    .. math::
-
-        W_{ij} = \frac{1}{\Delta q_\perp} \Delta u_j
-               = \frac{1}{\Delta q_\perp}
-                    \sqrt{q_{j+1}^2 - q_i^2} - \sqrt{q_j^2 - q_i^2}
-            \text{if} q_j \in [q_i, \sqrt{q_i^2 + q_\perp^2}]
-
-    where $I_s(q_i)$ is the theory function being computed and $q_j$ are the
-    mid-points between the calculated values in *q_calc*.  We tweak the
-    edges of the initial and final intervals so that they lie on integration
-    limits.
-
-    (To be precise, the transformed midpoint $u(q_j)$ is not necessarily the
-    midpoint of the edges $u((q_{j-1}+q_j)/2)$ and $u((q_j + q_{j+1})/2)$,
-    but it is at least in the interval, so the approximation is going to be
-    a little better than the left or right Riemann sum, and should be
-    good enough for our purposes.)
-
-    For $q_\perp = 0$, the $u$ substitution is simpler:
-
-    .. math::
-
-        u_j = |q_j - q|
-
-    so
-
-    .. math::
-
-        W_ij = \frac{1}{2 \Delta q_\parallel} \Delta u_j
-            = \frac{1}{2 \Delta q_\parallel} (q_{j+1} - q_j)
-            \text{if} q_j \in [q-\Delta q_\parallel, q+\Delta q_\parallel]
-
-    However, we need to support cases were $u_j < 0$, which means using
-    $2 (q_{j+1} - q_j)$ when $q_j \in [0, q_\parallel-q_i]$.  This is not
-    an issue for $q_i > q_\parallel$.
-
-    For bot $q_\perp > 0$ and $q_\parallel > 0$ we perform a 2 dimensional
-    integration with
-
-    .. math::
-
-        u_jk = \sqrt{q_j^2 - (q + (k\Delta q_\parallel/L))^2}
-            \text{for} k = -L \ldots L
-
-    for $L$ = *n_height*.  This gives
-
-    .. math::
-
-        W_{ij} = \frac{1}{2 \Delta q_\perp q_\parallel}
-            \sum_{k=-L}^L \Delta u_jk (\frac{\Delta q_\parallel}{2 L + 1}
-
-
-    """
-    #np.set_printoptions(precision=6, linewidth=10000)
-
-    # The current algorithm is a midpoint rectangle rule.
-    q_edges = bin_edges(q_calc) # Note: requires q > 0
-    q_edges[q_edges<0.0] = 0.0 # clip edges below zero
-    weights = np.zeros((len(q), len(q_calc)), 'd')
-
-    #print q_calc
-    for i, (qi, w, h) in enumerate(zip(q, width, height)):
-        if w == 0. and h == 0.:
-            # Perfect resolution, so return the theory value directly.
-            # Note: assumes that q is a subset of q_calc.  If qi need not be
-            # in q_calc, then we can do a weighted interpolation by looking
-            # up qi in q_calc, then weighting the result by the relative
-            # distance to the neighbouring points.
-            weights[i, :] = (q_calc == qi)
-        elif h == 0:
-            weights[i, :] = _q_perp_weights(q_edges, qi, w)
-        elif w == 0:
-            in_x = 1.0 * ((q_calc >= qi-h) & (q_calc <= qi+h))
-            abs_x = 1.0*(q_calc < abs(qi - h)) if qi < h else 0.
-            #print qi - h, qi + h
-            #print in_x + abs_x
-            weights[i,:] = (in_x + abs_x) * np.diff(q_edges) / (2*h)
-        else:
-            L = n_height
-            for k in range(-L, L+1):
-                weights[i,:] += _q_perp_weights(q_edges, qi+k*h/L, w)
-            weights[i,:] /= 2*L + 1
-
-    return weights.T
-
-
-def _q_perp_weights(q_edges, qi, w):
-    # Convert bin edges from q to u
-    u_limit = np.sqrt(qi**2 + w**2)
-    u_edges = q_edges**2 - qi**2
-    u_edges[q_edges < abs(qi)] = 0.
-    u_edges[q_edges > u_limit] = u_limit**2 - qi**2
-    weights = np.diff(np.sqrt(u_edges))/w
-    #print "i, qi",i,qi,qi+width
-    #print q_calc
-    #print weights
-    return weights
-
-
-def pinhole_extend_q(q, q_width, nsigma=3):
-    """
-    Given *q* and *q_width*, find a set of sampling points *q_calc* so
-    that each point I(q) has sufficient support from the underlying
-    function.
-    """
-    q_min, q_max = np.min(q - nsigma*q_width), np.max(q + nsigma*q_width)
-    return linear_extrapolation(q, q_min, q_max)
-
-
-def slit_extend_q(q, width, height):
-    """
-    Given *q*, *width* and *height*, find a set of sampling points *q_calc* so
-    that each point I(q) has sufficient support from the underlying
-    function.
-    """
-    q_min, q_max = np.min(q-height), np.max(np.sqrt((q+height)**2 + width**2))
-
-    return geometric_extrapolation(q, q_min, q_max)
-
-
-def bin_edges(x):
-    """
-    Determine bin edges from bin centers, assuming that edges are centered
-    between the bins.
-
-    Note: this uses the arithmetic mean, which may not be appropriate for
-    log-scaled data.
-    """
-    if len(x) < 2 or (np.diff(x)<0).any():
-        raise ValueError("Expected bins to be an increasing set")
-    edges = np.hstack([
-        x[0]  - 0.5*(x[1]  - x[0]),  # first point minus half first interval
-        0.5*(x[1:] + x[:-1]),        # mid points of all central intervals
-        x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval
-        ])
-    return edges
-
-
-def interpolate(q, max_step):
-    """
-    Returns *q_calc* with points spaced at most max_step apart.
-    """
-    step = np.diff(q)
-    index = step>max_step
-    if np.any(index):
-        inserts = []
-        for q_i,step_i in zip(q[:-1][index],step[index]):
-            n = np.ceil(step_i/max_step)
-            inserts.extend(q_i + np.arange(1,n)*(step_i/n))
-        # Extend a couple of fringes beyond the end of the data
-        inserts.extend(q[-1] + np.arange(1,8)*max_step)
-        q_calc = np.sort(np.hstack((q,inserts)))
-    else:
-        q_calc = q
-    return q_calc
-
-
-def linear_extrapolation(q, q_min, q_max):
-    """
-    Extrapolate *q* out to [*q_min*, *q_max*] using the step size in *q* as
-    a guide.  Extrapolation below uses about the same size as the first
-    interval.  Extrapolation above uses about the same size as the final
-    interval.
-
-    if *q_min* is zero or less then *q[0]/10* is used instead.
-    """
-    q = np.sort(q)
-    if q_min < q[0]:
-        if q_min <= 0: q_min = q_min*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION
-        n_low = np.ceil((q[0]-q_min) / (q[1]-q[0])) if q[1]>q[0] else 15
-        q_low = np.linspace(q_min, q[0], n_low+1)[:-1]
-    else:
-        q_low = []
-    if q_max > q[-1]:
-        n_high = np.ceil((q_max-q[-1]) / (q[-1]-q[-2])) if q[-1]>q[-2] else 15
-        q_high = np.linspace(q[-1], q_max, n_high+1)[1:]
-    else:
-        q_high = []
-    return np.concatenate([q_low, q, q_high])
-
-
-def geometric_extrapolation(q, q_min, q_max, points_per_decade=None):
-    r"""
-    Extrapolate *q* to [*q_min*, *q_max*] using geometric steps, with the
-    average geometric step size in *q* as the step size.
-
-    if *q_min* is zero or less then *q[0]/10* is used instead.
-
-    *points_per_decade* sets the ratio between consecutive steps such
-    that there will be $n$ points used for every factor of 10 increase
-    in *q*.
-
-    If *points_per_decade* is not given, it will be estimated as follows.
-    Starting at $q_1$ and stepping geometrically by $\Delta q$ to $q_n$
-    in $n$ points gives a geometric average of:
-
-    .. math::
-
-         \log \Delta q = (\log q_n - log q_1) / (n - 1)
-
-    From this we can compute the number of steps required to extend $q$
-    from $q_n$ to $q_\text{max}$ by $\Delta q$ as:
-
-    .. math::
-
-         n_\text{extend} = (\log q_\text{max} - \log q_n) / \log \Delta q
-
-    Substituting:
-
-        n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n)
-            / (\log q_n - log q_1)
-    """
-    q = np.sort(q)
-    if points_per_decade is None:
-        log_delta_q = (len(q) - 1) / (log(q[-1]) - log(q[0]))
-    else:
-        log_delta_q = log(10.) / points_per_decade
-    if q_min < q[0]:
-        if q_min < 0: q_min = q[0]*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION
-        n_low = log_delta_q * (log(q[0])-log(q_min))
-        q_low  = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1]
-    else:
-        q_low = []
-    if q_max > q[-1]:
-        n_high = log_delta_q * (log(q_max)-log(q[-1]))
-        q_high = np.logspace(log10(q[-1]), log10(q_max), np.ceil(n_high)+1)[1:]
-    else:
-        q_high = []
-    return np.concatenate([q_low, q, q_high])
-
-
-############################################################################
-# unit tests
-############################################################################
-import unittest
-
-
-def eval_form(q, form, pars):
-    from sasmodels import core
-    kernel = core.make_kernel(form, [q])
-    theory = core.call_kernel(kernel, pars)
-    kernel.release()
-    return theory
-
-
-def gaussian(q, q0, dq):
-    from numpy import exp, pi
-    return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq)
-
-
-def romberg_slit_1d(q, width, height, form, pars):
-    """
-    Romberg integration for slit resolution.
-
-    This is an adaptive integration technique.  It is called with settings
-    that make it slow to evaluate but give it good accuracy.
-    """
-    from scipy.integrate import romberg, dblquad
-
-    if any(k not in form.info['defaults'] for k in pars.keys()):
-        keys = set(form.info['defaults'].keys())
-        extra = set(pars.keys()) - keys
-        raise ValueError("bad parameters: [%s] not in [%s]"%
-                         (", ".join(sorted(extra)), ", ".join(sorted(keys))))
-
-    if np.isscalar(width):
-        width = [width]*len(q)
-    if np.isscalar(height):
-        height = [height]*len(q)
-    _int_w = lambda w, qi: eval_form(sqrt(qi**2 + w**2), form, pars)
-    _int_h = lambda h, qi: eval_form(abs(qi+h), form, pars)
-    # If both width and height are defined, then it is too slow to use dblquad.
-    # Instead use trapz on a fixed grid, interpolated into the I(Q) for
-    # the extended Q range.
-    #_int_wh = lambda w, h, qi: eval_form(sqrt((qi+h)**2 + w**2), form, pars)
-    q_calc = slit_extend_q(q, np.asarray(width), np.asarray(height))
-    Iq = eval_form(q_calc, form, pars)
-    result = np.empty(len(q))
-    for i, (qi, w, h) in enumerate(zip(q, width, height)):
-        if h == 0.:
-            r = romberg(_int_w, 0, w, args=(qi,),
-                        divmax=100, vec_func=True, tol=0, rtol=1e-8)
-            result[i] = r/w
-        elif w == 0.:
-            r = romberg(_int_h, -h, h, args=(qi,),
-                        divmax=100, vec_func=True, tol=0, rtol=1e-8)
-            result[i] = r/(2*h)
-        else:
-            w_grid = np.linspace(0, w, 21)[None,:]
-            h_grid = np.linspace(-h, h, 23)[:,None]
-            u = sqrt((qi+h_grid)**2 + w_grid**2)
-            Iu = np.interp(u, q_calc, Iq)
-            #print np.trapz(Iu, w_grid, axis=1)
-            Is = np.trapz(np.trapz(Iu, w_grid, axis=1), h_grid[:,0])
-            result[i] = Is / (2*h*w)
-            """
-            r, err = dblquad(_int_wh, -h, h, lambda h: 0., lambda h: w,
-                             args=(qi,))
-            result[i] = r/(w*2*h)
-            """
-
-    # r should be [float, ...], but it is [array([float]), array([float]),...]
-    return result
-
-
-def romberg_pinhole_1d(q, q_width, form, pars, nsigma=5):
-    """
-    Romberg integration for pinhole resolution.
-
-    This is an adaptive integration technique.  It is called with settings
-    that make it slow to evaluate but give it good accuracy.
-    """
-    from scipy.integrate import romberg
-
-    if any(k not in form.info['defaults'] for k in pars.keys()):
-        keys = set(form.info['defaults'].keys())
-        extra = set(pars.keys()) - keys
-        raise ValueError("bad parameters: [%s] not in [%s]"%
-                         (", ".join(sorted(extra)), ", ".join(sorted(keys))))
-
-    _fn = lambda q, q0, dq: eval_form(q, form, pars)*gaussian(q, q0, dq)
-    r = [romberg(_fn, max(qi-nsigma*dqi,1e-10*q[0]), qi+nsigma*dqi, args=(qi, dqi),
-                 divmax=100, vec_func=True, tol=0, rtol=1e-8)
-         for qi,dqi in zip(q,q_width)]
-    return np.asarray(r).flatten()
-
-
-class ResolutionTest(unittest.TestCase):
-
-    def setUp(self):
-        self.x = 0.001*np.arange(1, 11)
-        self.y = self.Iq(self.x)
-
-    def Iq(self, q):
-        "Linear function for resolution unit test"
-        return 12.0 - 1000.0*q
-
-    def test_perfect(self):
-        """
-        Perfect resolution and no smearing.
-        """
-        resolution = Perfect1D(self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        np.testing.assert_equal(output, self.y)
-
-    def test_slit_zero(self):
-        """
-        Slit smearing with perfect resolution.
-        """
-        resolution = Slit1D(self.x, width=0, height=0, q_calc=self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        np.testing.assert_equal(output, self.y)
-
-    @unittest.skip("not yet supported")
-    def test_slit_high(self):
-        """
-        Slit smearing with height 0.005
-        """
-        resolution = Slit1D(self.x, width=0, height=0.005, q_calc=self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        answer = [ 9.0618, 8.6402, 8.1187, 7.1392, 6.1528,
-                   5.5555, 4.5584, 3.5606, 2.5623, 2.0000 ]
-        np.testing.assert_allclose(output, answer, atol=1e-4)
-
-    @unittest.skip("not yet supported")
-    def test_slit_both_high(self):
-        """
-        Slit smearing with width < 100*height.
-        """
-        q = np.logspace(-4, -1, 10)
-        resolution = Slit1D(q, width=0.2, height=np.inf)
-        theory = 1000*self.Iq(resolution.q_calc**4)
-        output = resolution.apply(theory)
-        answer = [ 8.85785, 8.43012, 7.92687, 6.94566, 6.03660,
-                   5.40363, 4.40655, 3.40880, 2.41058, 2.00000 ]
-        np.testing.assert_allclose(output, answer, atol=1e-4)
-
-    @unittest.skip("not yet supported")
-    def test_slit_wide(self):
-        """
-        Slit smearing with width 0.0002
-        """
-        resolution = Slit1D(self.x, width=0.0002, height=0, q_calc=self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
-        np.testing.assert_allclose(output, answer, atol=1e-4)
-
-    @unittest.skip("not yet supported")
-    def test_slit_both_wide(self):
-        """
-        Slit smearing with width > 100*height.
-        """
-        resolution = Slit1D(self.x, width=0.0002, height=0.000001,
-                            q_calc=self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
-        np.testing.assert_allclose(output, answer, atol=1e-4)
-
-    def test_pinhole_zero(self):
-        """
-        Pinhole smearing with perfect resolution
-        """
-        resolution = Pinhole1D(self.x, 0.0*self.x)
-        theory = self.Iq(resolution.q_calc)
-        output = resolution.apply(theory)
-        np.testing.assert_equal(output, self.y)
-
-    def test_pinhole(self):
-        """
-        Pinhole smearing with dQ = 0.001 [Note: not dQ/Q = 0.001]
-        """
-        resolution = Pinhole1D(self.x, 0.001*np.ones_like(self.x),
-                               q_calc=self.x)
-        theory = 12.0-1000.0*resolution.q_calc
-        output = resolution.apply(theory)
-        answer = [ 10.44785079, 9.84991299, 8.98101708,
-                  7.99906585, 6.99998311, 6.00001689,
-                  5.00093415, 4.01898292, 3.15008701, 2.55214921]
-        np.testing.assert_allclose(output, answer, atol=1e-8)
-
-
-class IgorComparisonTest(unittest.TestCase):
-
-    def setUp(self):
-        self.pars = TEST_PARS_PINHOLE_SPHERE
-        from sasmodels import core
-        from sasmodels.models import sphere
-        self.model = core.load_model(sphere, dtype='double')
-
-    def Iq_sphere(self, pars, resolution):
-        from sasmodels import core
-        kernel = core.make_kernel(self.model, [resolution.q_calc])
-        theory = core.call_kernel(kernel, pars)
-        result = resolution.apply(theory)
-        kernel.release()
-        return result
-
-    def compare(self, q, output, answer, tolerance):
-        #err = (output - answer)/answer
-        #idx = abs(err) >= tolerance
-        #problem = zip(q[idx], output[idx], answer[idx], err[idx])
-        #print "\n".join(str(v) for v in problem)
-        np.testing.assert_allclose(output, answer, rtol=tolerance)
-
-    def test_perfect(self):
-        """
-        Compare sphere model with NIST Igor SANS, no resolution smearing.
-        """
-        pars = TEST_PARS_SLIT_SPHERE
-        data_string = TEST_DATA_SLIT_SPHERE
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, width, answer, _ = data
-        resolution = Perfect1D(q)
-        output = self.Iq_sphere(pars, resolution)
-        self.compare(q, output, answer, 1e-6)
-
-    def test_pinhole(self):
-        """
-        Compare pinhole resolution smearing with NIST Igor SANS
-        """
-        pars = TEST_PARS_PINHOLE_SPHERE
-        data_string = TEST_DATA_PINHOLE_SPHERE
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, q_width, answer = data
-        resolution = Pinhole1D(q, q_width)
-        output = self.Iq_sphere(pars, resolution)
-        # TODO: relative error should be lower
-        self.compare(q, output, answer, 3e-4)
-
-    def test_pinhole_romberg(self):
-        """
-        Compare pinhole resolution smearing with romberg integration result.
-        """
-        pars = TEST_PARS_PINHOLE_SPHERE
-        data_string = TEST_DATA_PINHOLE_SPHERE
-        pars['radius'] *= 5
-        radius = pars['radius']
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, q_width, answer = data
-        answer = romberg_pinhole_1d(q, q_width, self.model, pars)
-        ## Getting 0.1% requires 5 sigma and 200 points per fringe
-        #q_calc = interpolate(pinhole_extend_q(q, q_width, nsigma=5),
-        #                     2*np.pi/radius/200)
-        #tol = 0.001
-        ## The default 3 sigma and no extra points gets 1%
-        q_calc, tol = None, 0.01
-        resolution = Pinhole1D(q, q_width, q_calc=q_calc)
-        output = self.Iq_sphere(pars, resolution)
-        if 0: # debug plot
-            import matplotlib.pyplot as plt
-            resolution = Perfect1D(q)
-            source = self.Iq_sphere(pars, resolution)
-            plt.loglog(q, source, '.')
-            plt.loglog(q, answer, '-', hold=True)
-            plt.loglog(q, output, '-', hold=True)
-            plt.show()
-        self.compare(q, output, answer, tol)
-
-    def test_slit(self):
-        """
-        Compare slit resolution smearing with NIST Igor SANS
-        """
-        pars = TEST_PARS_SLIT_SPHERE
-        data_string = TEST_DATA_SLIT_SPHERE
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, delta_qv, _, answer = data
-        resolution = Slit1D(q, width=delta_qv, height=0)
-        output = self.Iq_sphere(pars, resolution)
-        # TODO: eliminate Igor test since it is too inaccurate to be useful.
-        # This means we can eliminate the test data as well, and instead
-        # use a generated q vector.
-        self.compare(q, output, answer, 0.5)
-
-    def test_slit_romberg(self):
-        """
-        Compare slit resolution smearing with romberg integration result.
-        """
-        pars = TEST_PARS_SLIT_SPHERE
-        data_string = TEST_DATA_SLIT_SPHERE
-        radius = pars['radius']
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, delta_qv, _, answer = data
-        answer = romberg_slit_1d(q, delta_qv, 0., self.model, pars)
-        q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20),
-                               delta_qv[0], 0.)
-        resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc)
-        output = self.Iq_sphere(pars, resolution)
-        # TODO: relative error should be lower
-        self.compare(q, output, answer, 0.025)
-
-    def test_ellipsoid(self):
-        """
-        Compare romberg integration for ellipsoid model.
-        """
-        from .core import load_model
-        pars = {
-            'scale':0.05,
-            'rpolar':500, 'requatorial':15000,
-            'sld':6, 'solvent_sld': 1,
-            }
-        form = load_model('ellipsoid', dtype='double')
-        q = np.logspace(log10(4e-5),log10(2.5e-2), 68)
-        width, height = 0.117, 0.
-        resolution = Slit1D(q, width=width, height=height)
-        answer = romberg_slit_1d(q, width, height, form, pars)
-        output = resolution.apply(eval_form(resolution.q_calc, form, pars))
-        # TODO: 10% is too much error; use better algorithm
-        #print np.max(abs(answer-output)/answer)
-        self.compare(q, output, answer, 0.1)
-
-    #TODO: can sas q spacing be too sparse for the resolution calculation?
-    @unittest.skip("suppress sparse data test; not supported by current code")
-    def test_pinhole_sparse(self):
-        """
-        Compare pinhole resolution smearing with NIST Igor SANS on sparse data
-        """
-        pars = TEST_PARS_PINHOLE_SPHERE
-        data_string = TEST_DATA_PINHOLE_SPHERE
-
-        data = np.loadtxt(data_string.split('\n')).T
-        q, q_width, answer = data[:, ::20] # Take every nth point
-        resolution = Pinhole1D(q, q_width)
-        output = self.Iq_sphere(pars, resolution)
-        self.compare(q, output, answer, 1e-6)
-
-
-# pinhole sphere parameters
-TEST_PARS_PINHOLE_SPHERE = {
-    'scale': 1.0, 'background': 0.01,
-    'radius': 60.0, 'sld': 1, 'solvent_sld': 6.3,
-    }
-# Q, dQ, I(Q) calculated by NIST Igor SANS package
-TEST_DATA_PINHOLE_SPHERE = """\
-0.001278 0.0002847 2538.41176383
-0.001562 0.0002905 2536.91820405
-0.001846 0.0002956 2535.13182479
-0.002130 0.0003017 2533.06217813
-0.002414 0.0003087 2530.70378586
-0.002698 0.0003165 2528.05024192
-0.002982 0.0003249 2525.10408349
-0.003266 0.0003340 2521.86667499
-0.003550 0.0003437 2518.33907750
-0.003834 0.0003539 2514.52246995
-0.004118 0.0003646 2510.41798319
-0.004402 0.0003757 2506.02690988
-0.004686 0.0003872 2501.35067884
-0.004970 0.0003990 2496.38678318
-0.005253 0.0004112 2491.16237596
-0.005537 0.0004237 2485.63911673
-0.005821 0.0004365 2479.83657083
-0.006105 0.0004495 2473.75676948
-0.006389 0.0004628 2467.40145990
-0.006673 0.0004762 2460.77293372
-0.006957 0.0004899 2453.86724627
-0.007241 0.0005037 2446.69623838
-0.007525 0.0005177 2439.25775219
-0.007809 0.0005318 2431.55421398
-0.008093 0.0005461 2423.58785521
-0.008377 0.0005605 2415.36158137
-0.008661 0.0005750 2406.87009473
-0.008945 0.0005896 2398.12841186
-0.009229 0.0006044 2389.13360806
-0.009513 0.0006192 2379.88958042
-0.009797 0.0006341 2370.39776774
-0.010080 0.0006491 2360.69528793
-0.010360 0.0006641 2350.85169027
-0.010650 0.0006793 2340.42023633
-0.010930 0.0006945 2330.11206013
-0.011220 0.0007097 2319.20109972
-0.011500 0.0007251 2308.43503981
-0.011780 0.0007404 2297.44820179
-0.012070 0.0007558 2285.83853677
-0.012350 0.0007713 2274.41290746
-0.012640 0.0007868 2262.36219581
-0.012920 0.0008024 2250.51169731
-0.013200 0.0008180 2238.45596231
-0.013490 0.0008336 2225.76495666
-0.013770 0.0008493 2213.29618391
-0.014060 0.0008650 2200.19110751
-0.014340 0.0008807 2187.34050325
-0.014620 0.0008965 2174.30529864
-0.014910 0.0009123 2160.61632548
-0.015190 0.0009281 2147.21038112
-0.015470 0.0009440 2133.62023580
-0.015760 0.0009598 2119.37907426
-0.016040 0.0009757 2105.45234903
-0.016330 0.0009916 2090.86319102
-0.016610 0.0010080 2076.60576032
-0.016890 0.0010240 2062.19214565
-0.017180 0.0010390 2047.10550219
-0.017460 0.0010550 2032.38715621
-0.017740 0.0010710 2017.52560123
-0.018030 0.0010880 2001.99124318
-0.018310 0.0011040 1986.84662060
-0.018600 0.0011200 1971.03389745
-0.018880 0.0011360 1955.61395119
-0.019160 0.0011520 1940.08291563
-0.019450 0.0011680 1923.87672225
-0.019730 0.0011840 1908.10656374
-0.020020 0.0012000 1891.66297192
-0.020300 0.0012160 1875.66789021
-0.020580 0.0012320 1859.56357196
-0.020870 0.0012490 1842.79468290
-0.021150 0.0012650 1826.50064489
-0.021430 0.0012810 1810.11533702
-0.021720 0.0012970 1793.06840882
-0.022000 0.0013130 1776.51153580
-0.022280 0.0013290 1759.87201249
-0.022570 0.0013460 1742.57354412
-0.022850 0.0013620 1725.79397319
-0.023140 0.0013780 1708.35831550
-0.023420 0.0013940 1691.45256069
-0.023700 0.0014110 1674.48561783
-0.023990 0.0014270 1656.86525366
-0.024270 0.0014430 1639.79847285
-0.024550 0.0014590 1622.68887088
-0.024840 0.0014760 1604.96421100
-0.025120 0.0014920 1587.85768129
-0.025410 0.0015080 1569.99297335
-0.025690 0.0015240 1552.84580279
-0.025970 0.0015410 1535.54074115
-0.026260 0.0015570 1517.75249337
-0.026540 0.0015730 1500.40115023
-0.026820 0.0015900 1483.03632237
-0.027110 0.0016060 1465.05942429
-0.027390 0.0016220 1447.67682181
-0.027670 0.0016390 1430.46495191
-0.027960 0.0016550 1412.49232282
-0.028240 0.0016710 1395.13182318
-0.028520 0.0016880 1377.93439837
-0.028810 0.0017040 1359.99528971
-0.029090 0.0017200 1342.67274512
-0.029370 0.0017370 1325.55375609
-"""
-
-# Slit sphere parameters
-TEST_PARS_SLIT_SPHERE = {
-    'scale': 0.01, 'background': 0.01,
-    'radius': 60000, 'sld': 1, 'solvent_sld': 4,
-    }
-# Q dQ I(Q) I_smeared(Q)
-TEST_DATA_SLIT_SPHERE = """\
-2.26097e-05 0.117 5.5781372896e+09 1.4626077708e+06
-2.53847e-05 0.117 5.0363141458e+09 1.3117318023e+06
-2.81597e-05 0.117 4.4850108103e+09 1.1594863713e+06
-3.09347e-05 0.117 3.9364658459e+09 1.0094881630e+06
-3.37097e-05 0.117 3.4019975074e+09 8.6518941303e+05
-3.92597e-05 0.117 2.4139519814e+09 6.0232158311e+05
-4.48097e-05 0.117 1.5816877820e+09 3.8739994090e+05
-5.03597e-05 0.117 9.3715407224e+08 2.2745304775e+05
-5.59097e-05 0.117 4.8387917428e+08 1.2101295768e+05
-6.14597e-05 0.117 2.0193586928e+08 6.0055107771e+04
-6.70097e-05 0.117 5.5886110911e+07 3.2749521065e+04
-7.25597e-05 0.117 3.7782348010e+06 2.6350963616e+04
-7.81097e-05 0.117 5.3407817904e+06 2.9624963314e+04
-8.36597e-05 0.117 2.7975485523e+07 3.4403962254e+04
-8.92097e-05 0.117 4.9845448282e+07 3.6130017903e+04
-9.47597e-05 0.117 6.0092588905e+07 3.3495107849e+04
-1.00310e-04 0.117 5.6823430831e+07 2.7475726279e+04
-1.05860e-04 0.117 4.3857024036e+07 2.0144282226e+04
-1.11410e-04 0.117 2.7277144760e+07 1.3647403260e+04
-1.22510e-04 0.117 3.3119334113e+06 6.6519711526e+03
-1.33610e-04 0.117 1.4412859402e+06 6.9726212813e+03
-1.44710e-04 0.117 8.5620162463e+06 8.1441335775e+03
-1.55810e-04 0.117 9.6957429033e+06 6.4559996521e+03
-1.66910e-04 0.117 4.3818341914e+06 3.6252154156e+03
-1.78010e-04 0.117 2.7448997387e+05 2.4006505342e+03
-1.89110e-04 0.117 8.0472009936e+05 2.8187789089e+03
-2.00210e-04 0.117 2.8149552834e+06 3.0915662855e+03
-2.11310e-04 0.117 2.7510907861e+06 2.3722530293e+03
-2.22410e-04 0.117 1.0053133293e+06 1.4473468311e+03
-2.33510e-04 0.117 5.8428305052e+03 1.2048540556e+03
-2.44610e-04 0.117 5.1699305004e+05 1.4625670042e+03
-2.55710e-04 0.117 1.2120227268e+06 1.5010705973e+03
-2.66810e-04 0.117 9.7896842846e+05 1.1336343426e+03
-2.77910e-04 0.117 2.5507264791e+05 8.1848818080e+02
-3.05660e-04 0.117 5.2403101181e+05 7.4913374357e+02
-3.33410e-04 0.117 5.8699343809e+04 4.4669964560e+02
-3.61160e-04 0.117 3.0844327150e+05 4.6774007542e+02
-3.88910e-04 0.117 8.3360142970e+03 2.7169550220e+02
-4.16660e-04 0.117 1.8630080583e+05 3.0710983679e+02
-4.44410e-04 0.117 3.1616804732e-01 1.7959006831e+02
-4.72160e-04 0.117 1.1299016314e+05 2.0763952339e+02
-4.99910e-04 0.117 2.9952522747e+03 1.2536542765e+02
-5.27660e-04 0.117 6.7625695649e+04 1.4013969777e+02
-5.55410e-04 0.117 7.6927460089e+03 8.2145593180e+01
-6.10910e-04 0.117 1.1229057779e+04 8.4519745643e+01
-6.66410e-04 0.117 1.3035567943e+04 8.1554625609e+01
-7.21910e-04 0.117 1.3309931343e+04 7.4437319172e+01
-7.77410e-04 0.117 1.2462626212e+04 6.4697088261e+01
-8.32910e-04 0.117 1.0912927143e+04 5.3773301044e+01
-8.88410e-04 0.117 9.0172597469e+03 4.2843375753e+01
-9.43910e-04 0.117 7.0496495917e+03 3.2771032724e+01
-9.99410e-04 0.117 5.2030483682e+03 2.4113557144e+01
-1.05491e-03 0.117 3.5988976711e+03 1.7160773658e+01
-1.11041e-03 0.117 2.2996060652e+03 1.2016626459e+01
-1.22141e-03 0.117 6.4766590598e+02 6.0373017740e+00
-1.33241e-03 0.117 4.1963483264e+01 4.5215452974e+00
-1.44341e-03 0.117 6.3370708246e+01 5.1054681903e+00
-1.55441e-03 0.117 3.0736750577e+02 5.9176165298e+00
-1.66541e-03 0.117 5.0327682399e+02 5.9815000189e+00
-1.77641e-03 0.117 5.4084331454e+02 5.1634639625e+00
-1.88741e-03 0.117 4.3488671756e+02 3.8535158148e+00
-1.99841e-03 0.117 2.6322287860e+02 2.5824997753e+00
-2.10941e-03 0.117 1.0793633150e+02 1.7315517194e+00
-2.22041e-03 0.117 1.8474448850e+01 1.4077213604e+00
-2.33141e-03 0.117 1.5864062279e+00 1.4771560682e+00
-2.44241e-03 0.117 3.2267213848e+01 1.6916253448e+00
-2.55341e-03 0.117 7.4289116207e+01 1.8274751193e+00
-2.66441e-03 0.117 9.9000521929e+01 1.7706812289e+00
-"""
-
-def main():
-    """
-    Run tests given is sys.argv.
-
-    Returns 0 if success or 1 if any tests fail.
-    """
-    import sys
-    import xmlrunner
-
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
-
-    runner = xmlrunner.XMLTestRunner(output='logs')
-    result = runner.run(suite)
-    return 1 if result.failures or result.errors else 0
-
-
-############################################################################
-# usage demo
-############################################################################
-
-def _eval_demo_1d(resolution, title):
-    import sys
-    from sasmodels import core
-    name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder'
-
-    if name == 'cylinder':
-        pars = {'length':210, 'radius':500}
-    elif name == 'teubner_strey':
-        pars = {'a2':0.003, 'c1':-1e4, 'c2':1e10, 'background':0.312643}
-    elif name == 'sphere' or name == 'spherepy':
-        pars = TEST_PARS_SLIT_SPHERE
-    elif name == 'ellipsoid':
-        pars = {
-            'scale':0.05,
-            'rpolar':500, 'requatorial':15000,
-            'sld':6, 'solvent_sld': 1,
-            }
-    else:
-        pars = {}
-    defn = core.load_model_definition(name)
-    model = core.load_model(defn)
-
-    kernel = core.make_kernel(model, [resolution.q_calc])
-    theory = core.call_kernel(kernel, pars)
-    Iq = resolution.apply(theory)
-
-    if isinstance(resolution, Slit1D):
-        width, height = resolution.width, resolution.height
-        Iq_romb = romberg_slit_1d(resolution.q, width, height, model, pars)
-    else:
-        dq = resolution.q_width
-        Iq_romb = romberg_pinhole_1d(resolution.q, dq, model, pars)
-
-    import matplotlib.pyplot as plt
-    plt.loglog(resolution.q_calc, theory, label='unsmeared')
-    plt.loglog(resolution.q, Iq, label='smeared', hold=True)
-    plt.loglog(resolution.q, Iq_romb, label='romberg smeared', hold=True)
-    plt.legend()
-    plt.title(title)
-    plt.xlabel("Q (1/Ang)")
-    plt.ylabel("I(Q) (1/cm)")
-
-def demo_pinhole_1d():
-    q = np.logspace(-4, np.log10(0.2), 400)
-    q_width = 0.1*q
-    resolution = Pinhole1D(q, q_width)
-    _eval_demo_1d(resolution, title="10% dQ/Q Pinhole Resolution")
-
-def demo_slit_1d():
-    q = np.logspace(-4, np.log10(0.2), 100)
-    w = h = 0.
-    #w = 0.000000277790
-    w = 0.0277790
-    #h = 0.00277790
-    #h = 0.0277790
-    resolution = Slit1D(q, w, h)
-    _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w,h))
-
-def demo():
-    import matplotlib.pyplot as plt
-    plt.subplot(121)
-    demo_pinhole_1d()
-    #plt.yscale('linear')
-    plt.subplot(122)
-    demo_slit_1d()
-    #plt.yscale('linear')
-    plt.show()
-
-
-if __name__ == "__main__":
-    demo()
-    #main()
Index: c/sas/models/smearing_2d.py
===================================================================
--- src/sas/models/smearing_2d.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ 	(revision )
@@ -1,298 +1,0 @@
-"""
-#This software was developed by the University of Tennessee as part of the
-#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
-#project funded by the US National Science Foundation. 
-#See the license text in license.txt
-"""
-import numpy
-import math
-
-## Singular point
-SIGMA_ZERO = 1.0e-010
-## Limit of how many sigmas to be covered for the Gaussian smearing
-# default: 2.5 to cover 98.7% of Gaussian
-LIMIT = 3.0
-## Defaults
-R_BIN = {'Xhigh':10, 'High':5, 'Med':5, 'Low':3}
-PHI_BIN ={'Xhigh':20, 'High':12, 'Med':6, 'Low':4}   
-
-class Smearer2D:
-    """
-    Gaussian Q smearing class for SAS 2d data
-    """
-     
-    def __init__(self, data=None, model=None, index=None, 
-                 limit=LIMIT, accuracy='Low', coords='polar', engine='c'):
-        """
-        Assumption: equally spaced bins in dq_r, dq_phi space.
-        
-        :param data: 2d data used to set the smearing parameters
-        :param model: model function
-        :param index: 1d array with len(data) to define the range 
-         of the calculation: elements are given as True or False
-        :param nr: number of bins in dq_r-axis
-        :param nphi: number of bins in dq_phi-axis
-        :param coord: coordinates [string], 'polar' or 'cartesian'
-        :param engine: engine name [string]; 'c' or 'numpy'
-        """
-        ## data
-        self.data = data
-        ## model
-        self.model = model
-        ## Accuracy: Higher stands for more sampling points in both directions 
-        ## of r and phi.
-        self.accuracy = accuracy
-        ## number of bins in r axis for over-sampling 
-        self.nr = R_BIN
-        ## number of bins in phi axis for over-sampling 
-        self.nphi = PHI_BIN
-        ## maximum nsigmas
-        self.limit = limit
-        self.index = index
-        self.coords = coords
-        self.smearer = True
-        self._engine = engine
-        self.qx_data = None
-        self.qy_data = None
-        self.q_data = None
-        # dqx and dqy mean dq_parr and dq_perp
-        self.dqx_data = None
-        self.dqy_data = None
-        self.phi_data = None
-        
-    def get_data(self):   
-        """
-        Get qx_data, qy_data, dqx_data,dqy_data,
-        and calculate phi_data=arctan(qx_data/qy_data)
-        """
-        if self.data == None or self.data.__class__.__name__ == 'Data1D':
-            return None
-        if self.data.dqx_data == None or self.data.dqy_data == None:
-            return None
-        self.qx_data = self.data.qx_data[self.index]
-        self.qy_data = self.data.qy_data[self.index]
-        self.q_data = self.data.q_data[self.index]
-        # Here dqx and dqy mean dq_parr and dq_perp
-        self.dqx_data = self.data.dqx_data[self.index]
-        self.dqy_data = self.data.dqy_data[self.index]
-        self.phi_data = numpy.arctan(self.qx_data / self.qy_data)
-        ## Remove singular points if exists
-        self.dqx_data[self.dqx_data < SIGMA_ZERO] = SIGMA_ZERO
-        self.dqy_data[self.dqy_data < SIGMA_ZERO] = SIGMA_ZERO
-        return True
-    
-    def set_accuracy(self, accuracy='Low'):   
-        """
-        Set accuracy.
-        
-        :param accuracy:  string
-        """
-        self.accuracy = accuracy
-
-    def set_smearer(self, smearer=True):
-        """
-        Set whether or not smearer will be used
-        
-        :param smearer: smear object
-        
-        """
-        self.smearer = smearer
-        
-    def set_data(self, data=None):   
-        """
-        Set data.
-        
-        :param data: DataLoader.Data_info type
-        """
-        self.data = data
-  
-            
-    def set_model(self, model=None):   
-        """
-        Set model.
-        
-        :param model: sas.models instance
-        """
-        self.model = model  
-           
-    def set_index(self, index=None):   
-        """
-        Set index.
-        
-        :param index: 1d arrays
-        """
-        self.index = index       
-    
-    def get_value(self):
-        """
-        Over sampling of r_nbins times phi_nbins, calculate Gaussian weights, 
-        then find smeared intensity
-        """    
-        valid = self.get_data()
-        if valid == None:
-            return valid
-        # all zero values of dq
-        if numpy.all(numpy.fabs(self.dqx_data <= 1.1e-10)) and \
-                        numpy.all(numpy.fabs(self.dqy_data <= 1.1e-10)):
-            self.smearer = False
-  
-        if self.smearer == False:
-            return self.model.evalDistribution([self.qx_data, self.qy_data]) 
-
-        nr = self.nr[self.accuracy]
-        nphi = self.nphi[self.accuracy]
-        # Number of bins in the dqr direction (polar coordinate of dqx and dqy)
-        bin_size = self.limit / nr
-        # Total number of bins = # of bins 
-        # in dq_r-direction times # of bins in dq_phi-direction
-        n_bins = nr * nphi
-        # data length in the range of self.index
-        len_data = len(self.qx_data)
-        #len_datay = len(self.qy_data)
-        if self._engine == 'c' and self.coords == 'polar':
-            try:
-                import sas.models.sas_extension.smearer2d_helper as smearer2dc
-                smearc = smearer2dc.new_Smearer_helper(self.qx_data, 
-                                              self.qy_data,
-                                              self.dqx_data, self.dqy_data,
-                                              self.limit, nr, nphi, 
-                                              int(len_data))
-                weight_res = numpy.zeros(nr * nphi )
-                qx_res = numpy.zeros(nr * nphi * int(len_data))
-                qy_res = numpy.zeros(nr * nphi * int(len_data))
-                smearer2dc.smearer2d_helper(smearc, weight_res, qx_res, qy_res)
-            except:
-                raise 
-        else:
-            # Mean values of dqr at each bins 
-            # starting from the half of bin size
-            r = bin_size / 2.0 + numpy.arange(nr) * bin_size
-            # mean values of qphi at each bines
-            phi = numpy.arange(nphi)
-            dphi = phi * 2.0 * math.pi / nphi
-            dphi = dphi.repeat(nr)
-    
-            ## Transform to polar coordinate, 
-            #  and set dphi at each data points ; 1d array
-            dphi = dphi.repeat(len_data)
-            q_phi = self.qy_data / self.qx_data
-            
-            # Starting angle is different between polar 
-            #  and cartesian coordinates.
-            #if self.coords != 'polar':
-            #    dphi += numpy.arctan( q_phi * self.dqx_data/ \
-            #                  self.dqy_data).repeat(n_bins).reshape(len_data,\
-            #                                n_bins).transpose().flatten()
-    
-            # The angle (phi) of the original q point
-            q_phi = numpy.arctan(q_phi).repeat(n_bins).reshape(len_data,\
-                                                n_bins).transpose().flatten()
-            ## Find Gaussian weight for each dq bins: The weight depends only 
-            #  on r-direction (The integration may not need)
-            weight_res = numpy.exp(-0.5 * ((r - bin_size / 2.0) * \
-                                    (r - bin_size / 2.0)))- \
-                                    numpy.exp(-0.5 * ((r + bin_size / 2.0 ) *\
-                                    (r + bin_size / 2.0)))
-            # No needs of normalization here.
-            #weight_res /= numpy.sum(weight_res)
-            weight_res = weight_res.repeat(nphi).reshape(nr, nphi)
-    
-            weight_res = weight_res.transpose().flatten()
-            
-            ## Set dr for all dq bins for averaging
-            dr = r.repeat(nphi).reshape(nr, nphi).transpose().flatten()
-            ## Set dqr for all data points
-            dqx = numpy.outer(dr, self.dqx_data).flatten()
-            dqy = numpy.outer(dr, self.dqy_data).flatten()
-    
-            qx = self.qx_data.repeat(n_bins).reshape(len_data, \
-                                                 n_bins).transpose().flatten()
-            qy = self.qy_data.repeat(n_bins).reshape(len_data, \
-                                                 n_bins).transpose().flatten()
-    
-            # The polar needs rotation by -q_phi
-            if self.coords == 'polar':
-                q_r = numpy.sqrt(qx * qx + qy * qy)
-                qx_res = ((dqx*numpy.cos(dphi) + q_r) * numpy.cos(-q_phi) +\
-                               dqy*numpy.sin(dphi) * numpy.sin(-q_phi))
-                qy_res = (-(dqx*numpy.cos(dphi) + q_r) * numpy.sin(-q_phi) +\
-                               dqy*numpy.sin(dphi) * numpy.cos(-q_phi))
-            else:
-                qx_res = qx +  dqx*numpy.cos(dphi)
-                qy_res = qy +  dqy*numpy.sin(dphi)
-            
-        ## Evaluate all points
-        val = self.model.evalDistribution([qx_res, qy_res]) 
-        ## Reshape into 2d array to use numpy weighted averaging
-        value_res= val.reshape(n_bins, len(self.qx_data))
-        ## Averaging with Gaussian weighting: normalization included.
-        value =numpy.average(value_res,axis=0, weights=weight_res)
-        ## Return the smeared values in the range of self.index
-        return value
-"""    
-if __name__ == '__main__':
-    ## Test w/ 2D linear function
-    x = 0.001*numpy.arange(1, 11)
-    dx = numpy.ones(len(x))*0.0003
-    y = 0.001*numpy.arange(1, 11)
-    dy = numpy.ones(len(x))*0.001
-    z = numpy.ones(10)
-    dz = numpy.sqrt(z)
-    
-    from sas.sascalc.dataloader import Data2D
-    #for i in range(10): print i, 0.001 + i*0.008/9.0 
-    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) )) 
-    out = Data2D()
-    out.data = z
-    out.qx_data = x
-    out.qy_data = y
-    out.dqx_data = dx
-    out.dqy_data = dy
-    out.q_data = numpy.sqrt(dx * dx + dy * dy)
-    index = numpy.ones(len(x), dtype = bool)
-    out.mask = index
-    from sas.models.LineModel import LineModel
-    model = LineModel()
-    model.setParam("A", 0)
-
-    smear = Smearer2D(out, model, index)
-    #smear.set_accuracy('Xhigh')
-    value = smear.get_value()
-    ## All data are ones, so the smeared should also be ones.
-    print "Data length =", len(value)
-    print " 2D linear function, I = 0 + 1*qy"
-    text = " Gaussian weighted averaging on a 2D linear function will "
-    text += "provides the results same as without the averaging."
-    print text
-    print "qx_data", "qy_data", "I_nonsmear", "I_smeared"
-    for ind in range(len(value)):
-        print x[ind], y[ind], model.evalDistribution([x, y])[ind], value[ind]
-        
-  
-if __name__ == '__main__':
-    ## Another Test w/ constant function
-    x = 0.001*numpy.arange(1,11)
-    dx = numpy.ones(len(x))*0.001
-    y = 0.001*numpy.arange(1,11)
-    dy = numpy.ones(len(x))*0.001
-    z = numpy.ones(10)
-    dz = numpy.sqrt(z)
-    
-    from DataLoader import Data2D
-    #for i in range(10): print i, 0.001 + i*0.008/9.0 
-    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) )) 
-    out = Data2D()
-    out.data = z
-    out.qx_data = x
-    out.qy_data = y
-    out.dqx_data = dx
-    out.dqy_data = dy
-    index = numpy.ones(len(x), dtype = bool)
-    out.mask = index
-    from sas.models.Constant import Constant
-    model = Constant()
-
-    value = Smearer2D(out,model,index).get_value()
-    ## All data are ones, so the smeared values should also be ones.
-    print "Data length =",len(value), ", Data=",value
-"""    
Index: src/sas/sascalc/data_util/qsmearing.py
===================================================================
--- src/sas/sascalc/data_util/qsmearing.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
+++ src/sas/sascalc/data_util/qsmearing.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -0,0 +1,651 @@
+"""
+    Handle Q smearing
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+import numpy
+import math
+import logging
+import sys
+import sas.models.sas_extension.smearer as smearer
+from sas.sascalc.data_util.smearing_2d import Smearer2D
+
+def smear_selection(data1D, model = None):
+    """
+    Creates the right type of smearer according 
+    to the data.
+
+    The canSAS format has a rule that either
+    slit smearing data OR resolution smearing data
+    is available. 
+    
+    For the present purpose, we choose the one that
+    has none-zero data. If both slit and resolution
+    smearing arrays are filled with good data 
+    (which should not happen), then we choose the
+    resolution smearing data. 
+    
+    :param data1D: Data1D object
+    :param model: sas.model instance
+    """
+    # Sanity check. If we are not dealing with a SAS Data1D
+    # object, just return None
+    if  data1D.__class__.__name__ not in ['Data1D', 'Theory1D']:
+        if data1D == None:
+            return None
+        elif data1D.dqx_data == None or data1D.dqy_data == None:
+            return None
+        return Smearer2D(data1D)
+    
+    if  not hasattr(data1D, "dx") and not hasattr(data1D, "dxl")\
+         and not hasattr(data1D, "dxw"):
+        return None
+    
+    # Look for resolution smearing data
+    _found_resolution = False
+    if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
+        
+        # Check that we have non-zero data
+        if data1D.dx[0] > 0.0:
+            _found_resolution = True
+            #print "_found_resolution",_found_resolution
+            #print "data1D.dx[0]",data1D.dx[0],data1D.dxl[0]
+    # If we found resolution smearing data, return a QSmearer
+    if _found_resolution == True:
+        return QSmearer(data1D, model)
+        #return pinhole_smear(data1D, model)
+
+    # Look for slit smearing data
+    _found_slit = False
+    if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x) \
+        and data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
+        
+        # Check that we have non-zero data
+        if data1D.dxl[0] > 0.0 or data1D.dxw[0] > 0.0:
+            _found_slit = True
+        
+        # Sanity check: all data should be the same as a function of Q
+        for item in data1D.dxl:
+            if data1D.dxl[0] != item:
+                _found_resolution = False
+                break
+            
+        for item in data1D.dxw:
+            if data1D.dxw[0] != item:
+                _found_resolution = False
+                break
+    # If we found slit smearing data, return a slit smearer
+    if _found_slit == True:
+        #return SlitSmearer(data1D, model)
+        return slit_smear(data1D, model)
+    return None
+            
+
+class _BaseSmearer(object):
+    """
+        Base class for smearers
+    """
+    def __init__(self):
+        self.nbins = 0
+        self.nbins_low = 0
+        self.nbins_high = 0
+        self._weights = None
+        ## Internal flag to keep track of C++ smearer initialization
+        self._init_complete = False
+        self._smearer = None
+        self.model = None
+        self.min = None
+        self.max = None
+        self.qvalues = []
+        
+    def __deepcopy__(self, memo=None):
+        """
+        Return a valid copy of self.
+        Avoid copying the _smearer C object and force a matrix recompute
+        when the copy is used.  
+        """
+        result = _BaseSmearer()
+        result.nbins = self.nbins
+        return result
+
+    def _compute_matrix(self):
+        """
+            Place holder for matrix computation 
+        """
+        return NotImplemented
+
+    def get_unsmeared_range(self, q_min=None, q_max=None):
+        """
+            Place holder for method returning unsmeared range
+        """
+        return NotImplemented
+    
+    def get_bin_range(self, q_min=None, q_max=None):
+        """
+        
+        :param q_min: minimum q-value to smear
+        :param q_max: maximum q-value to smear
+         
+        """
+        # If this is the first time we call for smearing,
+        # initialize the C++ smearer object first
+        if not self._init_complete:
+            self._initialize_smearer()
+        if q_min == None:
+            q_min = self.min
+        if q_max == None:
+            q_max = self.max
+
+        _qmin_unsmeared, _qmax_unsmeared = self.get_unsmeared_range(q_min,
+                                                                     q_max)
+        _first_bin = None
+        _last_bin  = None
+
+        #step = (self.max - self.min) / (self.nbins - 1.0)
+        # Find the first and last bin number in all extrapolated and real data
+        try:
+            for i in range(self.nbins):
+                q_i = smearer.get_q(self._smearer, i)
+                if (q_i >= _qmin_unsmeared) and (q_i <= _qmax_unsmeared):
+                    # Identify first and last bin
+                    if _first_bin is None:
+                        _first_bin = i
+                    else:
+                        _last_bin  = i
+        except:
+            msg = "_BaseSmearer.get_bin_range: "
+            msg += " error getting range\n  %s" % sys.exc_value
+            raise RuntimeError, msg
+   
+        #  Find the first and last bin number only in the real data
+        _first_bin, _last_bin = self._get_unextrapolated_bin( \
+                                        _first_bin, _last_bin)
+
+        return _first_bin, _last_bin
+
+    def __call__(self, iq_in, first_bin = 0, last_bin = None):
+        """
+        Perform smearing
+        """
+        # If this is the first time we call for smearing,
+        # initialize the C++ smearer object first
+        if not self._init_complete:
+            self._initialize_smearer()
+
+        if last_bin is None or last_bin >= len(iq_in):
+            last_bin = len(iq_in) - 1
+        # Check that the first bin is positive
+        if first_bin < 0:
+            first_bin = 0
+
+        # With a model given, compute I for the extrapolated points and append
+        # to the iq_in
+        iq_in_temp = iq_in
+        if self.model != None:
+            temp_first, temp_last = self._get_extrapolated_bin( \
+                                                        first_bin, last_bin)
+            if self.nbins_low > 0:
+                iq_in_low = self.model.evalDistribution( \
+                                    numpy.fabs(self.qvalues[0:self.nbins_low]))
+            iq_in_high = self.model.evalDistribution( \
+                                            self.qvalues[(len(self.qvalues) - \
+                                            self.nbins_high - 1):])
+            # Todo: find out who is sending iq[last_poin] = 0.
+            if iq_in[len(iq_in) - 1] == 0:
+                iq_in[len(iq_in) - 1] = iq_in_high[0]
+            # Append the extrapolated points to the data points
+            if self.nbins_low > 0:
+                iq_in_temp = numpy.append(iq_in_low, iq_in)
+            if self.nbins_high > 0:
+                iq_in_temp = numpy.append(iq_in_temp, iq_in_high[1:])
+        else:
+            temp_first = first_bin
+            temp_last = last_bin
+            #iq_in_temp = iq_in
+
+        # Sanity check
+        if len(iq_in_temp) != self.nbins:
+            msg = "Invalid I(q) vector: inconsistent array "
+            msg += " length %d != %s" % (len(iq_in_temp), str(self.nbins))
+            raise RuntimeError, msg
+
+        # Storage for smeared I(q)   
+        iq_out = numpy.zeros(self.nbins)
+
+        smear_output = smearer.smear(self._smearer, iq_in_temp, iq_out,
+                                      #0, self.nbins - 1)
+                                      temp_first, temp_last)
+                                      #first_bin, last_bin)
+        if smear_output < 0:
+            msg = "_BaseSmearer: could not smear, code = %g" % smear_output
+            raise RuntimeError, msg
+
+        temp_first = first_bin + self.nbins_low
+        temp_last = self.nbins - self.nbins_high
+        out = iq_out[temp_first: temp_last]
+
+        return out
+    
+    def _initialize_smearer(self):
+        """
+            Place holder for initializing data smearer
+        """
+        return NotImplemented
+            
+    
+    def _get_unextrapolated_bin(self, first_bin = 0, last_bin = 0):
+        """
+        Get unextrapolated first bin and the last bin
+        
+        : param first_bin: extrapolated first_bin
+        : param last_bin: extrapolated last_bin
+        
+        : return fist_bin, last_bin: unextrapolated first and last bin
+        """
+        #  For first bin
+        if first_bin <= self.nbins_low:
+            first_bin = 0
+        else:
+            first_bin = first_bin - self.nbins_low
+        # For last bin
+        if last_bin >= (self.nbins - self.nbins_high):
+            last_bin  = self.nbins - (self.nbins_high + self.nbins_low + 1)
+        elif last_bin >= self.nbins_low:
+            last_bin = last_bin - self.nbins_low
+        else:
+            last_bin = 0
+        return first_bin, last_bin
+    
+    def _get_extrapolated_bin(self, first_bin = 0, last_bin = 0):
+        """
+        Get extrapolated first bin and the last bin
+        
+        : param first_bin: unextrapolated first_bin
+        : param last_bin: unextrapolated last_bin
+        
+        : return first_bin, last_bin: extrapolated first and last bin
+        """
+        #  For the first bin
+        # In the case that needs low extrapolation data
+        first_bin = 0
+        # For last bin
+        if last_bin >= self.nbins - (self.nbins_high + self.nbins_low + 1):
+            # In the case that needs higher q extrapolation data 
+            last_bin = self.nbins - 1
+        else:
+            # In the case that doesn't need higher q extrapolation data 
+            last_bin += self.nbins_low
+
+        return first_bin, last_bin
+        
+class _SlitSmearer(_BaseSmearer):
+    """
+    Slit smearing for I(q) array
+    """
+    
+    def __init__(self, nbins=None, width=None, height=None, min=None, max=None):
+        """
+        Initialization
+            
+        :param iq: I(q) array [cm-1]
+        :param width: slit width [A-1]
+        :param height: slit height [A-1]
+        :param min: Q_min [A-1]
+        :param max: Q_max [A-1]
+        
+        """
+        _BaseSmearer.__init__(self)
+        ## Slit width in Q units
+        self.width  = width
+        ## Slit height in Q units
+        self.height = height
+        ## Q_min (Min Q-value for I(q))
+        self.min    = min
+        ## Q_max (Max Q_value for I(q))
+        self.max    = max
+        ## Number of Q bins 
+        self.nbins  = nbins
+        ## Number of points used in the smearing computation
+        self.npts   = 3000
+        ## Smearing matrix
+        self._weights = None
+        self.qvalues  = None
+        
+    def _initialize_smearer(self):
+        """
+        Initialize the C++ smearer object.
+        This method HAS to be called before smearing
+        """
+        #self._smearer = smearer.new_slit_smearer(self.width,
+        # self.height, self.min, self.max, self.nbins)
+        self._smearer = smearer.new_slit_smearer_with_q(self.width, 
+                                                    self.height, self.qvalues)
+        self._init_complete = True
+
+    def get_unsmeared_range(self, q_min, q_max):
+        """
+        Determine the range needed in unsmeared-Q to cover
+        the smeared Q range
+        """
+        # Range used for input to smearing
+        _qmin_unsmeared = q_min
+        _qmax_unsmeared = q_max 
+        try:
+            _qmin_unsmeared = self.min
+            _qmax_unsmeared = self.max
+        except:
+            logging.error("_SlitSmearer.get_bin_range: %s" % sys.exc_value)
+        return _qmin_unsmeared, _qmax_unsmeared
+
+class SlitSmearer(_SlitSmearer):
+    """
+    Adaptor for slit smearing class and SAS data
+    """
+    def __init__(self, data1D, model = None):
+        """
+        Assumption: equally spaced bins of increasing q-values.
+        
+        :param data1D: data used to set the smearing parameters
+        """
+        # Initialization from parent class
+        super(SlitSmearer, self).__init__()
+        
+        ## Slit width
+        self.width = 0
+        self.nbins_low = 0
+        self.nbins_high = 0
+        self.model = model
+        if data1D.dxw is not None and len(data1D.dxw) == len(data1D.x):
+            self.width = data1D.dxw[0]
+            # Sanity check
+            for value in data1D.dxw:
+                if value != self.width:
+                    msg = "Slit smearing parameters must "
+                    msg += " be the same for all data"
+                    raise RuntimeError, msg
+        ## Slit height
+        self.height = 0
+        if data1D.dxl is not None and len(data1D.dxl) == len(data1D.x):
+            self.height = data1D.dxl[0]
+            # Sanity check
+            for value in data1D.dxl:
+                if value != self.height:
+                    msg = "Slit smearing parameters must be"
+                    msg += " the same for all data"
+                    raise RuntimeError, msg
+        # If a model is given, get the q extrapolation
+        if self.model == None:
+            data1d_x = data1D.x
+        else:
+            # Take larger sigma
+            if self.height > self.width:
+                # The denominator (2.0) covers all the possible w^2 + h^2 range
+                sigma_in = data1D.dxl / 2.0
+            elif self.width > 0:
+                sigma_in = data1D.dxw / 2.0
+            else:
+                sigma_in = []
+
+            self.nbins_low, self.nbins_high, _, data1d_x = \
+                                get_qextrapolate(sigma_in, data1D.x)
+
+        ## Number of Q bins
+        self.nbins = len(data1d_x)
+        ## Minimum Q 
+        self.min = min(data1d_x)
+        ## Maximum
+        self.max = max(data1d_x)
+        ## Q-values
+        self.qvalues = data1d_x
+        
+
+class _QSmearer(_BaseSmearer):
+    """
+    Perform Gaussian Q smearing
+    """
+        
+    def __init__(self, nbins=None, width=None, min=None, max=None):
+        """
+        Initialization
+        
+        :param nbins: number of Q bins
+        :param width: array standard deviation in Q [A-1]
+        :param min: Q_min [A-1]
+        :param max: Q_max [A-1]
+        """
+        _BaseSmearer.__init__(self)
+        ## Standard deviation in Q [A-1]
+        self.width = width
+        ## Q_min (Min Q-value for I(q))
+        self.min = min
+        ## Q_max (Max Q_value for I(q))
+        self.max = max
+        ## Number of Q bins 
+        self.nbins = nbins
+        ## Smearing matrix
+        self._weights = None
+        self.qvalues  = None
+        
+    def _initialize_smearer(self):
+        """
+        Initialize the C++ smearer object.
+        This method HAS to be called before smearing
+        """
+        #self._smearer = smearer.new_q_smearer(numpy.asarray(self.width),
+        # self.min, self.max, self.nbins)
+        self._smearer = smearer.new_q_smearer_with_q(numpy.asarray(self.width),
+                                                      self.qvalues)
+        self._init_complete = True
+        
+    def get_unsmeared_range(self, q_min, q_max):
+        """
+        Determine the range needed in unsmeared-Q to cover
+        the smeared Q range
+        Take 3 sigmas as the offset between smeared and unsmeared space
+        """
+        # Range used for input to smearing
+        _qmin_unsmeared = q_min
+        _qmax_unsmeared = q_max 
+        try:
+            offset = 3.0 * max(self.width)
+            _qmin_unsmeared = self.min#max([self.min, q_min - offset])
+            _qmax_unsmeared = self.max#min([self.max, q_max + offset])
+        except:
+            logging.error("_QSmearer.get_bin_range: %s" % sys.exc_value)
+        return _qmin_unsmeared, _qmax_unsmeared
+        
+    
+class QSmearer(_QSmearer):
+    """
+    Adaptor for Gaussian Q smearing class and SAS data
+    """
+    def __init__(self, data1D, model = None):
+        """
+        Assumption: equally spaced bins of increasing q-values.
+        
+        :param data1D: data used to set the smearing parameters
+        """
+        # Initialization from parent class
+        super(QSmearer, self).__init__()
+        data1d_x = []
+        self.nbins_low = 0
+        self.nbins_high = 0
+        self.model = model
+        ## Resolution
+        #self.width = numpy.zeros(len(data1D.x))
+        if data1D.dx is not None and len(data1D.dx) == len(data1D.x):
+            self.width = data1D.dx
+        
+        if self.model == None:
+            data1d_x = data1D.x
+        else:
+            self.nbins_low, self.nbins_high, self.width, data1d_x = \
+                                get_qextrapolate(self.width, data1D.x)
+
+        ## Number of Q bins
+        self.nbins = len(data1d_x)
+        ## Minimum Q 
+        self.min = min(data1d_x)
+        ## Maximum
+        self.max = max(data1d_x)
+        ## Q-values
+        self.qvalues = data1d_x
+
+        
+def get_qextrapolate(width, data_x):
+    """
+    Make fake data_x points extrapolated outside of the data_x points
+    
+    :param width: array of std of q resolution
+    :param Data1D.x: Data1D.x array
+    
+    :return new_width, data_x_ext: extrapolated width array and x array
+    
+    :assumption1: data_x is ordered from lower q to higher q
+    :assumption2: len(data) = len(width)
+    :assumption3: the distance between the data points is more compact than the size of width 
+    :Todo1: Make sure that the assumptions are correct for Data1D
+    :Todo2: This fixes the edge problem in Qsmearer but still needs to make smearer interface 
+    """
+    # Length of the width
+    length = len(width)
+    width_low = math.fabs(width[0])   
+    width_high = math.fabs(width[length -1])
+    nbins_low = 0.0 
+    nbins_high = 0.0
+    # Compare width(dQ) to the data bin size and take smaller one as the bin 
+    # size of the extrapolation; this will correct some weird behavior 
+    # at the edge: This method was out (commented) 
+    # because it becomes very expansive when
+    # bin size is very small comparing to the width.
+    # Now on, we will just give the bin size of the extrapolated points 
+    # based on the width.
+    # Find bin sizes
+    #bin_size_low = math.fabs(data_x[1] - data_x[0])
+    #bin_size_high = math.fabs(data_x[length - 1] - data_x[length - 2])
+    # Let's set the bin size 1/3 of the width(sigma), it is good as long as
+    # the scattering is monotonous.
+    #if width_low < (bin_size_low):
+    bin_size_low = width_low / 10.0
+    #if width_high < (bin_size_high):
+    bin_size_high = width_high / 10.0
+        
+    # Number of q points required below the 1st data point in order to extend
+    # them 3 times of the width (std)
+    if width_low > 0.0:
+        nbins_low = math.ceil(3.0 * width_low / bin_size_low)
+    # Number of q points required above the last data point
+    if width_high > 0.0:
+        nbins_high = math.ceil(3.0 * width_high / bin_size_high)
+    # Make null q points        
+    extra_low = numpy.zeros(nbins_low)
+    extra_high = numpy.zeros(nbins_high)
+    # Give extrapolated values
+    ind = 0
+    qvalue = data_x[0] - bin_size_low
+    #if qvalue > 0:
+    while(ind < nbins_low):
+        extra_low[nbins_low - (ind + 1)] = qvalue
+        qvalue -= bin_size_low
+        ind += 1
+        #if qvalue <= 0:
+        #    break
+    # Redefine nbins_low
+    nbins_low = ind
+    # Reset ind for another extrapolation
+    ind = 0
+    qvalue = data_x[length -1] + bin_size_high
+    while(ind < nbins_high):
+        extra_high[ind] = qvalue
+        qvalue += bin_size_high
+        ind += 1
+    # Make a new qx array
+    if nbins_low > 0:  
+        data_x_ext = numpy.append(extra_low, data_x)
+    else:
+        data_x_ext = data_x
+    data_x_ext = numpy.append(data_x_ext, extra_high)
+    
+    # Redefine extra_low and high based on corrected nbins  
+    # And note that it is not necessary for extra_width to be a non-zero 
+    if nbins_low > 0:     
+        extra_low = numpy.zeros(nbins_low)
+    extra_high = numpy.zeros(nbins_high) 
+    # Make new width array
+    new_width = numpy.append(extra_low, width)
+    new_width = numpy.append(new_width, extra_high)
+    
+    # nbins corrections due to the negative q value
+    nbins_low = nbins_low - len(data_x_ext[data_x_ext <= 0])
+    return nbins_low, nbins_high, \
+             new_width[data_x_ext > 0], data_x_ext[data_x_ext > 0]
+
+
+
+from .resolution import Slit1D, Pinhole1D
+class PySmear(object):
+    """
+    Wrapper for pure python sasmodels resolution functions.
+    """
+    def __init__(self, resolution, model):
+        self.model = model
+        self.resolution = resolution
+        self.offset = numpy.searchsorted(self.resolution.q_calc, self.resolution.q[0])
+
+    def apply(self, iq_in, first_bin=0, last_bin=None):
+        """
+        Apply the resolution function to the data.
+
+        Note that this is called with iq_in matching data.x, but with
+        iq_in[first_bin:last_bin] set to theory values for these bins,
+        and the remainder left undefined.  The first_bin, last_bin values
+        should be those returned from get_bin_range.
+
+        The returned value is of the same length as iq_in, with the range
+        first_bin:last_bin set to the resolution smeared values.
+        """
+        if last_bin is None: last_bin = len(iq_in)
+        start, end = first_bin + self.offset, last_bin + self.offset
+        q_calc = self.resolution.q_calc
+        iq_calc = numpy.empty_like(q_calc)
+        if start > 0:
+            iq_calc[:start] = self.model.evalDistribution(q_calc[:start])
+        if end+1 < len(q_calc):
+            iq_calc[end+1:] = self.model.evalDistribution(q_calc[end+1:])
+        iq_calc[start:end+1] = iq_in[first_bin:last_bin+1]
+        smeared = self.resolution.apply(iq_calc)
+        return smeared
+    __call__ = apply
+
+    def get_bin_range(self, q_min=None, q_max=None):
+        """
+        For a given q_min, q_max, find the corresponding indices in the data.
+
+        Returns first, last.
+
+        Note that these are indexes into q from the data, not the q_calc
+        needed by the resolution function.  Note also that these are the
+        indices, not the range limits.  That is, the complete range will be
+        q[first:last+1].
+        """
+        q = self.resolution.q
+        first = numpy.searchsorted(q, q_min)
+        last = numpy.searchsorted(q, q_max)
+        return first, min(last,len(q)-1)
+
+def slit_smear(data, model=None):
+    q = data.x
+    width = data.dxw if data.dxw is not None else 0
+    height = data.dxl if data.dxl is not None else 0
+    # TODO: width and height seem to be reversed
+    return PySmear(Slit1D(q, height, width), model)
+
+def pinhole_smear(data, model=None):
+    q = data.x
+    width = data.dx if data.dx is not None else 0
+    return PySmear(Pinhole1D(q, width), model)
Index: src/sas/sascalc/data_util/resolution.py
===================================================================
--- src/sas/sascalc/data_util/resolution.py	(revision fdeabcc6a0073f9b82018049bfb82d3d01563e9e)
+++ src/sas/sascalc/data_util/resolution.py	(revision fdeabcc6a0073f9b82018049bfb82d3d01563e9e)
@@ -0,0 +1,1077 @@
+"""
+Define the resolution functions for the data.
+
+This defines classes for 1D and 2D resolution calculations.
+"""
+from __future__ import division
+from scipy.special import erf
+from numpy import sqrt, log, log10
+import numpy as np
+
+MINIMUM_RESOLUTION = 1e-8
+
+
+# When extrapolating to -q, what is the minimum positive q relative to q_min
+# that we wish to calculate?
+MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION = 0.01
+
+class Resolution(object):
+    """
+    Abstract base class defining a 1D resolution function.
+
+    *q* is the set of q values at which the data is measured.
+
+    *q_calc* is the set of q values at which the theory needs to be evaluated.
+    This may extend and interpolate the q values.
+
+    *apply* is the method to call with I(q_calc) to compute the resolution
+    smeared theory I(q).
+    """
+    q = None
+    q_calc = None
+    def apply(self, theory):
+        """
+        Smear *theory* by the resolution function, returning *Iq*.
+        """
+        raise NotImplementedError("Subclass does not define the apply function")
+
+
+class Perfect1D(Resolution):
+    """
+    Resolution function to use when there is no actual resolution smearing
+    to be applied.  It has the same interface as the other resolution
+    functions, but returns the identity function.
+    """
+    def __init__(self, q):
+        self.q_calc = self.q = q
+
+    def apply(self, theory):
+        return theory
+
+
+class Pinhole1D(Resolution):
+    r"""
+    Pinhole aperture with q-dependent gaussian resolution.
+
+    *q* points at which the data is measured.
+
+    *q_width* gaussian 1-sigma resolution at each data point.
+
+    *q_calc* is the list of points to calculate, or None if this should
+    be estimated from the *q* and *q_width*.
+    """
+    def __init__(self, q, q_width, q_calc=None, nsigma=3):
+        #*min_step* is the minimum point spacing to use when computing the
+        #underlying model.  It should be on the order of
+        #$\tfrac{1}{10}\tfrac{2\pi}{d_\text{max}}$ to make sure that fringes
+        #are computed with sufficient density to avoid aliasing effects.
+
+        # Protect against calls with q_width=0.  The extend_q function will
+        # not extend the q if q_width is 0, but q_width must be non-zero when
+        # constructing the weight matrix to avoid division by zero errors.
+        # In practice this should never be needed, since resolution should
+        # default to Perfect1D if the pinhole geometry is not defined.
+        self.q, self.q_width = q, q_width
+        self.q_calc = pinhole_extend_q(q, q_width, nsigma=nsigma) \
+            if q_calc is None else np.sort(q_calc)
+        self.weight_matrix = pinhole_resolution(self.q_calc,
+                self.q, np.maximum(q_width, MINIMUM_RESOLUTION))
+
+    def apply(self, theory):
+        return apply_resolution_matrix(self.weight_matrix, theory)
+
+
+class Slit1D(Resolution):
+    """
+    Slit aperture with a complicated resolution function.
+
+    *q* points at which the data is measured.
+
+    *qx_width* slit width
+
+    *qy_height* slit height
+
+    *q_calc* is the list of points to calculate, or None if this should
+    be estimated from the *q* and *q_width*.
+
+    The *weight_matrix* is computed by :func:`slit1d_resolution`
+    """
+    def __init__(self, q, width, height=0., q_calc=None):
+        # Remember what width/height was used even though we won't need them
+        # after the weight matrix is constructed
+        self.width, self.height = width, height
+
+        # Allow independent resolution on each point even though it is not
+        # needed in practice.
+        if np.isscalar(width):
+            width = np.ones(len(q))*width
+        else:
+            width = np.asarray(width)
+        if np.isscalar(height):
+            height = np.ones(len(q))*height
+        else:
+            height = np.asarray(height)
+
+        self.q = q.flatten()
+        self.q_calc = slit_extend_q(q, width, height) \
+            if q_calc is None else np.sort(q_calc)
+        self.weight_matrix = \
+            slit_resolution(self.q_calc, self.q, width, height)
+
+    def apply(self, theory):
+        return apply_resolution_matrix(self.weight_matrix, theory)
+
+
+def apply_resolution_matrix(weight_matrix, theory):
+    """
+    Apply the resolution weight matrix to the computed theory function.
+    """
+    #print "apply shapes", theory.shape, weight_matrix.shape
+    Iq = np.dot(theory[None,:], weight_matrix)
+    #print "result shape",Iq.shape
+    return Iq.flatten()
+
+
+def pinhole_resolution(q_calc, q, q_width):
+    """
+    Compute the convolution matrix *W* for pinhole resolution 1-D data.
+
+    Each row *W[i]* determines the normalized weight that the corresponding
+    points *q_calc* contribute to the resolution smeared point *q[i]*.  Given
+    *W*, the resolution smearing can be computed using *dot(W,q)*.
+
+    *q_calc* must be increasing.  *q_width* must be greater than zero.
+    """
+    # The current algorithm is a midpoint rectangle rule.  In the test case,
+    # neither trapezoid nor Simpson's rule improved the accuracy.
+    edges = bin_edges(q_calc)
+    edges[edges<0.0] = 0.0 # clip edges below zero
+    G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] )
+    weights = G[1:] - G[:-1]
+    weights /= np.sum(weights, axis=0)[None,:]
+    return weights
+
+
+def slit_resolution(q_calc, q, width, height, n_height=30):
+    r"""
+    Build a weight matrix to compute *I_s(q)* from *I(q_calc)*, given
+    $q_\perp$ = *width* and $q_\parallel$ = *height*.  *n_height* is
+    is the number of steps to use in the integration over $q_\parallel$
+    when both $q_\perp$ and $q_\parallel$ are non-zero.
+
+    Each $q$ can have an independent width and height value even though
+    current instruments use the same slit setting for all measured points.
+
+    If slit height is large relative to width, use:
+
+    .. math::
+
+        I_s(q_i) = \frac{1}{\Delta q_\perp}
+            \int_0^{\Delta q_\perp} I(\sqrt{q_i^2 + q_\perp^2} dq_\perp
+
+    If slit width is large relative to height, use:
+
+    .. math::
+
+        I_s(q_i) = \frac{1}{2 \Delta q_\parallel}
+            \int_{-\Delta q_\parallel}^{\Delta q_\parallel}
+                I(|q_i + q_\parallel|) dq_\parallel
+
+    For a mixture of slit width and height use:
+
+    .. math::
+
+        I_s(q_i) = \frac{1}{2 \Delta q_\parallel \Delta q_\perp}
+            \int_{-\Delta q_\parallel)^{\Delta q_parallel}
+            \int_0^[\Delta q_\perp}
+                I(\sqrt{(q_i + q_\parallel)^2 + q_\perp^2})
+                dq_\perp dq_\parallel
+
+
+    Definition
+    ----------
+
+    We are using the mid-point integration rule to assign weights to each
+    element of a weight matrix $W$ so that
+
+    .. math::
+
+        I_s(q) = W I(q_\text{calc})
+
+    If *q_calc* is at the mid-point, we can infer the bin edges from the
+    pairwise averages of *q_calc*, adding the missing edges before
+    *q_calc[0]* and after *q_calc[-1]*.
+
+    For $q_\parallel = 0$, the smeared value can be computed numerically
+    using the $u$ substitution
+
+    .. math::
+
+        u_j = \sqrt{q_j^2 - q^2}
+
+    This gives
+
+    .. math::
+
+        I_s(q) \approx \sum_j I(u_j) \Delta u_j
+
+    where $I(u_j)$ is the value at the mid-point, and $\Delta u_j$ is the
+    difference between consecutive edges which have been first converted
+    to $u$.  Only $u_j \in [0, \Delta q_\perp]$ are used, which corresponds
+    to $q_j \in [q, \sqrt{q^2 + \Delta q_\perp}]$, so
+
+    .. math::
+
+        W_{ij} = \frac{1}{\Delta q_\perp} \Delta u_j
+               = \frac{1}{\Delta q_\perp}
+                    \sqrt{q_{j+1}^2 - q_i^2} - \sqrt{q_j^2 - q_i^2}
+            \text{if} q_j \in [q_i, \sqrt{q_i^2 + q_\perp^2}]
+
+    where $I_s(q_i)$ is the theory function being computed and $q_j$ are the
+    mid-points between the calculated values in *q_calc*.  We tweak the
+    edges of the initial and final intervals so that they lie on integration
+    limits.
+
+    (To be precise, the transformed midpoint $u(q_j)$ is not necessarily the
+    midpoint of the edges $u((q_{j-1}+q_j)/2)$ and $u((q_j + q_{j+1})/2)$,
+    but it is at least in the interval, so the approximation is going to be
+    a little better than the left or right Riemann sum, and should be
+    good enough for our purposes.)
+
+    For $q_\perp = 0$, the $u$ substitution is simpler:
+
+    .. math::
+
+        u_j = |q_j - q|
+
+    so
+
+    .. math::
+
+        W_ij = \frac{1}{2 \Delta q_\parallel} \Delta u_j
+            = \frac{1}{2 \Delta q_\parallel} (q_{j+1} - q_j)
+            \text{if} q_j \in [q-\Delta q_\parallel, q+\Delta q_\parallel]
+
+    However, we need to support cases were $u_j < 0$, which means using
+    $2 (q_{j+1} - q_j)$ when $q_j \in [0, q_\parallel-q_i]$.  This is not
+    an issue for $q_i > q_\parallel$.
+
+    For bot $q_\perp > 0$ and $q_\parallel > 0$ we perform a 2 dimensional
+    integration with
+
+    .. math::
+
+        u_jk = \sqrt{q_j^2 - (q + (k\Delta q_\parallel/L))^2}
+            \text{for} k = -L \ldots L
+
+    for $L$ = *n_height*.  This gives
+
+    .. math::
+
+        W_{ij} = \frac{1}{2 \Delta q_\perp q_\parallel}
+            \sum_{k=-L}^L \Delta u_jk (\frac{\Delta q_\parallel}{2 L + 1}
+
+
+    """
+    #np.set_printoptions(precision=6, linewidth=10000)
+
+    # The current algorithm is a midpoint rectangle rule.
+    q_edges = bin_edges(q_calc) # Note: requires q > 0
+    q_edges[q_edges<0.0] = 0.0 # clip edges below zero
+    weights = np.zeros((len(q), len(q_calc)), 'd')
+
+    #print q_calc
+    for i, (qi, w, h) in enumerate(zip(q, width, height)):
+        if w == 0. and h == 0.:
+            # Perfect resolution, so return the theory value directly.
+            # Note: assumes that q is a subset of q_calc.  If qi need not be
+            # in q_calc, then we can do a weighted interpolation by looking
+            # up qi in q_calc, then weighting the result by the relative
+            # distance to the neighbouring points.
+            weights[i, :] = (q_calc == qi)
+        elif h == 0:
+            weights[i, :] = _q_perp_weights(q_edges, qi, w)
+        elif w == 0:
+            in_x = 1.0 * ((q_calc >= qi-h) & (q_calc <= qi+h))
+            abs_x = 1.0*(q_calc < abs(qi - h)) if qi < h else 0.
+            #print qi - h, qi + h
+            #print in_x + abs_x
+            weights[i,:] = (in_x + abs_x) * np.diff(q_edges) / (2*h)
+        else:
+            L = n_height
+            for k in range(-L, L+1):
+                weights[i,:] += _q_perp_weights(q_edges, qi+k*h/L, w)
+            weights[i,:] /= 2*L + 1
+
+    return weights.T
+
+
+def _q_perp_weights(q_edges, qi, w):
+    # Convert bin edges from q to u
+    u_limit = np.sqrt(qi**2 + w**2)
+    u_edges = q_edges**2 - qi**2
+    u_edges[q_edges < abs(qi)] = 0.
+    u_edges[q_edges > u_limit] = u_limit**2 - qi**2
+    weights = np.diff(np.sqrt(u_edges))/w
+    #print "i, qi",i,qi,qi+width
+    #print q_calc
+    #print weights
+    return weights
+
+
+def pinhole_extend_q(q, q_width, nsigma=3):
+    """
+    Given *q* and *q_width*, find a set of sampling points *q_calc* so
+    that each point I(q) has sufficient support from the underlying
+    function.
+    """
+    q_min, q_max = np.min(q - nsigma*q_width), np.max(q + nsigma*q_width)
+    return linear_extrapolation(q, q_min, q_max)
+
+
+def slit_extend_q(q, width, height):
+    """
+    Given *q*, *width* and *height*, find a set of sampling points *q_calc* so
+    that each point I(q) has sufficient support from the underlying
+    function.
+    """
+    q_min, q_max = np.min(q-height), np.max(np.sqrt((q+height)**2 + width**2))
+
+    return geometric_extrapolation(q, q_min, q_max)
+
+
+def bin_edges(x):
+    """
+    Determine bin edges from bin centers, assuming that edges are centered
+    between the bins.
+
+    Note: this uses the arithmetic mean, which may not be appropriate for
+    log-scaled data.
+    """
+    if len(x) < 2 or (np.diff(x)<0).any():
+        raise ValueError("Expected bins to be an increasing set")
+    edges = np.hstack([
+        x[0]  - 0.5*(x[1]  - x[0]),  # first point minus half first interval
+        0.5*(x[1:] + x[:-1]),        # mid points of all central intervals
+        x[-1] + 0.5*(x[-1] - x[-2]), # last point plus half last interval
+        ])
+    return edges
+
+
+def interpolate(q, max_step):
+    """
+    Returns *q_calc* with points spaced at most max_step apart.
+    """
+    step = np.diff(q)
+    index = step>max_step
+    if np.any(index):
+        inserts = []
+        for q_i,step_i in zip(q[:-1][index],step[index]):
+            n = np.ceil(step_i/max_step)
+            inserts.extend(q_i + np.arange(1,n)*(step_i/n))
+        # Extend a couple of fringes beyond the end of the data
+        inserts.extend(q[-1] + np.arange(1,8)*max_step)
+        q_calc = np.sort(np.hstack((q,inserts)))
+    else:
+        q_calc = q
+    return q_calc
+
+
+def linear_extrapolation(q, q_min, q_max):
+    """
+    Extrapolate *q* out to [*q_min*, *q_max*] using the step size in *q* as
+    a guide.  Extrapolation below uses about the same size as the first
+    interval.  Extrapolation above uses about the same size as the final
+    interval.
+
+    if *q_min* is zero or less then *q[0]/10* is used instead.
+    """
+    q = np.sort(q)
+    if q_min < q[0]:
+        if q_min <= 0: q_min = q_min*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION
+        n_low = np.ceil((q[0]-q_min) / (q[1]-q[0])) if q[1]>q[0] else 15
+        q_low = np.linspace(q_min, q[0], n_low+1)[:-1]
+    else:
+        q_low = []
+    if q_max > q[-1]:
+        n_high = np.ceil((q_max-q[-1]) / (q[-1]-q[-2])) if q[-1]>q[-2] else 15
+        q_high = np.linspace(q[-1], q_max, n_high+1)[1:]
+    else:
+        q_high = []
+    return np.concatenate([q_low, q, q_high])
+
+
+def geometric_extrapolation(q, q_min, q_max, points_per_decade=None):
+    r"""
+    Extrapolate *q* to [*q_min*, *q_max*] using geometric steps, with the
+    average geometric step size in *q* as the step size.
+
+    if *q_min* is zero or less then *q[0]/10* is used instead.
+
+    *points_per_decade* sets the ratio between consecutive steps such
+    that there will be $n$ points used for every factor of 10 increase
+    in *q*.
+
+    If *points_per_decade* is not given, it will be estimated as follows.
+    Starting at $q_1$ and stepping geometrically by $\Delta q$ to $q_n$
+    in $n$ points gives a geometric average of:
+
+    .. math::
+
+         \log \Delta q = (\log q_n - log q_1) / (n - 1)
+
+    From this we can compute the number of steps required to extend $q$
+    from $q_n$ to $q_\text{max}$ by $\Delta q$ as:
+
+    .. math::
+
+         n_\text{extend} = (\log q_\text{max} - \log q_n) / \log \Delta q
+
+    Substituting:
+
+         n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n)
+            / (\log q_n - log q_1)
+    """
+    q = np.sort(q)
+    if points_per_decade is None:
+        log_delta_q = (len(q) - 1) / (log(q[-1]) - log(q[0]))
+    else:
+        log_delta_q = log(10.) / points_per_decade
+    if q_min < q[0]:
+        if q_min < 0: q_min = q[0]*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION
+        n_low = log_delta_q * (log(q[0])-log(q_min))
+        q_low  = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1]
+    else:
+        q_low = []
+    if q_max > q[-1]:
+        n_high = log_delta_q * (log(q_max)-log(q[-1]))
+        q_high = np.logspace(log10(q[-1]), log10(q_max), np.ceil(n_high)+1)[1:]
+    else:
+        q_high = []
+    return np.concatenate([q_low, q, q_high])
+
+
+############################################################################
+# unit tests
+############################################################################
+import unittest
+
+
+def eval_form(q, form, pars):
+    from sasmodels import core
+    kernel = core.make_kernel(form, [q])
+    theory = core.call_kernel(kernel, pars)
+    kernel.release()
+    return theory
+
+
+def gaussian(q, q0, dq):
+    from numpy import exp, pi
+    return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq)
+
+
+def romberg_slit_1d(q, width, height, form, pars):
+    """
+    Romberg integration for slit resolution.
+
+    This is an adaptive integration technique.  It is called with settings
+    that make it slow to evaluate but give it good accuracy.
+    """
+    from scipy.integrate import romberg, dblquad
+
+    if any(k not in form.info['defaults'] for k in pars.keys()):
+        keys = set(form.info['defaults'].keys())
+        extra = set(pars.keys()) - keys
+        raise ValueError("bad parameters: [%s] not in [%s]"%
+                         (", ".join(sorted(extra)), ", ".join(sorted(keys))))
+
+    if np.isscalar(width):
+        width = [width]*len(q)
+    if np.isscalar(height):
+        height = [height]*len(q)
+    _int_w = lambda w, qi: eval_form(sqrt(qi**2 + w**2), form, pars)
+    _int_h = lambda h, qi: eval_form(abs(qi+h), form, pars)
+    # If both width and height are defined, then it is too slow to use dblquad.
+    # Instead use trapz on a fixed grid, interpolated into the I(Q) for
+    # the extended Q range.
+    #_int_wh = lambda w, h, qi: eval_form(sqrt((qi+h)**2 + w**2), form, pars)
+    q_calc = slit_extend_q(q, np.asarray(width), np.asarray(height))
+    Iq = eval_form(q_calc, form, pars)
+    result = np.empty(len(q))
+    for i, (qi, w, h) in enumerate(zip(q, width, height)):
+        if h == 0.:
+            r = romberg(_int_w, 0, w, args=(qi,),
+                        divmax=100, vec_func=True, tol=0, rtol=1e-8)
+            result[i] = r/w
+        elif w == 0.:
+            r = romberg(_int_h, -h, h, args=(qi,),
+                        divmax=100, vec_func=True, tol=0, rtol=1e-8)
+            result[i] = r/(2*h)
+        else:
+            w_grid = np.linspace(0, w, 21)[None,:]
+            h_grid = np.linspace(-h, h, 23)[:,None]
+            u = sqrt((qi+h_grid)**2 + w_grid**2)
+            Iu = np.interp(u, q_calc, Iq)
+            #print np.trapz(Iu, w_grid, axis=1)
+            Is = np.trapz(np.trapz(Iu, w_grid, axis=1), h_grid[:,0])
+            result[i] = Is / (2*h*w)
+            """
+            r, err = dblquad(_int_wh, -h, h, lambda h: 0., lambda h: w,
+                             args=(qi,))
+            result[i] = r/(w*2*h)
+            """
+
+    # r should be [float, ...], but it is [array([float]), array([float]),...]
+    return result
+
+
+def romberg_pinhole_1d(q, q_width, form, pars, nsigma=5):
+    """
+    Romberg integration for pinhole resolution.
+
+    This is an adaptive integration technique.  It is called with settings
+    that make it slow to evaluate but give it good accuracy.
+    """
+    from scipy.integrate import romberg
+
+    if any(k not in form.info['defaults'] for k in pars.keys()):
+        keys = set(form.info['defaults'].keys())
+        extra = set(pars.keys()) - keys
+        raise ValueError("bad parameters: [%s] not in [%s]"%
+                         (", ".join(sorted(extra)), ", ".join(sorted(keys))))
+
+    _fn = lambda q, q0, dq: eval_form(q, form, pars)*gaussian(q, q0, dq)
+    r = [romberg(_fn, max(qi-nsigma*dqi,1e-10*q[0]), qi+nsigma*dqi, args=(qi, dqi),
+                 divmax=100, vec_func=True, tol=0, rtol=1e-8)
+         for qi,dqi in zip(q,q_width)]
+    return np.asarray(r).flatten()
+
+
+class ResolutionTest(unittest.TestCase):
+
+    def setUp(self):
+        self.x = 0.001*np.arange(1, 11)
+        self.y = self.Iq(self.x)
+
+    def Iq(self, q):
+        "Linear function for resolution unit test"
+        return 12.0 - 1000.0*q
+
+    def test_perfect(self):
+        """
+        Perfect resolution and no smearing.
+        """
+        resolution = Perfect1D(self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        np.testing.assert_equal(output, self.y)
+
+    def test_slit_zero(self):
+        """
+        Slit smearing with perfect resolution.
+        """
+        resolution = Slit1D(self.x, width=0, height=0, q_calc=self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        np.testing.assert_equal(output, self.y)
+
+    @unittest.skip("not yet supported")
+    def test_slit_high(self):
+        """
+        Slit smearing with height 0.005
+        """
+        resolution = Slit1D(self.x, width=0, height=0.005, q_calc=self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        answer = [ 9.0618, 8.6402, 8.1187, 7.1392, 6.1528,
+                   5.5555, 4.5584, 3.5606, 2.5623, 2.0000 ]
+        np.testing.assert_allclose(output, answer, atol=1e-4)
+
+    @unittest.skip("not yet supported")
+    def test_slit_both_high(self):
+        """
+        Slit smearing with width < 100*height.
+        """
+        q = np.logspace(-4, -1, 10)
+        resolution = Slit1D(q, width=0.2, height=np.inf)
+        theory = 1000*self.Iq(resolution.q_calc**4)
+        output = resolution.apply(theory)
+        answer = [ 8.85785, 8.43012, 7.92687, 6.94566, 6.03660,
+                   5.40363, 4.40655, 3.40880, 2.41058, 2.00000 ]
+        np.testing.assert_allclose(output, answer, atol=1e-4)
+
+    @unittest.skip("not yet supported")
+    def test_slit_wide(self):
+        """
+        Slit smearing with width 0.0002
+        """
+        resolution = Slit1D(self.x, width=0.0002, height=0, q_calc=self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
+        np.testing.assert_allclose(output, answer, atol=1e-4)
+
+    @unittest.skip("not yet supported")
+    def test_slit_both_wide(self):
+        """
+        Slit smearing with width > 100*height.
+        """
+        resolution = Slit1D(self.x, width=0.0002, height=0.000001,
+                            q_calc=self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ]
+        np.testing.assert_allclose(output, answer, atol=1e-4)
+
+    def test_pinhole_zero(self):
+        """
+        Pinhole smearing with perfect resolution
+        """
+        resolution = Pinhole1D(self.x, 0.0*self.x)
+        theory = self.Iq(resolution.q_calc)
+        output = resolution.apply(theory)
+        np.testing.assert_equal(output, self.y)
+
+    def test_pinhole(self):
+        """
+        Pinhole smearing with dQ = 0.001 [Note: not dQ/Q = 0.001]
+        """
+        resolution = Pinhole1D(self.x, 0.001*np.ones_like(self.x),
+                               q_calc=self.x)
+        theory = 12.0-1000.0*resolution.q_calc
+        output = resolution.apply(theory)
+        answer = [ 10.44785079, 9.84991299, 8.98101708,
+                  7.99906585, 6.99998311, 6.00001689,
+                  5.00093415, 4.01898292, 3.15008701, 2.55214921]
+        np.testing.assert_allclose(output, answer, atol=1e-8)
+
+
+class IgorComparisonTest(unittest.TestCase):
+
+    def setUp(self):
+        self.pars = TEST_PARS_PINHOLE_SPHERE
+        from sasmodels import core
+        from sasmodels.models import sphere
+        self.model = core.load_model(sphere, dtype='double')
+
+    def Iq_sphere(self, pars, resolution):
+        from sasmodels import core
+        kernel = core.make_kernel(self.model, [resolution.q_calc])
+        theory = core.call_kernel(kernel, pars)
+        result = resolution.apply(theory)
+        kernel.release()
+        return result
+
+    def compare(self, q, output, answer, tolerance):
+        #err = (output - answer)/answer
+        #idx = abs(err) >= tolerance
+        #problem = zip(q[idx], output[idx], answer[idx], err[idx])
+        #print "\n".join(str(v) for v in problem)
+        np.testing.assert_allclose(output, answer, rtol=tolerance)
+
+    def test_perfect(self):
+        """
+        Compare sphere model with NIST Igor SANS, no resolution smearing.
+        """
+        pars = TEST_PARS_SLIT_SPHERE
+        data_string = TEST_DATA_SLIT_SPHERE
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, width, answer, _ = data
+        resolution = Perfect1D(q)
+        output = self.Iq_sphere(pars, resolution)
+        self.compare(q, output, answer, 1e-6)
+
+    def test_pinhole(self):
+        """
+        Compare pinhole resolution smearing with NIST Igor SANS
+        """
+        pars = TEST_PARS_PINHOLE_SPHERE
+        data_string = TEST_DATA_PINHOLE_SPHERE
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, q_width, answer = data
+        resolution = Pinhole1D(q, q_width)
+        output = self.Iq_sphere(pars, resolution)
+        # TODO: relative error should be lower
+        self.compare(q, output, answer, 3e-4)
+
+    def test_pinhole_romberg(self):
+        """
+        Compare pinhole resolution smearing with romberg integration result.
+        """
+        pars = TEST_PARS_PINHOLE_SPHERE
+        data_string = TEST_DATA_PINHOLE_SPHERE
+        pars['radius'] *= 5
+        radius = pars['radius']
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, q_width, answer = data
+        answer = romberg_pinhole_1d(q, q_width, self.model, pars)
+        ## Getting 0.1% requires 5 sigma and 200 points per fringe
+        #q_calc = interpolate(pinhole_extend_q(q, q_width, nsigma=5),
+        #                     2*np.pi/radius/200)
+        #tol = 0.001
+        ## The default 3 sigma and no extra points gets 1%
+        q_calc, tol = None, 0.01
+        resolution = Pinhole1D(q, q_width, q_calc=q_calc)
+        output = self.Iq_sphere(pars, resolution)
+        if 0: # debug plot
+            import matplotlib.pyplot as plt
+            resolution = Perfect1D(q)
+            source = self.Iq_sphere(pars, resolution)
+            plt.loglog(q, source, '.')
+            plt.loglog(q, answer, '-', hold=True)
+            plt.loglog(q, output, '-', hold=True)
+            plt.show()
+        self.compare(q, output, answer, tol)
+
+    def test_slit(self):
+        """
+        Compare slit resolution smearing with NIST Igor SANS
+        """
+        pars = TEST_PARS_SLIT_SPHERE
+        data_string = TEST_DATA_SLIT_SPHERE
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, delta_qv, _, answer = data
+        resolution = Slit1D(q, width=delta_qv, height=0)
+        output = self.Iq_sphere(pars, resolution)
+        # TODO: eliminate Igor test since it is too inaccurate to be useful.
+        # This means we can eliminate the test data as well, and instead
+        # use a generated q vector.
+        self.compare(q, output, answer, 0.5)
+
+    def test_slit_romberg(self):
+        """
+        Compare slit resolution smearing with romberg integration result.
+        """
+        pars = TEST_PARS_SLIT_SPHERE
+        data_string = TEST_DATA_SLIT_SPHERE
+        radius = pars['radius']
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, delta_qv, _, answer = data
+        answer = romberg_slit_1d(q, delta_qv, 0., self.model, pars)
+        q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20),
+                               delta_qv[0], 0.)
+        resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc)
+        output = self.Iq_sphere(pars, resolution)
+        # TODO: relative error should be lower
+        self.compare(q, output, answer, 0.025)
+
+    def test_ellipsoid(self):
+        """
+        Compare romberg integration for ellipsoid model.
+        """
+        from .core import load_model
+        pars = {
+            'scale':0.05,
+            'rpolar':500, 'requatorial':15000,
+            'sld':6, 'solvent_sld': 1,
+            }
+        form = load_model('ellipsoid', dtype='double')
+        q = np.logspace(log10(4e-5),log10(2.5e-2), 68)
+        width, height = 0.117, 0.
+        resolution = Slit1D(q, width=width, height=height)
+        answer = romberg_slit_1d(q, width, height, form, pars)
+        output = resolution.apply(eval_form(resolution.q_calc, form, pars))
+        # TODO: 10% is too much error; use better algorithm
+        #print np.max(abs(answer-output)/answer)
+        self.compare(q, output, answer, 0.1)
+
+    #TODO: can sas q spacing be too sparse for the resolution calculation?
+    @unittest.skip("suppress sparse data test; not supported by current code")
+    def test_pinhole_sparse(self):
+        """
+        Compare pinhole resolution smearing with NIST Igor SANS on sparse data
+        """
+        pars = TEST_PARS_PINHOLE_SPHERE
+        data_string = TEST_DATA_PINHOLE_SPHERE
+
+        data = np.loadtxt(data_string.split('\n')).T
+        q, q_width, answer = data[:, ::20] # Take every nth point
+        resolution = Pinhole1D(q, q_width)
+        output = self.Iq_sphere(pars, resolution)
+        self.compare(q, output, answer, 1e-6)
+
+
+# pinhole sphere parameters
+TEST_PARS_PINHOLE_SPHERE = {
+    'scale': 1.0, 'background': 0.01,
+    'radius': 60.0, 'sld': 1, 'solvent_sld': 6.3,
+    }
+# Q, dQ, I(Q) calculated by NIST Igor SANS package
+TEST_DATA_PINHOLE_SPHERE = """\
+0.001278 0.0002847 2538.41176383
+0.001562 0.0002905 2536.91820405
+0.001846 0.0002956 2535.13182479
+0.002130 0.0003017 2533.06217813
+0.002414 0.0003087 2530.70378586
+0.002698 0.0003165 2528.05024192
+0.002982 0.0003249 2525.10408349
+0.003266 0.0003340 2521.86667499
+0.003550 0.0003437 2518.33907750
+0.003834 0.0003539 2514.52246995
+0.004118 0.0003646 2510.41798319
+0.004402 0.0003757 2506.02690988
+0.004686 0.0003872 2501.35067884
+0.004970 0.0003990 2496.38678318
+0.005253 0.0004112 2491.16237596
+0.005537 0.0004237 2485.63911673
+0.005821 0.0004365 2479.83657083
+0.006105 0.0004495 2473.75676948
+0.006389 0.0004628 2467.40145990
+0.006673 0.0004762 2460.77293372
+0.006957 0.0004899 2453.86724627
+0.007241 0.0005037 2446.69623838
+0.007525 0.0005177 2439.25775219
+0.007809 0.0005318 2431.55421398
+0.008093 0.0005461 2423.58785521
+0.008377 0.0005605 2415.36158137
+0.008661 0.0005750 2406.87009473
+0.008945 0.0005896 2398.12841186
+0.009229 0.0006044 2389.13360806
+0.009513 0.0006192 2379.88958042
+0.009797 0.0006341 2370.39776774
+0.010080 0.0006491 2360.69528793
+0.010360 0.0006641 2350.85169027
+0.010650 0.0006793 2340.42023633
+0.010930 0.0006945 2330.11206013
+0.011220 0.0007097 2319.20109972
+0.011500 0.0007251 2308.43503981
+0.011780 0.0007404 2297.44820179
+0.012070 0.0007558 2285.83853677
+0.012350 0.0007713 2274.41290746
+0.012640 0.0007868 2262.36219581
+0.012920 0.0008024 2250.51169731
+0.013200 0.0008180 2238.45596231
+0.013490 0.0008336 2225.76495666
+0.013770 0.0008493 2213.29618391
+0.014060 0.0008650 2200.19110751
+0.014340 0.0008807 2187.34050325
+0.014620 0.0008965 2174.30529864
+0.014910 0.0009123 2160.61632548
+0.015190 0.0009281 2147.21038112
+0.015470 0.0009440 2133.62023580
+0.015760 0.0009598 2119.37907426
+0.016040 0.0009757 2105.45234903
+0.016330 0.0009916 2090.86319102
+0.016610 0.0010080 2076.60576032
+0.016890 0.0010240 2062.19214565
+0.017180 0.0010390 2047.10550219
+0.017460 0.0010550 2032.38715621
+0.017740 0.0010710 2017.52560123
+0.018030 0.0010880 2001.99124318
+0.018310 0.0011040 1986.84662060
+0.018600 0.0011200 1971.03389745
+0.018880 0.0011360 1955.61395119
+0.019160 0.0011520 1940.08291563
+0.019450 0.0011680 1923.87672225
+0.019730 0.0011840 1908.10656374
+0.020020 0.0012000 1891.66297192
+0.020300 0.0012160 1875.66789021
+0.020580 0.0012320 1859.56357196
+0.020870 0.0012490 1842.79468290
+0.021150 0.0012650 1826.50064489
+0.021430 0.0012810 1810.11533702
+0.021720 0.0012970 1793.06840882
+0.022000 0.0013130 1776.51153580
+0.022280 0.0013290 1759.87201249
+0.022570 0.0013460 1742.57354412
+0.022850 0.0013620 1725.79397319
+0.023140 0.0013780 1708.35831550
+0.023420 0.0013940 1691.45256069
+0.023700 0.0014110 1674.48561783
+0.023990 0.0014270 1656.86525366
+0.024270 0.0014430 1639.79847285
+0.024550 0.0014590 1622.68887088
+0.024840 0.0014760 1604.96421100
+0.025120 0.0014920 1587.85768129
+0.025410 0.0015080 1569.99297335
+0.025690 0.0015240 1552.84580279
+0.025970 0.0015410 1535.54074115
+0.026260 0.0015570 1517.75249337
+0.026540 0.0015730 1500.40115023
+0.026820 0.0015900 1483.03632237
+0.027110 0.0016060 1465.05942429
+0.027390 0.0016220 1447.67682181
+0.027670 0.0016390 1430.46495191
+0.027960 0.0016550 1412.49232282
+0.028240 0.0016710 1395.13182318
+0.028520 0.0016880 1377.93439837
+0.028810 0.0017040 1359.99528971
+0.029090 0.0017200 1342.67274512
+0.029370 0.0017370 1325.55375609
+"""
+
+# Slit sphere parameters
+TEST_PARS_SLIT_SPHERE = {
+    'scale': 0.01, 'background': 0.01,
+    'radius': 60000, 'sld': 1, 'solvent_sld': 4,
+    }
+# Q dQ I(Q) I_smeared(Q)
+TEST_DATA_SLIT_SPHERE = """\
+2.26097e-05 0.117 5.5781372896e+09 1.4626077708e+06
+2.53847e-05 0.117 5.0363141458e+09 1.3117318023e+06
+2.81597e-05 0.117 4.4850108103e+09 1.1594863713e+06
+3.09347e-05 0.117 3.9364658459e+09 1.0094881630e+06
+3.37097e-05 0.117 3.4019975074e+09 8.6518941303e+05
+3.92597e-05 0.117 2.4139519814e+09 6.0232158311e+05
+4.48097e-05 0.117 1.5816877820e+09 3.8739994090e+05
+5.03597e-05 0.117 9.3715407224e+08 2.2745304775e+05
+5.59097e-05 0.117 4.8387917428e+08 1.2101295768e+05
+6.14597e-05 0.117 2.0193586928e+08 6.0055107771e+04
+6.70097e-05 0.117 5.5886110911e+07 3.2749521065e+04
+7.25597e-05 0.117 3.7782348010e+06 2.6350963616e+04
+7.81097e-05 0.117 5.3407817904e+06 2.9624963314e+04
+8.36597e-05 0.117 2.7975485523e+07 3.4403962254e+04
+8.92097e-05 0.117 4.9845448282e+07 3.6130017903e+04
+9.47597e-05 0.117 6.0092588905e+07 3.3495107849e+04
+1.00310e-04 0.117 5.6823430831e+07 2.7475726279e+04
+1.05860e-04 0.117 4.3857024036e+07 2.0144282226e+04
+1.11410e-04 0.117 2.7277144760e+07 1.3647403260e+04
+1.22510e-04 0.117 3.3119334113e+06 6.6519711526e+03
+1.33610e-04 0.117 1.4412859402e+06 6.9726212813e+03
+1.44710e-04 0.117 8.5620162463e+06 8.1441335775e+03
+1.55810e-04 0.117 9.6957429033e+06 6.4559996521e+03
+1.66910e-04 0.117 4.3818341914e+06 3.6252154156e+03
+1.78010e-04 0.117 2.7448997387e+05 2.4006505342e+03
+1.89110e-04 0.117 8.0472009936e+05 2.8187789089e+03
+2.00210e-04 0.117 2.8149552834e+06 3.0915662855e+03
+2.11310e-04 0.117 2.7510907861e+06 2.3722530293e+03
+2.22410e-04 0.117 1.0053133293e+06 1.4473468311e+03
+2.33510e-04 0.117 5.8428305052e+03 1.2048540556e+03
+2.44610e-04 0.117 5.1699305004e+05 1.4625670042e+03
+2.55710e-04 0.117 1.2120227268e+06 1.5010705973e+03
+2.66810e-04 0.117 9.7896842846e+05 1.1336343426e+03
+2.77910e-04 0.117 2.5507264791e+05 8.1848818080e+02
+3.05660e-04 0.117 5.2403101181e+05 7.4913374357e+02
+3.33410e-04 0.117 5.8699343809e+04 4.4669964560e+02
+3.61160e-04 0.117 3.0844327150e+05 4.6774007542e+02
+3.88910e-04 0.117 8.3360142970e+03 2.7169550220e+02
+4.16660e-04 0.117 1.8630080583e+05 3.0710983679e+02
+4.44410e-04 0.117 3.1616804732e-01 1.7959006831e+02
+4.72160e-04 0.117 1.1299016314e+05 2.0763952339e+02
+4.99910e-04 0.117 2.9952522747e+03 1.2536542765e+02
+5.27660e-04 0.117 6.7625695649e+04 1.4013969777e+02
+5.55410e-04 0.117 7.6927460089e+03 8.2145593180e+01
+6.10910e-04 0.117 1.1229057779e+04 8.4519745643e+01
+6.66410e-04 0.117 1.3035567943e+04 8.1554625609e+01
+7.21910e-04 0.117 1.3309931343e+04 7.4437319172e+01
+7.77410e-04 0.117 1.2462626212e+04 6.4697088261e+01
+8.32910e-04 0.117 1.0912927143e+04 5.3773301044e+01
+8.88410e-04 0.117 9.0172597469e+03 4.2843375753e+01
+9.43910e-04 0.117 7.0496495917e+03 3.2771032724e+01
+9.99410e-04 0.117 5.2030483682e+03 2.4113557144e+01
+1.05491e-03 0.117 3.5988976711e+03 1.7160773658e+01
+1.11041e-03 0.117 2.2996060652e+03 1.2016626459e+01
+1.22141e-03 0.117 6.4766590598e+02 6.0373017740e+00
+1.33241e-03 0.117 4.1963483264e+01 4.5215452974e+00
+1.44341e-03 0.117 6.3370708246e+01 5.1054681903e+00
+1.55441e-03 0.117 3.0736750577e+02 5.9176165298e+00
+1.66541e-03 0.117 5.0327682399e+02 5.9815000189e+00
+1.77641e-03 0.117 5.4084331454e+02 5.1634639625e+00
+1.88741e-03 0.117 4.3488671756e+02 3.8535158148e+00
+1.99841e-03 0.117 2.6322287860e+02 2.5824997753e+00
+2.10941e-03 0.117 1.0793633150e+02 1.7315517194e+00
+2.22041e-03 0.117 1.8474448850e+01 1.4077213604e+00
+2.33141e-03 0.117 1.5864062279e+00 1.4771560682e+00
+2.44241e-03 0.117 3.2267213848e+01 1.6916253448e+00
+2.55341e-03 0.117 7.4289116207e+01 1.8274751193e+00
+2.66441e-03 0.117 9.9000521929e+01 1.7706812289e+00
+"""
+
+def main():
+    """
+    Run tests given is sys.argv.
+
+    Returns 0 if success or 1 if any tests fail.
+    """
+    import sys
+    import xmlrunner
+
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
+
+    runner = xmlrunner.XMLTestRunner(output='logs')
+    result = runner.run(suite)
+    return 1 if result.failures or result.errors else 0
+
+
+############################################################################
+# usage demo
+############################################################################
+
+def _eval_demo_1d(resolution, title):
+    import sys
+    from sasmodels import core
+    name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder'
+
+    if name == 'cylinder':
+        pars = {'length':210, 'radius':500}
+    elif name == 'teubner_strey':
+        pars = {'a2':0.003, 'c1':-1e4, 'c2':1e10, 'background':0.312643}
+    elif name == 'sphere' or name == 'spherepy':
+        pars = TEST_PARS_SLIT_SPHERE
+    elif name == 'ellipsoid':
+        pars = {
+            'scale':0.05,
+            'rpolar':500, 'requatorial':15000,
+            'sld':6, 'solvent_sld': 1,
+            }
+    else:
+        pars = {}
+    defn = core.load_model_definition(name)
+    model = core.load_model(defn)
+
+    kernel = core.make_kernel(model, [resolution.q_calc])
+    theory = core.call_kernel(kernel, pars)
+    Iq = resolution.apply(theory)
+
+    if isinstance(resolution, Slit1D):
+        width, height = resolution.width, resolution.height
+        Iq_romb = romberg_slit_1d(resolution.q, width, height, model, pars)
+    else:
+        dq = resolution.q_width
+        Iq_romb = romberg_pinhole_1d(resolution.q, dq, model, pars)
+
+    import matplotlib.pyplot as plt
+    plt.loglog(resolution.q_calc, theory, label='unsmeared')
+    plt.loglog(resolution.q, Iq, label='smeared', hold=True)
+    plt.loglog(resolution.q, Iq_romb, label='romberg smeared', hold=True)
+    plt.legend()
+    plt.title(title)
+    plt.xlabel("Q (1/Ang)")
+    plt.ylabel("I(Q) (1/cm)")
+
+def demo_pinhole_1d():
+    q = np.logspace(-4, np.log10(0.2), 400)
+    q_width = 0.1*q
+    resolution = Pinhole1D(q, q_width)
+    _eval_demo_1d(resolution, title="10% dQ/Q Pinhole Resolution")
+
+def demo_slit_1d():
+    q = np.logspace(-4, np.log10(0.2), 100)
+    w = h = 0.
+    #w = 0.000000277790
+    w = 0.0277790
+    #h = 0.00277790
+    #h = 0.0277790
+    resolution = Slit1D(q, w, h)
+    _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w,h))
+
+def demo():
+    import matplotlib.pyplot as plt
+    plt.subplot(121)
+    demo_pinhole_1d()
+    #plt.yscale('linear')
+    plt.subplot(122)
+    demo_slit_1d()
+    #plt.yscale('linear')
+    plt.show()
+
+
+if __name__ == "__main__":
+    demo()
+    #main()
Index: src/sas/sascalc/data_util/smearing_2d.py
===================================================================
--- src/sas/sascalc/data_util/smearing_2d.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
+++ src/sas/sascalc/data_util/smearing_2d.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -0,0 +1,298 @@
+"""
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#See the license text in license.txt
+"""
+import numpy
+import math
+
+## Singular point
+SIGMA_ZERO = 1.0e-010
+## Limit of how many sigmas to be covered for the Gaussian smearing
+# default: 2.5 to cover 98.7% of Gaussian
+LIMIT = 3.0
+## Defaults
+R_BIN = {'Xhigh':10, 'High':5, 'Med':5, 'Low':3}
+PHI_BIN ={'Xhigh':20, 'High':12, 'Med':6, 'Low':4}   
+
+class Smearer2D:
+    """
+    Gaussian Q smearing class for SAS 2d data
+    """
+     
+    def __init__(self, data=None, model=None, index=None, 
+                 limit=LIMIT, accuracy='Low', coords='polar', engine='c'):
+        """
+        Assumption: equally spaced bins in dq_r, dq_phi space.
+        
+        :param data: 2d data used to set the smearing parameters
+        :param model: model function
+        :param index: 1d array with len(data) to define the range 
+         of the calculation: elements are given as True or False
+        :param nr: number of bins in dq_r-axis
+        :param nphi: number of bins in dq_phi-axis
+        :param coord: coordinates [string], 'polar' or 'cartesian'
+        :param engine: engine name [string]; 'c' or 'numpy'
+        """
+        ## data
+        self.data = data
+        ## model
+        self.model = model
+        ## Accuracy: Higher stands for more sampling points in both directions 
+        ## of r and phi.
+        self.accuracy = accuracy
+        ## number of bins in r axis for over-sampling 
+        self.nr = R_BIN
+        ## number of bins in phi axis for over-sampling 
+        self.nphi = PHI_BIN
+        ## maximum nsigmas
+        self.limit = limit
+        self.index = index
+        self.coords = coords
+        self.smearer = True
+        self._engine = engine
+        self.qx_data = None
+        self.qy_data = None
+        self.q_data = None
+        # dqx and dqy mean dq_parr and dq_perp
+        self.dqx_data = None
+        self.dqy_data = None
+        self.phi_data = None
+        
+    def get_data(self):   
+        """
+        Get qx_data, qy_data, dqx_data,dqy_data,
+        and calculate phi_data=arctan(qx_data/qy_data)
+        """
+        if self.data == None or self.data.__class__.__name__ == 'Data1D':
+            return None
+        if self.data.dqx_data == None or self.data.dqy_data == None:
+            return None
+        self.qx_data = self.data.qx_data[self.index]
+        self.qy_data = self.data.qy_data[self.index]
+        self.q_data = self.data.q_data[self.index]
+        # Here dqx and dqy mean dq_parr and dq_perp
+        self.dqx_data = self.data.dqx_data[self.index]
+        self.dqy_data = self.data.dqy_data[self.index]
+        self.phi_data = numpy.arctan(self.qx_data / self.qy_data)
+        ## Remove singular points if exists
+        self.dqx_data[self.dqx_data < SIGMA_ZERO] = SIGMA_ZERO
+        self.dqy_data[self.dqy_data < SIGMA_ZERO] = SIGMA_ZERO
+        return True
+    
+    def set_accuracy(self, accuracy='Low'):   
+        """
+        Set accuracy.
+        
+        :param accuracy:  string
+        """
+        self.accuracy = accuracy
+
+    def set_smearer(self, smearer=True):
+        """
+        Set whether or not smearer will be used
+        
+        :param smearer: smear object
+        
+        """
+        self.smearer = smearer
+        
+    def set_data(self, data=None):   
+        """
+        Set data.
+        
+        :param data: DataLoader.Data_info type
+        """
+        self.data = data
+  
+            
+    def set_model(self, model=None):   
+        """
+        Set model.
+        
+        :param model: sas.models instance
+        """
+        self.model = model  
+           
+    def set_index(self, index=None):   
+        """
+        Set index.
+        
+        :param index: 1d arrays
+        """
+        self.index = index       
+    
+    def get_value(self):
+        """
+        Over sampling of r_nbins times phi_nbins, calculate Gaussian weights, 
+        then find smeared intensity
+        """    
+        valid = self.get_data()
+        if valid == None:
+            return valid
+        # all zero values of dq
+        if numpy.all(numpy.fabs(self.dqx_data <= 1.1e-10)) and \
+                        numpy.all(numpy.fabs(self.dqy_data <= 1.1e-10)):
+            self.smearer = False
+  
+        if self.smearer == False:
+            return self.model.evalDistribution([self.qx_data, self.qy_data]) 
+
+        nr = self.nr[self.accuracy]
+        nphi = self.nphi[self.accuracy]
+        # Number of bins in the dqr direction (polar coordinate of dqx and dqy)
+        bin_size = self.limit / nr
+        # Total number of bins = # of bins 
+        # in dq_r-direction times # of bins in dq_phi-direction
+        n_bins = nr * nphi
+        # data length in the range of self.index
+        len_data = len(self.qx_data)
+        #len_datay = len(self.qy_data)
+        if self._engine == 'c' and self.coords == 'polar':
+            try:
+                import sas.models.sas_extension.smearer2d_helper as smearer2dc
+                smearc = smearer2dc.new_Smearer_helper(self.qx_data, 
+                                              self.qy_data,
+                                              self.dqx_data, self.dqy_data,
+                                              self.limit, nr, nphi, 
+                                              int(len_data))
+                weight_res = numpy.zeros(nr * nphi )
+                qx_res = numpy.zeros(nr * nphi * int(len_data))
+                qy_res = numpy.zeros(nr * nphi * int(len_data))
+                smearer2dc.smearer2d_helper(smearc, weight_res, qx_res, qy_res)
+            except:
+                raise 
+        else:
+            # Mean values of dqr at each bins 
+            # starting from the half of bin size
+            r = bin_size / 2.0 + numpy.arange(nr) * bin_size
+            # mean values of qphi at each bines
+            phi = numpy.arange(nphi)
+            dphi = phi * 2.0 * math.pi / nphi
+            dphi = dphi.repeat(nr)
+    
+            ## Transform to polar coordinate, 
+            #  and set dphi at each data points ; 1d array
+            dphi = dphi.repeat(len_data)
+            q_phi = self.qy_data / self.qx_data
+            
+            # Starting angle is different between polar 
+            #  and cartesian coordinates.
+            #if self.coords != 'polar':
+            #    dphi += numpy.arctan( q_phi * self.dqx_data/ \
+            #                  self.dqy_data).repeat(n_bins).reshape(len_data,\
+            #                                n_bins).transpose().flatten()
+    
+            # The angle (phi) of the original q point
+            q_phi = numpy.arctan(q_phi).repeat(n_bins).reshape(len_data,\
+                                                n_bins).transpose().flatten()
+            ## Find Gaussian weight for each dq bins: The weight depends only 
+            #  on r-direction (The integration may not need)
+            weight_res = numpy.exp(-0.5 * ((r - bin_size / 2.0) * \
+                                    (r - bin_size / 2.0)))- \
+                                    numpy.exp(-0.5 * ((r + bin_size / 2.0 ) *\
+                                    (r + bin_size / 2.0)))
+            # No needs of normalization here.
+            #weight_res /= numpy.sum(weight_res)
+            weight_res = weight_res.repeat(nphi).reshape(nr, nphi)
+    
+            weight_res = weight_res.transpose().flatten()
+            
+            ## Set dr for all dq bins for averaging
+            dr = r.repeat(nphi).reshape(nr, nphi).transpose().flatten()
+            ## Set dqr for all data points
+            dqx = numpy.outer(dr, self.dqx_data).flatten()
+            dqy = numpy.outer(dr, self.dqy_data).flatten()
+    
+            qx = self.qx_data.repeat(n_bins).reshape(len_data, \
+                                                 n_bins).transpose().flatten()
+            qy = self.qy_data.repeat(n_bins).reshape(len_data, \
+                                                 n_bins).transpose().flatten()
+    
+            # The polar needs rotation by -q_phi
+            if self.coords == 'polar':
+                q_r = numpy.sqrt(qx * qx + qy * qy)
+                qx_res = ((dqx*numpy.cos(dphi) + q_r) * numpy.cos(-q_phi) +\
+                               dqy*numpy.sin(dphi) * numpy.sin(-q_phi))
+                qy_res = (-(dqx*numpy.cos(dphi) + q_r) * numpy.sin(-q_phi) +\
+                               dqy*numpy.sin(dphi) * numpy.cos(-q_phi))
+            else:
+                qx_res = qx +  dqx*numpy.cos(dphi)
+                qy_res = qy +  dqy*numpy.sin(dphi)
+            
+        ## Evaluate all points
+        val = self.model.evalDistribution([qx_res, qy_res]) 
+        ## Reshape into 2d array to use numpy weighted averaging
+        value_res= val.reshape(n_bins, len(self.qx_data))
+        ## Averaging with Gaussian weighting: normalization included.
+        value =numpy.average(value_res,axis=0, weights=weight_res)
+        ## Return the smeared values in the range of self.index
+        return value
+"""    
+if __name__ == '__main__':
+    ## Test w/ 2D linear function
+    x = 0.001*numpy.arange(1, 11)
+    dx = numpy.ones(len(x))*0.0003
+    y = 0.001*numpy.arange(1, 11)
+    dy = numpy.ones(len(x))*0.001
+    z = numpy.ones(10)
+    dz = numpy.sqrt(z)
+    
+    from sas.sascalc.dataloader import Data2D
+    #for i in range(10): print i, 0.001 + i*0.008/9.0 
+    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) )) 
+    out = Data2D()
+    out.data = z
+    out.qx_data = x
+    out.qy_data = y
+    out.dqx_data = dx
+    out.dqy_data = dy
+    out.q_data = numpy.sqrt(dx * dx + dy * dy)
+    index = numpy.ones(len(x), dtype = bool)
+    out.mask = index
+    from sas.models.LineModel import LineModel
+    model = LineModel()
+    model.setParam("A", 0)
+
+    smear = Smearer2D(out, model, index)
+    #smear.set_accuracy('Xhigh')
+    value = smear.get_value()
+    ## All data are ones, so the smeared should also be ones.
+    print "Data length =", len(value)
+    print " 2D linear function, I = 0 + 1*qy"
+    text = " Gaussian weighted averaging on a 2D linear function will "
+    text += "provides the results same as without the averaging."
+    print text
+    print "qx_data", "qy_data", "I_nonsmear", "I_smeared"
+    for ind in range(len(value)):
+        print x[ind], y[ind], model.evalDistribution([x, y])[ind], value[ind]
+        
+  
+if __name__ == '__main__':
+    ## Another Test w/ constant function
+    x = 0.001*numpy.arange(1,11)
+    dx = numpy.ones(len(x))*0.001
+    y = 0.001*numpy.arange(1,11)
+    dy = numpy.ones(len(x))*0.001
+    z = numpy.ones(10)
+    dz = numpy.sqrt(z)
+    
+    from DataLoader import Data2D
+    #for i in range(10): print i, 0.001 + i*0.008/9.0 
+    #for i in range(100): print i, int(math.floor( (i/ (100/9.0)) )) 
+    out = Data2D()
+    out.data = z
+    out.qx_data = x
+    out.qy_data = y
+    out.dqx_data = dx
+    out.dqy_data = dy
+    index = numpy.ones(len(x), dtype = bool)
+    out.mask = index
+    from sas.models.Constant import Constant
+    model = Constant()
+
+    value = Smearer2D(out,model,index).get_value()
+    ## All data are ones, so the smeared values should also be ones.
+    print "Data length =",len(value), ", Data=",value
+"""    
Index: src/sas/sascalc/fit/AbstractFitEngine.py
===================================================================
--- src/sas/sascalc/fit/AbstractFitEngine.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/fit/AbstractFitEngine.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -140,5 +140,5 @@
             do the following: ::
             
-                from sas.models.qsmearing import smear_selection
+                from sas.sascalc.data_util.qsmearing import smear_selection
                 smearer = smear_selection(some_data)
                 fitdata1d = FitData1D( x= [1,3,..,],
Index: src/sas/sascalc/pr/fit/AbstractFitEngine.py
===================================================================
--- src/sas/sascalc/pr/fit/AbstractFitEngine.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/pr/fit/AbstractFitEngine.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -140,5 +140,5 @@
             do the following: ::
             
-                from sas.models.qsmearing import smear_selection
+                from sas.sascalc.data_util.qsmearing import smear_selection
                 smearer = smear_selection(some_data)
                 fitdata1d = FitData1D( x= [1,3,..,],
Index: src/sas/sasgui/guiframe/data_processor.py
===================================================================
--- src/sas/sasgui/guiframe/data_processor.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/guiframe/data_processor.py	(revision fce0139c1a0044b502cd736ab4de587f5297141d)
@@ -10,5 +10,7 @@
 The organization of the classes goes as:
 
-.. image::  ../../user/guiframe/BatchGridClassLayout.png
+#Path to this is:           /sasview/src/sas/sasgui/guiframe/data_processor.py
+#Path to image is:          /sasview/src/sas/sasgui/guiframe/media/BatchGridClassLayout.png
+.. image::  ./guiframe/media/BatchGridClassLayout.png
    :align:   center
 
Index: src/sas/sasgui/guiframe/dummyapp.py
===================================================================
--- src/sas/sasgui/guiframe/dummyapp.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/guiframe/dummyapp.py	(revision b080ba832c690bc7605edb1b413eaca7c8330cc6)
@@ -7,5 +7,5 @@
 from sas.sasgui.guiframe.plugin_base import PluginBase
 
-class DummyView(gui_manager.ViewApp):
+class DummyView(gui_manager.SasViewApp):
     """
     """
Index: src/sas/sasgui/guiframe/gui_manager.py
===================================================================
--- src/sas/sasgui/guiframe/gui_manager.py	(revision 0d534de9f869910429443e7898a0b7ab65db05a7)
+++ src/sas/sasgui/guiframe/gui_manager.py	(revision b080ba832c690bc7605edb1b413eaca7c8330cc6)
@@ -3480,4 +3480,4 @@
 
 if __name__ == "__main__":
-    app = ViewApp(0)
+    app = SasViewApp(0)
     app.MainLoop()
Index: src/sas/sasgui/perspectives/fitting/batchfitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/batchfitpage.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/batchfitpage.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -15,5 +15,5 @@
 
 from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent
-from sas.models.qsmearing import smear_selection
+from sas.sascalc.data_util.qsmearing import smear_selection
 from sas.sasgui.perspectives.fitting.fitpage import FitPage
 from sas.sasgui.perspectives.fitting.fitpage import check_data_validity
Index: src/sas/sasgui/perspectives/fitting/fit_thread.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision aac161f1844b41bce09c87d7b6c7a3fc1f1f8712)
@@ -96,4 +96,6 @@
             #some mssg and reset progress bar.
 
+            # Shouldn't this be re-raising? ConsoleUpdate doesn't act on it.
+            # raise KeyboardInterrupt
             if self.handler is not None:
                 self.handler.stop(msg=msg)
Index: src/sas/sasgui/perspectives/fitting/fitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitpage.py	(revision 7d265a865a8c1cafe9ff03df4daf316b140a51e8)
+++ src/sas/sasgui/perspectives/fitting/fitpage.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -26,5 +26,5 @@
 from sas.sasgui.perspectives.fitting.basepage import BasicPage as BasicPage
 from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent as PageInfoEvent
-from sas.models.qsmearing import smear_selection
+from sas.sascalc.data_util.qsmearing import smear_selection
 from .basepage import ModelTextCtrl
 
Index: src/sas/sasgui/perspectives/fitting/fitproblem.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -14,5 +14,5 @@
 ################################################################################
 import copy
-from sas.models.qsmearing import smear_selection
+from sas.sascalc.data_util.qsmearing import smear_selection
 
 class FitProblemComponent(object):
Index: src/sas/sasgui/perspectives/fitting/fitting.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitting.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/fitting.py	(revision aac161f1844b41bce09c87d7b6c7a3fc1f1f8712)
@@ -1483,4 +1483,10 @@
         try:
             index = 0
+            # Update potential simfit page(s)
+            if self.sim_page is not None:
+                self.sim_page._on_fit_complete()
+            if self.batch_page:
+                self.batch_page._on_fit_complete()
+            # Update all fit pages
             for uid in page_id:
                 res = result[index]
Index: src/sas/sasgui/perspectives/fitting/pagestate.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/pagestate.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/pagestate.py	(revision c10d9d6c8667422d3db329ac32d0528a6cce3fc6)
@@ -1613,5 +1613,5 @@
 
         if state.data is None:
-            data = sas.dataloader.data_info.Data1D(x=[], y=[])
+            data = sas.sascalc.dataloader.data_info.Data1D(x=[], y=[])
             return None
         elif not state.data.is_data:
@@ -1626,5 +1626,5 @@
 
             if issubclass(state.data.__class__,
-                          sas.dataloader.data_info.Data1D):
+                          sas.sascalc.dataloader.data_info.Data1D):
                 data = state.data
                 doc, sasentry = self._to_xml_doc(data)
Index: src/sas/sasgui/perspectives/fitting/simfitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision aac161f1844b41bce09c87d7b6c7a3fc1f1f8712)
@@ -91,4 +91,6 @@
         ## selected mdoel to fit
         self.model_toFit = []
+        ## Control the fit state
+        self.fit_started = False
         ## number of constraint
         self.nb_constraint = 0
@@ -423,4 +425,9 @@
 
         """
+        if self.fit_started:
+            self._stop_fit()
+            self.fit_started = False
+            return
+
         flag = False
         # check if the current page a simultaneous fit page or a batch page
@@ -439,4 +446,6 @@
                     self.manager.schedule_for_fit(value=1, uid=item[2])
             try:
+                self.fit_started = True
+                wx.CallAfter(self.set_fitbutton)
                 if not self.manager.onFit(uid=self.uid):
                     return
@@ -447,4 +456,32 @@
             msg = "Select at least one model check box to fit "
             wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
+
+    def _on_fit_complete(self):
+        """
+        Set the completion flag and display the updated fit button label.
+        """
+        self.fit_started = False
+        self.set_fitbutton()
+
+    def _stop_fit(self, event=None):
+        """
+        Attempt to stop the fitting thread
+        """
+        if event != None:
+            event.Skip()
+        self.manager.stop_fit(self.uid)
+        self.manager._reset_schedule_problem(value=0)
+        self._on_fit_complete()
+
+    def set_fitbutton(self):
+        """
+        Set fit button label depending on the fit_started
+        """
+        label = "Stop" if self.fit_started else "Fit"
+        color = "red" if self.fit_started else "black"
+
+        self.btFit.SetLabel(label)
+        self.btFit.SetForegroundColour(color)
+        self.btFit.Enable(True)
 
     def _onHelp(self, event):
Index: src/sas/sasgui/perspectives/invariant/invariant.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/invariant/invariant.py	(revision c10d9d6c8667422d3db329ac32d0528a6cce3fc6)
@@ -237,5 +237,5 @@
         else:
             msg = "invariant.save_file: the data being saved is"
-            msg += " not a sas.dataloader.data_info.Data1D object"
+            msg += " not a sas.sascalc.dataloader.data_info.Data1D object"
             raise RuntimeError, msg
 
Index: src/sas/sasgui/perspectives/invariant/invariant_state.py
===================================================================
--- src/sas/sasgui/perspectives/invariant/invariant_state.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/invariant/invariant_state.py	(revision c10d9d6c8667422d3db329ac32d0528a6cce3fc6)
@@ -773,6 +773,6 @@
         """
         if datainfo is None:
-            datainfo = sas.dataloader.data_info.Data1D(x=[], y=[])
-        elif not issubclass(datainfo.__class__, sas.dataloader.data_info.Data1D):
+            datainfo = sas.sascalc.dataloader.data_info.Data1D(x=[], y=[])
+        elif not issubclass(datainfo.__class__, sas.sascalc.dataloader.data_info.Data1D):
             msg = "The cansas writer expects a Data1D"
             msg += " instance: %s" % str(datainfo.__class__.__name__)
Index: src/sas/sasgui/perspectives/pr/pr.py
===================================================================
--- src/sas/sasgui/perspectives/pr/pr.py	(revision d85c194ea2f78cb2b9b3de9c969e1b8ebea6b6d2)
+++ src/sas/sasgui/perspectives/pr/pr.py	(revision c10d9d6c8667422d3db329ac32d0528a6cce3fc6)
@@ -896,5 +896,5 @@
         # First, check that the data is of the right type
         if issubclass(self.current_plottable.__class__,
-                      sas.dataloader.data_info.Data1D):
+                      sas.sascalc.dataloader.data_info.Data1D):
             self.state_reader.write(filepath, self.current_plottable, prstate)
         else:
Index: test/sasdataloader/test/CMSphere5.txt
===================================================================
--- test/sasdataloader/test/CMSphere5.txt	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
+++ test/sasdataloader/test/CMSphere5.txt	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -0,0 +1,100 @@
+0.001278	54.9380924003798	37.0601175120843	0.0002847	0.001309	0.9969
+0.001562	55.9867418157196	18.7060721785266	0.0002905	0.001586	1
+0.001846	53.560827622232	12.1975438809796	0.0002956	0.001866	1
+0.00213	56.0611368219615	9.35924816875345	0.0003017	0.002147	1
+0.002414	54.9105450986387	7.41016498457617	0.0003087	0.002429	1
+0.002698	52.6206676026623	6.045008707986	0.0003165	0.002712	1
+0.002982	54.3487185926871	5.26582738584293	0.0003249	0.002994	1
+0.003266	53.1519252869778	4.55658543376789	0.000334	0.003277	1
+0.00355	50.3956985471045	3.94388487999297	0.0003437	0.00356	1
+0.003834	52.3077243923509	3.61620396245673	0.0003539	0.003843	1
+0.004118	48.9775315111468	3.18108860178174	0.0003646	0.004127	1
+0.004402	47.851863958769	2.88229340467268	0.0003757	0.00441	1
+0.004686	48.120403795601	2.66803353208751	0.0003872	0.004694	1
+0.00497	46.6358007278057	2.43894320769947	0.000399	0.004977	1
+0.005253	46.7096327873935	2.27814819504774	0.0004112	0.005261	1
+0.005537	44.7668346304486	2.0908757003657	0.0004237	0.005544	1
+0.005821	44.5102618760759	1.9622356317224	0.0004365	0.005828	1
+0.006105	43.8317929116779	1.83904398707451	0.0004495	0.006111	1
+0.006389	42.8115820491762	1.7218568467313	0.0004628	0.006395	1
+0.006673	40.6287017456067	1.59351619354822	0.0004762	0.006679	1
+0.006957	38.9273825772724	1.48551934346209	0.0004899	0.006963	1
+0.007241	39.4347686851588	1.42720701725474	0.0005037	0.007246	1
+0.007525	37.3896037689509	1.32928345624222	0.0005177	0.00753	1
+0.007809	35.6306787321893	1.24357164292396	0.0005318	0.007814	1
+0.008093	35.530501695638	1.19214934795332	0.0005461	0.008098	1
+0.008377	34.0993161167952	1.12297345606099	0.0005605	0.008381	1
+0.008661	32.9897133648153	1.06364208149705	0.000575	0.008665	1
+0.008945	32.1518950821748	1.01254716257906	0.0005896	0.008949	1
+0.009229	29.8897142444819	0.942611834366517	0.0006044	0.009233	1
+0.009513	28.9877911868164	0.897338521326267	0.0006192	0.009517	1
+0.009797	28.8671346812142	0.866582969308467	0.0006341	0.0098	1
+0.01008	26.9433496519676	0.811046621571466	0.0006491	0.01008	1
+0.01036	26.6574283650013	0.782285336779699	0.0006641	0.01037	1
+0.01065	24.3082221946571	0.725049547803642	0.0006793	0.01065	1
+0.01093	23.8126250954449	0.697116877376645	0.0006945	0.01094	1
+0.01122	23.1785075655241	0.668667535089826	0.0007097	0.01122	1
+0.0115	21.9740009615986	0.633465328879644	0.0007251	0.0115	1
+0.01178	20.6925854394778	0.59854083649735	0.0007404	0.01179	1
+0.01207	19.7807301988479	0.570199138755696	0.0007558	0.01207	1
+0.01235	18.3816218241588	0.535922420693967	0.0007713	0.01235	1
+0.01264	18.3908707649353	0.522982665113434	0.0007868	0.01264	1
+0.01292	16.6161700838934	0.485273055949626	0.0008024	0.01292	1
+0.0132	16.0777395370208	0.466244845015208	0.000818	0.01321	1
+0.01349	15.263459197006	0.443959969486881	0.0008336	0.01349	1
+0.01377	14.1821386233677	0.418435336883356	0.0008493	0.01377	1
+0.01406	13.502938686688	0.399416793154326	0.000865	0.01406	1
+0.01434	12.7120240406905	0.379297079863246	0.0008807	0.01434	1
+0.01462	12.3555872971682	0.366151201613803	0.0008965	0.01463	1
+0.01491	11.2547046019901	0.342326858530752	0.0009123	0.01491	1
+0.01519	10.9244250567462	0.330521180210077	0.0009281	0.01519	1
+0.01547	10.035672955121	0.310579708660702	0.000944	0.01548	1
+0.01576	9.54857095265672	0.297122712018445	0.0009598	0.01576	1
+0.01604	9.13262277183969	0.285096505677118	0.0009757	0.01604	1
+0.01633	8.08125471450095	0.263218034782734	0.0009916	0.01633	1
+0.01661	7.81396931095573	0.25412252707745	0.001008	0.01661	1
+0.01689	7.15622633154717	0.238849321076674	0.001024	0.0169	1
+0.01718	6.58315875573461	0.225067254333071	0.001039	0.01718	1
+0.01746	6.43230960958337	0.218637916921313	0.001055	0.01746	1
+0.01774	5.90542211293399	0.205941307480525	0.001071	0.01775	1
+0.01803	5.4581186471037	0.194688473505747	0.001088	0.01803	1
+0.01831	5.22827912984443	0.187421561660762	0.001104	0.01831	1
+0.0186	4.88437306640718	0.178230788618277	0.00112	0.0186	1
+0.01888	4.42899966633438	0.167025306994807	0.001136	0.01888	1
+0.01916	4.17549684846924	0.159640879027805	0.001152	0.01917	1
+0.01945	3.78069785571845	0.149569359258851	0.001168	0.01945	1
+0.01973	3.57736677843592	0.143287334425684	0.001184	0.01973	1
+0.02002	3.32715311708635	0.136123026207964	0.0012	0.02002	1
+0.0203	3.0976943461779	0.129413735491718	0.001216	0.0203	1
+0.02058	2.8778429506358	0.122929031089926	0.001232	0.02058	1
+0.02087	2.59549794256613	0.115075351147255	0.001249	0.02087	1
+0.02115	2.34203393868523	0.107772572065675	0.001265	0.02115	1
+0.02143	2.23234126433142	0.103757094440139	0.001281	0.02144	1
+0.02172	2.06670267290386	0.0984659657480468	0.001297	0.02172	1
+0.022	1.96040951516539	0.0946044762011819	0.001313	0.022	1
+0.02228	1.77196064410215	0.088743341386574	0.001329	0.02229	1
+0.02257	1.63708077448337	0.0841766166945712	0.001346	0.02257	1
+0.02285	1.53159863781081	0.0803621902602349	0.001362	0.02285	1
+0.02314	1.42479800116411	0.0765159633096708	0.001378	0.02314	1
+0.02342	1.34419916363248	0.073379495822172	0.001394	0.02342	1
+0.0237	1.28950309519543	0.0709726811217327	0.001411	0.0237	1
+0.02399	1.18575259309099	0.0672174491013361	0.001427	0.02399	1
+0.02427	1.11661367746353	0.0644328918452755	0.001443	0.02427	1
+0.02455	1.03725820906637	0.0613529362624028	0.001459	0.02456	1
+0.02484	0.963936666624243	0.0584406406908292	0.001476	0.02484	1
+0.02512	0.946666784713813	0.0572334134481483	0.001492	0.02512	1
+0.02541	0.85517174555837	0.0537648235548803	0.001508	0.02541	1
+0.02569	0.837653826942447	0.0525996725094271	0.001524	0.02569	1
+0.02597	0.779716006334579	0.0501713234228845	0.001541	0.02597	1
+0.02626	0.758831276438753	0.0489387203301556	0.001557	0.02626	1
+0.02654	0.737853170893451	0.0477213222943686	0.001573	0.02654	1
+0.02682	0.683436680838241	0.0454232028983561	0.00159	0.02682	1
+0.02711	0.687265505181365	0.0450551510778741	0.001606	0.02711	1
+0.02739	0.655078310492671	0.0435144667607471	0.001622	0.02739	1
+0.02767	0.638970027703769	0.0425189366016694	0.001639	0.02767	1
+0.02796	0.60633272894638	0.0409828262439632	0.001655	0.02796	1
+0.02824	0.604236153070516	0.0404857440009898	0.001671	0.02824	1
+0.02852	0.56286255473902	0.0386722507332448	0.001688	0.02853	1
+0.02881	0.568578929096012	0.0384715167864823	0.001704	0.02881	1
+0.02909	0.55573597175355	0.0376503540172005	0.00172	0.02909	1
+0.02937	0.534835145987317	0.0365662120675398	0.001737	0.02938	1
Index: test/sasdataloader/test/CMSphere5smearsphere.txt
===================================================================
--- test/sasdataloader/test/CMSphere5smearsphere.txt	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
+++ test/sasdataloader/test/CMSphere5smearsphere.txt	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -0,0 +1,100 @@
+0.001278 2538.41176383
+0.001562 2536.91820405
+0.001846 2535.13182479
+0.00213 2533.06217813
+0.002414 2530.70378586
+0.002698 2528.05024192
+0.002982 2525.10408349
+0.003266 2521.86667499
+0.00355 2518.3390775
+0.003834 2514.52246995
+0.004118 2510.41798319
+0.004402 2506.02690988
+0.004686 2501.35067884
+0.00497 2496.38678318
+0.005253 2491.16237596
+0.005537 2485.63911673
+0.005821 2479.83657083
+0.006105 2473.75676948
+0.006389 2467.4014599
+0.006673 2460.77293372
+0.006957 2453.86724627
+0.007241 2446.69623838
+0.007525 2439.25775219
+0.007809 2431.55421398
+0.008093 2423.58785521
+0.008377 2415.36158137
+0.008661 2406.87009473
+0.008945 2398.12841186
+0.009229 2389.13360806
+0.009513 2379.88958042
+0.009797 2370.39776774
+0.01008 2360.69528793
+0.01036 2350.85169027
+0.01065 2340.42023633
+0.01093 2330.11206013
+0.01122 2319.20109972
+0.0115 2308.43503981
+0.01178 2297.44820179
+0.01207 2285.83853677
+0.01235 2274.41290746
+0.01264 2262.36219581
+0.01292 2250.51169731
+0.0132 2238.45596231
+0.01349 2225.76495666
+0.01377 2213.29618391
+0.01406 2200.19110751
+0.01434 2187.34050325
+0.01462 2174.30529864
+0.01491 2160.61632548
+0.01519 2147.21038112
+0.01547 2133.6202358
+0.01576 2119.37907426
+0.01604 2105.45234903
+0.01633 2090.86319102
+0.01661 2076.60576032
+0.01689 2062.19214565
+0.01718 2047.10550219
+0.01746 2032.38715621
+0.01774 2017.52560123
+0.01803 2001.99124318
+0.01831 1986.8466206
+0.0186 1971.03389745
+0.01888 1955.61395119
+0.01916 1940.08291563
+0.01945 1923.87672225
+0.01973 1908.10656374
+0.02002 1891.66297192
+0.0203 1875.66789021
+0.02058 1859.56357196
+0.02087 1842.7946829
+0.02115 1826.50064489
+0.02143 1810.11533702
+0.02172 1793.06840882
+0.022 1776.5115358
+0.02228 1759.87201249
+0.02257 1742.57354412
+0.02285 1725.79397319
+0.02314 1708.3583155
+0.02342 1691.45256069
+0.0237 1674.48561783
+0.02399 1656.86525366
+0.02427 1639.79847285
+0.02455 1622.68887088
+0.02484 1604.964211
+0.02512 1587.85768129
+0.02541 1569.99297335
+0.02569 1552.84580279
+0.02597 1535.54074115
+0.02626 1517.75249337
+0.02654 1500.40115023
+0.02682 1483.03632237
+0.02711 1465.05942429
+0.02739 1447.67682181
+0.02767 1430.46495191
+0.02796 1412.49232282
+0.02824 1395.13182318
+0.02852 1377.93439837
+0.02881 1359.99528971
+0.02909 1342.67274512
+0.02937 1325.55375609
Index: test/sasdataloader/test/utest_smearing.py
===================================================================
--- test/sasdataloader/test/utest_smearing.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
+++ test/sasdataloader/test/utest_smearing.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -0,0 +1,151 @@
+"""
+    Unit tests for data manipulations
+"""
+
+
+import unittest
+import numpy, math
+from sas.sascalc.dataloader.loader import  Loader
+from sas.sascalc.dataloader.data_info import Data1D, Data2D
+#from DataLoader.qsmearing import SlitSmearer, QSmearer, smear_selection
+from sas.sascalc.data_util.qsmearing import SlitSmearer, QSmearer, smear_selection
+from sas.models.SphereModel import SphereModel
+import os.path
+from time import time
+class smear_tests(unittest.TestCase):
+    
+    def setUp(self):
+        data = Loader().load("cansas1d_slit.xml")
+        self.data = data[0]
+        
+        x = 0.001*numpy.arange(1,11)
+        y = 12.0-numpy.arange(1,11)
+        dxl = 0.00*numpy.ones(10)
+        dxw = 0.00*numpy.ones(10)
+        dx = 0.00*numpy.ones(10)
+        
+        self.data.dx = dx
+        self.data.x = x
+        self.data.y = y
+        self.data.dxl = dxl
+        self.data.dxw = dxw
+        
+    def test_slit(self):
+        """
+            Test identity smearing
+        """
+        # Create smearer for our data
+        s = SlitSmearer(self.data)
+        
+        input = 12.0-numpy.arange(1,11)
+        output = s(input)
+        for i in range(len(input)):
+            self.assertEquals(input[i], output[i])
+
+    def test_slit2(self):
+        """
+            Test basic smearing
+        """
+        dxl = 0.005*numpy.ones(10)
+        dxw = 0.0*numpy.ones(10)
+        self.data.dxl = dxl
+        self.data.dxw = dxw
+        # Create smearer for our data
+        s = SlitSmearer(self.data)
+        
+        input = 12.0-numpy.arange(1,11)
+        output = s(input)
+        # The following commented line was the correct output for even bins [see smearer.cpp for details] 
+        #answer = [ 9.666,  9.056,  8.329,  7.494,  6.642,  5.721,  4.774,  3.824,  2.871, 2.   ]
+        answer = [ 9.0618,  8.6401,  8.1186,  7.1391,  6.1528,  5.5555,     4.5584,  3.5606,  2.5623, 2.    ]
+        for i in range(len(input)):
+            self.assertAlmostEqual(answer[i], output[i], 3)
+        
+    def test_q(self):
+        """
+            Test identity resolution smearing
+        """
+        # Create smearer for our data
+        s = QSmearer(self.data)
+        
+        input = 12.0-numpy.arange(1,11)
+        output = s(input)
+        for i in range(len(input)):
+            self.assertAlmostEquals(input[i], output[i], 5)
+
+    def test_q2(self):
+        """
+            Test basic smearing
+        """
+        dx = 0.001*numpy.ones(10)
+        self.data.dx = dx
+
+        # Create smearer for our data
+        s = QSmearer(self.data)
+        
+        input = 12.0-numpy.arange(1,11)
+        output = s(input)
+        
+        answer = [ 10.44785079,   9.84991299,   8.98101708,   
+                  7.99906585,   6.99998311,   6.00001689,
+                  5.00093415,   4.01898292,   3.15008701,   2.55214921]
+        for i in range(len(input)):
+            self.assertAlmostEqual(answer[i], output[i], 2)
+      
+class smear_test_1Dpinhole(unittest.TestCase):
+    
+    def setUp(self):
+        # NIST sample data
+        self.data = Loader().load("CMSphere5.txt")
+        # NIST smeared sphere w/ param values below
+        self.answer = Loader().load("CMSphere5smearsphere.txt")
+        # call spheremodel
+        self.model = SphereModel()
+        # setparams consistent with Igor default
+        self.model.setParam('scale', 1.0)
+        self.model.setParam('background', 0.01)
+        self.model.setParam('radius', 60.0)
+        self.model.setParam('sldSolv', 6.3e-06)
+        self.model.setParam('sldSph', 1.0e-06)
+        
+    def test_q(self):
+        """
+        Compare Pinhole resolution smearing with NIST
+        """
+        # x values
+        input = numpy.zeros(len(self.data.x))
+        # set time
+        st1 = time()
+        # cal I w/o smear
+        input = self.model.evalDistribution(self.data.x)
+        # Cal_smear (first call)
+        for i in range(1000):
+            s = QSmearer(self.data, self.model)
+        # stop and record time taken
+        first_call_time = time()-st1
+        # set new time
+        st = time()
+        # cal I w/o smear (this is not neccessary to call but just to be fare
+        input = self.model.evalDistribution(self.data.x)
+        # smear cal (after first call done above)
+        for i in range(1000):
+            output = s(input)
+
+        # record time taken
+        last_call_time = time()-st
+        # compare the ratio of ((NIST_answer-SsanView_answer)/NIST_answer)
+        # If the ratio less than 1%, pass the test 
+        for i in range(len(self.data.x)):
+            ratio  = math.fabs((self.answer.y[i]-output[i])/self.answer.y[i])
+            if ratio > 0.006:
+                ratio = 0.006
+            self.assertEqual(math.fabs((self.answer.y[i]-output[i])/ \
+                                       self.answer.y[i]), ratio) 
+        # print
+        print "\n NIST_time = 10sec:"
+        print "Cal_time(1000 times of first_calls; ) = ",  first_call_time  
+        print "Cal_time(1000 times of calls) = ",  last_call_time 
+        
+if __name__ == '__main__':
+    unittest.main()
+   
Index: test/sasfit/test/batch_fit.py
===================================================================
--- test/sasfit/test/batch_fit.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ test/sasfit/test/batch_fit.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -6,5 +6,5 @@
 from sas.models.CylinderModel import CylinderModel
 import sas.models.dispersion_models 
-from sas.models.qsmearing import smear_selection
+from sas.sascalc.data_util.qsmearing import smear_selection
 
 NPTS = 1
Index: test/sasfit/test/utest_fit_smeared.py
===================================================================
--- test/sasfit/test/utest_fit_smeared.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ test/sasfit/test/utest_fit_smeared.py	(revision fc18690cd84cc4dd6e33a9d344f47b08cef71290)
@@ -10,5 +10,5 @@
 from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
 from sas.sascalc.dataloader.loader import Loader
-from sas.models.qsmearing import smear_selection
+from sas.sascalc.data_util.qsmearing import smear_selection
 from sas.models.CylinderModel import CylinderModel
 from sas.models.SphereModel import SphereModel
Index: st/sasmodels/test/CMSphere5.txt
===================================================================
--- test/sasmodels/test/CMSphere5.txt	(revision 8c9ffde290eafed946f800881bf4901b48445d4c)
+++ 	(revision )
@@ -1,100 +1,0 @@
-0.001278	54.9380924003798	37.0601175120843	0.0002847	0.001309	0.9969
-0.001562	55.9867418157196	18.7060721785266	0.0002905	0.001586	1
-0.001846	53.560827622232	12.1975438809796	0.0002956	0.001866	1
-0.00213	56.0611368219615	9.35924816875345	0.0003017	0.002147	1
-0.002414	54.9105450986387	7.41016498457617	0.0003087	0.002429	1
-0.002698	52.6206676026623	6.045008707986	0.0003165	0.002712	1
-0.002982	54.3487185926871	5.26582738584293	0.0003249	0.002994	1
-0.003266	53.1519252869778	4.55658543376789	0.000334	0.003277	1
-0.00355	50.3956985471045	3.94388487999297	0.0003437	0.00356	1
-0.003834	52.3077243923509	3.61620396245673	0.0003539	0.003843	1
-0.004118	48.9775315111468	3.18108860178174	0.0003646	0.004127	1
-0.004402	47.851863958769	2.88229340467268	0.0003757	0.00441	1
-0.004686	48.120403795601	2.66803353208751	0.0003872	0.004694	1
-0.00497	46.6358007278057	2.43894320769947	0.000399	0.004977	1
-0.005253	46.7096327873935	2.27814819504774	0.0004112	0.005261	1
-0.005537	44.7668346304486	2.0908757003657	0.0004237	0.005544	1
-0.005821	44.5102618760759	1.9622356317224	0.0004365	0.005828	1
-0.006105	43.8317929116779	1.83904398707451	0.0004495	0.006111	1
-0.006389	42.8115820491762	1.7218568467313	0.0004628	0.006395	1
-0.006673	40.6287017456067	1.59351619354822	0.0004762	0.006679	1
-0.006957	38.9273825772724	1.48551934346209	0.0004899	0.006963	1
-0.007241	39.4347686851588	1.42720701725474	0.0005037	0.007246	1
-0.007525	37.3896037689509	1.32928345624222	0.0005177	0.00753	1
-0.007809	35.6306787321893	1.24357164292396	0.0005318	0.007814	1
-0.008093	35.530501695638	1.19214934795332	0.0005461	0.008098	1
-0.008377	34.0993161167952	1.12297345606099	0.0005605	0.008381	1
-0.008661	32.9897133648153	1.06364208149705	0.000575	0.008665	1
-0.008945	32.1518950821748	1.01254716257906	0.0005896	0.008949	1
-0.009229	29.8897142444819	0.942611834366517	0.0006044	0.009233	1
-0.009513	28.9877911868164	0.897338521326267	0.0006192	0.009517	1
-0.009797	28.8671346812142	0.866582969308467	0.0006341	0.0098	1
-0.01008	26.9433496519676	0.811046621571466	0.0006491	0.01008	1
-0.01036	26.6574283650013	0.782285336779699	0.0006641	0.01037	1
-0.01065	24.3082221946571	0.725049547803642	0.0006793	0.01065	1
-0.01093	23.8126250954449	0.697116877376645	0.0006945	0.01094	1
-0.01122	23.1785075655241	0.668667535089826	0.0007097	0.01122	1
-0.0115	21.9740009615986	0.633465328879644	0.0007251	0.0115	1
-0.01178	20.6925854394778	0.59854083649735	0.0007404	0.01179	1
-0.01207	19.7807301988479	0.570199138755696	0.0007558	0.01207	1
-0.01235	18.3816218241588	0.535922420693967	0.0007713	0.01235	1
-0.01264	18.3908707649353	0.522982665113434	0.0007868	0.01264	1
-0.01292	16.6161700838934	0.485273055949626	0.0008024	0.01292	1
-0.0132	16.0777395370208	0.466244845015208	0.000818	0.01321	1
-0.01349	15.263459197006	0.443959969486881	0.0008336	0.01349	1
-0.01377	14.1821386233677	0.418435336883356	0.0008493	0.01377	1
-0.01406	13.502938686688	0.399416793154326	0.000865	0.01406	1
-0.01434	12.7120240406905	0.379297079863246	0.0008807	0.01434	1
-0.01462	12.3555872971682	0.366151201613803	0.0008965	0.01463	1
-0.01491	11.2547046019901	0.342326858530752	0.0009123	0.01491	1
-0.01519	10.9244250567462	0.330521180210077	0.0009281	0.01519	1
-0.01547	10.035672955121	0.310579708660702	0.000944	0.01548	1
-0.01576	9.54857095265672	0.297122712018445	0.0009598	0.01576	1
-0.01604	9.13262277183969	0.285096505677118	0.0009757	0.01604	1
-0.01633	8.08125471450095	0.263218034782734	0.0009916	0.01633	1
-0.01661	7.81396931095573	0.25412252707745	0.001008	0.01661	1
-0.01689	7.15622633154717	0.238849321076674	0.001024	0.0169	1
-0.01718	6.58315875573461	0.225067254333071	0.001039	0.01718	1
-0.01746	6.43230960958337	0.218637916921313	0.001055	0.01746	1
-0.01774	5.90542211293399	0.205941307480525	0.001071	0.01775	1
-0.01803	5.4581186471037	0.194688473505747	0.001088	0.01803	1
-0.01831	5.22827912984443	0.187421561660762	0.001104	0.01831	1
-0.0186	4.88437306640718	0.178230788618277	0.00112	0.0186	1
-0.01888	4.42899966633438	0.167025306994807	0.001136	0.01888	1
-0.01916	4.17549684846924	0.159640879027805	0.001152	0.01917	1
-0.01945	3.78069785571845	0.149569359258851	0.001168	0.01945	1
-0.01973	3.57736677843592	0.143287334425684	0.001184	0.01973	1
-0.02002	3.32715311708635	0.136123026207964	0.0012	0.02002	1
-0.0203	3.0976943461779	0.129413735491718	0.001216	0.0203	1
-0.02058	2.8778429506358	0.122929031089926	0.001232	0.02058	1
-0.02087	2.59549794256613	0.115075351147255	0.001249	0.02087	1
-0.02115	2.34203393868523	0.107772572065675	0.001265	0.02115	1
-0.02143	2.23234126433142	0.103757094440139	0.001281	0.02144	1
-0.02172	2.06670267290386	0.0984659657480468	0.001297	0.02172	1
-0.022	1.96040951516539	0.0946044762011819	0.001313	0.022	1
-0.02228	1.77196064410215	0.088743341386574	0.001329	0.02229	1
-0.02257	1.63708077448337	0.0841766166945712	0.001346	0.02257	1
-0.02285	1.53159863781081	0.0803621902602349	0.001362	0.02285	1
-0.02314	1.42479800116411	0.0765159633096708	0.001378	0.02314	1
-0.02342	1.34419916363248	0.073379495822172	0.001394	0.02342	1
-0.0237	1.28950309519543	0.0709726811217327	0.001411	0.0237	1
-0.02399	1.18575259309099	0.0672174491013361	0.001427	0.02399	1
-0.02427	1.11661367746353	0.0644328918452755	0.001443	0.02427	1
-0.02455	1.03725820906637	0.0613529362624028	0.001459	0.02456	1
-0.02484	0.963936666624243	0.0584406406908292	0.001476	0.02484	1
-0.02512	0.946666784713813	0.0572334134481483	0.001492	0.02512	1
-0.02541	0.85517174555837	0.0537648235548803	0.001508	0.02541	1
-0.02569	0.837653826942447	0.0525996725094271	0.001524	0.02569	1
-0.02597	0.779716006334579	0.0501713234228845	0.001541	0.02597	1
-0.02626	0.758831276438753	0.0489387203301556	0.001557	0.02626	1
-0.02654	0.737853170893451	0.0477213222943686	0.001573	0.02654	1
-0.02682	0.683436680838241	0.0454232028983561	0.00159	0.02682	1
-0.02711	0.687265505181365	0.0450551510778741	0.001606	0.02711	1
-0.02739	0.655078310492671	0.0435144667607471	0.001622	0.02739	1
-0.02767	0.638970027703769	0.0425189366016694	0.001639	0.02767	1
-0.02796	0.60633272894638	0.0409828262439632	0.001655	0.02796	1
-0.02824	0.604236153070516	0.0404857440009898	0.001671	0.02824	1
-0.02852	0.56286255473902	0.0386722507332448	0.001688	0.02853	1
-0.02881	0.568578929096012	0.0384715167864823	0.001704	0.02881	1
-0.02909	0.55573597175355	0.0376503540172005	0.00172	0.02909	1
-0.02937	0.534835145987317	0.0365662120675398	0.001737	0.02938	1
Index: st/sasmodels/test/CMSphere5smearsphere.txt
===================================================================
--- test/sasmodels/test/CMSphere5smearsphere.txt	(revision 8c9ffde290eafed946f800881bf4901b48445d4c)
+++ 	(revision )
@@ -1,100 +1,0 @@
-0.001278 2538.41176383
-0.001562 2536.91820405
-0.001846 2535.13182479
-0.00213 2533.06217813
-0.002414 2530.70378586
-0.002698 2528.05024192
-0.002982 2525.10408349
-0.003266 2521.86667499
-0.00355 2518.3390775
-0.003834 2514.52246995
-0.004118 2510.41798319
-0.004402 2506.02690988
-0.004686 2501.35067884
-0.00497 2496.38678318
-0.005253 2491.16237596
-0.005537 2485.63911673
-0.005821 2479.83657083
-0.006105 2473.75676948
-0.006389 2467.4014599
-0.006673 2460.77293372
-0.006957 2453.86724627
-0.007241 2446.69623838
-0.007525 2439.25775219
-0.007809 2431.55421398
-0.008093 2423.58785521
-0.008377 2415.36158137
-0.008661 2406.87009473
-0.008945 2398.12841186
-0.009229 2389.13360806
-0.009513 2379.88958042
-0.009797 2370.39776774
-0.01008 2360.69528793
-0.01036 2350.85169027
-0.01065 2340.42023633
-0.01093 2330.11206013
-0.01122 2319.20109972
-0.0115 2308.43503981
-0.01178 2297.44820179
-0.01207 2285.83853677
-0.01235 2274.41290746
-0.01264 2262.36219581
-0.01292 2250.51169731
-0.0132 2238.45596231
-0.01349 2225.76495666
-0.01377 2213.29618391
-0.01406 2200.19110751
-0.01434 2187.34050325
-0.01462 2174.30529864
-0.01491 2160.61632548
-0.01519 2147.21038112
-0.01547 2133.6202358
-0.01576 2119.37907426
-0.01604 2105.45234903
-0.01633 2090.86319102
-0.01661 2076.60576032
-0.01689 2062.19214565
-0.01718 2047.10550219
-0.01746 2032.38715621
-0.01774 2017.52560123
-0.01803 2001.99124318
-0.01831 1986.8466206
-0.0186 1971.03389745
-0.01888 1955.61395119
-0.01916 1940.08291563
-0.01945 1923.87672225
-0.01973 1908.10656374
-0.02002 1891.66297192
-0.0203 1875.66789021
-0.02058 1859.56357196
-0.02087 1842.7946829
-0.02115 1826.50064489
-0.02143 1810.11533702
-0.02172 1793.06840882
-0.022 1776.5115358
-0.02228 1759.87201249
-0.02257 1742.57354412
-0.02285 1725.79397319
-0.02314 1708.3583155
-0.02342 1691.45256069
-0.0237 1674.48561783
-0.02399 1656.86525366
-0.02427 1639.79847285
-0.02455 1622.68887088
-0.02484 1604.964211
-0.02512 1587.85768129
-0.02541 1569.99297335
-0.02569 1552.84580279
-0.02597 1535.54074115
-0.02626 1517.75249337
-0.02654 1500.40115023
-0.02682 1483.03632237
-0.02711 1465.05942429
-0.02739 1447.67682181
-0.02767 1430.46495191
-0.02796 1412.49232282
-0.02824 1395.13182318
-0.02852 1377.93439837
-0.02881 1359.99528971
-0.02909 1342.67274512
-0.02937 1325.55375609
Index: st/sasmodels/test/utest_smearing.py
===================================================================
--- test/sasmodels/test/utest_smearing.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ 	(revision )
@@ -1,151 +1,0 @@
-"""
-    Unit tests for data manipulations
-"""
-
-
-import unittest
-import numpy, math
-from sas.sascalc.dataloader.loader import  Loader
-from sas.sascalc.dataloader.data_info import Data1D, Data2D
-#from DataLoader.qsmearing import SlitSmearer, QSmearer, smear_selection
-from sas.models.qsmearing import SlitSmearer, QSmearer, smear_selection
-from sas.models.SphereModel import SphereModel
-import os.path
-from time import time
-class smear_tests(unittest.TestCase):
-    
-    def setUp(self):
-        data = Loader().load("cansas1d_slit.xml")
-        self.data = data[0]
-        
-        x = 0.001*numpy.arange(1,11)
-        y = 12.0-numpy.arange(1,11)
-        dxl = 0.00*numpy.ones(10)
-        dxw = 0.00*numpy.ones(10)
-        dx = 0.00*numpy.ones(10)
-        
-        self.data.dx = dx
-        self.data.x = x
-        self.data.y = y
-        self.data.dxl = dxl
-        self.data.dxw = dxw
-        
-    def test_slit(self):
-        """
-            Test identity smearing
-        """
-        # Create smearer for our data
-        s = SlitSmearer(self.data)
-        
-        input = 12.0-numpy.arange(1,11)
-        output = s(input)
-        for i in range(len(input)):
-            self.assertEquals(input[i], output[i])
-
-    def test_slit2(self):
-        """
-            Test basic smearing
-        """
-        dxl = 0.005*numpy.ones(10)
-        dxw = 0.0*numpy.ones(10)
-        self.data.dxl = dxl
-        self.data.dxw = dxw
-        # Create smearer for our data
-        s = SlitSmearer(self.data)
-        
-        input = 12.0-numpy.arange(1,11)
-        output = s(input)
-        # The following commented line was the correct output for even bins [see smearer.cpp for details] 
-        #answer = [ 9.666,  9.056,  8.329,  7.494,  6.642,  5.721,  4.774,  3.824,  2.871, 2.   ]
-        answer = [ 9.0618,  8.6401,  8.1186,  7.1391,  6.1528,  5.5555,     4.5584,  3.5606,  2.5623, 2.    ]
-        for i in range(len(input)):
-            self.assertAlmostEqual(answer[i], output[i], 3)
-        
-    def test_q(self):
-        """
-            Test identity resolution smearing
-        """
-        # Create smearer for our data
-        s = QSmearer(self.data)
-        
-        input = 12.0-numpy.arange(1,11)
-        output = s(input)
-        for i in range(len(input)):
-            self.assertAlmostEquals(input[i], output[i], 5)
-
-    def test_q2(self):
-        """
-            Test basic smearing
-        """
-        dx = 0.001*numpy.ones(10)
-        self.data.dx = dx
-
-        # Create smearer for our data
-        s = QSmearer(self.data)
-        
-        input = 12.0-numpy.arange(1,11)
-        output = s(input)
-        
-        answer = [ 10.44785079,   9.84991299,   8.98101708,   
-                  7.99906585,   6.99998311,   6.00001689,
-                  5.00093415,   4.01898292,   3.15008701,   2.55214921]
-        for i in range(len(input)):
-            self.assertAlmostEqual(answer[i], output[i], 2)
-      
-class smear_test_1Dpinhole(unittest.TestCase):
-    
-    def setUp(self):
-        # NIST sample data
-        self.data = Loader().load("CMSphere5.txt")
-        # NIST smeared sphere w/ param values below
-        self.answer = Loader().load("CMSphere5smearsphere.txt")
-        # call spheremodel
-        self.model = SphereModel()
-        # setparams consistent with Igor default
-        self.model.setParam('scale', 1.0)
-        self.model.setParam('background', 0.01)
-        self.model.setParam('radius', 60.0)
-        self.model.setParam('sldSolv', 6.3e-06)
-        self.model.setParam('sldSph', 1.0e-06)
-        
-    def test_q(self):
-        """
-        Compare Pinhole resolution smearing with NIST
-        """
-        # x values
-        input = numpy.zeros(len(self.data.x))
-        # set time
-        st1 = time()
-        # cal I w/o smear
-        input = self.model.evalDistribution(self.data.x)
-        # Cal_smear (first call)
-        for i in range(1000):
-            s = QSmearer(self.data, self.model)
-        # stop and record time taken
-        first_call_time = time()-st1
-        # set new time
-        st = time()
-        # cal I w/o smear (this is not neccessary to call but just to be fare
-        input = self.model.evalDistribution(self.data.x)
-        # smear cal (after first call done above)
-        for i in range(1000):
-            output = s(input)
-
-        # record time taken
-        last_call_time = time()-st
-        # compare the ratio of ((NIST_answer-SsanView_answer)/NIST_answer)
-        # If the ratio less than 1%, pass the test 
-        for i in range(len(self.data.x)):
-            ratio  = math.fabs((self.answer.y[i]-output[i])/self.answer.y[i])
-            if ratio > 0.006:
-                ratio = 0.006
-            self.assertEqual(math.fabs((self.answer.y[i]-output[i])/ \
-                                       self.answer.y[i]), ratio) 
-        # print
-        print "\n NIST_time = 10sec:"
-        print "Cal_time(1000 times of first_calls; ) = ",  first_call_time  
-        print "Cal_time(1000 times of calls) = ",  last_call_time 
-        
-if __name__ == '__main__':
-    unittest.main()
-   
