Index: doc/guide/index.rst
===================================================================
--- doc/guide/index.rst	(revision 19dcb93366741a275baa8ad855ce8a310dca78f8)
+++ doc/guide/index.rst	(revision bb6f0f3f04f718187ac81a56563b8bb7a1d216af)
@@ -1,5 +1,15 @@
-**********************
-SAS Model Organization
-**********************
+**********
+SAS Models
+**********
+
+Small angle X-ray and Neutron (SAXS and SANS) scattering examines the
+scattering patterns produced by a beam travelling through the sample
+and scattering at low angles.  The scattering is computed as a function
+of $q_x$ and $q_y$, which for a given beam wavelength corresponds to
+particular scattering angles. Each pixel on the detector corresponds to
+a different scattering angle. If the sample is unoriented, the scattering
+pattern will appear as rings on the detector.  In this case, a circular
+average can be taken with 1-dimension data at $q = \surd (q_x^2 + q_y^2)$
+compared to the orientationally averaged SAS scattering pattern.
 
 Models have certain features in common.
Index: extra/pylint.rc
===================================================================
--- extra/pylint.rc	(revision 5ef0633b151126a2a8e376666e05ccbcc0341316)
+++ extra/pylint.rc	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -6,8 +6,9 @@
 # Python code to execute, usually for sys.path manipulation such as
 # pygtk.require().
-#init-hook=
+#init-hook='import os, sys; sys.path.extend([os.getcwd(),os.path.join(os.getcwd(),"extra")])'
+init-hook='import sys; sys.path.append('extra')
 
 # Profiled execution.
-profile=no
+#profile=no
 
 # Add files or directories to the blacklist. They should be base names, not
@@ -64,4 +65,5 @@
     star-args,
     unbalanced-tuple-unpacking,
+    locally-enabled,
     locally-disabled,
 
@@ -90,5 +92,5 @@
 # Add a comment according to your evaluation note. This is used by the global
 # evaluation report (RP0004).
-comment=no
+#comment=no
 
 # Template used to display messages. This is a python new-style format string
@@ -99,5 +101,5 @@
 
 # Required attributes for module, separated by a comma
-required-attributes=
+#required-attributes=
 
 # List of builtins function names that should not be used, separated by a comma
@@ -106,11 +108,11 @@
 # Good variable names which should always be accepted, separated by a comma
 good-names=_,
-    input,
-    i,j,k,n,x,y,z,
-    q,qx,qy,qz,
-    dt,dx,dy,dz,id,
-    Iq,dIq,Qx,Qy,Qz,
-    p,
+    input, id,
+    h, i, j, k, n, p, v, w, x, y, z,
+    q, qx, qy, qz, Q, Qx, Qy, Qz,
+    dt, dx, dy, dz, dq,
+    Iq, dIq, Iqxy, Iq_calc,
     ER, call_ER, VR, call_VR,
+    Rmax, SElength,
 
 # Bad variable names which should always be refused, separated by a comma
@@ -278,5 +280,6 @@
 # (useful for modules/projects where namespaces are manipulated during runtime
 # and thus existing member attributes cannot be deduced by static analysis
-ignored-modules=numpy,np
+ignored-modules=numpy,np,numpy.random,
+    bumps,sas,
 
 # List of classes names for which member attributes should not be checked
@@ -286,5 +289,5 @@
 # When zope mode is activated, add a predefined set of Zope acquired attributes
 # to generated-members.
-zope=no
+#zope=no
 
 # List of members which are set dynamically and missed by pylint inference
@@ -316,5 +319,5 @@
 # List of interface methods to ignore, separated by a comma. This is used for
 # instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+#ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
 
 # List of method names used to declare (i.e. assign) instance attributes.
@@ -335,5 +338,5 @@
 
 # Maximum number of arguments for function / method
-max-args=5
+max-args=15
 
 # Argument names that match this expression will be ignored. Default to name
@@ -342,5 +345,5 @@
 
 # Maximum number of locals for function / method body
-max-locals=15
+max-locals=25
 
 # Maximum number of return / yield for function / method body
Index: extra/pylint_numpy.py
===================================================================
--- extra/pylint_numpy.py	(revision 63b32bbc85e7dea7a022cf604ac020dee56d7a88)
+++ extra/pylint_numpy.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -8,5 +8,8 @@
     #print("processing",module.name)
     if module.name.startswith('numpy'):
-        if module.name == 'numpy':  import numpy
-        elif module.name == 'numpy.random': import numpy.random
+        if module.name == 'numpy':
+            import numpy
+        elif module.name == 'numpy.random':
+            import numpy.random
+            from numpy.random import seed, get_state, set_state
 
Index: extra/pylint_pyopencl.py
===================================================================
--- extra/pylint_pyopencl.py	(revision 3c56da87f0995bdde77f4183b68e77b81584dde0)
+++ extra/pylint_pyopencl.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -9,2 +9,3 @@
     if module.name == 'pyopencl':
         import pyopencl
+        import pyopencl as cl
Index: extra/run-pylint.py
===================================================================
--- extra/run-pylint.py	(revision f01e53d5a96b16346f85048d4f125bc41f154611)
+++ extra/run-pylint.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -6,11 +6,21 @@
 
 def main():
+    extra = abspath(dirname(__file__))
+    root = abspath(joinpath(extra, '..'))
+
     envpath = os.environ.get('PYTHONPATH',None)
     path = [envpath] if envpath else []
-    path.append(abspath(dirname(__file__))) # so we can find the plugins
+    path.append(extra)
+
+    #bumps = abspath(joinpath(root, "..", "bumps"))
+    #periodictable = abspath(joinpath(root, "..", "periodictable"))
+    #sasview = abspath(joinpath(root, "..", "sasview", "src"))
+    #path.extend((bumps, periodictable, sasview))
+
     os.environ['PYTHONPATH'] = ':'.join(path)
-    root = abspath(joinpath(dirname(__file__), '..'))
+
+    # Run the lint command
+    cmd = "pylint --rcfile extra/pylint.rc -f parseable sasmodels"
     os.chdir(root)
-    cmd = "pylint --rcfile extra/pylint.rc -f parseable sasmodels"
     status = os.system(cmd)
     sys.exit(status)
Index: sasmodels/__init__.py
===================================================================
--- sasmodels/__init__.py	(revision 989005365096670e54afec254875979f35bd395a)
+++ sasmodels/__init__.py	(revision 37a725200a1edb6dbd7a029a236c3ea2cfc7522c)
@@ -1,1 +1,17 @@
+"""
+sasmodels
+=========
+
+**sasmodels** is a package containing models for small angle neutron and xray
+scattering.  Models supported are the one dimensional circular average and
+two dimensional oriented patterns.  As well as the form factor calculations
+for the individual shapes **sasmodels** also provides automatic shape
+polydispersity, angular dispersion and resolution convolution.  SESANS
+patterns can be computed for any model.
+
+Models can be written in python or in C.  C models can run on the GPU if
+OpenCL drivers are available.  See :mod:`generate` for details on
+defining new models.
+"""
+
 __version__ = "0.9"
Index: sasmodels/bumps_model.py
===================================================================
--- sasmodels/bumps_model.py	(revision ec7e360eb9b8ec7bb8f5006ed71720a9fa7d593b)
+++ sasmodels/bumps_model.py	(revision 190fc2b167d3d89ca9c3b7d3128bdf8bde9f67d0)
@@ -6,6 +6,6 @@
 arguments to set the initial value for each parameter.
 
-:class:`Experiment` combines the *Model* function with a data file loaded by the
-sasview data loader.  *Experiment* takes a *cutoff* parameter controlling
+:class:`Experiment` combines the *Model* function with a data file loaded by
+the sasview data loader.  *Experiment* takes a *cutoff* parameter controlling
 how far the polydispersity integral extends.
 
@@ -19,19 +19,65 @@
 from .direct_model import DataMixin
 
+__all__ = [
+    "Model", "Experiment",
+    ]
+
 # CRUFT: old style bumps wrapper which doesn't separate data and model
+# pylint: disable=invalid-name
 def BumpsModel(data, model, cutoff=1e-5, **kw):
+    r"""
+    Bind a model to data, along with a polydispersity cutoff.
+
+    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
+    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
+    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
+    points for displaying the SANS curve when there is no measured data.
+
+    *model* is a runnable module as returned from :func:`core.load_model`.
+
+    *cutoff* is the polydispersity weight cutoff.
+
+    Any additional *key=value* pairs are model dependent parameters.
+
+    Returns an :class:`Experiment` object.
+
+    Note that the usual Bumps semantics is not fully supported, since
+    assigning *M.name = parameter* on the returned experiment object
+    does not set that parameter in the model.  Range setting will still
+    work as expected though.
+
+    .. deprecated:: 0.1
+        Use :class:`Experiment` instead.
+    """
     warnings.warn("Use of BumpsModel is deprecated.  Use bumps_model.Experiment instead.")
+
+    # Create the model and experiment
     model = Model(model, **kw)
     experiment = Experiment(data=data, model=model, cutoff=cutoff)
-    for k in model._parameter_names:
-        setattr(experiment, k, getattr(model, k))
+
+    # Copy the model parameters up to the experiment object.
+    for k, v in model.parameters().items():
+        setattr(experiment, k, v)
     return experiment
 
+
 def create_parameters(model_info, **kwargs):
+    """
+    Generate Bumps parameters from the model info.
+
+    *model_info* is returned from :func:`generate.model_info` on the
+    model definition module.
+
+    Any additional *key=value* pairs are initial values for the parameters
+    to the models.  Uninitialized parameters will use the model default
+    value.
+
+    Returns a dictionary of *{name: Parameter}* containing the bumps
+    parameters for each model parameter, and a dictionary of
+    *{name: str}* containing the polydispersity distribution types.
+    """
     # lazy import; this allows the doc builder and nosetests to run even
     # when bumps is not on the path.
     from bumps.names import Parameter
-
-    partype = model_info['partype']
 
     pars = {}
@@ -40,10 +86,10 @@
         value = kwargs.pop(name, default)
         pars[name] = Parameter.default(value, name=name, limits=limits)
-    for name in partype['pd-2d']:
+    for name in model_info['partype']['pd-2d']:
         for xpart, xdefault, xlimits in [
-            ('_pd', 0., limits),
-            ('_pd_n', 35., (0, 1000)),
-            ('_pd_nsigma', 3., (0, 10)),
-        ]:
+                ('_pd', 0., limits),
+                ('_pd_n', 35., (0, 1000)),
+                ('_pd_nsigma', 3., (0, 10)),
+            ]:
             xname = name + xpart
             xvalue = kwargs.pop(xname, xdefault)
@@ -51,5 +97,5 @@
 
     pd_types = {}
-    for name in partype['pd-2d']:
+    for name in model_info['partype']['pd-2d']:
         xname = name + '_pd_type'
         xvalue = kwargs.pop(xname, 'gaussian')
@@ -63,10 +109,19 @@
 
 class Model(object):
+    """
+    Bumps wrapper for a SAS model.
+
+    *model* is a runnable module as returned from :func:`core.load_model`.
+
+    *cutoff* is the polydispersity weight cutoff.
+
+    Any additional *key=value* pairs are model dependent parameters.
+    """
     def __init__(self, model, **kwargs):
         self._sasmodel = model
         pars, pd_types = create_parameters(model.info, **kwargs)
-        for k,v in pars.items():
+        for k, v in pars.items():
             setattr(self, k, v)
-        for k,v in pd_types.items():
+        for k, v in pd_types.items():
             setattr(self, k, v)
         self._parameter_names = list(pars.keys())
@@ -75,9 +130,14 @@
     def parameters(self):
         """
-        Return a dictionary of parameters
+        Return a dictionary of parameters objects for the parameters,
+        excluding polydispersity distribution type.
         """
         return dict((k, getattr(self, k)) for k in self._parameter_names)
 
     def state(self):
+        """
+        Return a dictionary of current values for all the parameters,
+        including polydispersity distribution type.
+        """
         pars = dict((k, getattr(self, k).value) for k in self._parameter_names)
         pars.update((k, getattr(self, k)) for k in self._pd_type_names)
@@ -85,18 +145,18 @@
 
 class Experiment(DataMixin):
-    """
-    Return a bumps wrapper for a SAS model.
-
-    *data* is the data to be fitted.
-
-    *model* is the SAS model from :func:`core.load_model`.
+    r"""
+    Bumps wrapper for a SAS experiment.
+
+    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
+    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
+    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
+    points for displaying the SANS curve when there is no measured data.
+
+    *model* is a :class:`Model` object.
 
     *cutoff* is the integration cutoff, which avoids computing the
     the SAS model where the polydispersity weight is low.
 
-    Model parameters can be initialized with additional keyword
-    arguments, or by assigning to model.parameter_name.value.
-
-    The resulting bumps model can be used directly in a FitProblem call.
+    The resulting model can be used directly in a Bumps FitProblem call.
     """
     def __init__(self, data, model, cutoff=1e-5):
@@ -109,9 +169,13 @@
 
     def update(self):
+        """
+        Call when model parameters have changed and theory needs to be
+        recalculated.
+        """
         self._cache = {}
 
     def numpoints(self):
         """
-            Return the number of points
+        Return the number of data points
         """
         return len(self.Iq)
@@ -124,4 +188,10 @@
 
     def theory(self):
+        """
+        Return the theory corresponding to the model parameters.
+
+        This method uses lazy evaluation, and requires model.update() to be
+        called when the parameters have changed.
+        """
         if 'theory' not in self._cache:
             pars = self.model.state()
@@ -130,8 +200,16 @@
 
     def residuals(self):
+        """
+        Return theory minus data normalized by uncertainty.
+        """
         #if np.any(self.err ==0): print("zeros in err")
         return (self.theory() - self.Iq) / self.dIq
 
     def nllf(self):
+        """
+        Return the negative log likelihood of seeing data given the model
+        parameters, up to a normalizing constant which depends on the data
+        uncertainty.
+        """
         delta = self.residuals()
         #if np.any(np.isnan(R)): print("NaN in residuals")
@@ -149,8 +227,16 @@
 
     def simulate_data(self, noise=None):
+        """
+        Generate simulated data.
+        """
         Iq = self.theory()
         self._set_data(Iq, noise)
 
     def save(self, basename):
+        """
+        Save the model parameters and data into a file.
+
+        Not Implemented.
+        """
         pass
 
Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision 608e31e3a7d22b75d2b3b9ec919739d9345a9afb)
+++ sasmodels/compare.py	(revision d5e650da432c58f8cd7c4d86e8ecb26828e17a21)
@@ -28,4 +28,20 @@
 
 from __future__ import print_function
+
+import sys
+import math
+from os.path import basename, dirname, join as joinpath
+import glob
+import datetime
+import traceback
+
+import numpy as np
+
+from . import core
+from . import kerneldll
+from . import generate
+from .data import plot_theory, empty_data1D, empty_data2D
+from .direct_model import DirectModel
+from .convert import revert_model, constrain_new_to_old
 
 USAGE = """
@@ -72,34 +88,16 @@
 # doc string so that we can display it at run time if there is an error.
 # lin
-__doc__ = __doc__ + """
+__doc__ = (__doc__  # pylint: disable=redefined-builtin
+           + """
 Program description
 -------------------
 
-""" + USAGE
-
-
-
-import sys
-import math
-from os.path import basename, dirname, join as joinpath
-import glob
-import datetime
-import traceback
-
-import numpy as np
-
+"""
+           + USAGE)
+
+kerneldll.ALLOW_SINGLE_PRECISION_DLLS = True
+
+# List of available models
 ROOT = dirname(__file__)
-sys.path.insert(0, ROOT)  # Make sure sasmodels is first on the path
-
-
-from . import core
-from . import kerneldll
-from . import generate
-from .data import plot_theory, empty_data1D, empty_data2D
-from .direct_model import DirectModel
-from .convert import revert_model, constrain_new_to_old
-kerneldll.ALLOW_SINGLE_PRECISION_DLLS = True
-
-# List of available models
 MODELS = [basename(f)[:-3]
           for f in sorted(glob.glob(joinpath(ROOT, "models", "[a-zA-Z]*.py")))]
@@ -115,4 +113,72 @@
         return dt.total_seconds()
 
+
+class push_seed(object):
+    """
+    Set the seed value for the random number generator.
+
+    When used in a with statement, the random number generator state is
+    restored after the with statement is complete.
+
+    :Parameters:
+
+    *seed* : int or array_like, optional
+        Seed for RandomState
+
+    :Example:
+
+    Seed can be used directly to set the seed::
+
+        >>> from numpy.random import randint
+        >>> push_seed(24)
+        <...push_seed object at...>
+        >>> print(randint(0,1000000,3))
+        [242082    899 211136]
+
+    Seed can also be used in a with statement, which sets the random
+    number generator state for the enclosed computations and restores
+    it to the previous state on completion::
+
+        >>> with push_seed(24):
+        ...    print(randint(0,1000000,3))
+        [242082    899 211136]
+
+    Using nested contexts, we can demonstrate that state is indeed
+    restored after the block completes::
+
+        >>> with push_seed(24):
+        ...    print(randint(0,1000000))
+        ...    with push_seed(24):
+        ...        print(randint(0,1000000,3))
+        ...    print(randint(0,1000000))
+        242082
+        [242082    899 211136]
+        899
+
+    The restore step is protected against exceptions in the block::
+
+        >>> with push_seed(24):
+        ...    print(randint(0,1000000))
+        ...    try:
+        ...        with push_seed(24):
+        ...            print(randint(0,1000000,3))
+        ...            raise Exception()
+        ...    except:
+        ...        print("Exception raised")
+        ...    print(randint(0,1000000))
+        242082
+        [242082    899 211136]
+        Exception raised
+        899
+    """
+    def __init__(self, seed=None):
+        self._state = np.random.get_state()
+        np.random.seed(seed)
+
+    def __enter__(self):
+        return None
+
+    def __exit__(self, *args):
+        np.random.set_state(self._state)
 
 def tic():
@@ -177,4 +243,5 @@
         return [0, (2*v if v > 0 else 1)]
 
+
 def _randomize_one(p, v):
     """
@@ -186,4 +253,5 @@
         return np.random.uniform(*parameter_range(p, v))
 
+
 def randomize_pars(pars, seed=None):
     """
@@ -195,8 +263,8 @@
     :func:`constrain_pars` needs to be called afterward..
     """
-    np.random.seed(seed)
-    # Note: the sort guarantees order `of calls to random number generator
-    pars = dict((p, _randomize_one(p, v))
-                for p, v in sorted(pars.items()))
+    with push_seed(seed):
+        # Note: the sort guarantees order `of calls to random number generator
+        pars = dict((p, _randomize_one(p, v))
+                    for p, v in sorted(pars.items()))
     return pars
 
@@ -281,5 +349,6 @@
             theory = lambda: smearer.get_value()
         else:
-            theory = lambda: model.evalDistribution([data.qx_data[index], data.qy_data[index]])
+            theory = lambda: model.evalDistribution([data.qx_data[index],
+                                                     data.qy_data[index]])
     elif smearer is not None:
         theory = lambda: smearer(model.evalDistribution(data.x))
@@ -416,5 +485,6 @@
         try:
             base_value, base_time = time_calculation(base, pars, Nbase)
-            print("%s t=%.1f ms, intensity=%.0f"%(base.engine, base_time, sum(base_value)))
+            print("%s t=%.1f ms, intensity=%.0f"
+                  % (base.engine, base_time, sum(base_value)))
         except ImportError:
             traceback.print_exc()
@@ -426,5 +496,6 @@
         try:
             comp_value, comp_time = time_calculation(comp, pars, Ncomp)
-            print("%s t=%.1f ms, intensity=%.0f"%(comp.engine, comp_time, sum(comp_value)))
+            print("%s t=%.1f ms, intensity=%.0f"
+                  % (comp.engine, comp_time, sum(comp_value)))
         except ImportError:
             traceback.print_exc()
@@ -433,12 +504,11 @@
     # Compare, but only if computing both forms
     if Nbase > 0 and Ncomp > 0:
-        #print("speedup %.2g"%(comp_time/base_time))
-        #print("max |base/comp|", max(abs(base_value/comp_value)), "%.15g"%max(abs(base_value)), "%.15g"%max(abs(comp_value)))
-        #comp *= max(base_value/comp_value)
         resid = (base_value - comp_value)
         relerr = resid/comp_value
-        _print_stats("|%s-%s|"%(base.engine, comp.engine) + (" "*(3+len(comp.engine))),
+        _print_stats("|%s-%s|"
+                     % (base.engine, comp.engine) + (" "*(3+len(comp.engine))),
                      resid)
-        _print_stats("|(%s-%s)/%s|"%(base.engine, comp.engine, comp.engine),
+        _print_stats("|(%s-%s)/%s|"
+                     % (base.engine, comp.engine, comp.engine),
                      relerr)
 
@@ -459,20 +529,22 @@
     if Nbase > 0:
         if Ncomp > 0: plt.subplot(131)
-        plot_theory(data, base_value, view=view, plot_data=False, limits=limits)
+        plot_theory(data, base_value, view=view, use_data=False, limits=limits)
         plt.title("%s t=%.1f ms"%(base.engine, base_time))
         #cbar_title = "log I"
     if Ncomp > 0:
         if Nbase > 0: plt.subplot(132)
-        plot_theory(data, comp_value, view=view, plot_data=False, limits=limits)
+        plot_theory(data, comp_value, view=view, use_data=False, limits=limits)
         plt.title("%s t=%.1f ms"%(comp.engine, comp_time))
         #cbar_title = "log I"
     if Ncomp > 0 and Nbase > 0:
         plt.subplot(133)
-        if '-abs' in opts:
+        if not opts['rel_err']:
             err, errstr, errview = resid, "abs err", "linear"
         else:
             err, errstr, errview = abs(relerr), "rel err", "log"
         #err,errstr = base/comp,"ratio"
-        plot_theory(data, None, resid=err, view=errview, plot_data=False)
+        plot_theory(data, None, resid=err, view=errview, use_data=False)
+        if view == 'linear':
+            plt.xscale('linear')
         plt.title("max %s = %.3g"%(errstr, max(abs(err))))
         #cbar_title = errstr if errview=="linear" else "log "+errstr
@@ -534,5 +606,7 @@
 def columnize(L, indent="", width=79):
     """
-    Format a list of strings into columns for printing.
+    Format a list of strings into columns.
+
+    Returns a string with carriage returns ready for printing.
     """
     column_width = max(len(w) for w in L) + 1
@@ -591,5 +665,5 @@
     invalid = [o[1:] for o in flags
                if o[1:] not in NAME_OPTIONS
-                   and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
+               and not any(o.startswith('-%s='%t) for t in VALUE_OPTIONS)]
     if invalid:
         print("Invalid options: %s"%(", ".join(invalid)))
@@ -597,4 +671,5 @@
 
 
+    # pylint: disable=bad-whitespace
     # Interpret the flags
     opts = {
@@ -651,4 +726,5 @@
         elif arg == '-sasview': engines.append(arg[1:])
         elif arg == '-edit':    opts['explore'] = True
+    # pylint: enable=bad-whitespace
 
     if len(engines) == 0:
@@ -675,9 +751,9 @@
     presets = {}
     for arg in values:
-        k,v = arg.split('=',1)
+        k, v = arg.split('=', 1)
         if k not in pars:
             # extract base name without polydispersity info
             s = set(p.split('_pd')[0] for p in pars)
-            print("%r invalid; parameters are: %s"%(k,", ".join(sorted(s))))
+            print("%r invalid; parameters are: %s"%(k, ", ".join(sorted(s))))
             sys.exit(1)
         presets[k] = float(v) if not k.endswith('type') else v
@@ -697,5 +773,5 @@
 
     # Create the computational engines
-    data, _index = make_data(opts)
+    data, _ = make_data(opts)
     if n1:
         base = make_engine(model_definition, data, engines[0], opts['cutoff'])
@@ -707,4 +783,5 @@
         comp = None
 
+    # pylint: disable=bad-whitespace
     # Remember it all
     opts.update({
@@ -718,15 +795,12 @@
         'engines'   : [base, comp],
     })
+    # pylint: enable=bad-whitespace
 
     return opts
 
-def main():
-    opts = parse_opts()
-    if opts['explore']:
-        explore(opts)
-    else:
-        compare(opts)
-
 def explore(opts):
+    """
+    Explore the model using the Bumps GUI.
+    """
     import wx
     from bumps.names import FitProblem
@@ -734,17 +808,20 @@
 
     problem = FitProblem(Explore(opts))
-    isMac = "cocoa" in wx.version()
+    is_mac = "cocoa" in wx.version()
     app = wx.App()
     frame = AppFrame(parent=None, title="explore")
-    if not isMac: frame.Show()
+    if not is_mac: frame.Show()
     frame.panel.set_model(model=problem)
     frame.panel.Layout()
     frame.panel.aui.Split(0, wx.TOP)
-    if isMac: frame.Show()
+    if is_mac: frame.Show()
     app.MainLoop()
 
 class Explore(object):
     """
-    Return a bumps wrapper for a SAS model comparison.
+    Bumps wrapper for a SAS model comparison.
+
+    The resulting object can be used as a Bumps fit problem so that
+    parameters can be adjusted in the GUI, with plots updated on the fly.
     """
     def __init__(self, opts):
@@ -787,4 +864,5 @@
         Return cost.
         """
+        # pylint: disable=no-self-use
         return 0.  # No nllf
 
@@ -804,4 +882,14 @@
 
 
+def main():
+    """
+    Main program.
+    """
+    opts = parse_opts()
+    if opts['explore']:
+        explore(opts)
+    else:
+        compare(opts)
+
 if __name__ == "__main__":
     main()
Index: sasmodels/compare_many.py
===================================================================
--- sasmodels/compare_many.py	(revision 6458608d36e65099c9e90af3b188b8a10cadb474)
+++ sasmodels/compare_many.py	(revision 4f2478e77cdc4e495cf403a9aeb9c6544bcdf468)
@@ -1,3 +1,15 @@
 #!/usr/bin/env python
+"""
+Program to compare results from many random parameter sets for a given model.
+
+The result is a comma separated value (CSV) table that can be redirected
+from standard output into a file and loaded into a spreadsheet.
+
+The models are compared for each parameter set and if the difference is
+greater than expected for that precision, the parameter set is labeled
+as bad and written to the output, along with the random seed used to
+generate that parameter value.  This seed can be used with :mod:`compare`
+to reload and display the details of the model.
+"""
 from __future__ import print_function
 
@@ -14,16 +26,38 @@
 
 def calc_stats(target, value, index):
+    """
+    Calculate statistics between the target value and the computed value.
+
+    *target* and *value* are the vectors being compared, with the
+    difference normalized by *target* to get relative error.  Only
+    the elements listed in *index* are used, though index may be
+    and empty slice defined by *slice(None, None)*.
+
+    Returns:
+
+        *maxrel* the maximum relative difference
+
+        *rel95* the relative difference with the 5% biggest differences ignored
+
+        *maxabs* the maximum absolute difference for the 5% biggest differences
+
+        *maxval* the maximum value for the 5% biggest differences
+    """
     resid = abs(value-target)[index]
     relerr = resid/target[index]
-    srel = np.argsort(relerr)
+    sorted_rel_index = np.argsort(relerr)
     #p90 = int(len(relerr)*0.90)
     p95 = int(len(relerr)*0.95)
     maxrel = np.max(relerr)
-    rel95 = relerr[srel[p95]]
-    maxabs = np.max(resid[srel[p95:]])
-    maxval = np.max(value[srel[p95:]])
-    return maxrel,rel95,maxabs,maxval
+    rel95 = relerr[sorted_rel_index[p95]]
+    maxabs = np.max(resid[sorted_rel_index[p95:]])
+    maxval = np.max(value[sorted_rel_index[p95:]])
+    return maxrel, rel95, maxabs, maxval
 
 def print_column_headers(pars, parts):
+    """
+    Generate column headers for the differences and for the parameters,
+    and print them to standard output.
+    """
     stats = list('Max rel err|95% rel err|Max abs err above 90% rel|Max value above 90% rel'.split('|'))
     groups = ['']
@@ -36,4 +70,5 @@
     print(','.join('"%s"'%c for c in columns))
 
+# Target 'good' value for various precision levels.
 PRECISION = {
     'fast': 1e-3,
@@ -48,13 +83,30 @@
 def compare_instance(name, data, index, N=1, mono=True, cutoff=1e-5,
                      base='sasview', comp='double'):
-    is2D = hasattr(data, 'qx_data')
+    r"""
+    Compare the model under different calculation engines.
+
+    *name* is the name of the model.
+
+    *data* is the data object giving $q, \Delta q$ calculation points.
+
+    *index* is the active set of points.
+
+    *N* is the number of comparisons to make.
+
+    *cutoff* is the polydispersity weight cutoff to make the calculation
+    a little bit faster.
+
+    *base* and *comp* are the names of the calculation engines to compare.
+    """
+
+    is_2d = hasattr(data, 'qx_data')
     model_definition = core.load_model_definition(name)
     pars = get_demo_pars(model_definition)
     header = ('\n"Model","%s","Count","%d","Dimension","%s"'
-              % (name, N, "2D" if is2D else "1D"))
+              % (name, N, "2D" if is_2d else "1D"))
     if not mono: header += ',"Cutoff",%g'%(cutoff,)
     print(header)
 
-    if is2D:
+    if is_2d:
         info = generate.make_info(model_definition)
         partype = info['partype']
@@ -69,4 +121,8 @@
     # declarations are not available in python 2.7.
     def try_model(fn, pars):
+        """
+        Return the model evaluated at *pars*.  If there is an exception,
+        print it and return NaN of the right shape.
+        """
         try:
             result = fn(**pars)
@@ -82,4 +138,8 @@
         return result
     def check_model(pars):
+        """
+        Run the two calculators against *pars*, returning statistics
+        on the differences.  See :func:`calc_stats` for the list of stats.
+        """
         base_value = try_model(calc_base, pars)
         comp_value = try_model(calc_comp, pars)
@@ -108,5 +168,5 @@
         good = [True]
         columns = check_model(pars_i)
-        columns += [v for _,v in sorted(pars_i.items())]
+        columns += [v for _, v in sorted(pars_i.items())]
         if first:
             labels = [" vs. ".join((calc_base.engine, calc_comp.engine))]
@@ -121,12 +181,21 @@
 
 def print_usage():
+    """
+    Print the command usage string.
+    """
     print("usage: compare_many.py MODEL COUNT (1dNQ|2dNQ) (CUTOFF|mono) (single|double|quad)")
 
 
 def print_models():
+    """
+    Print the list of available models in columns.
+    """
     print(columnize(MODELS, indent="  "))
 
 
 def print_help():
+    """
+    Print usage string, the option description and the list of available models.
+    """
     print_usage()
     print("""\
@@ -158,5 +227,8 @@
 
 def main():
-    if len(sys.argv) not in (6,7):
+    """
+    Main program.
+    """
+    if len(sys.argv) not in (6, 7):
         print_help()
         sys.exit(1)
@@ -182,5 +254,5 @@
 
     data, index = make_data({'qmax':1.0, 'is2d':is2D, 'nq':Nq, 'res':0.,
-                              'accuracy': 'Low', 'view':'log'})
+                             'accuracy': 'Low', 'view':'log'})
     model_list = [model] if model != "all" else MODELS
     for model in model_list:
@@ -189,3 +261,5 @@
 
 if __name__ == "__main__":
+    #from .compare import push_seed
+    #with push_seed(1): main()
     main()
Index: sasmodels/convert.py
===================================================================
--- sasmodels/convert.py	(revision 03582f9957d77aa9e008b25634fbd7ced77ce50d)
+++ sasmodels/convert.py	(revision 03582f9957d77aa9e008b25634fbd7ced77ce50d)
@@ -1,139 +1,1134 @@
-"""
-Convert models to and from sasview.
-"""
-import warnings
-
-PD_DOT = [
-    ("", ""),
-    ("_pd", ".width"),
-    ("_pd_n", ".npts"),
-    ("_pd_nsigma", ".nsigmas"),
-    ("_pd_type", ".type"),
-    ]
-def _convert_pars(pars, mapping):
-    """
-    Rename the parameters and any associated polydispersity attributes.
-    """
-    newpars = pars.copy()
-    for new, old in mapping.items():
-        if old == new: continue
-        for pd, dot in PD_DOT:
-            if old+dot in newpars:
-                if new is not None:
-                    newpars[new+pd] = pars[old+dot]
-                del newpars[old+dot]
-    return newpars
-
-def _rescale_sld(pars):
-    """
-    rescale all sld parameters in the new model definition by 1e6 so the
-    numbers are nicer.  Relies on the fact that all sld parameters in the
-    new model definition end with sld.
-    """
-    return dict((p, (v*1e6 if p.endswith('sld') else v))
-                for p,v in pars.items())
-
-def convert_model(name, pars):
-    """
-    Convert model from old style parameter names to new style.
-    """
-    _,_ = name,pars # lint
-    raise NotImplementedError
-    # need to load all new models in order to determine old=>new
-    # model name mapping
-
-def _unscale_sld(pars):
-    """
-    rescale all sld parameters in the new model definition by 1e6 so the
-    numbers are nicer.  Relies on the fact that all sld parameters in the
-    new model definition end with sld.
-    """
-    return dict((p, (v*1e-6 if p.endswith('sld') else v))
-                for p,v in pars.items())
-
-def _remove_pd(pars, key, name):
-    """
-    Remove polydispersity from the parameter list.
-
-    Note: operates in place
-    """
-    # Bumps style parameter names
-    pd = pars.pop(key+".width", 0.0)
-    pd_n = pars.pop(key+".npts", 0)
-    if pd != 0.0 and pd_n != 0:
-        warnings.warn("parameter %s not polydisperse in sasview %s"%(key, name))
-    pars.pop(key+".nsigmas", None)
-    pars.pop(key+".type", None)
-    return pars
-
-def _revert_pars(pars, mapping):
-    """
-    Rename the parameters and any associated polydispersity attributes.
-    """
-    newpars = pars.copy()
-
-    for new, old in mapping.items():
-        for pd, dot in PD_DOT:
-            if old and old+pd == new+dot:
-                continue
-            if new+pd in newpars:
-                if old is not None:
-                    newpars[old+dot] = pars[new+pd]
-                del newpars[new+pd]
-    for k in list(newpars.keys()):
-        for pd, dot in PD_DOT[1:]:  # skip "" => ""
-            if k.endswith(pd):
-                newpars[k[:-len(pd)]+dot] = newpars[k]
-                del newpars[k]
-    return newpars
-
-def revert_model(model_definition, pars):
-    """
-    Convert model from new style parameter names to old style.
-    """
-    mapping = model_definition.oldpars
-    oldname = model_definition.oldname
-    oldpars = _revert_pars(_unscale_sld(pars), mapping)
-
-    # Note: update compare.constrain_pars to match
-    name = model_definition.name
-    if name in ('teubner_strey', 'broad_peak', 'two_lorentzian', 'gel_fit',
-                'binary_hard_sphere'):
-        if oldpars.pop('scale', 1.0) != 1.0:
-            warnings.warn("parameter scale not used in sasview %s"%name)
-    elif name in ('guinier',):
-        if oldpars.pop('background', 0.0) != 0.0:
-            warnings.warn("parameter background not used in sasview %s"%name)
-    elif getattr(model_definition, 'category', None) == 'structure-factor':
-        if oldpars.pop('scale', 1.0) != 1.0:
-            warnings.warn("parameter scale not used in sasview %s"%name)
-        if oldpars.pop('background', 0.0) != 0.0:
-            warnings.warn("parameter background not used in sasview %s"%name)
-    elif name == 'pearl_necklace':
-        _remove_pd(oldpars, 'num_pearls', name)
-        _remove_pd(oldpars, 'thick_string', name)
-    elif name == 'rpa':
-        # convert scattering lengths from femtometers to centimeters
-        for p in "La", "Lb", "Lc", "Ld":
-            if p in oldpars: oldpars[p] *= 1e-13
-
-    return oldname, oldpars
-
-def constrain_new_to_old(model_definition, pars):
-    """
-    Restrict parameter values to those that will match sasview.
-    """
-    # Note: update convert.revert_model to match
-    name = model_definition.name
-    if name in ('teubner_strey','broad_peak', 'two_lorentzian', 'gel_fit'):
-        pars['scale'] = 1
-    elif name in ('guinier',):
-        pars['background'] = 0
-    elif name == 'pearl_necklace':
-        pars['string_thickness_pd_n'] = 0
-        pars['number_of_pearls_pd_n'] = 0
-    elif name == 'rpa':
-        pars['case_num'] = int(pars['case_num'])
-    elif getattr(model_definition, 'category', None) == 'structure-factor':
-        pars['scale'], pars['background'] = 1, 0
-
+
+
+
+
+<!DOCTYPE html>
+<html lang="en" class=" is-copy-enabled is-u2f-enabled">
+  <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
+    <meta charset='utf-8'>
+
+    <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-e3dd2ae433414e240167f740f90ff2599e28804648787933de7c0183fae81c29.css" integrity="sha256-490q5DNBTiQBZ/dA+Q/yWZ4ogEZIeHkz3nwBg/roHCk=" media="all" rel="stylesheet" />
+    <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github2-fd8d48abb9063f51f186b0da98caa88d32b7ca8baecaa10d8a91c18a6f129b7f.css" integrity="sha256-/Y1Iq7kGP1HxhrDamMqojTK3youuyqENipHBim8Sm38=" media="all" rel="stylesheet" />
+    
+    
+    
+
+    <link as="script" href="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js" rel="preload" />
+    <link as="script" href="https://assets-cdn.github.com/assets/github-696336964b7c42e4c6f4dfbaf1f8e57f425cd9a9d18a12f3fe6e3dd744fd7d13.js" rel="preload" />
+
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta http-equiv="Content-Language" content="en">
+    <meta name="viewport" content="width=1020">
+    
+    
+    <title>sasmodels/convert.py at master Â· SasView/sasmodels Â· GitHub</title>
+    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
+    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
+    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
+    <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
+    <meta property="fb:app_id" content="1401488693436528">
+
+      <meta content="https://avatars1.githubusercontent.com/u/10882470?v=3&amp;s=400" name="twitter:image:src" /><meta content="@github" name="twitter:site" /><meta content="summary" name="twitter:card" /><meta content="SasView/sasmodels" name="twitter:title" /><meta content="sasmodels - Package for calculation of small angle scattering models using OpenCL. Builds here: https://jenkins.esss.dk/sasview/view/Sasmodels-Builds/" name="twitter:description" />
+      <meta content="https://avatars1.githubusercontent.com/u/10882470?v=3&amp;s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="SasView/sasmodels" property="og:title" /><meta content="https://github.com/SasView/sasmodels" property="og:url" /><meta content="sasmodels - Package for calculation of small angle scattering models using OpenCL. Builds here: https://jenkins.esss.dk/sasview/view/Sasmodels-Builds/" property="og:description" />
+      <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
+    <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
+    <link rel="assets" href="https://assets-cdn.github.com/">
+    
+    <meta name="pjax-timeout" content="1000">
+    
+
+    <meta name="msapplication-TileImage" content="/windows-tile.png">
+    <meta name="msapplication-TileColor" content="#ffffff">
+    <meta name="selected-link" value="repo_source" data-pjax-transient>
+
+    <meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
+<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
+    <meta name="google-analytics" content="UA-3769691-2">
+
+<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="32FD15EB:517B:95519E0:56B61366" name="octolytics-dimension-request_id" />
+<meta content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" name="analytics-location" />
+
+
+
+  <meta class="js-ga-set" name="dimension1" content="Logged Out">
+
+
+
+        <meta name="hostname" content="github.com">
+    <meta name="user-login" content="">
+
+        <meta name="expected-hostname" content="github.com">
+
+      <link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#4078c0">
+      <link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
+
+    <meta content="ed1699ee8f88e1fc0f2738f860d9b53cf2d34560" name="form-nonce" />
+
+    <meta http-equiv="x-pjax-version" content="2330245d99bfcba85ea2b6d282c0cb53">
+
+      
+  <meta name="description" content="sasmodels - Package for calculation of small angle scattering models using OpenCL. Builds here: https://jenkins.esss.dk/sasview/view/Sasmodels-Builds/">
+  <meta name="go-import" content="github.com/SasView/sasmodels git https://github.com/SasView/sasmodels.git">
+
+  <meta content="10882470" name="octolytics-dimension-user_id" /><meta content="SasView" name="octolytics-dimension-user_login" /><meta content="30761174" name="octolytics-dimension-repository_id" /><meta content="SasView/sasmodels" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="30761174" name="octolytics-dimension-repository_network_root_id" /><meta content="SasView/sasmodels" name="octolytics-dimension-repository_network_root_nwo" />
+  <link href="https://github.com/SasView/sasmodels/commits/master.atom" rel="alternate" title="Recent Commits to sasmodels:master" type="application/atom+xml">
+
+
+      <link rel="canonical" href="https://github.com/SasView/sasmodels/blob/master/sasmodels/convert.py" data-pjax-transient>
+  </head>
+
+
+  <body class="logged_out   env-production windows vis-public page-blob">
+    <a href="#start-of-content" tabindex="1" class="accessibility-aid js-skip-to-content">Skip to content</a>
+
+    
+    
+    
+
+
+
+      
+      <div class="header header-logged-out" role="banner">
+  <div class="container clearfix">
+
+    <a class="header-logo-wordmark" href="https://github.com/" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
+      <svg aria-hidden="true" class="octicon octicon-logo-github" height="28" role="img" version="1.1" viewBox="0 0 45 16" width="78"><path d="M8.64 5.19H4.88c-0.11 0-0.19 0.08-0.19 0.17v1.84c0 0.09 0.08 0.17 0.19 0.17h1.47v2.3s-0.33 0.11-1.25 0.11c-1.08 0-2.58-0.39-2.58-3.7s1.58-3.73 3.05-3.73c1.27 0 1.81 0.22 2.17 0.33 0.11 0.03 0.2-0.08 0.2-0.17l0.42-1.78c0-0.05-0.02-0.09-0.06-0.14-0.14-0.09-1.02-0.58-3.2-0.58C2.58 0 0 1.06 0 6.2s2.95 5.92 5.44 5.92c2.06 0 3.31-0.89 3.31-0.89 0.05-0.02 0.06-0.09 0.06-0.13V5.36c0-0.09-0.08-0.17-0.19-0.17h0.02zM27.7 0.44h-2.13c-0.09 0-0.17 0.08-0.17 0.17v4.09h-3.31V0.61c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17v11.11c0 0.09 0.09 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V6.97h3.31l-0.02 4.75c0 0.09 0.08 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V0.61c0-0.09-0.08-0.17-0.17-0.17h0.02zM11.19 0.69c-0.77 0-1.38 0.61-1.38 1.38s0.61 1.38 1.38 1.38c0.75 0 1.36-0.61 1.36-1.38s-0.61-1.38-1.36-1.38z m1.22 3.55c0-0.09-0.08-0.17-0.17-0.17H10.11c-0.09 0-0.17 0.09-0.17 0.2 0 0 0 6.17 0 7.34 0 0.2 0.13 0.27 0.3 0.27 0 0 0.91 0 1.92 0 0.2 0 0.25-0.09 0.25-0.27 0-0.39 0-7.36 0-7.36v-0.02z m23.52-0.16h-2.09c-0.11 0-0.17 0.08-0.17 0.19v5.44s-0.55 0.39-1.3 0.39-0.97-0.34-0.97-1.09c0-0.73 0-4.75 0-4.75 0-0.09-0.08-0.17-0.17-0.17h-2.14c-0.09 0-0.17 0.08-0.17 0.17 0 0 0 2.91 0 5.11s1.23 2.75 2.92 2.75c1.39 0 2.52-0.77 2.52-0.77s0.05 0.39 0.08 0.45c0.02 0.05 0.09 0.09 0.16 0.09h1.34c0.11 0 0.17-0.08 0.17-0.17l0.02-7.47c0-0.09-0.08-0.17-0.19-0.17z m5.77-0.25c-1.2 0-2.02 0.53-2.02 0.53V0.59c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17l-0.02 11.11c0 0.09 0.09 0.17 0.19 0.17h1.48c0.06 0 0.11-0.02 0.14-0.08 0.05-0.06 0.09-0.52 0.09-0.52s0.88 0.83 2.52 0.83c1.94 0 3.05-0.98 3.05-4.41s-1.77-3.88-2.97-3.88z m-0.83 6.27c-0.73-0.02-1.22-0.36-1.22-0.36V6.22s0.48-0.3 1.08-0.34c0.77-0.08 1.5 0.16 1.5 1.97 0 1.91-0.33 2.28-1.36 2.25z m-22.33-0.05c-0.09 0-0.33 0.05-0.58 0.05-0.78 0-1.05-0.36-1.05-0.83s0-3.13 0-3.13h1.59c0.09 0 0.16-0.08 0.16-0.19V4.25c0-0.09-0.08-0.17-0.16-0.17h-1.59V1.97c0-0.08-0.05-0.13-0.14-0.13H14.61c-0.09 0-0.14 0.05-0.14 0.13v2.17s-1.09 0.27-1.16 0.28c-0.08 0.02-0.13 0.09-0.13 0.17v1.36c0 0.11 0.08 0.19 0.17 0.19h1.11s0 1.44 0 3.28c0 2.44 1.7 2.69 2.86 2.69 0.53 0 1.17-0.17 1.27-0.22 0.06-0.02 0.09-0.09 0.09-0.16v-1.5c0-0.11-0.08-0.19-0.17-0.19h0.02z"></path></svg>
+    </a>
+
+    <div class="header-actions" role="navigation">
+        <a class="btn btn-primary" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
+      <a class="btn" href="/login?return_to=%2FSasView%2Fsasmodels%2Fblob%2Fmaster%2Fsasmodels%2Fconvert.py" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
+    </div>
+
+    <div class="site-search repo-scope js-site-search" role="search">
+      <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/SasView/sasmodels/search" class="js-site-search-form" data-global-search-url="/search" data-repo-search-url="/SasView/sasmodels/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
+  <label class="js-chromeless-input-container form-control">
+    <div class="scope-badge">This repository</div>
+    <input type="text"
+      class="js-site-search-focus js-site-search-field is-clearable chromeless-input"
+      data-hotkey="s"
+      name="q"
+      placeholder="Search"
+      aria-label="Search this repository"
+      data-global-scope-placeholder="Search GitHub"
+      data-repo-scope-placeholder="Search"
+      tabindex="1"
+      autocapitalize="off">
+  </label>
+</form>
+    </div>
+
+      <ul class="header-nav left" role="navigation">
+          <li class="header-nav-item">
+            <a class="header-nav-link" href="/explore" data-ga-click="(Logged out) Header, go to explore, text:explore">Explore</a>
+          </li>
+          <li class="header-nav-item">
+            <a class="header-nav-link" href="/features" data-ga-click="(Logged out) Header, go to features, text:features">Features</a>
+          </li>
+          <li class="header-nav-item">
+            <a class="header-nav-link" href="https://enterprise.github.com/" data-ga-click="(Logged out) Header, go to enterprise, text:enterprise">Enterprise</a>
+          </li>
+          <li class="header-nav-item">
+            <a class="header-nav-link" href="/pricing" data-ga-click="(Logged out) Header, go to pricing, text:pricing">Pricing</a>
+          </li>
+      </ul>
+
+  </div>
+</div>
+
+
+
+    <div id="start-of-content" class="accessibility-aid"></div>
+
+      <div id="js-flash-container">
+</div>
+
+
+    <div role="main" class="main-content">
+        <div itemscope itemtype="http://schema.org/WebPage">
+    <div id="js-repo-pjax-container" class="context-loader-container js-repo-nav-next" data-pjax-container>
+      
+<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
+  <div class="container repohead-details-container">
+
+    
+
+<ul class="pagehead-actions">
+
+  <li>
+      <a href="/login?return_to=%2FSasView%2Fsasmodels"
+    class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+    aria-label="You must be signed in to watch a repository" rel="nofollow">
+    <svg aria-hidden="true" class="octicon octicon-eye" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6c4.94 0 7.94-6 7.94-6S13 2 8.06 2z m-0.06 10c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4z m2-4c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z"></path></svg>
+    Watch
+  </a>
+  <a class="social-count" href="/SasView/sasmodels/watchers">
+    16
+  </a>
+
+  </li>
+
+  <li>
+      <a href="/login?return_to=%2FSasView%2Fsasmodels"
+    class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+    aria-label="You must be signed in to star a repository" rel="nofollow">
+    <svg aria-hidden="true" class="octicon octicon-star" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z"></path></svg>
+    Star
+  </a>
+
+    <a class="social-count js-social-count" href="/SasView/sasmodels/stargazers">
+      0
+    </a>
+
+  </li>
+
+  <li>
+      <a href="/login?return_to=%2FSasView%2Fsasmodels"
+        class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+        aria-label="You must be signed in to fork a repository" rel="nofollow">
+        <svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" role="img" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg>
+        Fork
+      </a>
+
+    <a href="/SasView/sasmodels/network" class="social-count">
+      0
+    </a>
+  </li>
+</ul>
+
+    <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public ">
+  <svg aria-hidden="true" class="octicon octicon-repo" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z"></path></svg>
+  <span class="author"><a href="/SasView" class="url fn" itemprop="url" rel="author"><span itemprop="title">SasView</span></a></span><!--
+--><span class="path-divider">/</span><!--
+--><strong><a href="/SasView/sasmodels" data-pjax="#js-repo-pjax-container">sasmodels</a></strong>
+
+  <span class="page-context-loader">
+    <img alt="" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+  </span>
+
+</h1>
+
+  </div>
+  <div class="container">
+    
+<nav class="reponav js-repo-nav js-sidenav-container-pjax js-octicon-loaders"
+     role="navigation"
+     data-pjax="#js-repo-pjax-container">
+
+  <a href="/SasView/sasmodels" aria-label="Code" aria-selected="true" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /SasView/sasmodels">
+    <svg aria-hidden="true" class="octicon octicon-code" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M9.5 3l-1.5 1.5 3.5 3.5L8 11.5l1.5 1.5 4.5-5L9.5 3zM4.5 3L0 8l4.5 5 1.5-1.5L2.5 8l3.5-3.5L4.5 3z"></path></svg>
+    Code
+</a>
+
+  <a href="/SasView/sasmodels/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /SasView/sasmodels/pulls">
+    <svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.72z m-0.8 10c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg>
+    Pull requests
+    <span class="counter">0</span>
+</a>
+
+  <a href="/SasView/sasmodels/pulse" class="js-selected-navigation-item reponav-item" data-selected-links="pulse /SasView/sasmodels/pulse">
+    <svg aria-hidden="true" class="octicon octicon-pulse" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z"></path></svg>
+    Pulse
+</a>
+  <a href="/SasView/sasmodels/graphs" class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors /SasView/sasmodels/graphs">
+    <svg aria-hidden="true" class="octicon octicon-graph" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 14v1H0V0h1v14h15z m-11-1H3V8h2v5z m4 0H7V3h2v10z m4 0H11V6h2v7z"></path></svg>
+    Graphs
+</a>
+
+</nav>
+
+  </div>
+</div>
+
+<div class="container new-discussion-timeline experiment-repo-nav">
+  <div class="repository-content">
+
+    
+
+<a href="/SasView/sasmodels/blob/d5e650da432c58f8cd7c4d86e8ecb26828e17a21/sasmodels/convert.py" class="hidden js-permalink-shortcut" data-hotkey="y">Permalink</a>
+
+<!-- blob contrib key: blob_contributors:v21:db8856a43e364dee3a304855aa1d4b41 -->
+
+<div class="file-navigation js-zeroclipboard-container">
+  
+<div class="select-menu js-menu-container js-select-menu left">
+  <button class="btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
+    title="master"
+    type="button" aria-label="Switch branches or tags" tabindex="0" aria-haspopup="true">
+    <i>Branch:</i>
+    <span class="js-select-button css-truncate-target">master</span>
+  </button>
+
+  <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax aria-hidden="true">
+
+    <div class="select-menu-modal">
+      <div class="select-menu-header">
+        <svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
+        <span class="select-menu-title">Switch branches/tags</span>
+      </div>
+
+      <div class="select-menu-filters">
+        <div class="select-menu-text-filter">
+          <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
+        </div>
+        <div class="select-menu-tabs">
+          <ul>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
+            </li>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tagâŠ" class="js-select-menu-tab" role="tab">Tags</a>
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
+
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+            <a class="select-menu-item js-navigation-item js-navigation-open "
+               href="/SasView/sasmodels/blob/develop_ric/sasmodels/convert.py"
+               data-name="develop_ric"
+               data-skip-pjax="true"
+               rel="nofollow">
+              <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
+              <span class="select-menu-item-text css-truncate-target" title="develop_ric">
+                develop_ric
+              </span>
+            </a>
+            <a class="select-menu-item js-navigation-item js-navigation-open "
+               href="/SasView/sasmodels/blob/gh-pages/sasmodels/convert.py"
+               data-name="gh-pages"
+               data-skip-pjax="true"
+               rel="nofollow">
+              <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
+              <span class="select-menu-item-text css-truncate-target" title="gh-pages">
+                gh-pages
+              </span>
+            </a>
+            <a class="select-menu-item js-navigation-item js-navigation-open selected"
+               href="/SasView/sasmodels/blob/master/sasmodels/convert.py"
+               data-name="master"
+               data-skip-pjax="true"
+               rel="nofollow">
+              <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
+              <span class="select-menu-item-text css-truncate-target" title="master">
+                master
+              </span>
+            </a>
+        </div>
+
+          <div class="select-menu-no-results">Nothing to show</div>
+      </div>
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+        </div>
+
+        <div class="select-menu-no-results">Nothing to show</div>
+      </div>
+
+    </div>
+  </div>
+</div>
+
+  <div class="btn-group right">
+    <a href="/SasView/sasmodels/find/master"
+          class="js-show-file-finder btn btn-sm"
+          data-pjax
+          data-hotkey="t">
+      Find file
+    </a>
+    <button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button>
+  </div>
+  <div class="breadcrumb js-zeroclipboard-target">
+    <span class="repo-root js-repo-root"><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/SasView/sasmodels" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">sasmodels</span></a></span></span><span class="separator">/</span><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/SasView/sasmodels/tree/master/sasmodels" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">sasmodels</span></a></span><span class="separator">/</span><strong class="final-path">convert.py</strong>
+  </div>
+</div>
+
+<include-fragment class="commit-tease" src="/SasView/sasmodels/contributors/master/sasmodels/convert.py">
+  <div>
+    Fetching contributors&hellip;
+  </div>
+
+  <div class="commit-tease-contributors">
+    <img alt="" class="loader-loading left" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32-EAF2F5.gif" width="16" />
+    <span class="loader-error">Cannot retrieve contributors at this time</span>
+  </div>
+</include-fragment>
+<div class="file">
+  <div class="file-header">
+  <div class="file-actions">
+
+    <div class="btn-group">
+      <a href="/SasView/sasmodels/raw/master/sasmodels/convert.py" class="btn btn-sm " id="raw-url">Raw</a>
+        <a href="/SasView/sasmodels/blame/master/sasmodels/convert.py" class="btn btn-sm js-update-url-with-hash">Blame</a>
+      <a href="/SasView/sasmodels/commits/master/sasmodels/convert.py" class="btn btn-sm " rel="nofollow">History</a>
+    </div>
+
+        <a class="btn-octicon tooltipped tooltipped-nw"
+           href="https://windows.github.com"
+           aria-label="Open this file in GitHub Desktop"
+           data-ga-click="Repository, open with desktop, type:windows">
+            <svg aria-hidden="true" class="octicon octicon-device-desktop" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15 2H1c-0.55 0-1 0.45-1 1v9c0 0.55 0.45 1 1 1h5.34c-0.25 0.61-0.86 1.39-2.34 2h8c-1.48-0.61-2.09-1.39-2.34-2h5.34c0.55 0 1-0.45 1-1V3c0-0.55-0.45-1-1-1z m0 9H1V3h14v8z"></path></svg>
+        </a>
+
+        <button type="button" class="btn-octicon disabled tooltipped tooltipped-nw"
+          aria-label="You must be signed in to make or propose changes">
+          <svg aria-hidden="true" class="octicon octicon-pencil" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M0 12v3h3l8-8-3-3L0 12z m3 2H1V12h1v1h1v1z m10.3-9.3l-1.3 1.3-3-3 1.3-1.3c0.39-0.39 1.02-0.39 1.41 0l1.59 1.59c0.39 0.39 0.39 1.02 0 1.41z"></path></svg>
+        </button>
+        <button type="button" class="btn-octicon btn-octicon-danger disabled tooltipped tooltipped-nw"
+          aria-label="You must be signed in to make or propose changes">
+          <svg aria-hidden="true" class="octicon octicon-trashcan" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M10 2H8c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1H1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1v9c0 0.55 0.45 1 1 1h7c0.55 0 1-0.45 1-1V5c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1z m-1 12H2V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9z m1-10H1v-1h9v1z"></path></svg>
+        </button>
+  </div>
+
+  <div class="file-info">
+      157 lines (141 sloc)
+      <span class="file-info-divider"></span>
+    5.04 KB
+  </div>
+</div>
+
+  
+
+  <div class="blob-wrapper data type-python">
+      <table class="highlight tab-size js-file-line-container" data-tab-size="8">
+      <tr>
+        <td id="L1" class="blob-num js-line-number" data-line-number="1"></td>
+        <td id="LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L2" class="blob-num js-line-number" data-line-number="2"></td>
+        <td id="LC2" class="blob-code blob-code-inner js-file-line"><span class="pl-s">Convert models to and from sasview.</span></td>
+      </tr>
+      <tr>
+        <td id="L3" class="blob-num js-line-number" data-line-number="3"></td>
+        <td id="LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L4" class="blob-num js-line-number" data-line-number="4"></td>
+        <td id="LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-k">import</span> warnings</td>
+      </tr>
+      <tr>
+        <td id="L5" class="blob-num js-line-number" data-line-number="5"></td>
+        <td id="LC5" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L6" class="blob-num js-line-number" data-line-number="6"></td>
+        <td id="LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># List of models which SasView versions don&#39;t contain the explicit &#39;scale&#39; argument.</span></td>
+      </tr>
+      <tr>
+        <td id="L7" class="blob-num js-line-number" data-line-number="7"></td>
+        <td id="LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># When converting such a model, please update this list.</span></td>
+      </tr>
+      <tr>
+        <td id="L8" class="blob-num js-line-number" data-line-number="8"></td>
+        <td id="LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">MODELS_WITHOUT_SCALE</span> <span class="pl-k">=</span> [</td>
+      </tr>
+      <tr>
+        <td id="L9" class="blob-num js-line-number" data-line-number="9"></td>
+        <td id="LC9" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>teubner_strey<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L10" class="blob-num js-line-number" data-line-number="10"></td>
+        <td id="LC10" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>broad_peak<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L11" class="blob-num js-line-number" data-line-number="11"></td>
+        <td id="LC11" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>two_lorentzian<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L12" class="blob-num js-line-number" data-line-number="12"></td>
+        <td id="LC12" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>gel_fit<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L13" class="blob-num js-line-number" data-line-number="13"></td>
+        <td id="LC13" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>gauss_lorentz_gel<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L14" class="blob-num js-line-number" data-line-number="14"></td>
+        <td id="LC14" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>be_polyelectrolyte<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L15" class="blob-num js-line-number" data-line-number="15"></td>
+        <td id="LC15" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>correlation_length<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L16" class="blob-num js-line-number" data-line-number="16"></td>
+        <td id="LC16" class="blob-code blob-code-inner js-file-line">]</td>
+      </tr>
+      <tr>
+        <td id="L17" class="blob-num js-line-number" data-line-number="17"></td>
+        <td id="LC17" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L18" class="blob-num js-line-number" data-line-number="18"></td>
+        <td id="LC18" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># List of models which SasView versions don&#39;t contain the explicit &#39;background&#39; argument.</span></td>
+      </tr>
+      <tr>
+        <td id="L19" class="blob-num js-line-number" data-line-number="19"></td>
+        <td id="LC19" class="blob-code blob-code-inner js-file-line"><span class="pl-c"># When converting such a model, please update this list.</span></td>
+      </tr>
+      <tr>
+        <td id="L20" class="blob-num js-line-number" data-line-number="20"></td>
+        <td id="LC20" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">MODELS_WITHOUT_BACKGROUND</span> <span class="pl-k">=</span> [</td>
+      </tr>
+      <tr>
+        <td id="L21" class="blob-num js-line-number" data-line-number="21"></td>
+        <td id="LC21" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&#39;</span>guinier<span class="pl-pds">&#39;</span></span>,</td>
+      </tr>
+      <tr>
+        <td id="L22" class="blob-num js-line-number" data-line-number="22"></td>
+        <td id="LC22" class="blob-code blob-code-inner js-file-line">]</td>
+      </tr>
+      <tr>
+        <td id="L23" class="blob-num js-line-number" data-line-number="23"></td>
+        <td id="LC23" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L24" class="blob-num js-line-number" data-line-number="24"></td>
+        <td id="LC24" class="blob-code blob-code-inner js-file-line"><span class="pl-c1">PD_DOT</span> <span class="pl-k">=</span> [</td>
+      </tr>
+      <tr>
+        <td id="L25" class="blob-num js-line-number" data-line-number="25"></td>
+        <td id="LC25" class="blob-code blob-code-inner js-file-line">    (<span class="pl-s"><span class="pl-pds">&quot;</span><span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span><span class="pl-pds">&quot;</span></span>),</td>
+      </tr>
+      <tr>
+        <td id="L26" class="blob-num js-line-number" data-line-number="26"></td>
+        <td id="LC26" class="blob-code blob-code-inner js-file-line">    (<span class="pl-s"><span class="pl-pds">&quot;</span>_pd<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>.width<span class="pl-pds">&quot;</span></span>),</td>
+      </tr>
+      <tr>
+        <td id="L27" class="blob-num js-line-number" data-line-number="27"></td>
+        <td id="LC27" class="blob-code blob-code-inner js-file-line">    (<span class="pl-s"><span class="pl-pds">&quot;</span>_pd_n<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>.npts<span class="pl-pds">&quot;</span></span>),</td>
+      </tr>
+      <tr>
+        <td id="L28" class="blob-num js-line-number" data-line-number="28"></td>
+        <td id="LC28" class="blob-code blob-code-inner js-file-line">    (<span class="pl-s"><span class="pl-pds">&quot;</span>_pd_nsigma<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>.nsigmas<span class="pl-pds">&quot;</span></span>),</td>
+      </tr>
+      <tr>
+        <td id="L29" class="blob-num js-line-number" data-line-number="29"></td>
+        <td id="LC29" class="blob-code blob-code-inner js-file-line">    (<span class="pl-s"><span class="pl-pds">&quot;</span>_pd_type<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>.type<span class="pl-pds">&quot;</span></span>),</td>
+      </tr>
+      <tr>
+        <td id="L30" class="blob-num js-line-number" data-line-number="30"></td>
+        <td id="LC30" class="blob-code blob-code-inner js-file-line">    ]</td>
+      </tr>
+      <tr>
+        <td id="L31" class="blob-num js-line-number" data-line-number="31"></td>
+        <td id="LC31" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">_convert_pars</span>(<span class="pl-smi">pars</span>, <span class="pl-smi">mapping</span>):</td>
+      </tr>
+      <tr>
+        <td id="L32" class="blob-num js-line-number" data-line-number="32"></td>
+        <td id="LC32" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L33" class="blob-num js-line-number" data-line-number="33"></td>
+        <td id="LC33" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Rename the parameters and any associated polydispersity attributes.</span></td>
+      </tr>
+      <tr>
+        <td id="L34" class="blob-num js-line-number" data-line-number="34"></td>
+        <td id="LC34" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L35" class="blob-num js-line-number" data-line-number="35"></td>
+        <td id="LC35" class="blob-code blob-code-inner js-file-line">    newpars <span class="pl-k">=</span> pars.copy()</td>
+      </tr>
+      <tr>
+        <td id="L36" class="blob-num js-line-number" data-line-number="36"></td>
+        <td id="LC36" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">for</span> new, old <span class="pl-k">in</span> mapping.items():</td>
+      </tr>
+      <tr>
+        <td id="L37" class="blob-num js-line-number" data-line-number="37"></td>
+        <td id="LC37" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">if</span> old <span class="pl-k">==</span> new: <span class="pl-k">continue</span></td>
+      </tr>
+      <tr>
+        <td id="L38" class="blob-num js-line-number" data-line-number="38"></td>
+        <td id="LC38" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">for</span> pd, dot <span class="pl-k">in</span> <span class="pl-c1">PD_DOT</span>:</td>
+      </tr>
+      <tr>
+        <td id="L39" class="blob-num js-line-number" data-line-number="39"></td>
+        <td id="LC39" class="blob-code blob-code-inner js-file-line">            <span class="pl-k">if</span> old<span class="pl-k">+</span>dot <span class="pl-k">in</span> newpars:</td>
+      </tr>
+      <tr>
+        <td id="L40" class="blob-num js-line-number" data-line-number="40"></td>
+        <td id="LC40" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">if</span> new <span class="pl-k">is</span> <span class="pl-k">not</span> <span class="pl-c1">None</span>:</td>
+      </tr>
+      <tr>
+        <td id="L41" class="blob-num js-line-number" data-line-number="41"></td>
+        <td id="LC41" class="blob-code blob-code-inner js-file-line">                    newpars[new<span class="pl-k">+</span>pd] <span class="pl-k">=</span> pars[old<span class="pl-k">+</span>dot]</td>
+      </tr>
+      <tr>
+        <td id="L42" class="blob-num js-line-number" data-line-number="42"></td>
+        <td id="LC42" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">del</span> newpars[old<span class="pl-k">+</span>dot]</td>
+      </tr>
+      <tr>
+        <td id="L43" class="blob-num js-line-number" data-line-number="43"></td>
+        <td id="LC43" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> newpars</td>
+      </tr>
+      <tr>
+        <td id="L44" class="blob-num js-line-number" data-line-number="44"></td>
+        <td id="LC44" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L45" class="blob-num js-line-number" data-line-number="45"></td>
+        <td id="LC45" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">_rescale_sld</span>(<span class="pl-smi">pars</span>):</td>
+      </tr>
+      <tr>
+        <td id="L46" class="blob-num js-line-number" data-line-number="46"></td>
+        <td id="LC46" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L47" class="blob-num js-line-number" data-line-number="47"></td>
+        <td id="LC47" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    rescale all sld parameters in the new model definition by 1e6 so the</span></td>
+      </tr>
+      <tr>
+        <td id="L48" class="blob-num js-line-number" data-line-number="48"></td>
+        <td id="LC48" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    numbers are nicer.  Relies on the fact that all sld parameters in the</span></td>
+      </tr>
+      <tr>
+        <td id="L49" class="blob-num js-line-number" data-line-number="49"></td>
+        <td id="LC49" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    new model definition end with sld.</span></td>
+      </tr>
+      <tr>
+        <td id="L50" class="blob-num js-line-number" data-line-number="50"></td>
+        <td id="LC50" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L51" class="blob-num js-line-number" data-line-number="51"></td>
+        <td id="LC51" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> <span class="pl-c1">dict</span>((p, (v<span class="pl-k">*</span><span class="pl-c1">1e6</span> <span class="pl-k">if</span> p.endswith(<span class="pl-s"><span class="pl-pds">&#39;</span>sld<span class="pl-pds">&#39;</span></span>) <span class="pl-k">else</span> v))</td>
+      </tr>
+      <tr>
+        <td id="L52" class="blob-num js-line-number" data-line-number="52"></td>
+        <td id="LC52" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">for</span> p, v <span class="pl-k">in</span> pars.items())</td>
+      </tr>
+      <tr>
+        <td id="L53" class="blob-num js-line-number" data-line-number="53"></td>
+        <td id="LC53" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L54" class="blob-num js-line-number" data-line-number="54"></td>
+        <td id="LC54" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">convert_model</span>(<span class="pl-smi">name</span>, <span class="pl-smi">pars</span>):</td>
+      </tr>
+      <tr>
+        <td id="L55" class="blob-num js-line-number" data-line-number="55"></td>
+        <td id="LC55" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L56" class="blob-num js-line-number" data-line-number="56"></td>
+        <td id="LC56" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Convert model from old style parameter names to new style.</span></td>
+      </tr>
+      <tr>
+        <td id="L57" class="blob-num js-line-number" data-line-number="57"></td>
+        <td id="LC57" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L58" class="blob-num js-line-number" data-line-number="58"></td>
+        <td id="LC58" class="blob-code blob-code-inner js-file-line">    _, _ <span class="pl-k">=</span> name, pars <span class="pl-c"># lint</span></td>
+      </tr>
+      <tr>
+        <td id="L59" class="blob-num js-line-number" data-line-number="59"></td>
+        <td id="LC59" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">raise</span> <span class="pl-c1">NotImplementedError</span></td>
+      </tr>
+      <tr>
+        <td id="L60" class="blob-num js-line-number" data-line-number="60"></td>
+        <td id="LC60" class="blob-code blob-code-inner js-file-line">    <span class="pl-c"># need to load all new models in order to determine old=&gt;new</span></td>
+      </tr>
+      <tr>
+        <td id="L61" class="blob-num js-line-number" data-line-number="61"></td>
+        <td id="LC61" class="blob-code blob-code-inner js-file-line">    <span class="pl-c"># model name mapping</span></td>
+      </tr>
+      <tr>
+        <td id="L62" class="blob-num js-line-number" data-line-number="62"></td>
+        <td id="LC62" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L63" class="blob-num js-line-number" data-line-number="63"></td>
+        <td id="LC63" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">_unscale_sld</span>(<span class="pl-smi">pars</span>):</td>
+      </tr>
+      <tr>
+        <td id="L64" class="blob-num js-line-number" data-line-number="64"></td>
+        <td id="LC64" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L65" class="blob-num js-line-number" data-line-number="65"></td>
+        <td id="LC65" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    rescale all sld parameters in the new model definition by 1e6 so the</span></td>
+      </tr>
+      <tr>
+        <td id="L66" class="blob-num js-line-number" data-line-number="66"></td>
+        <td id="LC66" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    numbers are nicer.  Relies on the fact that all sld parameters in the</span></td>
+      </tr>
+      <tr>
+        <td id="L67" class="blob-num js-line-number" data-line-number="67"></td>
+        <td id="LC67" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    new model definition end with sld.</span></td>
+      </tr>
+      <tr>
+        <td id="L68" class="blob-num js-line-number" data-line-number="68"></td>
+        <td id="LC68" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L69" class="blob-num js-line-number" data-line-number="69"></td>
+        <td id="LC69" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> <span class="pl-c1">dict</span>((p, (v<span class="pl-k">*</span><span class="pl-c1">1e-6</span> <span class="pl-k">if</span> p.endswith(<span class="pl-s"><span class="pl-pds">&#39;</span>sld<span class="pl-pds">&#39;</span></span>) <span class="pl-k">else</span> v))</td>
+      </tr>
+      <tr>
+        <td id="L70" class="blob-num js-line-number" data-line-number="70"></td>
+        <td id="LC70" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">for</span> p, v <span class="pl-k">in</span> pars.items())</td>
+      </tr>
+      <tr>
+        <td id="L71" class="blob-num js-line-number" data-line-number="71"></td>
+        <td id="LC71" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L72" class="blob-num js-line-number" data-line-number="72"></td>
+        <td id="LC72" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">_remove_pd</span>(<span class="pl-smi">pars</span>, <span class="pl-smi">key</span>, <span class="pl-smi">name</span>):</td>
+      </tr>
+      <tr>
+        <td id="L73" class="blob-num js-line-number" data-line-number="73"></td>
+        <td id="LC73" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L74" class="blob-num js-line-number" data-line-number="74"></td>
+        <td id="LC74" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Remove polydispersity from the parameter list.</span></td>
+      </tr>
+      <tr>
+        <td id="L75" class="blob-num js-line-number" data-line-number="75"></td>
+        <td id="LC75" class="blob-code blob-code-inner js-file-line"><span class="pl-s"></span></td>
+      </tr>
+      <tr>
+        <td id="L76" class="blob-num js-line-number" data-line-number="76"></td>
+        <td id="LC76" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Note: operates in place</span></td>
+      </tr>
+      <tr>
+        <td id="L77" class="blob-num js-line-number" data-line-number="77"></td>
+        <td id="LC77" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L78" class="blob-num js-line-number" data-line-number="78"></td>
+        <td id="LC78" class="blob-code blob-code-inner js-file-line">    <span class="pl-c"># Bumps style parameter names</span></td>
+      </tr>
+      <tr>
+        <td id="L79" class="blob-num js-line-number" data-line-number="79"></td>
+        <td id="LC79" class="blob-code blob-code-inner js-file-line">    pd <span class="pl-k">=</span> pars.pop(key<span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">&quot;</span>.width<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">0.0</span>)</td>
+      </tr>
+      <tr>
+        <td id="L80" class="blob-num js-line-number" data-line-number="80"></td>
+        <td id="LC80" class="blob-code blob-code-inner js-file-line">    pd_n <span class="pl-k">=</span> pars.pop(key<span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">&quot;</span>.npts<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">0</span>)</td>
+      </tr>
+      <tr>
+        <td id="L81" class="blob-num js-line-number" data-line-number="81"></td>
+        <td id="LC81" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">if</span> pd <span class="pl-k">!=</span> <span class="pl-c1">0.0</span> <span class="pl-k">and</span> pd_n <span class="pl-k">!=</span> <span class="pl-c1">0</span>:</td>
+      </tr>
+      <tr>
+        <td id="L82" class="blob-num js-line-number" data-line-number="82"></td>
+        <td id="LC82" class="blob-code blob-code-inner js-file-line">        warnings.warn(<span class="pl-s"><span class="pl-pds">&quot;</span>parameter <span class="pl-c1">%s</span> not polydisperse in sasview <span class="pl-c1">%s</span><span class="pl-pds">&quot;</span></span><span class="pl-k">%</span>(key, name))</td>
+      </tr>
+      <tr>
+        <td id="L83" class="blob-num js-line-number" data-line-number="83"></td>
+        <td id="LC83" class="blob-code blob-code-inner js-file-line">    pars.pop(key<span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">&quot;</span>.nsigmas<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">None</span>)</td>
+      </tr>
+      <tr>
+        <td id="L84" class="blob-num js-line-number" data-line-number="84"></td>
+        <td id="LC84" class="blob-code blob-code-inner js-file-line">    pars.pop(key<span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">&quot;</span>.type<span class="pl-pds">&quot;</span></span>, <span class="pl-c1">None</span>)</td>
+      </tr>
+      <tr>
+        <td id="L85" class="blob-num js-line-number" data-line-number="85"></td>
+        <td id="LC85" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> pars</td>
+      </tr>
+      <tr>
+        <td id="L86" class="blob-num js-line-number" data-line-number="86"></td>
+        <td id="LC86" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L87" class="blob-num js-line-number" data-line-number="87"></td>
+        <td id="LC87" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">_revert_pars</span>(<span class="pl-smi">pars</span>, <span class="pl-smi">mapping</span>):</td>
+      </tr>
+      <tr>
+        <td id="L88" class="blob-num js-line-number" data-line-number="88"></td>
+        <td id="LC88" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L89" class="blob-num js-line-number" data-line-number="89"></td>
+        <td id="LC89" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Rename the parameters and any associated polydispersity attributes.</span></td>
+      </tr>
+      <tr>
+        <td id="L90" class="blob-num js-line-number" data-line-number="90"></td>
+        <td id="LC90" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L91" class="blob-num js-line-number" data-line-number="91"></td>
+        <td id="LC91" class="blob-code blob-code-inner js-file-line">    newpars <span class="pl-k">=</span> pars.copy()</td>
+      </tr>
+      <tr>
+        <td id="L92" class="blob-num js-line-number" data-line-number="92"></td>
+        <td id="LC92" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L93" class="blob-num js-line-number" data-line-number="93"></td>
+        <td id="LC93" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">for</span> new, old <span class="pl-k">in</span> mapping.items():</td>
+      </tr>
+      <tr>
+        <td id="L94" class="blob-num js-line-number" data-line-number="94"></td>
+        <td id="LC94" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">for</span> pd, dot <span class="pl-k">in</span> <span class="pl-c1">PD_DOT</span>:</td>
+      </tr>
+      <tr>
+        <td id="L95" class="blob-num js-line-number" data-line-number="95"></td>
+        <td id="LC95" class="blob-code blob-code-inner js-file-line">            <span class="pl-k">if</span> old <span class="pl-k">and</span> old<span class="pl-k">+</span>pd <span class="pl-k">==</span> new<span class="pl-k">+</span>dot:</td>
+      </tr>
+      <tr>
+        <td id="L96" class="blob-num js-line-number" data-line-number="96"></td>
+        <td id="LC96" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">continue</span></td>
+      </tr>
+      <tr>
+        <td id="L97" class="blob-num js-line-number" data-line-number="97"></td>
+        <td id="LC97" class="blob-code blob-code-inner js-file-line">            <span class="pl-k">if</span> new<span class="pl-k">+</span>pd <span class="pl-k">in</span> newpars:</td>
+      </tr>
+      <tr>
+        <td id="L98" class="blob-num js-line-number" data-line-number="98"></td>
+        <td id="LC98" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">if</span> old <span class="pl-k">is</span> <span class="pl-k">not</span> <span class="pl-c1">None</span>:</td>
+      </tr>
+      <tr>
+        <td id="L99" class="blob-num js-line-number" data-line-number="99"></td>
+        <td id="LC99" class="blob-code blob-code-inner js-file-line">                    newpars[old<span class="pl-k">+</span>dot] <span class="pl-k">=</span> pars[new<span class="pl-k">+</span>pd]</td>
+      </tr>
+      <tr>
+        <td id="L100" class="blob-num js-line-number" data-line-number="100"></td>
+        <td id="LC100" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">del</span> newpars[new<span class="pl-k">+</span>pd]</td>
+      </tr>
+      <tr>
+        <td id="L101" class="blob-num js-line-number" data-line-number="101"></td>
+        <td id="LC101" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">for</span> k <span class="pl-k">in</span> <span class="pl-c1">list</span>(newpars.keys()):</td>
+      </tr>
+      <tr>
+        <td id="L102" class="blob-num js-line-number" data-line-number="102"></td>
+        <td id="LC102" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">for</span> pd, dot <span class="pl-k">in</span> <span class="pl-c1">PD_DOT</span>[<span class="pl-c1">1</span>:]:  <span class="pl-c"># skip &quot;&quot; =&gt; &quot;&quot;</span></td>
+      </tr>
+      <tr>
+        <td id="L103" class="blob-num js-line-number" data-line-number="103"></td>
+        <td id="LC103" class="blob-code blob-code-inner js-file-line">            <span class="pl-k">if</span> k.endswith(pd):</td>
+      </tr>
+      <tr>
+        <td id="L104" class="blob-num js-line-number" data-line-number="104"></td>
+        <td id="LC104" class="blob-code blob-code-inner js-file-line">                newpars[k[:<span class="pl-k">-</span><span class="pl-c1">len</span>(pd)]<span class="pl-k">+</span>dot] <span class="pl-k">=</span> newpars[k]</td>
+      </tr>
+      <tr>
+        <td id="L105" class="blob-num js-line-number" data-line-number="105"></td>
+        <td id="LC105" class="blob-code blob-code-inner js-file-line">                <span class="pl-k">del</span> newpars[k]</td>
+      </tr>
+      <tr>
+        <td id="L106" class="blob-num js-line-number" data-line-number="106"></td>
+        <td id="LC106" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> newpars</td>
+      </tr>
+      <tr>
+        <td id="L107" class="blob-num js-line-number" data-line-number="107"></td>
+        <td id="LC107" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L108" class="blob-num js-line-number" data-line-number="108"></td>
+        <td id="LC108" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">revert_model</span>(<span class="pl-smi">model_definition</span>, <span class="pl-smi">pars</span>):</td>
+      </tr>
+      <tr>
+        <td id="L109" class="blob-num js-line-number" data-line-number="109"></td>
+        <td id="LC109" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L110" class="blob-num js-line-number" data-line-number="110"></td>
+        <td id="LC110" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Convert model from new style parameter names to old style.</span></td>
+      </tr>
+      <tr>
+        <td id="L111" class="blob-num js-line-number" data-line-number="111"></td>
+        <td id="LC111" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L112" class="blob-num js-line-number" data-line-number="112"></td>
+        <td id="LC112" class="blob-code blob-code-inner js-file-line">    mapping <span class="pl-k">=</span> model_definition.oldpars</td>
+      </tr>
+      <tr>
+        <td id="L113" class="blob-num js-line-number" data-line-number="113"></td>
+        <td id="LC113" class="blob-code blob-code-inner js-file-line">    oldname <span class="pl-k">=</span> model_definition.oldname</td>
+      </tr>
+      <tr>
+        <td id="L114" class="blob-num js-line-number" data-line-number="114"></td>
+        <td id="LC114" class="blob-code blob-code-inner js-file-line">    oldpars <span class="pl-k">=</span> _revert_pars(_unscale_sld(pars), mapping)</td>
+      </tr>
+      <tr>
+        <td id="L115" class="blob-num js-line-number" data-line-number="115"></td>
+        <td id="LC115" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L116" class="blob-num js-line-number" data-line-number="116"></td>
+        <td id="LC116" class="blob-code blob-code-inner js-file-line">    <span class="pl-c"># Note: update compare.constrain_pars to match</span></td>
+      </tr>
+      <tr>
+        <td id="L117" class="blob-num js-line-number" data-line-number="117"></td>
+        <td id="LC117" class="blob-code blob-code-inner js-file-line">    name <span class="pl-k">=</span> model_definition.name</td>
+      </tr>
+      <tr>
+        <td id="L118" class="blob-num js-line-number" data-line-number="118"></td>
+        <td id="LC118" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">if</span> name <span class="pl-k">in</span> <span class="pl-c1">MODELS_WITHOUT_SCALE</span>:</td>
+      </tr>
+      <tr>
+        <td id="L119" class="blob-num js-line-number" data-line-number="119"></td>
+        <td id="LC119" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">if</span> oldpars.pop(<span class="pl-s"><span class="pl-pds">&#39;</span>scale<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">1.0</span>) <span class="pl-k">!=</span> <span class="pl-c1">1.0</span>:</td>
+      </tr>
+      <tr>
+        <td id="L120" class="blob-num js-line-number" data-line-number="120"></td>
+        <td id="LC120" class="blob-code blob-code-inner js-file-line">            warnings.warn(<span class="pl-s"><span class="pl-pds">&quot;</span>parameter scale not used in sasview <span class="pl-c1">%s</span><span class="pl-pds">&quot;</span></span><span class="pl-k">%</span>name)</td>
+      </tr>
+      <tr>
+        <td id="L121" class="blob-num js-line-number" data-line-number="121"></td>
+        <td id="LC121" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">in</span> <span class="pl-c1">MODELS_WITHOUT_BACKGROUND</span>:</td>
+      </tr>
+      <tr>
+        <td id="L122" class="blob-num js-line-number" data-line-number="122"></td>
+        <td id="LC122" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">if</span> oldpars.pop(<span class="pl-s"><span class="pl-pds">&#39;</span>background<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">0.0</span>) <span class="pl-k">!=</span> <span class="pl-c1">0.0</span>:</td>
+      </tr>
+      <tr>
+        <td id="L123" class="blob-num js-line-number" data-line-number="123"></td>
+        <td id="LC123" class="blob-code blob-code-inner js-file-line">            warnings.warn(<span class="pl-s"><span class="pl-pds">&quot;</span>parameter background not used in sasview <span class="pl-c1">%s</span><span class="pl-pds">&quot;</span></span><span class="pl-k">%</span>name)</td>
+      </tr>
+      <tr>
+        <td id="L124" class="blob-num js-line-number" data-line-number="124"></td>
+        <td id="LC124" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> <span class="pl-c1">getattr</span>(model_definition, <span class="pl-s"><span class="pl-pds">&#39;</span>category<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">None</span>) <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>structure-factor<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L125" class="blob-num js-line-number" data-line-number="125"></td>
+        <td id="LC125" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">if</span> oldpars.pop(<span class="pl-s"><span class="pl-pds">&#39;</span>scale<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">1.0</span>) <span class="pl-k">!=</span> <span class="pl-c1">1.0</span>:</td>
+      </tr>
+      <tr>
+        <td id="L126" class="blob-num js-line-number" data-line-number="126"></td>
+        <td id="LC126" class="blob-code blob-code-inner js-file-line">            warnings.warn(<span class="pl-s"><span class="pl-pds">&quot;</span>parameter scale not used in sasview <span class="pl-c1">%s</span><span class="pl-pds">&quot;</span></span><span class="pl-k">%</span>name)</td>
+      </tr>
+      <tr>
+        <td id="L127" class="blob-num js-line-number" data-line-number="127"></td>
+        <td id="LC127" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">if</span> oldpars.pop(<span class="pl-s"><span class="pl-pds">&#39;</span>background<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">0.0</span>) <span class="pl-k">!=</span> <span class="pl-c1">0.0</span>:</td>
+      </tr>
+      <tr>
+        <td id="L128" class="blob-num js-line-number" data-line-number="128"></td>
+        <td id="LC128" class="blob-code blob-code-inner js-file-line">            warnings.warn(<span class="pl-s"><span class="pl-pds">&quot;</span>parameter background not used in sasview <span class="pl-c1">%s</span><span class="pl-pds">&quot;</span></span><span class="pl-k">%</span>name)</td>
+      </tr>
+      <tr>
+        <td id="L129" class="blob-num js-line-number" data-line-number="129"></td>
+        <td id="LC129" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>pearl_necklace<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L130" class="blob-num js-line-number" data-line-number="130"></td>
+        <td id="LC130" class="blob-code blob-code-inner js-file-line">        _remove_pd(oldpars, <span class="pl-s"><span class="pl-pds">&#39;</span>num_pearls<span class="pl-pds">&#39;</span></span>, name)</td>
+      </tr>
+      <tr>
+        <td id="L131" class="blob-num js-line-number" data-line-number="131"></td>
+        <td id="LC131" class="blob-code blob-code-inner js-file-line">        _remove_pd(oldpars, <span class="pl-s"><span class="pl-pds">&#39;</span>thick_string<span class="pl-pds">&#39;</span></span>, name)</td>
+      </tr>
+      <tr>
+        <td id="L132" class="blob-num js-line-number" data-line-number="132"></td>
+        <td id="LC132" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>rpa<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L133" class="blob-num js-line-number" data-line-number="133"></td>
+        <td id="LC133" class="blob-code blob-code-inner js-file-line">        <span class="pl-c"># convert scattering lengths from femtometers to centimeters</span></td>
+      </tr>
+      <tr>
+        <td id="L134" class="blob-num js-line-number" data-line-number="134"></td>
+        <td id="LC134" class="blob-code blob-code-inner js-file-line">        <span class="pl-k">for</span> p <span class="pl-k">in</span> <span class="pl-s"><span class="pl-pds">&quot;</span>La<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>Lb<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>Lc<span class="pl-pds">&quot;</span></span>, <span class="pl-s"><span class="pl-pds">&quot;</span>Ld<span class="pl-pds">&quot;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L135" class="blob-num js-line-number" data-line-number="135"></td>
+        <td id="LC135" class="blob-code blob-code-inner js-file-line">            <span class="pl-k">if</span> p <span class="pl-k">in</span> oldpars: oldpars[p] <span class="pl-k">*=</span> <span class="pl-c1">1e-13</span></td>
+      </tr>
+      <tr>
+        <td id="L136" class="blob-num js-line-number" data-line-number="136"></td>
+        <td id="LC136" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L137" class="blob-num js-line-number" data-line-number="137"></td>
+        <td id="LC137" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">return</span> oldname, oldpars</td>
+      </tr>
+      <tr>
+        <td id="L138" class="blob-num js-line-number" data-line-number="138"></td>
+        <td id="LC138" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+      <tr>
+        <td id="L139" class="blob-num js-line-number" data-line-number="139"></td>
+        <td id="LC139" class="blob-code blob-code-inner js-file-line"><span class="pl-k">def</span> <span class="pl-en">constrain_new_to_old</span>(<span class="pl-smi">model_definition</span>, <span class="pl-smi">pars</span>):</td>
+      </tr>
+      <tr>
+        <td id="L140" class="blob-num js-line-number" data-line-number="140"></td>
+        <td id="LC140" class="blob-code blob-code-inner js-file-line">    <span class="pl-s"><span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L141" class="blob-num js-line-number" data-line-number="141"></td>
+        <td id="LC141" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    Restrict parameter values to those that will match sasview.</span></td>
+      </tr>
+      <tr>
+        <td id="L142" class="blob-num js-line-number" data-line-number="142"></td>
+        <td id="LC142" class="blob-code blob-code-inner js-file-line"><span class="pl-s">    <span class="pl-pds">&quot;&quot;&quot;</span></span></td>
+      </tr>
+      <tr>
+        <td id="L143" class="blob-num js-line-number" data-line-number="143"></td>
+        <td id="LC143" class="blob-code blob-code-inner js-file-line">    <span class="pl-c"># Note: update convert.revert_model to match</span></td>
+      </tr>
+      <tr>
+        <td id="L144" class="blob-num js-line-number" data-line-number="144"></td>
+        <td id="LC144" class="blob-code blob-code-inner js-file-line">    name <span class="pl-k">=</span> model_definition.name</td>
+      </tr>
+      <tr>
+        <td id="L145" class="blob-num js-line-number" data-line-number="145"></td>
+        <td id="LC145" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">if</span> name <span class="pl-k">in</span> <span class="pl-c1">MODELS_WITHOUT_SCALE</span>:</td>
+      </tr>
+      <tr>
+        <td id="L146" class="blob-num js-line-number" data-line-number="146"></td>
+        <td id="LC146" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>scale<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">1</span></td>
+      </tr>
+      <tr>
+        <td id="L147" class="blob-num js-line-number" data-line-number="147"></td>
+        <td id="LC147" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">in</span> <span class="pl-c1">MODELS_WITHOUT_BACKGROUND</span>:</td>
+      </tr>
+      <tr>
+        <td id="L148" class="blob-num js-line-number" data-line-number="148"></td>
+        <td id="LC148" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>background<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">0</span></td>
+      </tr>
+      <tr>
+        <td id="L149" class="blob-num js-line-number" data-line-number="149"></td>
+        <td id="LC149" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>pearl_necklace<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L150" class="blob-num js-line-number" data-line-number="150"></td>
+        <td id="LC150" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>string_thickness_pd_n<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">0</span></td>
+      </tr>
+      <tr>
+        <td id="L151" class="blob-num js-line-number" data-line-number="151"></td>
+        <td id="LC151" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>number_of_pearls_pd_n<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">0</span></td>
+      </tr>
+      <tr>
+        <td id="L152" class="blob-num js-line-number" data-line-number="152"></td>
+        <td id="LC152" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> name <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>rpa<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L153" class="blob-num js-line-number" data-line-number="153"></td>
+        <td id="LC153" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>case_num<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">int</span>(pars[<span class="pl-s"><span class="pl-pds">&#39;</span>case_num<span class="pl-pds">&#39;</span></span>])</td>
+      </tr>
+      <tr>
+        <td id="L154" class="blob-num js-line-number" data-line-number="154"></td>
+        <td id="LC154" class="blob-code blob-code-inner js-file-line">    <span class="pl-k">elif</span> <span class="pl-c1">getattr</span>(model_definition, <span class="pl-s"><span class="pl-pds">&#39;</span>category<span class="pl-pds">&#39;</span></span>, <span class="pl-c1">None</span>) <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">&#39;</span>structure-factor<span class="pl-pds">&#39;</span></span>:</td>
+      </tr>
+      <tr>
+        <td id="L155" class="blob-num js-line-number" data-line-number="155"></td>
+        <td id="LC155" class="blob-code blob-code-inner js-file-line">        pars[<span class="pl-s"><span class="pl-pds">&#39;</span>scale<span class="pl-pds">&#39;</span></span>], pars[<span class="pl-s"><span class="pl-pds">&#39;</span>background<span class="pl-pds">&#39;</span></span>] <span class="pl-k">=</span> <span class="pl-c1">1</span>, <span class="pl-c1">0</span></td>
+      </tr>
+      <tr>
+        <td id="L156" class="blob-num js-line-number" data-line-number="156"></td>
+        <td id="LC156" class="blob-code blob-code-inner js-file-line">
+</td>
+      </tr>
+</table>
+
+  </div>
+
+</div>
+
+<a href="#jump-to-line" rel="facebox[.linejump]" data-hotkey="l" style="display:none">Jump to Line</a>
+<div id="jump-to-line" style="display:none">
+  <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
+    <input class="linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
+    <button type="submit" class="btn">Go</button>
+</form></div>
+
+  </div>
+  <div class="modal-backdrop"></div>
+</div>
+
+    </div>
+  </div>
+
+    </div>
+
+        <div class="container">
+  <div class="site-footer" role="contentinfo">
+    <ul class="site-footer-links right">
+        <li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
+      <li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
+      <li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
+      <li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
+        <li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
+        <li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
+        <li><a href="https://github.com/pricing" data-ga-click="Footer, go to pricing, text:pricing">Pricing</a></li>
+
+    </ul>
+
+    <a href="https://github.com" aria-label="Homepage">
+      <svg aria-hidden="true" class="octicon octicon-mark-github" height="24" role="img" title="GitHub " version="1.1" viewBox="0 0 16 16" width="24"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg>
+</a>
+    <ul class="site-footer-links">
+      <li>&copy; 2016 <span title="0.05637s from github-fe127-cp1-prd.iad.github.net">GitHub</span>, Inc.</li>
+        <li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
+        <li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
+        <li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
+        <li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact</a></li>
+        <li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
+    </ul>
+  </div>
+</div>
+
+
+
+    
+    
+    
+
+    <div id="ajax-error-message" class="flash flash-error">
+      <svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg>
+      <button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
+        <svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
+      </button>
+      Something went wrong with that request. Please try again.
+    </div>
+
+
+      
+      <script crossorigin="anonymous" integrity="sha256-7lIbjp+srGj/J+k/w64PjtgR17+eQ06E9LnqIneAsIQ=" src="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js"></script>
+      <script async="async" crossorigin="anonymous" integrity="sha256-aWM2lkt8QuTG9N+68fjlf0Jc2anRihLz/m4910T9fRM=" src="https://assets-cdn.github.com/assets/github-696336964b7c42e4c6f4dfbaf1f8e57f425cd9a9d18a12f3fe6e3dd744fd7d13.js"></script>
+      
+      
+      
+    <div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner hidden">
+      <svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg>
+      <span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
+      <span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
+    </div>
+    <div class="facebox" id="facebox" style="display:none;">
+  <div class="facebox-popup">
+    <div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
+    </div>
+    <button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
+      <svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
+    </button>
+  </div>
+</div>
+
+  </body>
+</html>
+
Index: sasmodels/core.py
===================================================================
--- sasmodels/core.py	(revision cde11f0f857d1861f37ded681777699e3aa91738)
+++ sasmodels/core.py	(revision d18582e2d66b12afb03e1c3a7c0af6e4449c6bfe)
@@ -2,6 +2,4 @@
 Core model handling routines.
 """
-__all__ = ["list_models", "load_model_definition",  "precompile_dll",
-           "load_model", "make_kernel", "call_kernel", "call_ER", "call_VR" ]
 
 from os.path import basename, dirname, join as joinpath
@@ -22,4 +20,8 @@
     HAVE_OPENCL = False
 
+__all__ = [
+    "list_models", "load_model_definition", "precompile_dll",
+    "load_model", "make_kernel", "call_kernel", "call_ER", "call_VR",
+]
 
 def list_models():
@@ -36,4 +38,7 @@
     """
     Load a model definition given the model name.
+
+    This returns a handle to the module defining the model.  This can be
+    used with functions in generate to build the docs or extract model info.
     """
     __import__('sasmodels.models.'+model_name)
@@ -61,9 +66,12 @@
 
 def isstr(s):
+    """
+    Return True if *s* is a string-like object.
+    """
     try: s + ''
     except: return False
     return True
 
-def load_model(model_definition, dtype="single", platform="ocl"):
+def load_model(model_definition, dtype=None, platform="ocl"):
     """
     Prepare the model for the default execution platform.
@@ -79,5 +87,6 @@
     for the calculation. Any valid numpy single or double precision identifier
     is valid, such as 'single', 'f', 'f32', or np.float32 for single, or
-    'double', 'd', 'f64'  and np.float64 for double.
+    'double', 'd', 'f64'  and np.float64 for double.  If *None*, then use
+    'single' unless the model defines single=False.
 
     *platform* should be "dll" to force the dll to be used for C models,
@@ -86,4 +95,6 @@
     if isstr(model_definition):
         model_definition = load_model_definition(model_definition)
+    if dtype is None:
+        dtype = 'single' if getattr(model_definition, 'single', True) else 'double'
     source, info = generate.make(model_definition)
     if callable(info.get('Iq', None)):
@@ -99,5 +110,5 @@
     # source = open(info['name']+'.cl','r').read()
 
-    if (platform=="dll"
+    if (platform == "dll"
             or not HAVE_OPENCL
             or not kernelcl.environment().has_type(dtype)):
@@ -110,6 +121,5 @@
     Return a computation kernel from the model definition and the q input.
     """
-    model_input = model.make_input(q_vectors)
-    return model(model_input)
+    return model(q_vectors)
 
 def get_weights(info, pars, name):
@@ -128,5 +138,5 @@
     width = pars.get(name+'_pd', 0.0)
     nsigma = pars.get(name+'_pd_nsigma', 3.0)
-    value,weight = weights.get_weights(
+    value, weight = weights.get_weights(
         disperser, npts, width, nsigma, value, limits, relative)
     return value, weight / np.sum(weight)
@@ -195,5 +205,5 @@
                     for name in info['partype']['volume']]
         value, weight = dispersion_mesh(vol_pars)
-        whole,part = VR(*value)
+        whole, part = VR(*value)
         return np.sum(weight*part)/np.sum(weight*whole)
 
Index: sasmodels/data.py
===================================================================
--- sasmodels/data.py	(revision 013adb7ddc1b713ccb5eff4052d37a298cc674e4)
+++ sasmodels/data.py	(revision d18582e2d66b12afb03e1c3a7c0af6e4449c6bfe)
@@ -87,7 +87,42 @@
 
 class Data1D(object):
+    """
+    1D data object.
+
+    Note that this definition matches the attributes from sasview, with
+    some generic 1D data vectors and some SAS specific definitions.  Some
+    refactoring to allow consistent naming conventions between 1D, 2D and
+    SESANS data would be helpful.
+
+    **Attributes**
+
+    *x*, *dx*: $q$ vector and gaussian resolution
+
+    *y*, *dy*: $I(q)$ vector and measurement uncertainty
+
+    *mask*: values to include in plotting/analysis
+
+    *dxl*: slit widths for slit smeared data, with *dx* ignored
+
+    *qmin*, *qmax*: range of $q$ values in *x*
+
+    *filename*: label for the data line
+
+    *_xaxis*, *_xunit*: label and units for the *x* axis
+
+    *_yaxis*, *_yunit*: label and units for the *y* axis
+    """
     def __init__(self, x=None, y=None, dx=None, dy=None):
         self.x, self.y, self.dx, self.dy = x, y, dx, dy
         self.dxl = None
+        self.filename = None
+        self.qmin = x.min() if x is not None else np.NaN
+        self.qmax = x.max() if x is not None else np.NaN
+        # TODO: why is 1D mask False and 2D mask True?
+        self.mask = (np.isnan(y) if y is not None
+                     else np.zeros_like(x, 'b') if x is not None
+                     else None)
+        self._xaxis, self._xunit = "x", ""
+        self._yaxis, self._yunit = "y", ""
 
     def xaxis(self, label, unit):
@@ -108,7 +143,56 @@
 
 class Data2D(object):
-    def __init__(self):
+    """
+    2D data object.
+
+    Note that this definition matches the attributes from sasview. Some
+    refactoring to allow consistent naming conventions between 1D, 2D and
+    SESANS data would be helpful.
+
+    **Attributes**
+
+    *qx_data*, *dqx_data*: $q_x$ matrix and gaussian resolution
+
+    *qy_data*, *dqy_data*: $q_y$ matrix and gaussian resolution
+
+    *data*, *err_data*: $I(q)$ matrix and measurement uncertainty
+
+    *mask*: values to exclude from plotting/analysis
+
+    *qmin*, *qmax*: range of $q$ values in *x*
+
+    *filename*: label for the data line
+
+    *_xaxis*, *_xunit*: label and units for the *x* axis
+
+    *_yaxis*, *_yunit*: label and units for the *y* axis
+
+    *_zaxis*, *_zunit*: label and units for the *y* axis
+
+    *Q_unit*, *I_unit*: units for Q and intensity
+
+    *x_bins*, *y_bins*: grid steps in *x* and *y* directions
+    """
+    def __init__(self, x=None, y=None, z=None, dx=None, dy=None, dz=None):
+        self.qx_data, self.dqx_data = x, dx
+        self.qy_data, self.dqy_data = y, dy
+        self.data, self.err_data = z, dz
+        self.mask = (~np.isnan(z) if z is not None
+                     else np.ones_like(x) if x is not None
+                     else None)
+        self.q_data = np.sqrt(x**2 + y**2)
+        self.qmin = 1e-16
+        self.qmax = np.inf
         self.detector = []
         self.source = Source()
+        self.Q_unit = "1/A"
+        self.I_unit = "1/cm"
+        self.xaxis("Q_x", "1/A")
+        self.yaxis("Q_y", "1/A")
+        self.zaxis("Intensity", "1/cm")
+        self._xaxis, self._xunit = "x", ""
+        self._yaxis, self._yunit = "y", ""
+        self._zaxis, self._zunit = "z", ""
+        self.x_bins, self.y_bins = None, None
 
     def xaxis(self, label, unit):
@@ -135,16 +219,28 @@
 
 class Vector(object):
+    """
+    3-space vector of *x*, *y*, *z*
+    """
     def __init__(self, x=None, y=None, z=None):
         self.x, self.y, self.z = x, y, z
 
 class Detector(object):
+    """
+    Detector attributes.
+    """
+    def __init__(self, pixel_size=(None, None), distance=None):
+        self.pixel_size = Vector(*pixel_size)
+        self.distance = distance
+
+class Source(object):
+    """
+    Beam attributes.
+    """
     def __init__(self):
-        self.pixel_size = Vector()
-
-class Source(object):
-    pass
-
-
-def empty_data1D(q, resolution=0.05):
+        self.wavelength = np.NaN
+        self.wavelength_unit = "A"
+
+
+def empty_data1D(q, resolution=0.0):
     """
     Create empty 1D data using the given *q* as the x value.
@@ -156,12 +252,11 @@
     #dIq = np.sqrt(Iq)
     Iq, dIq = None, None
+    q = np.asarray(q)
     data = Data1D(q, Iq, dx=resolution * q, dy=dIq)
     data.filename = "fake data"
-    data.qmin, data.qmax = q.min(), q.max()
-    data.mask = np.zeros(len(q), dtype='bool')
     return data
 
 
-def empty_data2D(qx, qy=None, resolution=0.05):
+def empty_data2D(qx, qy=None, resolution=0.0):
     """
     Create empty 2D data using the given mesh.
@@ -173,21 +268,10 @@
     if qy is None:
         qy = qx
+    qx, qy = np.asarray(qx), np.asarray(qy)
+    # 5% dQ/Q resolution
     Qx, Qy = np.meshgrid(qx, qy)
     Qx, Qy = Qx.flatten(), Qy.flatten()
     Iq = 100 * np.ones_like(Qx)
     dIq = np.sqrt(Iq)
-    mask = np.ones(len(Iq), dtype='bool')
-
-    data = Data2D()
-    data.filename = "fake data"
-    data.qx_data = Qx
-    data.qy_data = Qy
-    data.data = Iq
-    data.err_data = dIq
-    data.mask = mask
-    data.qmin = 1e-16
-    data.qmax = np.inf
-
-    # 5% dQ/Q resolution
     if resolution != 0:
         # https://www.ncnr.nist.gov/staff/hammouda/distance_learning/chapter_15.pdf
@@ -197,24 +281,19 @@
         # radial (which instead it should be inverse).
         Q = np.sqrt(Qx**2 + Qy**2)
-        data.dqx_data = resolution * Q
-        data.dqy_data = resolution * Q
+        dqx = resolution * Q
+        dqy = resolution * Q
     else:
-        data.dqx_data = data.dqy_data = None
-
-    detector = Detector()
-    detector.pixel_size.x = 5 # mm
-    detector.pixel_size.y = 5 # mm
-    detector.distance = 4 # m
-    data.detector.append(detector)
+        dqx = dqy = None
+
+    data = Data2D(x=Qx, y=Qy, z=Iq, dx=dqx, dy=dqy, dz=dIq)
     data.x_bins = qx
     data.y_bins = qy
+    data.filename = "fake data"
+
+    # pixel_size in mm, distance in m
+    detector = Detector(pixel_size=(5, 5), distance=4)
+    data.detector.append(detector)
     data.source.wavelength = 5 # angstroms
     data.source.wavelength_unit = "A"
-    data.Q_unit = "1/A"
-    data.I_unit = "1/cm"
-    data.q_data = np.sqrt(Qx ** 2 + Qy ** 2)
-    data.xaxis("Q_x", "A^{-1}")
-    data.yaxis("Q_y", "A^{-1}")
-    data.zaxis("Intensity", r"\text{cm}^{-1}")
     return data
 
@@ -223,4 +302,11 @@
     """
     Plot data loaded by the sasview loader.
+
+    *data* is a sasview data object, either 1D, 2D or SESANS.
+
+    *view* is log or linear.
+
+    *limits* sets the intensity limits on the plot; if None then the limits
+    are inferred from the data.
     """
     # Note: kind of weird using the plot result functions to plot just the
@@ -228,28 +314,51 @@
     # do not repeat.
     if hasattr(data, 'lam'):
-        _plot_result_sesans(data, None, None, plot_data=True, limits=limits)
+        _plot_result_sesans(data, None, None, use_data=True, limits=limits)
     elif hasattr(data, 'qx_data'):
-        _plot_result2D(data, None, None, view, plot_data=True, limits=limits)
+        _plot_result2D(data, None, None, view, use_data=True, limits=limits)
     else:
-        _plot_result1D(data, None, None, view, plot_data=True, limits=limits)
+        _plot_result1D(data, None, None, view, use_data=True, limits=limits)
 
 
 def plot_theory(data, theory, resid=None, view='log',
-                plot_data=True, limits=None):
+                use_data=True, limits=None):
+    """
+    Plot theory calculation.
+
+    *data* is needed to define the graph properties such as labels and
+    units, and to define the data mask.
+
+    *theory* is a matrix of the same shape as the data.
+
+    *view* is log or linear
+
+    *use_data* is True if the data should be plotted as well as the theory.
+
+    *limits* sets the intensity limits on the plot; if None then the limits
+    are inferred from the data.
+    """
     if hasattr(data, 'lam'):
-        _plot_result_sesans(data, theory, resid, plot_data=True, limits=limits)
+        _plot_result_sesans(data, theory, resid, use_data=True, limits=limits)
     elif hasattr(data, 'qx_data'):
-        _plot_result2D(data, theory, resid, view, plot_data, limits=limits)
+        _plot_result2D(data, theory, resid, view, use_data, limits=limits)
     else:
-        _plot_result1D(data, theory, resid, view, plot_data, limits=limits)
+        _plot_result1D(data, theory, resid, view, use_data, limits=limits)
 
 
 def protect(fn):
+    """
+    Decorator to wrap calls in an exception trapper which prints the
+    exception and continues.  Keyboard interrupts are ignored.
+    """
     def wrapper(*args, **kw):
+        """
+        Trap and print errors from function.
+        """
         try:
             return fn(*args, **kw)
+        except KeyboardInterrupt:
+            raise
         except:
             traceback.print_exc()
-            pass
 
     return wrapper
@@ -257,5 +366,5 @@
 
 @protect
-def _plot_result1D(data, theory, resid, view, plot_data, limits=None):
+def _plot_result1D(data, theory, resid, view, use_data, limits=None):
     """
     Plot the data and residuals for 1D data.
@@ -264,19 +373,16 @@
     from numpy.ma import masked_array, masked
 
-    plot_theory = theory is not None
-    plot_resid = resid is not None
-
-    if data.y is None:
-        plot_data = False
+    use_data = use_data and data.y is not None
+    use_theory = theory is not None
+    use_resid = resid is not None
+    num_plots = (use_data or use_theory) + use_resid
+
     scale = data.x**4 if view == 'q4' else 1.0
 
-    if plot_data or plot_theory:
-        if plot_resid:
-            plt.subplot(121)
-
+    if use_data or use_theory:
         #print(vmin, vmax)
         all_positive = True
         some_present = False
-        if plot_data:
+        if use_data:
             mdata = masked_array(data.y, data.mask.copy())
             mdata[~np.isfinite(mdata)] = masked
@@ -284,19 +390,22 @@
                 mdata[mdata <= 0] = masked
             plt.errorbar(data.x/10, scale*mdata, yerr=data.dy, fmt='.')
-            all_positive = all_positive and (mdata>0).all()
+            all_positive = all_positive and (mdata > 0).all()
             some_present = some_present or (mdata.count() > 0)
 
 
-        if plot_theory:
+        if use_theory:
             mtheory = masked_array(theory, data.mask.copy())
             mtheory[~np.isfinite(mtheory)] = masked
             if view is 'log':
-                mtheory[mtheory<=0] = masked
+                mtheory[mtheory <= 0] = masked
             plt.plot(data.x/10, scale*mtheory, '-', hold=True)
-            all_positive = all_positive and (mtheory>0).all()
+            all_positive = all_positive and (mtheory > 0).all()
             some_present = some_present or (mtheory.count() > 0)
 
         if limits is not None:
             plt.ylim(*limits)
+
+        if num_plots > 1:
+            plt.subplot(1, num_plots, 1)
         plt.xscale('linear' if not some_present else view)
         plt.yscale('linear'
@@ -306,11 +415,11 @@
         plt.ylabel('$I(q)$')
 
-    if plot_resid:
-        if plot_data or plot_theory:
-            plt.subplot(122)
-
+    if use_resid:
         mresid = masked_array(resid, data.mask.copy())
         mresid[~np.isfinite(mresid)] = masked
         some_present = (mresid.count() > 0)
+
+        if num_plots > 1:
+            plt.subplot(1, num_plots, (use_data or use_theory) + 1)
         plt.plot(data.x/10, mresid, '-')
         plt.xlabel("$q$/nm$^{-1}$")
@@ -320,15 +429,18 @@
 
 @protect
-def _plot_result_sesans(data, theory, resid, plot_data, limits=None):
+def _plot_result_sesans(data, theory, resid, use_data, limits=None):
+    """
+    Plot SESANS results.
+    """
     import matplotlib.pyplot as plt
-    if data.y is None:
-        plot_data = False
-    plot_theory = theory is not None
-    plot_resid = resid is not None
-
-    if plot_data or plot_theory:
-        if plot_resid:
-            plt.subplot(121)
-        if plot_data:
+    use_data = use_data and data.y is not None
+    use_theory = theory is not None
+    use_resid = resid is not None
+    num_plots = (use_data or use_theory) + use_resid
+
+    if use_data or use_theory:
+        if num_plots > 1:
+            plt.subplot(1, num_plots, 1)
+        if use_data:
             plt.errorbar(data.x, data.y, yerr=data.dy)
         if theory is not None:
@@ -340,7 +452,6 @@
 
     if resid is not None:
-        if plot_data or plot_theory:
-            plt.subplot(122)
-
+        if num_plots > 1:
+            plt.subplot(1, num_plots, (use_data or use_theory) + 1)
         plt.plot(data.x, resid, 'x')
         plt.xlabel('spin echo length (nm)')
@@ -349,36 +460,36 @@
 
 @protect
-def _plot_result2D(data, theory, resid, view, plot_data, limits=None):
+def _plot_result2D(data, theory, resid, view, use_data, limits=None):
     """
     Plot the data and residuals for 2D data.
     """
     import matplotlib.pyplot as plt
-    if data.data is None:
-        plot_data = False
-    plot_theory = theory is not None
-    plot_resid = resid is not None
+    use_data = use_data and data.data is not None
+    use_theory = theory is not None
+    use_resid = resid is not None
+    num_plots = use_data + use_theory + use_resid
 
     # Put theory and data on a common colormap scale
-    if limits is None:
-        vmin, vmax = np.inf, -np.inf
-        if plot_data:
-            target = data.data[~data.mask]
-            datamin = target[target>0].min() if view == 'log' else target.min()
-            datamax = target.max()
-            vmin = min(vmin, datamin)
-            vmax = max(vmax, datamax)
-        if plot_theory:
-            theorymin = theory[theory>0].min() if view=='log' else theory.min()
-            theorymax = theory.max()
-            vmin = min(vmin, theorymin)
-            vmax = max(vmax, theorymax)
-    else:
+    vmin, vmax = np.inf, -np.inf
+    if use_data:
+        target = data.data[~data.mask]
+        datamin = target[target > 0].min() if view == 'log' else target.min()
+        datamax = target.max()
+        vmin = min(vmin, datamin)
+        vmax = max(vmax, datamax)
+    if use_theory:
+        theorymin = theory[theory > 0].min() if view == 'log' else theory.min()
+        theorymax = theory.max()
+        vmin = min(vmin, theorymin)
+        vmax = max(vmax, theorymax)
+
+    # Override data limits from the caller
+    if limits is not None:
         vmin, vmax = limits
 
-    if plot_data:
-        if plot_theory and plot_resid:
-            plt.subplot(131)
-        elif plot_theory or plot_resid:
-            plt.subplot(121)
+    # Plot data
+    if use_data:
+        if num_plots > 1:
+            plt.subplot(1, num_plots, 1)
         _plot_2d_signal(data, target, view=view, vmin=vmin, vmax=vmax)
         plt.title('data')
@@ -386,30 +497,23 @@
         h.set_label('$I(q)$')
 
-    if plot_theory:
-        if plot_data and plot_resid:
-            plt.subplot(132)
-        elif plot_data:
-            plt.subplot(122)
-        elif plot_resid:
-            plt.subplot(121)
+    # plot theory
+    if use_theory:
+        if num_plots > 1:
+            plt.subplot(1, num_plots, use_data+1)
         _plot_2d_signal(data, theory, view=view, vmin=vmin, vmax=vmax)
         plt.title('theory')
         h = plt.colorbar()
-        h.set_label(r'$\log_{10}I(q)$' if view=='log'
+        h.set_label(r'$\log_{10}I(q)$' if view == 'log'
                     else r'$q^4 I(q)$' if view == 'q4'
                     else '$I(q)$')
 
-    #if plot_data or plot_theory:
-    #    plt.colorbar()
-
-    if plot_resid:
-        if plot_data and plot_theory:
-            plt.subplot(133)
-        elif plot_data or plot_theory:
-            plt.subplot(122)
+    # plot resid
+    if use_resid:
+        if num_plots > 1:
+            plt.subplot(1, num_plots, use_data+use_theory+1)
         _plot_2d_signal(data, resid, view='linear')
         plt.title('residuals')
         h = plt.colorbar()
-        h.set_label('$\Delta I(q)$')
+        h.set_label(r'$\Delta I(q)$')
 
 
@@ -456,4 +560,7 @@
 
 def demo():
+    """
+    Load and plot a SAS dataset.
+    """
     data = load_data('DEC07086.DAT')
     set_beam_stop(data, 0.004)
Index: sasmodels/direct_model.py
===================================================================
--- sasmodels/direct_model.py	(revision 9404dd3f599fcbe72790aae8a6d176e64f292ea6)
+++ sasmodels/direct_model.py	(revision d18582e2d66b12afb03e1c3a7c0af6e4449c6bfe)
@@ -1,3 +1,25 @@
-import warnings
+"""
+Class interface to the model calculator.
+
+Calling a model is somewhat non-trivial since the functions called depend
+on the data type.  For 1D data the *Iq* kernel needs to be called, for
+2D data the *Iqxy* kernel needs to be called, and for SESANS data the
+*Iq* kernel needs to be called followed by a Hankel transform.  Before
+the kernel is called an appropriate *q* calculation vector needs to be
+constructed.  This is not the simple *q* vector where you have measured
+the data since the resolution calculation will require values beyond the
+range of the measured data.  After the calculation the resolution calculator
+must be called to return the predicted value for each measured data point.
+
+:class:`DirectModel` is a callable object that takes *parameter=value*
+keyword arguments and returns the appropriate theory values for the data.
+
+:class:`DataMixin` does the real work of interpreting the data and calling
+the model calculator.  This is used by :class:`DirectModel`, which uses
+direct parameter values and by :class:`bumps_model.Experiment` which wraps
+the parameter values in boxes so that the user can set fitting ranges, etc.
+on the individual parameters and send the model to the Bumps optimizers.
+"""
+from __future__ import print_function
 
 import numpy as np
@@ -19,6 +41,18 @@
     currently used by :class:`sasview_model.SasviewModel` since this will
     require a number of changes to SasView before we can do it.
+
+    :meth:`_interpret_data` initializes the data structures necessary
+    to manage the calculations.  This sets attributes in the child class
+    such as *data_type* and *resolution*.
+
+    :meth:`_calc_theory` evaluates the model at the given control values.
+
+    :meth:`_set_data` sets the intensity data in the data object,
+    possibly with random noise added.  This is useful for simulating a
+    dataset with the results from :meth:`_calc_theory`.
     """
     def _interpret_data(self, data, model):
+        # pylint: disable=attribute-defined-outside-init
+
         self._data = data
         self._model = model
@@ -36,8 +70,10 @@
         if self.data_type == 'sesans':
             q = sesans.make_q(data.sample.zacceptance, data.Rmax)
-            self.index = slice(None, None)
+            index = slice(None, None)
+            res = None
             if data.y is not None:
-                self.Iq = data.y
-                self.dIq = data.dy
+                Iq, dIq = data.y, data.dy
+            else:
+                Iq, dIq = None, None
             #self._theory = np.zeros_like(q)
             q_vectors = [q]
@@ -49,35 +85,39 @@
             qmax = getattr(data, 'qmax', np.inf)
             accuracy = getattr(data, 'accuracy', 'Low')
-            self.index = ~data.mask & (q >= qmin) & (q <= qmax)
+            index = ~data.mask & (q >= qmin) & (q <= qmax)
             if data.data is not None:
-                self.index &= ~np.isnan(data.data)
-                self.Iq = data.data[self.index]
-                self.dIq = data.err_data[self.index]
-            self.resolution = resolution2d.Pinhole2D(data=data, index=self.index,
-                                                     nsigma=3.0, accuracy=accuracy)
+                index &= ~np.isnan(data.data)
+                Iq = data.data[index]
+                dIq = data.err_data[index]
+            else:
+                Iq, dIq = None, None
+            res = resolution2d.Pinhole2D(data=data, index=index,
+                                         nsigma=3.0, accuracy=accuracy)
             #self._theory = np.zeros_like(self.Iq)
-            q_vectors = self.resolution.q_calc
+            q_vectors = res.q_calc
         elif self.data_type == 'Iq':
-            self.index = (data.x >= data.qmin) & (data.x <= data.qmax)
+            index = (data.x >= data.qmin) & (data.x <= data.qmax)
             if data.y is not None:
-                self.index &= ~np.isnan(data.y)
-                self.Iq = data.y[self.index]
-                self.dIq = data.dy[self.index]
+                index &= ~np.isnan(data.y)
+                Iq = data.y[index]
+                dIq = data.dy[index]
+            else:
+                Iq, dIq = None, None
             if getattr(data, 'dx', None) is not None:
-                q, dq = data.x[self.index], data.dx[self.index]
-                if (dq>0).any():
-                    self.resolution = resolution.Pinhole1D(q, dq)
+                q, dq = data.x[index], data.dx[index]
+                if (dq > 0).any():
+                    res = resolution.Pinhole1D(q, dq)
                 else:
-                    self.resolution = resolution.Perfect1D(q)
-            elif (getattr(data, 'dxl', None) is not None and
-                          getattr(data, 'dxw', None) is not None):
-                self.resolution = resolution.Slit1D(data.x[self.index],
-                                                    width=data.dxh[self.index],
-                                                    height=data.dxw[self.index])
-            else:
-                self.resolution = resolution.Perfect1D(data.x[self.index])
+                    res = resolution.Perfect1D(q)
+            elif (getattr(data, 'dxl', None) is not None
+                  and getattr(data, 'dxw', None) is not None):
+                res = resolution.Slit1D(data.x[index],
+                                        width=data.dxh[index],
+                                        height=data.dxw[index])
+            else:
+                res = resolution.Perfect1D(data.x[index])
 
             #self._theory = np.zeros_like(self.Iq)
-            q_vectors = [self.resolution.q_calc]
+            q_vectors = [res.q_calc]
         else:
             raise ValueError("Unknown data type") # never gets here
@@ -87,6 +127,9 @@
         self._kernel_inputs = [v for v in q_vectors]
         self._kernel = None
+        self.Iq, self.dIq, self.index = Iq, dIq, index
+        self.resolution = res
 
     def _set_data(self, Iq, noise=None):
+        # pylint: disable=attribute-defined-outside-init
         if noise is not None:
             self.dIq = Iq*noise*0.01
@@ -106,6 +149,5 @@
     def _calc_theory(self, pars, cutoff=0.0):
         if self._kernel is None:
-            q_input = self._model.make_input(self._kernel_inputs)
-            self._kernel = self._model(q_input)
+            self._kernel = make_kernel(self._model, self._kernel_inputs)  # pylint: disable=attribute-defined-outside-init
 
         Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
@@ -120,20 +162,50 @@
 
 class DirectModel(DataMixin):
+    """
+    Create a calculator object for a model.
+
+    *data* is 1D SAS, 2D SAS or SESANS data
+
+    *model* is a model calculator return from :func:`generate.load_model`
+
+    *cutoff* is the polydispersity weight cutoff.
+    """
     def __init__(self, data, model, cutoff=1e-5):
         self.model = model
         self.cutoff = cutoff
+        # Note: _interpret_data defines the model attributes
         self._interpret_data(data, model)
-        self.kernel = make_kernel(self.model, self._kernel_inputs)
+
     def __call__(self, **pars):
         return self._calc_theory(pars, cutoff=self.cutoff)
+
     def ER(self, **pars):
+        """
+        Compute the equivalent radius for the model.
+
+        Return 0. if not defined.
+        """
         return call_ER(self.model.info, pars)
+
     def VR(self, **pars):
+        """
+        Compute the equivalent volume for the model, including polydispersity
+        effects.
+
+        Return 1. if not defined.
+        """
         return call_VR(self.model.info, pars)
+
     def simulate_data(self, noise=None, **pars):
+        """
+        Generate simulated data for the model.
+        """
         Iq = self.__call__(**pars)
         self._set_data(Iq, noise=noise)
 
-def demo():
+def main():
+    """
+    Program to evaluate a particular model at a set of q values.
+    """
     import sys
     from .data import empty_data1D, empty_data2D
@@ -144,5 +216,5 @@
     model_name = sys.argv[1]
     call = sys.argv[2].upper()
-    if call not in ("ER","VR"):
+    if call not in ("ER", "VR"):
         try:
             values = [float(v) for v in call.split(',')]
@@ -153,6 +225,6 @@
             data = empty_data1D([q])
         elif len(values) == 2:
-            qx,qy = values
-            data = empty_data2D([qx],[qy])
+            qx, qy = values
+            data = empty_data2D([qx], [qy])
         else:
             print("use q or qx,qy or ER or VR")
@@ -162,9 +234,9 @@
 
     model_definition = load_model_definition(model_name)
-    model = load_model(model_definition, dtype='single')
+    model = load_model(model_definition)
     calculator = DirectModel(data, model)
-    pars = dict((k,float(v))
+    pars = dict((k, float(v))
                 for pair in sys.argv[3:]
-                for k,v in [pair.split('=')])
+                for k, v in [pair.split('=')])
     if call == "ER":
         print(calculator.ER(**pars))
@@ -176,3 +248,3 @@
 
 if __name__ == "__main__":
-    demo()
+    main()
Index: sasmodels/exception.py
===================================================================
--- sasmodels/exception.py	(revision eaca9ebe6d0022b8ac658c30e21ef334fb4f3861)
+++ sasmodels/exception.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -8,4 +8,7 @@
 except NameError:
     class WindowsError(Exception):
+        """
+        Fake WindowsException when not on Windows.
+        """
         pass
 
@@ -31,5 +34,5 @@
     # TODO: try to incorporate current stack trace in the raised exception
     if isinstance(exc, WindowsError):
-        raise OSError(str(exc)+" "+msg)
+        raise OSError(str(exc) + " " + msg)
 
     args = exc.args
@@ -38,6 +41,6 @@
     else:
         try:
-            arg0 = " ".join((args[0],msg))
+            arg0 = " ".join((args[0], msg))
             exc.args = tuple([arg0] + list(args[1:]))
         except:
-            exc.args = (" ".join((str(exc),msg)),)
+            exc.args = (" ".join((str(exc), msg)),)
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision e66c9f9bddfbf0bb75d5d64484195eaf6802ca82)
+++ sasmodels/generate.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -7,5 +7,5 @@
     particular dimensions averaged over all orientations.
 
-    *Iqxy(qx, qy, p1, p2, ...)* returns the scattering at qx,qy for a form
+    *Iqxy(qx, qy, p1, p2, ...)* returns the scattering at qx, qy for a form
     with particular dimensions for a single orientation.
 
@@ -47,5 +47,5 @@
 the *sin* and *cos* values are needed for a particular argument.  Since
 this function does not exist in C99, all use of *sincos* should be
-replaced by the macro *SINCOS(value,sn,cn)* where *sn* and *cn* are
+replaced by the macro *SINCOS(value, sn, cn)* where *sn* and *cn* are
 previously declared *double* variables.  When compiled for systems without
 OpenCL, *SINCOS* will be replaced by *sin* and *cos* calls.   If *value* is
@@ -194,8 +194,7 @@
 returns a list of files required by the model.
 """
+from __future__ import print_function
 
 # TODO: identify model files which have changed since loading and reload them.
-
-__all__ = ["make", "doc", "sources", "convert_type"]
 
 import sys
@@ -206,4 +205,7 @@
 
 import numpy as np
+
+__all__ = ["make", "doc", "sources", "convert_type"]
+
 C_KERNEL_TEMPLATE_PATH = joinpath(dirname(__file__), 'kernel_template.c')
 
@@ -216,5 +218,4 @@
     F128 = None
 
-
 # Scale and background, which are parameters common to every form factor
 COMMON_PARAMETERS = [
@@ -222,5 +223,4 @@
     ["background", "1/cm", 0, [0, np.inf], "", "Source background"],
     ]
-
 
 # Conversion from units defined in the parameter table for each model
@@ -266,4 +266,7 @@
 
 def format_units(units):
+    """
+    Convert units into ReStructured Text format.
+    """
     return "string" if isinstance(units, list) else RST_UNITS.get(units, units)
 
@@ -310,5 +313,5 @@
     raise ValueError("%r not found in %s" % (filename, search_path))
 
-def sources(info):
+def model_sources(info):
     """
     Return a list of the sources file paths for the module.
@@ -335,4 +338,7 @@
     """
     Convert code from double precision to the desired type.
+
+    Floating point constants are tagged with 'f' for single precision or 'L'
+    for long double precision.
     """
     if dtype == F16:
@@ -350,4 +356,8 @@
 
 def _convert_type(source, type_name, constant_flag):
+    """
+    Replace 'double' with *type_name* in *source*, tagging floating point
+    constants with *constant_flag*.
+    """
     # Convert double keyword to float/long double/half.
     # Accept an 'n' # parameter for vector # values, where n is 2, 4, 8 or 16.
@@ -363,9 +373,9 @@
 
 
-def kernel_name(info, is_2D):
+def kernel_name(info, is_2d):
     """
     Name of the exported kernel symbol.
     """
-    return info['name'] + "_" + ("Iqxy" if is_2D else "Iq")
+    return info['name'] + "_" + ("Iqxy" if is_2d else "Iq")
 
 
@@ -410,15 +420,15 @@
 
 
-def build_polydispersity_loops(pd_pars):
-    """
-    Build polydispersity loops
-
-    Returns loop opening and loop closing
-    """
-    LOOP_OPEN = """\
+LOOP_OPEN = """\
 for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) {
   const double %(name)s = loops[2*(%(name)s_i%(offset)s)];
   const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\
 """
+def build_polydispersity_loops(pd_pars):
+    """
+    Build polydispersity loops
+
+    Returns loop opening and loop closing
+    """
     depth = 4
     offset = ""
@@ -455,5 +465,5 @@
 
     # Load additional sources
-    source = [open(f).read() for f in sources(info)]
+    source = [open(f).read() for f in model_sources(info)]
 
     # Prepare defines
@@ -563,9 +573,9 @@
 
     #for d in defines: print(d)
-    DEFINES = '\n'.join('#define %s %s' % (k, v) for k, v in defines)
-    SOURCES = '\n\n'.join(source)
+    defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines)
+    sources = '\n\n'.join(source)
     return C_KERNEL_TEMPLATE % {
-        'DEFINES':DEFINES,
-        'SOURCES':SOURCES,
+        'DEFINES': defines,
+        'SOURCES': sources,
         }
 
@@ -581,5 +591,5 @@
     demo_parameters = getattr(kernel_module, 'demo', None)
     if demo_parameters is None:
-        demo_parameters = dict((p[0],p[2]) for p in parameters)
+        demo_parameters = dict((p[0], p[2]) for p in parameters)
     filename = abspath(kernel_module.__file__)
     kernel_id = splitext(basename(filename))[0]
@@ -588,5 +598,5 @@
         name = " ".join(w.capitalize() for w in kernel_id.split('_'))
     info = dict(
-        id = kernel_id,  # string used to load the kernel
+        id=kernel_id,  # string used to load the kernel
         filename=abspath(kernel_module.__file__),
         name=name,
@@ -625,4 +635,7 @@
                             %re.escape(string.punctuation))
 def _convert_section_titles_to_boldface(lines):
+    """
+    Do the actual work of identifying and converting section headings.
+    """
     prior = None
     for line in lines:
@@ -631,5 +644,5 @@
         elif section_marker.match(line):
             if len(line) >= len(prior):
-                yield "".join( ("**",prior,"**") )
+                yield "".join(("**", prior, "**"))
                 prior = None
             else:
@@ -642,6 +655,13 @@
         yield prior
 
-def convert_section_titles_to_boldface(string):
-    return "\n".join(_convert_section_titles_to_boldface(string.split('\n')))
+def convert_section_titles_to_boldface(s):
+    """
+    Use explicit bold-face rather than section headings so that the table of
+    contents is not polluted with section names from the model documentation.
+
+    Sections are identified as the title line followed by a line of punctuation
+    at least as long as the title line.
+    """
+    return "\n".join(_convert_section_titles_to_boldface(s.split('\n')))
 
 def doc(kernel_module):
@@ -666,4 +686,7 @@
 
 def demo_time():
+    """
+    Show how long it takes to process a model.
+    """
     from .models import cylinder
     import datetime
@@ -674,4 +697,7 @@
 
 def main():
+    """
+    Program which prints the source produced by the model.
+    """
     if len(sys.argv) <= 1:
         print("usage: python -m sasmodels.generate modelname")
Index: sasmodels/kernel_template.c
===================================================================
--- sasmodels/kernel_template.c	(revision 9c79c32da6740e2bbac1a67d4d4794a71533de93)
+++ sasmodels/kernel_template.c	(revision 840b85918586f724a23f78252f90ed33ac27fdf7)
@@ -16,10 +16,38 @@
      using namespace std;
      #if defined(_MSC_VER)
-     #   define kernel extern "C" __declspec( dllexport )
+         #include <limits>
+         #include <float.h>
+         #define kernel extern "C" __declspec( dllexport )
          inline double trunc(double x) { return x>=0?floor(x):-floor(-x); }
-	 inline double fmin(double x, double y) { return x>y ? y : x; }
-	 inline double fmax(double x, double y) { return x<y ? y : x; }
+	     inline double fmin(double x, double y) { return x>y ? y : x; }
+	     inline double fmax(double x, double y) { return x<y ? y : x; }
+	     inline double isnan(double x) { return _isnan(x); }
+	     #define NAN (std::numeric_limits<double>::quiet_NaN()) // non-signalling NaN
+	     static double cephes_expm1(double x) {
+	         // Adapted from the cephes math library.
+	         // Copyright 1984 - 1992 by Stephen L. Moshier
+	         if (x != x || x == 0.0) {
+	             return x; // NaN and +/- 0
+	         } else if (x < -0.5 || x > 0.5) {
+	             return exp(x) - 1.0;
+	         } else {
+	             const double xsq = x*x;
+	             const double p = (((
+	                  +1.2617719307481059087798E-4)*xsq
+                      +3.0299440770744196129956E-2)*xsq
+                      +9.9999999999999999991025E-1);
+                 const double q = ((((
+                      +3.0019850513866445504159E-6)*xsq
+                      +2.5244834034968410419224E-3)*xsq
+                      +2.2726554820815502876593E-1)*xsq
+                      +2.0000000000000000000897E0);
+                 double r = x * p;
+	             r =  r / (q - r);
+	             return r+r;
+	         }
+	     }
+	     #define expm1 cephes_expm1
      #else
-     #   define kernel extern "C"
+         #define kernel extern "C"
      #endif
      inline void SINCOS(double angle, double &svar, double &cvar) { svar=sin(angle); cvar=cos(angle); }
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision cde11f0f857d1861f37ded681777699e3aa91738)
+++ sasmodels/kernelcl.py	(revision e6a5556cf35ef5c00c42dc56786102efc2b7d5c1)
@@ -1,4 +1,4 @@
 """
-GPU support through OpenCL
+GPU driver for C kernels
 
 There should be a single GPU environment running on the system.  This
@@ -152,10 +152,4 @@
 
 
-def make_result(self, size):
-    self.res = np.empty(size, dtype=self.dtype)
-    self.res_b = cl.Buffer(self.program.context, mf.READ_WRITE, self.res.nbytes)
-    return self.res, self.res_b
-
-
 # for now, this returns one device in the context
 # TODO: create a context that contains all devices on all platforms
@@ -178,15 +172,41 @@
         #self.data_boundary = max(d.min_data_type_align_size
         #                         for d in self.context.devices)
-        self.queues = [cl.CommandQueue(self.context, d)
-                       for d in self.context.devices]
+        self.queues = [cl.CommandQueue(context, context.devices[0])
+                       for context in self.context]
         self.compiled = {}
 
     def has_type(self, dtype):
+        """
+        Return True if all devices support a given type.
+        """
         dtype = generate.F32 if dtype == 'fast' else np.dtype(dtype)
-        return all(has_type(d, dtype) for d in self.context.devices)
+        return any(has_type(d, dtype)
+                   for context in self.context
+                   for d in context.devices)
+
+    def get_queue(self, dtype):
+        """
+        Return a command queue for the kernels of type dtype.
+        """
+        for context, queue in zip(self.context, self.queues):
+            if all(has_type(d, dtype) for d in context.devices):
+                return queue
+
+    def get_context(self, dtype):
+        """
+        Return a OpenCL context for the kernels of type dtype.
+        """
+        for context, queue in zip(self.context, self.queues):
+            if all(has_type(d, dtype) for d in context.devices):
+                return context
 
     def _create_some_context(self):
+        """
+        Protected call to cl.create_some_context without interactivity.  Use
+        this if PYOPENCL_CTX is set in the environment.  Sets the *context*
+        attribute.
+        """
         try:
-            self.context = cl.create_some_context(interactive=False)
+            self.context = [cl.create_some_context(interactive=False)]
         except Exception as exc:
             warnings.warn(str(exc))
@@ -195,13 +215,19 @@
 
     def compile_program(self, name, source, dtype, fast=False):
+        """
+        Compile the program for the device in the given context.
+        """
         key = "%s-%s-%s"%(name, dtype, fast)
         if key not in self.compiled:
             #print("compiling",name)
             dtype = np.dtype(dtype)
-            program = compile_model(self.context, source, dtype, fast)
+            program = compile_model(self.get_context(dtype), source, dtype, fast)
             self.compiled[key] = program
         return self.compiled[key]
 
     def release_program(self, name):
+        """
+        Free memory associated with the program on the device.
+        """
         if name in self.compiled:
             self.compiled[name].release()
@@ -209,16 +235,52 @@
 
 def _get_default_context():
-    default = None
+    """
+    Get an OpenCL context, preferring GPU over CPU, and preferring Intel
+    drivers over AMD drivers.
+    """
+    # Note: on mobile devices there is automatic clock scaling if either the
+    # CPU or the GPU is underutilized; probably doesn't affect us, but we if
+    # it did, it would mean that putting a busy loop on the CPU while the GPU
+    # is running may increase throughput.
+    #
+    # Macbook pro, base install:
+    #     {'Apple': [Intel CPU, NVIDIA GPU]}
+    # Macbook pro, base install:
+    #     {'Apple': [Intel CPU, Intel GPU]}
+    # 2 x nvidia 295 with Intel and NVIDIA opencl drivers installed
+    #     {'Intel': [CPU], 'NVIDIA': [GPU, GPU, GPU, GPU]}
+    gpu, cpu = None, None
     for platform in cl.get_platforms():
+        # AMD provides a much weaker CPU driver than Intel/Apple, so avoid it.
+        # If someone has bothered to install the AMD/NVIDIA drivers, prefer them over the integrated
+        # graphics driver that may have been supplied with the CPU chipset.
+        preferred_cpu = platform.vendor.startswith('Intel') or platform.vendor.startswith('Apple')
+        preferred_gpu = platform.vendor.startswith('Advanced') or platform.vendor.startswith('NVIDIA')
         for device in platform.get_devices():
             if device.type == cl.device_type.GPU:
-                return cl.Context([device])
-            if default is None:
-                default = device
-
-    if not default:
-        raise RuntimeError("OpenCL device not found")
-
-    return cl.Context([default])
+                # If the existing type is not GPU then it will be CUSTOM or ACCELERATOR,
+                # so don't override it.
+                if gpu is None or (preferred_gpu and gpu.type == cl.device_type.GPU):
+                    gpu = device
+            elif device.type == cl.device_type.CPU:
+                if cpu is None or preferred_cpu:
+                    cpu = device
+            else:
+                # System has cl.device_type.ACCELERATOR or cl.device_type.CUSTOM
+                # Intel Phi for example registers as an accelerator
+                # Since the user installed a custom device on their system and went through the
+                # pain of sorting out OpenCL drivers for it, lets assume they really do want to
+                # use it as their primary compute device.
+                gpu = device
+
+    # order the devices by gpu then by cpu; when searching for an available device by data type they
+    # will be checked in this order, which means that if the gpu supports double then the cpu will never
+    # be used (though we may make it possible to explicitly request the cpu at some point).
+    devices = []
+    if gpu is not None:
+        devices.append(gpu)
+    if cpu is not None:
+        devices.append(cpu)
+    return [cl.Context([d]) for d in devices]
 
 
@@ -241,42 +303,35 @@
         self.info = info
         self.source = source
-        self.dtype = generate.F32 if dtype=='fast' else np.dtype(dtype)
+        self.dtype = generate.F32 if dtype == 'fast' else np.dtype(dtype)
         self.fast = (dtype == 'fast')
         self.program = None # delay program creation
 
     def __getstate__(self):
-        state = self.__dict__.copy()
-        state['program'] = None
-        return state
+        return self.info, self.source, self.dtype, self.fast
 
     def __setstate__(self, state):
-        self.__dict__ = state.copy()
-
-    def __call__(self, q_input):
-        if self.dtype != q_input.dtype:
-            raise TypeError("data is %s kernel is %s"
-                            % (q_input.dtype, self.dtype))
+        self.info, self.source, self.dtype, self.fast = state
+        self.program = None
+
+    def __call__(self, q_vectors):
         if self.program is None:
             compiler = environment().compile_program
             self.program = compiler(self.info['name'], self.source, self.dtype,
                                     self.fast)
-        kernel_name = generate.kernel_name(self.info, q_input.is_2D)
+        is_2d = len(q_vectors) == 2
+        kernel_name = generate.kernel_name(self.info, is_2d)
         kernel = getattr(self.program, kernel_name)
-        return GpuKernel(kernel, self.info, q_input)
+        return GpuKernel(kernel, self.info, q_vectors, self.dtype)
 
     def release(self):
+        """
+        Free the resources associated with the model.
+        """
         if self.program is not None:
             environment().release_program(self.info['name'])
             self.program = None
 
-    def make_input(self, q_vectors):
-        """
-        Make q input vectors available to the model.
-
-        Note that each model needs its own q vector even if the case of
-        mixture models because some models may be OpenCL, some may be
-        ctypes and some may be pure python.
-        """
-        return GpuInput(q_vectors, dtype=self.dtype)
+    def __del__(self):
+        self.release()
 
 # TODO: check that we don't need a destructor for buffers which go out of scope
@@ -304,5 +359,5 @@
         self.nq = q_vectors[0].size
         self.dtype = np.dtype(dtype)
-        self.is_2D = (len(q_vectors) == 2)
+        self.is_2d = (len(q_vectors) == 2)
         # TODO: stretch input based on get_warp()
         # not doing it now since warp depends on kernel, which is not known
@@ -310,6 +365,7 @@
         # architectures tested so far.
         self.q_vectors = [_stretch_input(q, self.dtype, 32) for q in q_vectors]
+        context = env.get_context(self.dtype)
         self.q_buffers = [
-            cl.Buffer(env.context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=q)
+            cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=q)
             for q in self.q_vectors
         ]
@@ -317,18 +373,25 @@
 
     def release(self):
+        """
+        Free the memory.
+        """
         for b in self.q_buffers:
             b.release()
         self.q_buffers = []
 
+    def __del__(self):
+        self.release()
+
 class GpuKernel(object):
     """
     Callable SAS kernel.
 
-    *kernel* is the GpuKernel object to call.
+    *kernel* is the GpuKernel object to call
 
     *info* is the module information
 
-    *q_input* is the DllInput q vectors at which the kernel should be
-    evaluated.
+    *q_vectors* is the q vectors at which the kernel should be evaluated
+
+    *dtype* is the kernel precision
 
     The resulting call method takes the *pars*, a list of values for
@@ -340,10 +403,10 @@
     Call :meth:`release` when done with the kernel instance.
     """
-    def __init__(self, kernel, info, q_input):
-        self.q_input = q_input
+    def __init__(self, kernel, info, q_vectors, dtype):
+        q_input = GpuInput(q_vectors, dtype)
         self.kernel = kernel
         self.info = info
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         self.fixed_pars = info['partype']['fixed-' + dim]
         self.pd_pars = info['partype']['pd-' + dim]
@@ -352,11 +415,12 @@
         # Note: res may be shorter than res_b if global_size != nq
         env = environment()
-        self.loops_b = [cl.Buffer(env.context, mf.READ_WRITE,
-                                  2 * MAX_LOOPS * q_input.dtype.itemsize)
-                        for _ in env.queues]
-        self.res_b = [cl.Buffer(env.context, mf.READ_WRITE,
-                                q_input.global_size[0] * q_input.dtype.itemsize)
-                      for _ in env.queues]
-
+        self.queue = env.get_queue(dtype)
+        self.loops_b = cl.Buffer(self.queue.context, mf.READ_WRITE,
+                                 2 * MAX_LOOPS * q_input.dtype.itemsize)
+        self.res_b = cl.Buffer(self.queue.context, mf.READ_WRITE,
+                               q_input.global_size[0] * q_input.dtype.itemsize)
+        self.q_input = q_input
+
+        self._need_release = [self.loops_b, self.res_b, self.q_input]
 
     def __call__(self, fixed_pars, pd_pars, cutoff=1e-5):
@@ -366,7 +430,5 @@
                 else np.float32)  # will never get here, so use np.float32
 
-        device_num = 0
-        queuei = environment().queues[device_num]
-        res_bi = self.res_b[device_num]
+        res_bi = self.res_b
         nq = np.uint32(self.q_input.nq)
         if pd_pars:
@@ -383,6 +445,6 @@
                 raise ValueError("too many polydispersity points")
 
-            loops_bi = self.loops_b[device_num]
-            cl.enqueue_copy(queuei, loops_bi, loops)
+            loops_bi = self.loops_b
+            cl.enqueue_copy(self.queue, loops_bi, loops)
             loops_l = cl.LocalMemory(len(loops.data))
             #ctx = environment().context
@@ -393,16 +455,16 @@
         fixed = [real(p) for p in fixed_pars]
         args = self.q_input.q_buffers + [res_bi, nq] + dispersed + fixed
-        self.kernel(queuei, self.q_input.global_size, None, *args)
-        cl.enqueue_copy(queuei, self.res, res_bi)
+        self.kernel(self.queue, self.q_input.global_size, None, *args)
+        cl.enqueue_copy(self.queue, self.res, res_bi)
 
         return self.res
 
     def release(self):
-        for b in self.loops_b:
-            b.release()
-        self.loops_b = []
-        for b in self.res_b:
-            b.release()
-        self.res_b = []
+        """
+        Release resources associated with the kernel.
+        """
+        for v in self._need_release:
+            v.release()
+        self._need_release = []
 
     def __del__(self):
Index: sasmodels/kerneldll.py
===================================================================
--- sasmodels/kerneldll.py	(revision 74667d322dd33e8f42805e77bc688d1a9a99b4fa)
+++ sasmodels/kerneldll.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -1,4 +1,4 @@
 r"""
-C types wrapper for sasview models.
+DLL driver for C kernels
 
 The global attribute *ALLOW_SINGLE_PRECISION_DLLS* should be set to *True* if
@@ -44,4 +44,5 @@
 directory for this application, then OpenMP should be supported.
 """
+from __future__ import print_function
 
 import sys
@@ -122,5 +123,5 @@
     models are allowed as DLLs.
     """
-    if callable(info.get('Iq',None)):
+    if callable(info.get('Iq', None)):
         return PyModel(info)
 
@@ -139,11 +140,11 @@
 
     source = generate.convert_type(source, dtype)
-    source_files = generate.sources(info) + [info['filename']]
-    dll= dll_path(info, dtype)
+    source_files = generate.model_sources(info) + [info['filename']]
+    dll = dll_path(info, dtype)
     newest = max(os.path.getmtime(f) for f in source_files)
-    if not os.path.exists(dll) or os.path.getmtime(dll)<newest:
+    if not os.path.exists(dll) or os.path.getmtime(dll) < newest:
         # Replace with a proper temp file
-        fid, filename = tempfile.mkstemp(suffix=".c",prefix=tempfile_prefix)
-        os.fdopen(fid,"w").write(source)
+        fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix)
+        os.fdopen(fid, "w").write(source)
         command = COMPILE%{"source":filename, "output":dll}
         print("Compile command: "+command)
@@ -160,5 +161,5 @@
 def load_dll(source, info, dtype="double"):
     """
-    Create and load a dll corresponding to the source,info pair returned
+    Create and load a dll corresponding to the source, info pair returned
     from :func:`sasmodels.generate.make` compiled for the target precision.
 
@@ -199,5 +200,5 @@
         Npd2d = len(self.info['partype']['pd-2d'])
 
-        #print("dll",self.dllpath)
+        #print("dll", self.dllpath)
         try:
             self.dll = ct.CDLL(self.dllpath)
@@ -210,5 +211,5 @@
               else c_longdouble)
         pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else []
-        pd_args_2d= [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
+        pd_args_2d = [c_void_p, fp] + [c_int]*Npd2d if Npd2d else []
         self.Iq = self.dll[generate.kernel_name(self.info, False)]
         self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d
@@ -218,28 +219,20 @@
 
     def __getstate__(self):
-        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
+        return self.info, self.dllpath
 
     def __setstate__(self, state):
-        self.__dict__ = state
-
-    def __call__(self, q_input):
-        if self.dtype != q_input.dtype:
-            raise TypeError("data is %s kernel is %s" % (q_input.dtype, self.dtype))
+        self.info, self.dllpath = state
+        self.dll = None
+
+    def __call__(self, q_vectors):
+        q_input = PyInput(q_vectors, self.dtype)
         if self.dll is None: self._load_dll()
-        kernel = self.Iqxy if q_input.is_2D else self.Iq
+        kernel = self.Iqxy if q_input.is_2d else self.Iq
         return DllKernel(kernel, self.info, q_input)
 
-    # pylint: disable=no-self-use
-    def make_input(self, q_vectors):
-        """
-        Make q input vectors available to the model.
-
-        Note that each model needs its own q vector even if the case of
-        mixture models because some models may be OpenCL, some may be
-        ctypes and some may be pure python.
-        """
-        return PyInput(q_vectors, dtype=self.dtype)
-
     def release(self):
+        """
+        Release any resources associated with the model.
+        """
         pass # TODO: should release the dll
 
@@ -257,5 +250,5 @@
 
     The resulting call method takes the *pars*, a list of values for
-    the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight)
+    the fixed parameters to the kernel, and *pd_pars*, a list of (value, weight)
     vectors for the polydisperse parameters.  *cutoff* determines the
     integration limits: any points with combined weight less than *cutoff*
@@ -269,5 +262,5 @@
         self.kernel = kernel
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         self.fixed_pars = info['partype']['fixed-'+dim]
         self.pd_pars = info['partype']['pd-'+dim]
@@ -299,3 +292,6 @@
 
     def release(self):
+        """
+        Release any resources associated with the kernel.
+        """
         pass
Index: sasmodels/kernelpy.py
===================================================================
--- sasmodels/kernelpy.py	(revision 4c2c535bfe4a2a460e9aaf4ef0ed8b22d96965fd)
+++ sasmodels/kernelpy.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -1,2 +1,10 @@
+"""
+Python driver for python kernels
+
+Calls the kernel with a vector of $q$ values for a single parameter set.
+Polydispersity is supported by looping over different parameter sets and
+summing the results.  The interface to :class:`PyModel` matches those for
+:class:`kernelcl.GpuModel` and :class:`kerneldll.DllModel`.
+"""
 import numpy as np
 from numpy import pi, cos
@@ -5,16 +13,19 @@
 
 class PyModel(object):
+    """
+    Wrapper for pure python models.
+    """
     def __init__(self, info):
         self.info = info
 
-    def __call__(self, q_input):
-        kernel = self.info['Iqxy'] if q_input.is_2D else self.info['Iq']
+    def __call__(self, q_vectors):
+        q_input = PyInput(q_vectors, dtype=F64)
+        kernel = self.info['Iqxy'] if q_input.is_2d else self.info['Iq']
         return PyKernel(kernel, self.info, q_input)
 
-    # pylint: disable=no-self-use
-    def make_input(self, q_vectors):
-        return PyInput(q_vectors, dtype=F64)
-
     def release(self):
+        """
+        Free resources associated with the model.
+        """
         pass
 
@@ -41,9 +52,12 @@
         self.nq = q_vectors[0].size
         self.dtype = dtype
-        self.is_2D = (len(q_vectors) == 2)
+        self.is_2d = (len(q_vectors) == 2)
         self.q_vectors = [np.ascontiguousarray(q, self.dtype) for q in q_vectors]
         self.q_pointers = [q.ctypes.data for q in self.q_vectors]
 
     def release(self):
+        """
+        Free resources associated with the model inputs.
+        """
         self.q_vectors = []
 
@@ -71,5 +85,5 @@
         self.q_input = q_input
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         # Loop over q unless user promises that the kernel is vectorized by
         # taggining it with vectorized=True
@@ -77,8 +91,14 @@
             if dim == '2d':
                 def vector_kernel(qx, qy, *args):
+                    """
+                    Vectorized 2D kernel.
+                    """
                     return np.array([kernel(qxi, qyi, *args)
                                      for qxi, qyi in zip(qx, qy)])
             else:
                 def vector_kernel(q, *args):
+                    """
+                    Vectorized 1D kernel.
+                    """
                     return np.array([kernel(qi, *args)
                                      for qi in q])
@@ -122,4 +142,7 @@
 
     def release(self):
+        """
+        Free resources associated with the kernel.
+        """
         self.q_input = None
 
Index: sasmodels/list_pars.py
===================================================================
--- sasmodels/list_pars.py	(revision 5c962dff13d0a0a2f6d779be8df775f540958dbc)
+++ sasmodels/list_pars.py	(revision 5c962dff13d0a0a2f6d779be8df775f540958dbc)
@@ -0,0 +1,63 @@
+"""
+List all parameters used along with the models which use them.
+
+Usage:
+
+    python -m sasmodels.list_pars [-v]
+
+If '-v' is given, then list the models containing the parameter in
+addition to just the parameter name.
+"""
+from __future__ import print_function
+
+import sys
+
+from .core import load_model_definition
+from .generate import make_info
+from .compare import MODELS, columnize
+
+def find_pars():
+    """
+    Find all parameters in all models.
+
+    Returns the reference table *{parameter: [model, model, ...]}*
+    """
+    partable = {}
+    for name in sorted(MODELS):
+        definition = load_model_definition(name)
+        info = make_info(definition)
+        for p in info['parameters']:
+            pname = p[0]
+            partable.setdefault(pname, [])
+            partable[pname].append(name)
+    return partable
+
+def list_pars(names_only=True):
+    """
+    Print all parameters in all models.
+
+    If *names_only* then only print the parameter name, not the models it
+    occurs in.
+    """
+    partable = find_pars()
+    if names_only:
+        print(columnize(list(sorted(partable.keys()))))
+    else:
+        for k, v in sorted(partable.items()):
+            print("%s: %s"%(k, ", ".join(v)))
+
+def main():
+    """
+    Program to list the parameters used across all models.
+    """
+    if len(sys.argv) == 2 and sys.argv[1] == '-v':
+        verbose = True
+    elif len(sys.argv) == 1:
+        verbose = False
+    else:
+        print(__doc__)
+        sys.exit(1)
+    list_pars(names_only=not verbose)
+
+if __name__ == "__main__":
+    main()
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 9404dd3f599fcbe72790aae8a6d176e64f292ea6)
+++ sasmodels/model_test.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -9,5 +9,5 @@
     if model1 is 'all', then all except the remaining models will be tested
 
-Each model is tested using the default parameters at q=0.1, (qx,qy)=(0.1,0.1),
+Each model is tested using the default parameters at q=0.1, (qx, qy)=(0.1, 0.1),
 and the ER and VR are computed.  The return values at these points are not
 considered.  The test is only to verify that the models run to completion,
@@ -30,5 +30,5 @@
         [ {parameters}, (qx, qy), I(qx, Iqy)],
         [ {parameters}, [(qx1, qy1), (qx2, qy2), ...],
-                        [I(qx1,qy1), I(qx2,qy2), ...]],
+                        [I(qx1, qy1), I(qx2, qy2), ...]],
 
         [ {parameters}, 'ER', ER(pars) ],
@@ -43,4 +43,5 @@
 Precision defaults to 5 digits (relative).
 """
+from __future__ import print_function
 
 import sys
@@ -55,4 +56,13 @@
 
 def make_suite(loaders, models):
+    """
+    Construct the pyunit test suite.
+
+    *loaders* is the list of kernel drivers to use, which is one of
+    *["dll", "opencl"]*, *["dll"]* or *["opencl"]*.  For python models,
+    the python driver is always used.
+
+    *models* is the list of models to test, or *["all"]* to test all models.
+    """
 
     ModelTestCase = _hide_model_case_from_nosetests()
@@ -75,5 +85,5 @@
         # don't try to call cl kernel since it will not be
         # available in some environmentes.
-        is_py = callable(getattr(model_definition,'Iq', None))
+        is_py = callable(getattr(model_definition, 'Iq', None))
 
         if is_py:  # kernel implemented in python
@@ -90,7 +100,11 @@
                 test_name = "Model: %s, Kernel: OpenCL"%model_name
                 test_method_name = "test_%s_opencl" % model_name
+                # Using dtype=None so that the models that are only
+                # correct for double precision are not tested using
+                # single precision.  The choice is determined by the
+                # presence of *single=False* in the model file.
                 test = ModelTestCase(test_name, model_definition,
                                      test_method_name,
-                                     platform="ocl", dtype='single')
+                                     platform="ocl", dtype=None)
                 #print("defining", test_name)
                 suite.addTest(test)
@@ -111,4 +125,11 @@
 def _hide_model_case_from_nosetests():
     class ModelTestCase(unittest.TestCase):
+        """
+        Test suit for a particular model with a particular kernel driver.
+
+        The test suite runs a simple smoke test to make sure the model
+        functions, then runs the list of tests at the bottom of the model
+        description file.
+        """
         def __init__(self, test_name, definition, test_method_name,
                      platform, dtype):
@@ -123,8 +144,8 @@
         def _runTest(self):
             smoke_tests = [
-                [{},0.1,None],
-                [{},(0.1,0.1),None],
-                [{},'ER',None],
-                [{},'VR',None],
+                [{}, 0.1, None],
+                [{}, (0.1, 0.1), None],
+                [{}, 'ER', None],
+                [{}, 'VR', None],
                 ]
 
@@ -140,5 +161,5 @@
                     ## values an error.  Only do so for the "dll" tests
                     ## to reduce noise from both opencl and dll, and because
-                    ## python kernels us
+                    ## python kernels use platform="dll".
                     #raise Exception("No test cases provided")
                     pass
@@ -163,5 +184,5 @@
                 actual = [call_VR(model.info, pars)]
             elif isinstance(x[0], tuple):
-                Qx,Qy = zip(*x)
+                Qx, Qy = zip(*x)
                 q_vectors = [np.array(Qx), np.array(Qy)]
                 kernel = make_kernel(model, q_vectors)
@@ -179,14 +200,19 @@
                     # smoke test --- make sure it runs and produces a value
                     self.assertTrue(np.isfinite(actual_yi),
-                        'invalid f(%s): %s' % (xi, actual_yi))
+                                    'invalid f(%s): %s' % (xi, actual_yi))
                 else:
-                    err = abs(yi - actual_yi)
-                    nrm = abs(yi)
-                    self.assertLess(err * 10**5, nrm,
-                        'f(%s); expected:%s; actual:%s' % (xi, yi, actual_yi))
+                    self.assertTrue(is_near(yi, actual_yi, 5),
+                                    'f(%s); expected:%s; actual:%s'
+                                    % (xi, yi, actual_yi))
 
     return ModelTestCase
 
-
+def is_near(target, actual, digits=5):
+    """
+    Returns true if *actual* is within *digits* significant digits of *target*.
+    """
+    import math
+    shift = 10**math.ceil(math.log10(abs(target)))
+    return abs(target-actual)/shift < 1.5*10**-digits
 
 def main():
@@ -199,4 +225,9 @@
 
     models = sys.argv[1:]
+    if models and models[0] == '-v':
+        verbosity = 2
+        models = models[1:]
+    else:
+        verbosity = 1
     if models and models[0] == 'opencl':
         if not HAVE_OPENCL:
@@ -217,14 +248,18 @@
         print("""\
 usage:
-  python -m sasmodels.model_test [opencl|dll|opencl_and_dll] model1 model2 ...
+  python -m sasmodels.model_test [-v] [opencl|dll] model1 model2 ...
+
+If -v is included on the
+If neither opencl nor dll is specified, then models will be tested with
+both opencl and dll; the compute target is ignored for pure python models.
 
 If model1 is 'all', then all except the remaining models will be tested.
-If no compute target is specified, then models will be tested with both opencl
-and dll; the compute target is ignored for pure python models.""")
+
+""")
 
         return 1
 
     #runner = unittest.TextTestRunner()
-    runner = xmlrunner.XMLTestRunner(output='logs')
+    runner = xmlrunner.XMLTestRunner(output='logs', verbosity=verbosity)
     result = runner.run(make_suite(loaders, models))
     return 1 if result.failures or result.errors else 0
@@ -237,5 +272,5 @@
     Run "nosetests sasmodels" on the command line to invoke it.
     """
-    tests = make_suite(['opencl','dll'],['all'])
+    tests = make_suite(['opencl', 'dll'], ['all'])
     for test_i in tests:
         yield test_i._runTest
Index: sasmodels/models/HayterMSAsq.py
===================================================================
--- sasmodels/models/HayterMSAsq.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/HayterMSAsq.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -56,4 +56,5 @@
         parameters used in P(Q).
 """
+single = False  # double precision only for now
 #             [ "name", "units", default, [lower, upper], "type", "description" ],
 parameters = [["effect_radius", "Ang", 20.75, [0, inf], "volume",
@@ -90,3 +91,9 @@
 # odd that the default st has saltconc zero
 demo = dict(effect_radius = 20.75,charge=19.0,volfraction = 0.0192,temperature=318.16,saltconc=0.05,dielectconst=71.08,effect_radius_pd = 0.1,effect_radius_pd_n = 40)
+#
+# attempt to use same values as old sasview unit test
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'effect_radius' : 20.75, 'charge' : 19.0, 'volfraction' : 0.0192, 'temperature' : 298.0,
+          'saltconc' : 0,'dielectconst' : 78.0, 'effect_radius_pd' : 0}, [0.0010], [0.0712928]]
+        ]
 
Index: sasmodels/models/barbell.py
===================================================================
--- sasmodels/models/barbell.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/barbell.py	(revision dcdf29d606cfaa2f3bb9d578ac394c6f167fa360)
@@ -100,20 +100,24 @@
 title = "Cylinder with spherical end caps"
 description = """
-        Calculates the scattering from a barbell-shaped cylinder. That is a sphereocylinder with spherical end caps that have a radius larger than that of the cylinder and the center of the end cap
-        radius lies outside of the cylinder.
-        Note: As the length of cylinder(bar) -->0,it becomes a dumbbell. And when rad_bar = rad_bell, it is a spherocylinder.
-        It must be that rad_bar <(=) rad_bell.
+    Calculates the scattering from a barbell-shaped cylinder.
+    That is a sphereocylinder with spherical end caps that have a radius larger
+    than that of the cylinder and the center of the end cap radius lies outside
+    of the cylinder.
+    Note: As the length of cylinder(bar) -->0,it becomes a dumbbell. And when
+    rad_bar = rad_bell, it is a spherocylinder.
+    It must be that rad_bar <(=) rad_bell.
 """
 category = "shape:cylinder"
-
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
-parameters = [["sld", "4e-6/Ang^2", 4, [-inf, inf], "", "Barbell scattering length density"],
-              ["solvent_sld", "1e-6/Ang^2", 1, [-inf, inf], "", "Solvent scattering length density"],
-              ["bell_radius", "Ang", 40, [0, inf], "volume", "Spherical bell radius"],
-              ["radius", "Ang", 20, [0, inf], "volume", "Cylindrical bar radius"],
-              ["length", "Ang", 400, [0, inf], "volume", "Cylinder bar length"],
-              ["theta", "degrees", 60, [-inf, inf], "orientation", "In plane angle"],
-              ["phi", "degrees", 60, [-inf, inf], "orientation", "Out of plane angle"],
+parameters = [["sld",         "4e-6/Ang^2",   4, [-inf, inf], "",            "Barbell scattering length density"],
+              ["solvent_sld", "1e-6/Ang^2",   1, [-inf, inf], "",            "Solvent scattering length density"],
+              ["bell_radius", "Ang",         40, [0, inf],    "volume",      "Spherical bell radius"],
+              ["radius",      "Ang",         20, [0, inf],    "volume",      "Cylindrical bar radius"],
+              ["length",      "Ang",        400, [0, inf],    "volume",      "Cylinder bar length"],
+              ["theta",       "degrees",     60, [-inf, inf], "orientation", "In plane angle"],
+              ["phi",         "degrees",     60, [-inf, inf], "orientation", "Out of plane angle"],
              ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["lib/J1.c", "lib/gauss76.c", "barbell.c"]
Index: sasmodels/models/bcc.py
===================================================================
--- sasmodels/models/bcc.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/bcc.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -109,20 +109,26 @@
 title = "Body-centred cubic lattic with paracrystalline distortion"
 description = """
-    Calculates the scattering from a **body-centered cubic lattice** with paracrystalline distortion. Thermal vibrations
-    are considered to be negligible, and the size of the paracrystal is infinitely large. Paracrystalline distortion is
-    assumed to be isotropic and characterized by a Gaussian distribution.
+    Calculates the scattering from a **body-centered cubic lattice** with
+    paracrystalline distortion. Thermal vibrations are considered to be
+    negligible, and the size of the paracrystal is infinitely large.
+    Paracrystalline distortion is assumed to be isotropic and characterized
+    by a Gaussian distribution.
     """
 category = "shape:paracrystal"
 
+single = False
+
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description" ],
-parameters = [["dnn", "Ang", 220, [-inf, inf], "", "Nearest neighbour distance"],
-              ["d_factor", "", 0.06, [-inf, inf], "", "Paracrystal distortion factor"],
-              ["radius", "Ang", 40, [0, inf], "volume", "Particle radius"],
-              ["sld", "1e-6/Ang^2", 4, [-inf, inf], "", "Particle scattering length density"],
-              ["solvent_sld", "1e-6/Ang^2", 1, [-inf, inf], "", "Solvent scattering length density"],
-              ["theta", "degrees", 60, [-inf, inf], "orientation", "In plane angle"],
-              ["phi", "degrees", 60, [-inf, inf], "orientation", "Out of plane angle"],
-              ["psi", "degrees", 60, [-inf, inf], "orientation", "Out of plane angle"]
+parameters = [["dnn",         "Ang",       220,    [-inf, inf], "",            "Nearest neighbour distance"],
+              ["d_factor",    "",            0.06, [-inf, inf], "",            "Paracrystal distortion factor"],
+              ["radius",      "Ang",        40,    [0, inf],    "volume",      "Particle radius"],
+              ["sld",         "1e-6/Ang^2",  4,    [-inf, inf], "",            "Particle scattering length density"],
+              ["solvent_sld", "1e-6/Ang^2",  1,    [-inf, inf], "",            "Solvent scattering length density"],
+              ["theta",       "degrees",    60,    [-inf, inf], "orientation", "In plane angle"],
+              ["phi",         "degrees",    60,    [-inf, inf], "orientation", "Out of plane angle"],
+              ["psi",         "degrees",    60,    [-inf, inf], "orientation", "Out of plane angle"]
              ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["lib/J1.c", "lib/gauss150.c", "bcc.c"]
Index: sasmodels/models/be_polyelectrolyte.py
===================================================================
--- sasmodels/models/be_polyelectrolyte.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
+++ sasmodels/models/be_polyelectrolyte.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -0,0 +1,196 @@
+r"""
+This model calculates the structure factor of a polyelectrolyte solution with
+the RPA expression derived by Borue and Erukhimovich.
+
+Definition
+----------
+
+The scattering intensity $I(q)$ is calculated as
+
+.. math::
+
+    I(q) = K\frac{q^2+k^2}{4\pi L\alpha ^2}
+    \frac{1}{1+r_{0}^2(q^2+k^2)(q^2-12hC_a/b^2)} + background
+
+    k^2 = 4\pi L(2C_s + \alpha C_a)
+
+    r_{0}^2 = \frac{1}{\alpha \sqrt{C_a} \left( b/\sqrt{48\pi L_b}\right)}
+
+where $K$ is the contrast factor for the polymer, $L_b$ is the Bjerrum length,
+$h$ is the virial parameter, $b$ is the monomer length,
+$C_s$ is the concentration of monovalent salt, $\alpha$ is the ionization
+degree, $C_a$ is the polymer molar concentration, and $background$ is the
+incoherent background.
+
+For 2D data the scattering intensity is calculated in the same way as 1D,
+where the $q$ vector is defined as
+
+.. math::
+
+    q = \sqrt{q_x^2 + q_y^2}
+
+
+.. figure:: img/be_polyelectrolyte_1d.jpg
+
+    1D plot using the default values (w/500 data point).
+
+NB: $1 barn = 10^{-24} cm^2$
+
+References
+----------
+
+V Y Borue, I Y Erukhimovich, *Macromolecules*, 21 (1988) 3240
+
+J F Joanny, L Leibler, *Journal de Physique*, 51 (1990) 545
+
+A Moussaid, F Schosseler, J P Munch, S Candau,
+*J. Journal de Physique II France*, 3 (1993) 573
+
+E Raphael, J F Joanny, *Europhysics Letters*, 11 (1990) 179
+
+"""
+
+from numpy import inf, pi, sqrt
+
+name = "be_polyelectrolyte"
+title = "Polyelectrolyte with the RPA expression derived by Borue and Erukhimovich"
+description = """
+            Evaluate
+            F(x) = K 1/(4 pi Lb (alpha)^(2)) (q^(2)+k2)/(1+(r02)^(2))
+                 (q^(2)+k2) (q^(2)-(12 h C/b^(2)))
+
+            has 3 internal parameters :
+                   The inverse Debye Length: K2 = 4 pi Lb (2 Cs+alpha C)
+                   r02 =1/alpha/Ca^(0.5) (B/(48 pi Lb)^(0.5))
+                   Ca = 6.022136e-4 C
+            """
+category = "shape-independent"
+
+# pylint: disable=bad-whitespace, line-too-long
+#   ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["contrast_factor",       "barns",   10.0,  [-inf, inf], "", "Contrast factor of the polymer"],
+    ["bjerrum_length",        "Ang",      7.1,  [0, inf],    "", "Bjerrum length"],
+    ["virial_param",          "1/Ang^2", 12.0,  [-inf, inf], "", "Virial parameter"],
+    ["monomer_length",        "Ang",     10.0,  [0, inf],    "", "Monomer length"],
+    ["salt_concentration",    "mol/L",    0.0,  [-inf, inf], "", "Concentration of monovalent salt"],
+    ["ionization_degree",     "",         0.05, [0, inf],    "", "Degree of ionization"],
+    ["polymer_concentration", "mol/L",    0.7,  [0, inf],    "", "Polymer molar concentration"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+
+
+def Iq(q,
+       contrast_factor=10.0,
+       bjerrum_length=7.1,
+       virial_param=12.0,
+       monomer_length=10.0,
+       salt_concentration=0.0,
+       ionization_degree=0.05,
+       polymer_concentration=0.7):
+    """
+    :param q:                     Input q-value
+    :param contrast_factor:       Contrast factor of the polymer
+    :param bjerrum_length:        Bjerrum length
+    :param virial_param:          Virial parameter
+    :param monomer_length:        Monomer length
+    :param salt_concentration:    Concentration of monovalent salt
+    :param ionization_degree:     Degree of ionization
+    :param polymer_concentration: Polymer molar concentration
+    :return:                      1-D intensity
+    """
+
+    concentration = polymer_concentration * 6.022136e-4
+
+    k_square = 4.0 * pi * bjerrum_length * (2*salt_concentration +
+                                            ionization_degree * concentration)
+
+    r0_square = 1.0/ionization_degree/sqrt(concentration) * \
+                (monomer_length/sqrt((48.0*pi*bjerrum_length)))
+
+    term1 = contrast_factor/(4.0 * pi * bjerrum_length *
+                             ionization_degree**2) * (q**2 + k_square)
+
+    term2 = 1.0 + r0_square**2 * (q**2 + k_square) * \
+        (q**2 - (12.0 * virial_param * concentration/(monomer_length**2)))
+
+    return term1/term2
+
+Iq.vectorized = True  # Iq accepts an array of q values
+
+
+def Iqxy(qx, qy, *args):
+    """
+    :param qx:   Input q_x-value
+    :param qy:   Input q_y-value
+    :param args: Remaining arguments
+    :return:     2D-Intensity
+    """
+    iq = Iq(sqrt(qx**2 + qy**2), *args)
+    return iq
+
+Iqxy.vectorized = True  # Iqxy accepts an array of qx, qy values
+
+
+demo = dict(scale=1, background=0.1,
+            contrast_factor=10.0,
+            bjerrum_length=7.1,
+            virial_param=12.0,
+            monomer_length=10.0,
+            salt_concentration=0.0,
+            ionization_degree=0.05,
+            polymer_concentration=0.7)
+
+oldname = "BEPolyelectrolyte"
+
+oldpars = dict(background='background',
+               contrast_factor='k',
+               bjerrum_length='lb',
+               virial_param='h',
+               monomer_length='b',
+               salt_concentration='cs',
+               ionization_degree='alpha',
+               polymer_concentration='c')
+
+tests = [
+
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'contrast_factor':       10.0,
+      'bjerrum_length':         7.1,
+      'virial_param':          12.0,
+      'monomer_length':        10.0,
+      'salt_concentration':     0.0,
+      'ionization_degree':      0.05,
+      'polymer_concentration':  0.7,
+      'background':             0.001,
+     }, 0.001, 0.0948379],
+
+    # Additional tests with larger range of parameters
+    [{'contrast_factor':       10.0,
+      'bjerrum_length':       100.0,
+      'virial_param':           3.0,
+      'monomer_length':         1.0,
+      'salt_concentration':    10.0,
+      'ionization_degree':      2.0,
+      'polymer_concentration': 10.0,
+     }, 0.1, -3.75693800588],
+
+    [{'contrast_factor':       10.0,
+      'bjerrum_length':       100.0,
+      'virial_param':           3.0,
+      'monomer_length':         1.0,
+      'salt_concentration':    10.0,
+      'ionization_degree':      2.0,
+      'polymer_concentration': 10.0,
+      'background':           100.0
+     }, 5.0, 100.029142149],
+
+    [{'contrast_factor':     100.0,
+      'bjerrum_length':       10.0,
+      'virial_param':        180.0,
+      'monomer_length':        1.0,
+      'salt_concentration':    0.1,
+      'ionization_degree':     0.5,
+      'polymer_concentration': 0.1,
+     }, 200., 1.80664667511e-06],
+    ]
Index: sasmodels/models/broad_peak.py
===================================================================
--- sasmodels/models/broad_peak.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/broad_peak.py	(revision dcdf29d606cfaa2f3bb9d578ac394c6f167fa360)
@@ -60,14 +60,33 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type", "description"],
-parameters = [["porod_scale", "", 1.0e-05, [-inf, inf], "", "Power law scale factor"],
-              ["porod_exp", "", 3.0, [-inf, inf], "", "Exponent of power law"],
-              ["lorentz_scale", "", 10.0, [-inf, inf], "", "Scale factor for broad Lorentzian peak"],
-              ["lorentz_length", "Ang", 50.0, [-inf, inf], "", "Lorentzian screening length"],
-              ["peak_pos", "1/Ang", 0.1, [-inf, inf], "", "Peak postion in q"],
-              ["lorentz_exp", "", 2.0, [-inf, inf], "", "exponent of Lorentz function"],
+parameters = [["porod_scale",    "",  1.0e-05, [-inf, inf], "", "Power law scale factor"],
+              ["porod_exp",      "",      3.0, [-inf, inf], "", "Exponent of power law"],
+              ["lorentz_scale",  "",     10.0, [-inf, inf], "", "Scale factor for broad Lorentzian peak"],
+              ["lorentz_length", "Ang",  50.0, [-inf, inf], "", "Lorentzian screening length"],
+              ["peak_pos",       "1/Ang", 0.1, [-inf, inf], "", "Peak position in q"],
+              ["lorentz_exp",    "",      2.0, [-inf, inf], "", "Exponent of Lorentz function"],
              ]
+# pylint: enable=bad-whitespace, line-too-long
 
-def Iq(q, porod_scale, porod_exp, lorentz_scale, lorentz_length, peak_pos, lorentz_exp):
+def Iq(q,
+       porod_scale=1.0e-5,
+       porod_exp=3.0,
+       lorentz_scale=10.0,
+       lorentz_length=50.0,
+       peak_pos=0.1,
+       lorentz_exp=2.0):
+    """
+    :param q:              Input q-value
+    :param porod_scale:    Power law scale factor
+    :param porod_exp:      Exponent of power law
+    :param lorentz_scale:  Scale factor for broad Lorentzian peak
+    :param lorentz_length: Lorentzian screening length
+    :param peak_pos:       Peak position in q
+    :param lorentz_exp:    Exponent of Lorentz function
+    :return:               Calculated intensity
+    """
+
     inten = (porod_scale / q ** porod_exp + lorentz_scale
              / (1.0 + (abs(q - peak_pos) * lorentz_length) ** lorentz_exp))
@@ -76,7 +95,13 @@
 
 def Iqxy(qx, qy, *args):
+    """
+    :param qx:   Input q_x-value
+    :param qy:   Input q_y-value
+    :param args: Remaining arguments
+    :return:     2D-Intensity
+    """
     return Iq(sqrt(qx ** 2 + qy ** 2), *args)
+
 Iqxy.vectorized = True # Iqxy accepts an array of qx, qy values
-
 
 demo = dict(scale=1, background=0,
Index: sasmodels/models/capped_cylinder.py
===================================================================
--- sasmodels/models/capped_cylinder.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/capped_cylinder.py	(revision dcdf29d606cfaa2f3bb9d578ac394c6f167fa360)
@@ -125,11 +125,10 @@
 """
 category = "shape:cylinder"
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [["sld",         "1e-6/Ang^2", 4, [-inf, inf], "",       "Cylinder scattering length density"],
+              ["solvent_sld", "1e-6/Ang^2", 1, [-inf, inf], "",       "Solvent scattering length density"],
+              ["radius",      "Ang",       20, [0, inf],    "volume", "Cylinder radius"],
 
-#             ["name", "units", default, [lower, upper], "type", "description"],
-parameters = [["sld", "1e-6/Ang^2", 4, [-inf, inf], "",
-               "Cylinder scattering length density"],
-              ["solvent_sld", "1e-6/Ang^2", 1, [-inf, inf], "",
-               "Solvent scattering length density"],
-              ["radius", "Ang", 20, [0, inf], "volume", "Cylinder radius"],
               # TODO: use an expression for cap radius with fixed bounds.
               # The current form requires cap radius R bigger than cylinder radius r.
@@ -141,9 +140,10 @@
               # in the capped cylinder, and zero for no bar in the barbell model.  In
               # both models, one would be a pill.
-              ["cap_radius", "Ang", 20, [0, inf], "volume", "Cap radius"],
-              ["length", "Ang", 400, [0, inf], "volume", "Cylinder length"],
-              ["theta", "degrees", 60, [-inf, inf], "orientation", "In plane angle"],
-              ["phi", "degrees", 60, [-inf, inf], "orientation", "Out of plane angle"],
+              ["cap_radius", "Ang",     20, [0, inf],    "volume", "Cap radius"],
+              ["length",     "Ang",    400, [0, inf],    "volume", "Cylinder length"],
+              ["theta",      "degrees", 60, [-inf, inf], "orientation", "In plane angle"],
+              ["phi",        "degrees", 60, [-inf, inf], "orientation", "Out of plane angle"],
              ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["lib/J1.c", "lib/gauss76.c", "capped_cylinder.c"]
@@ -159,4 +159,7 @@
             phi_pd=15, phi_pd_n=1)
 oldname = 'CappedCylinderModel'
-oldpars = dict(sld='sld_capcyl', solvent_sld='sld_solv',
-               length='len_cyl', radius='rad_cyl', cap_radius='rad_cap')
+oldpars = dict(sld='sld_capcyl',
+               solvent_sld='sld_solv',
+               length='len_cyl',
+               radius='rad_cyl',
+               cap_radius='rad_cap')
Index: sasmodels/models/core_shell_bicelle.c
===================================================================
--- sasmodels/models/core_shell_bicelle.c	(revision 8007311db20a23588c62551c8a69f089cf8835e1)
+++ sasmodels/models/core_shell_bicelle.c	(revision 8007311db20a23588c62551c8a69f089cf8835e1)
@@ -0,0 +1,209 @@
+double form_volume(double radius, double rim_thickness, double face_thickness, double length);
+double Iq(double q,
+          double radius,
+          double rim_thickness,
+          double face_thickness,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld);
+
+
+double Iqxy(double qx, double qy,
+          double radius,
+          double rim_thickness,
+          double face_thickness,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld,
+          double theta,
+          double phi);
+
+
+double form_volume(double radius, double rim_thickness, double face_thickness, double length)
+{
+    return M_PI*(radius+rim_thickness)*(radius+rim_thickness)*(length+2*face_thickness);
+}
+
+static double
+bicelle_kernel(double qq,
+              double rad,
+              double radthick,
+              double facthick,
+              double length,
+              double rhoc,
+              double rhoh,
+              double rhor,
+              double rhosolv,
+              double dum)
+{
+	double dr1,dr2,dr3;
+	double besarg1,besarg2;
+	double vol1,vol2,vol3;
+	double sinarg1,sinarg2;
+	double t1,t2,t3;
+	double retval,si1,si2,be1,be2;
+
+	dr1 = rhoc-rhoh;
+	dr2 = rhor-rhosolv;
+	dr3=  rhoh-rhor;
+	vol1 = M_PI*rad*rad*(2.0*length);
+	vol2 = M_PI*(rad+radthick)*(rad+radthick)*(2.0*length+2.0*facthick);
+	vol3= M_PI*(rad)*(rad)*(2.0*length+2.0*facthick);
+	besarg1 = qq*rad*sin(dum);
+	besarg2 = qq*(rad+radthick)*sin(dum);
+	sinarg1 = qq*length*cos(dum);
+	sinarg2 = qq*(length+facthick)*cos(dum);
+
+	if(besarg1 == 0) {
+		be1 = 0.5;
+	} else {
+		be1 = J1(besarg1)/besarg1;
+	}
+	if(besarg2 == 0) {
+		be2 = 0.5;
+	} else {
+		be2 = J1(besarg2)/besarg2;
+	}
+	if(sinarg1 == 0) {
+		si1 = 1.0;
+	} else {
+		si1 = sin(sinarg1)/sinarg1;
+	}
+	if(sinarg2 == 0) {
+		si2 = 1.0;
+	} else {
+		si2 = sin(sinarg2)/sinarg2;
+	}
+	t1 = 2.0*vol1*dr1*si1*be1;
+	t2 = 2.0*vol2*dr2*si2*be2;
+	t3 = 2.0*vol3*dr3*si2*be1;
+
+	retval = ((t1+t2+t3)*(t1+t2+t3))*sin(dum);
+	return(retval);
+
+}
+
+static double
+bicelle_integration(double qq,
+                   double rad,
+                   double radthick,
+                   double facthick,
+                   double length,
+                   double rhoc,
+                   double rhoh,
+                   double rhor,
+                   double rhosolv)
+{
+
+
+	double answer,halfheight;
+	double lolim,uplim,summ,yyy,zi;
+	int nord,i;
+
+	// set up the integration end points
+	nord = 76;
+	lolim = 0.0;
+	uplim = M_PI/2;
+	halfheight = length/2.0;
+
+	summ = 0.0;
+	i=0;
+	for(i=0;i<nord;i++) {
+		zi = ( Gauss76Z[i]*(uplim-lolim) + uplim + lolim )/2.0;
+		yyy = Gauss76Wt[i] * bicelle_kernel(qq, rad, radthick, facthick,
+		                     halfheight, rhoc, rhoh, rhor,rhosolv, zi);
+		summ += yyy;
+	}
+
+	// calculate value of integral to return
+	answer = (uplim-lolim)/2.0*summ;
+	return(answer);
+}
+
+static double
+bicelle_kernel_2d(double q, double q_x, double q_y,
+          double radius,
+          double rim_thickness,
+          double face_thickness,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double cyl_x, cyl_y;
+    double alpha, cos_val;
+    double answer;
+
+    //convert angle degree to radian
+    theta *= M_PI/180.0;
+    phi *= M_PI/180.0;
+
+    // Cylinder orientation
+    cyl_x = cos(theta) * cos(phi);
+    cyl_y = sin(theta);
+
+    // Compute the angle btw vector q and the axis of the cylinder
+    cos_val = cyl_x*q_x + cyl_y*q_y;
+    alpha = acos( cos_val );
+
+    // Get the kernel
+    answer = bicelle_kernel(q, radius, rim_thickness, face_thickness,
+                           length/2.0, core_sld, face_sld, rim_sld,
+                           solvent_sld, alpha) / fabs(sin(alpha));
+
+    answer *= 1.0e-4;
+
+    return answer;
+}
+
+double Iq(double q,
+          double radius,
+          double rim_thickness,
+          double face_thickness,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld)
+{
+    double intensity = bicelle_integration(q, radius, rim_thickness, face_thickness,
+                       length, core_sld, face_sld, rim_sld, solvent_sld);
+    return intensity*1.0e-4;
+}
+
+
+double Iqxy(double qx, double qy,
+          double radius,
+          double rim_thickness,
+          double face_thickness,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double q;
+    q = sqrt(qx*qx+qy*qy);
+    double intensity = bicelle_kernel_2d(q, qx/q, qy/q,
+                      radius,
+                      rim_thickness,
+                      face_thickness,
+                      length,
+                      core_sld,
+                      face_sld,
+                      rim_sld,
+                      solvent_sld,
+                      theta,
+                      phi);
+
+    return intensity;
+}
Index: sasmodels/models/core_shell_bicelle.py
===================================================================
--- sasmodels/models/core_shell_bicelle.py	(revision 8007311db20a23588c62551c8a69f089cf8835e1)
+++ sasmodels/models/core_shell_bicelle.py	(revision 8007311db20a23588c62551c8a69f089cf8835e1)
@@ -0,0 +1,151 @@
+r"""
+
+Definition
+----------
+This model provides the form factor for a circular cylinder with a core-shell
+scattering length density profile.
+The form factor is normalized by the particle volume.
+
+.. _core-shell-cylinder-geometry:
+
+.. figure:: img/core_shell_bicelle_geometry.png
+
+    (Graphic from DOI: 10.1039/C0NP00002G)
+
+
+The output of the 1D scattering intensity function for randomly oriented
+cylinders is then given by the equation above.
+
+The *theta* and *phi* parameters are not used for the 1D output.
+Our implementation of the scattering kernel and the 1D scattering intensity
+use the c-library from NIST.
+
+.. figure:: img/core_shell_bicelle_1d.jpg
+
+    1D plot using the default values (w/200 data point).
+
+.. figure:: img/core_shell_bicelle_fig1.jpg
+
+    Definition of the angles for the oriented CoreShellBicelleModel.
+
+.. figure:: img/core_shell_bicelle_fig2.jpg
+
+    Examples of the angles for oriented pp against the detector plane.
+
+References
+----------
+
+L A Feigin and D I Svergun,
+*Structure Analysis by Small-Angle X-Ray and Neutron Scattering,*
+Plenum Press, New York, (1987)
+
+"""
+
+from numpy import inf, sin, cos
+
+name = "core_shell_bicelle"
+title = "Circular cylinder with a core-shell scattering length density profile.."
+description = """
+    P(q,alpha)= scale/Vs*f(q)^(2) + bkg,  where: f(q)= 2(core_sld
+    - solvant_sld)* Vc*sin[qLcos(alpha/2)]
+    /[qLcos(alpha/2)]*J1(qRsin(alpha))
+    /[qRsin(alpha)]+2(shell_sld-solvent_sld)
+    *Vs*sin[q(L+T)cos(alpha/2)][[q(L+T)
+    *cos(alpha/2)]*J1(q(R+T)sin(alpha))
+    /q(R+T)sin(alpha)]
+
+    alpha:is the angle between the axis of
+    the cylinder and the q-vector
+    Vs: the volume of the outer shell
+    Vc: the volume of the core
+    L: the length of the core
+    shell_sld: the scattering length density
+    of the shell
+    solvent_sld: the scattering length density
+    of the solvent
+    bkg: the background
+    T: the thickness
+    R+T: is the outer radius
+    L+2T: The total length of the outershell
+    J1: the first order Bessel function
+    theta: axis_theta of the cylinder
+    phi: the axis_phi of the cylinder...
+        """
+category = "shape:cylinder"
+
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["radius",         "Ang",       20, [0, inf],    "volume",      "Cylinder core radius"],
+    ["rim_thickness",  "Ang",       10, [0, inf],    "volume",      "Rim shell thickness"],
+    ["face_thickness", "Ang",       10, [0, inf],    "volume",      "Cylinder face thickness"],
+    ["length",         "Ang",      400, [0, inf],    "volume",      "Cylinder length"],
+    ["core_sld",       "1e-6/Ang^2", 1, [-inf, inf], "",            "Cylinder core scattering length density"],
+    ["face_sld",       "1e-6/Ang^2", 4, [-inf, inf], "",            "Cylinder face scattering length density"],
+    ["rim_sld",        "1e-6/Ang^2", 4, [-inf, inf], "",            "Cylinder rim scattering length density"],
+    ["solvent_sld",    "1e-6/Ang^2", 1, [-inf, inf], "",            "Solvent scattering length density"],
+    ["theta",          "degrees",   90, [-inf, inf], "orientation", "In plane angle"],
+    ["phi",            "degrees",    0, [-inf, inf], "orientation", "Out of plane angle"],
+    ]
+
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["lib/J1.c", "lib/gauss76.c", "core_shell_bicelle.c"]
+
+demo = dict(scale=1, background=0,
+            radius=20.0,
+            rim_thickness=10.0,
+            face_thickness=10.0,
+            length=400.0,
+            core_sld=1.0,
+            face_sld=4.0,
+            rim_sld=4.0,
+            solvent_sld=1.0,
+            theta=90,
+            phi=0)
+
+oldname = 'CoreShellBicelleModel'
+
+oldpars = dict(rim_thickness='rim_thick',
+               face_thickness='face_thick',
+               theta='axis_theta',
+               phi='axis_phi')
+
+qx, qy = 0.4 * cos(90), 0.5 * sin(0)
+tests = [
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'radius': 20.0,
+      'rim_thickness': 10.0,
+      'face_thickness': 10.0,
+      'length': 400.0,
+      'core_sld': 1.0,
+      'face_sld': 4.0,
+      'rim_sld': 4.0,
+      'solvent_sld': 1.0,
+      'background': 0.0,
+     }, 0.001, 353.550],
+
+    [{'radius': 20.0,
+      'rim_thickness': 10.0,
+      'face_thickness': 10.0,
+      'length': 400.0,
+      'core_sld': 1.0,
+      'face_sld': 4.0,
+      'rim_sld': 4.0,
+      'solvent_sld': 1.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'background': 0.00,
+     }, (qx, qy), 24.9167],
+
+    # Additional tests with larger range of parameters
+    [{'radius': 3.0,
+      'rim_thickness': 100.0,
+      'face_thickness': 100.0,
+      'length': 1200.0,
+      'core_sld': 5.0,
+      'face_sld': 41.0,
+      'rim_sld': 42.0,
+      'solvent_sld': 21.0,
+     }, 0.05, 1670.1828],
+    ]
Index: sasmodels/models/core_shell_ellipsoid.c
===================================================================
--- sasmodels/models/core_shell_ellipsoid.c	(revision 81dd61963c319ce8b1c38d14a6f3056a517fccdd)
+++ sasmodels/models/core_shell_ellipsoid.c	(revision 81dd61963c319ce8b1c38d14a6f3056a517fccdd)
@@ -0,0 +1,175 @@
+double form_volume(double equat_core,
+                   double polar_core,
+                   double equat_shell,
+                   double polar_shell);
+double Iq(double q,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld);
+
+
+double Iqxy(double qx, double qy,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi);
+
+
+double form_volume(double equat_core,
+                   double polar_core,
+                   double equat_shell,
+                   double polar_shell)
+{
+    double vol = 4.0*M_PI/3.0*equat_shell*equat_shell*polar_shell;
+    return vol;
+}
+
+static double
+core_shell_ellipsoid_kernel(double q,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld)
+{
+	double delpc,delps;
+	double uplim,lolim;		//upper and lower integration limits
+	double summ,zi,yyy,answer; //running tally of integration
+
+	lolim = 0.0;
+	uplim = 1.0;
+
+	summ = 0.0;	 //initialize intergral
+
+	delpc = core_sld - shell_sld; //core - shell
+	delps = shell_sld - solvent_sld; //shell - solvent
+
+	for(int i=0;i<76;i++) {
+		zi = ( Gauss76Z[i]*(uplim-lolim) + uplim + lolim )/2.0;
+		yyy = Gauss76Wt[i] * gfn4(zi,
+		                          equat_core,
+		                          polar_core,
+		                          equat_shell,
+		                          polar_shell,
+		                          delpc,
+		                          delps,
+		                          q);
+		summ += yyy;
+	}
+
+	answer = (uplim-lolim)/2.0*summ;
+
+	//convert to [cm-1]
+	answer *= 1.0e-4;
+
+	return answer;
+}
+
+static double
+core_shell_ellipsoid_kernel_2d(double q, double q_x, double q_y,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double cyl_x, cyl_y;
+    double cos_val;
+    double answer;
+    double sldcs,sldss;
+
+    //convert angle degree to radian
+    theta = theta * M_PI/180.0;
+    phi = phi * M_PI/180.0;
+
+
+    // ellipsoid orientation, the axis of the rotation is consistent with the ploar axis.
+    cyl_x = cos(theta) * cos(phi);
+    cyl_y = sin(theta);
+
+    sldcs = core_sld - shell_sld;
+    sldss = shell_sld- solvent_sld;
+
+    // Compute the angle btw vector q and the
+    // axis of the cylinder
+    cos_val = cyl_x*q_x + cyl_y*q_y;
+
+    // Call the IGOR library function to get the kernel: MUST use gfn4 not gf2 because of the def of params.
+    answer = gfn4(cos_val,
+                  equat_core,
+                  polar_core,
+                  equat_shell,
+                  polar_shell,
+                  sldcs,
+                  sldss,
+                  q);
+
+    //convert to [cm-1]
+    answer *= 1.0e-4;
+
+    return answer;
+}
+
+double Iq(double q,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld)
+{
+    double intensity = core_shell_ellipsoid_kernel(q,
+           equat_core,
+           polar_core,
+           equat_shell,
+           polar_shell,
+           core_sld,
+           shell_sld,
+           solvent_sld);
+
+    return intensity;
+}
+
+
+double Iqxy(double qx, double qy,
+          double equat_core,
+          double polar_core,
+          double equat_shell,
+          double polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double q;
+    q = sqrt(qx*qx+qy*qy);
+    double intensity = core_shell_ellipsoid_kernel_2d(q, qx/q, qy/q,
+                       equat_core,
+                       polar_core,
+                       equat_shell,
+                       polar_shell,
+                       core_sld,
+                       shell_sld,
+                       solvent_sld,
+                       theta,
+                       phi);
+
+    return intensity;
+}
Index: sasmodels/models/core_shell_ellipsoid.py
===================================================================
--- sasmodels/models/core_shell_ellipsoid.py	(revision 177c1a138481a9833b4929af687e86583b5c16d5)
+++ sasmodels/models/core_shell_ellipsoid.py	(revision 177c1a138481a9833b4929af687e86583b5c16d5)
@@ -0,0 +1,179 @@
+r"""
+This model provides the form factor, $P(q)$, for a core shell ellipsoid (below)
+where the form factor is normalized by the volume of the cylinder.
+
+.. math::
+
+    P(q) = scale * \left<f^2\right>/V + background
+
+where the volume $V = (4/3)\pi(r_{maj}r_{min}^2)$ and the averaging $< >$ is
+applied over all orientations for 1D.
+
+.. figure:: img/core_shell_ellipsoid_fig1.gif
+
+    The returned value is in units of $cm^{-1}$, on absolute scale.
+
+Definition
+----------
+
+The form factor calculated is
+
+.. math::
+
+    P(q) = \frac{scale}{V}\int_0^1
+        \left|F(q,r_{min},r_{max},\alpha)\right|^2d\alpha + background
+
+    \left|F(q,r_{min},r_{max},\alpha)\right|=V\Delta \rho \cdot (3j_1(u)/u)
+
+    u = q\left[ r_{maj}^2\alpha ^2 + r_{min}^2(1-\alpha ^2)\right]^{1/2}
+
+where
+
+.. math::
+
+    j_1(u)=(\sin x - x \cos x)/x^2
+
+To provide easy access to the orientation of the core-shell ellipsoid,
+we define the axis of the solid ellipsoid using two angles $\theta$ and $\phi$.
+These angles are defined on Figure 2 of the CylinderModel.
+The contrast is defined as SLD(core) - SLD(shell) and SLD(shell) - SLD(solvent).
+
+In the parameters, *equat_core* = equatorial core radius, *polar_core* =
+polar core radius, *equat_shell* = $r_{min}$ (or equatorial outer radius),
+and *polar_shell* = $r_{maj}$ (or polar outer radius).
+
+.. note::
+    The 2nd virial coefficient of the solid ellipsoid is calculated based on
+    the *radius_a* (= *polar_shell)* and *radius_b (= equat_shell)* values,
+    and used as the effective radius for *S(Q)* when $P(Q) * S(Q)$ is applied.
+
+.. figure:: img/core_shell_ellipsoid_1d.jpg
+
+    1D plot using the default values (w/200 data point).
+
+.. figure:: img/core_shell_ellipsoid_fig2.jpg
+
+    The angles for oriented core_shell_ellipsoid.
+
+Our model uses the form factor calculations implemented in a c-library provided
+by the NIST Center for Neutron Research (Kline, 2006).
+
+References
+----------
+
+M Kotlarchyk, S H Chen, *J. Chem. Phys.*, 79 (1983) 2461
+
+S J Berr, *Phys. Chem.*, 91 (1987) 4760
+
+"""
+
+from numpy import inf, sin, cos, pi
+
+name = "core_shell_ellipsoid"
+title = "Form factor for an spheroid ellipsoid particle with a core shell structure."
+description = """
+    [SpheroidCoreShellModel] Calculates the form factor for an spheroid
+    ellipsoid particle with a core_shell structure.
+    The form factor is averaged over all possible
+    orientations of the ellipsoid such that P(q)
+    = scale*<f^2>/Vol + bkg, where f is the
+    single particle scattering amplitude.
+    [Parameters]:
+    equat_core = equatorial radius of core,
+    polar_core = polar radius of core,
+    equat_shell = equatorial radius of shell,
+    polar_shell = polar radius (revolution axis) of shell,
+    core_sld = SLD_core
+    shell_sld = SLD_shell
+    solvent_sld = SLD_solvent
+    background = Incoherent bkg
+    scale =scale
+    Note:It is the users' responsibility to ensure
+    that shell radii are larger than core radii.
+    oblate: polar radius < equatorial radius
+    prolate :  polar radius > equatorial radius
+    """
+category = "shape:ellipsoid"
+
+single = False  # TODO: maybe using sph_j1c inside gfn would help?
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["equat_core",  "Ang",      200,   [0, inf],    "volume",      "Equatorial radius of core"],
+    ["polar_core",  "Ang",       10,   [0, inf],    "volume",      "Polar radius of core"],
+    ["equat_shell", "Ang",      250,   [0, inf],    "volume",      "Equatorial radius of shell"],
+    ["polar_shell", "Ang",       30,   [0, inf],    "volume",      "Polar radius of shell"],
+    ["core_sld",    "1e-6/Ang^2", 2,   [-inf, inf], "",            "Core scattering length density"],
+    ["shell_sld",   "1e-6/Ang^2", 1,   [-inf, inf], "",            "Shell scattering length density"],
+    ["solvent_sld", "1e-6/Ang^2", 6.3, [-inf, inf], "",            "Solvent scattering length density"],
+    ["theta",       "degrees",    0,   [-inf, inf], "orientation", "Oblate orientation wrt incoming beam"],
+    ["phi",         "degrees",    0,   [-inf, inf], "orientation", "Oblate orientation in the plane of the detector"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["lib/sph_j1c.c", "lib/gfn.c", "lib/gauss76.c", "core_shell_ellipsoid.c"]
+
+demo = dict(scale=1, background=0.001,
+            equat_core=200.0,
+            polar_core=10.0,
+            equat_shell=250.0,
+            polar_shell=30.0,
+            core_sld=2.0,
+            shell_sld=1.0,
+            solvent_sld=6.3,
+            theta=0,
+            phi=0)
+
+oldname = 'CoreShellEllipsoidModel'
+
+oldpars = dict(core_sld='sld_core',
+               shell_sld='sld_shell',
+               solvent_sld='sld_solvent',
+               theta='axis_theta',
+               phi='axis_phi')
+
+q = 0.1
+phi = pi/6
+qx = q*cos(phi)
+qy = q*sin(phi)
+
+tests = [
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'equat_core': 200.0,
+      'polar_core': 20.0,
+      'equat_shell': 250.0,
+      'polar_shell': 30.0,
+      'core_sld': 2.0,
+      'shell_sld': 1.0,
+      'solvent_sld': 6.3,
+      'background': 0.001,
+      'scale': 1.0,
+     }, 1.0, 0.00189402],
+
+    # Additional tests with larger range of parameters
+    [{'background': 0.01}, 0.1, 8.86741],
+
+    [{'equat_core': 20.0,
+      'polar_core': 200.0,
+      'equat_shell': 54.0,
+      'polar_shell': 3.0,
+      'core_sld': 20.0,
+      'shell_sld': 10.0,
+      'solvent_sld': 6.0,
+      'background': 0.0,
+      'scale': 1.0,
+     }, 0.01, 26150.4],
+
+    [{'background': 0.001}, (0.4, 0.5), 0.00170471],
+
+    [{'equat_core': 20.0,
+      'polar_core': 200.0,
+      'equat_shell': 54.0,
+      'polar_shell': 3.0,
+      'core_sld': 20.0,
+      'shell_sld': 10.0,
+      'solvent_sld': 6.0,
+      'background': 0.01,
+      'scale': 0.01,
+     }, (qx, qy), 0.105764],
+    ]
Index: sasmodels/models/core_shell_ellipsoid_xt.c
===================================================================
--- sasmodels/models/core_shell_ellipsoid_xt.c	(revision 81bb6683df7d596fa70869ba742026b60f836b22)
+++ sasmodels/models/core_shell_ellipsoid_xt.c	(revision 81bb6683df7d596fa70869ba742026b60f836b22)
@@ -0,0 +1,190 @@
+double form_volume(double equat_core,
+                   double polar_core,
+                   double equat_shell,
+                   double polar_shell);
+double Iq(double q,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld);
+
+
+double Iqxy(double qx, double qy,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi);
+
+
+double form_volume(double equat_core,
+                   double x_core,
+                   double t_shell,
+                   double x_polar_shell)
+{
+	double equat_shell, polar_shell;
+    equat_shell = equat_core + t_shell;
+    polar_shell = equat_core*x_core + t_shell*x_polar_shell;
+    double vol = 4.0*M_PI/3.0*equat_shell*equat_shell*polar_shell;
+    return vol;
+}
+
+static double
+core_shell_ellipsoid_xt_kernel(double q,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld)
+{
+	double delpc,delps;
+	double uplim,lolim;		//upper and lower integration limits
+	double summ,zi,yyy,answer; //running tally of integration
+	double polar_core, equat_shell, polar_shell;
+
+	lolim = 0.0;
+	uplim = 1.0;
+
+	summ = 0.0;	 //initialize intergral
+
+	delpc = core_sld - shell_sld; //core - shell
+	delps = shell_sld - solvent_sld; //shell - solvent
+
+
+    polar_core = equat_core*x_core;
+    equat_shell = equat_core + t_shell;
+    polar_shell = equat_core*x_core + t_shell*x_polar_shell;
+
+	for(int i=0;i<76;i++) {
+		zi = ( Gauss76Z[i]*(uplim-lolim) + uplim + lolim )/2.0;
+		yyy = Gauss76Wt[i] * gfn4(zi,
+		                          equat_core,
+                                  polar_core,
+                                  equat_shell,
+                                  polar_shell,
+		                          delpc,
+		                          delps,
+		                          q);
+		summ += yyy;
+	}
+
+	answer = (uplim-lolim)/2.0*summ;
+
+	//convert to [cm-1]
+	answer *= 1.0e-4;
+
+	return answer;
+}
+
+static double
+core_shell_ellipsoid_xt_kernel_2d(double q, double q_x, double q_y,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double cyl_x, cyl_y;
+    double cos_val;
+    double answer;
+    double sldcs,sldss;
+	double polar_core, equat_shell, polar_shell;
+
+    //convert angle degree to radian
+    theta = theta * M_PI/180.0;
+    phi = phi * M_PI/180.0;
+
+
+    // ellipsoid orientation, the axis of the rotation is consistent with the ploar axis.
+    cyl_x = cos(theta) * cos(phi);
+    cyl_y = sin(theta);
+
+    sldcs = core_sld - shell_sld;
+    sldss = shell_sld- solvent_sld;
+
+    // Compute the angle btw vector q and the
+    // axis of the cylinder
+    cos_val = cyl_x*q_x + cyl_y*q_y;
+
+    polar_core = equat_core*x_core;
+    equat_shell = equat_core + t_shell;
+    polar_shell = equat_core*x_core + t_shell*x_polar_shell;
+
+    // Call the IGOR library function to get the kernel:
+    // MUST use gfn4 not gf2 because of the def of params.
+    answer = gfn4(cos_val,
+                  equat_core,
+                  polar_core,
+                  equat_shell,
+                  polar_shell,
+                  sldcs,
+                  sldss,
+                  q);
+
+    //convert to [cm-1]
+    answer *= 1.0e-4;
+
+    return answer;
+}
+
+double Iq(double q,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld)
+{
+    double intensity = core_shell_ellipsoid_xt_kernel(q,
+           equat_core,
+           x_core,
+           t_shell,
+           x_polar_shell,
+           core_sld,
+           shell_sld,
+           solvent_sld);
+
+    return intensity;
+}
+
+
+double Iqxy(double qx, double qy,
+          double equat_core,
+          double x_core,
+          double t_shell,
+          double x_polar_shell,
+          double core_sld,
+          double shell_sld,
+          double solvent_sld,
+          double theta,
+          double phi)
+{
+    double q;
+    q = sqrt(qx*qx+qy*qy);
+    double intensity = core_shell_ellipsoid_xt_kernel_2d(q, qx/q, qy/q,
+                       equat_core,
+                       x_core,
+                       t_shell,
+                       x_polar_shell,
+                       core_sld,
+                       shell_sld,
+                       solvent_sld,
+                       theta,
+                       phi);
+
+    return intensity;
+}
Index: sasmodels/models/core_shell_ellipsoid_xt.py
===================================================================
--- sasmodels/models/core_shell_ellipsoid_xt.py	(revision 3882eeb5b3de1f8eff75651cb6dcd8f9dba766d6)
+++ sasmodels/models/core_shell_ellipsoid_xt.py	(revision 3882eeb5b3de1f8eff75651cb6dcd8f9dba766d6)
@@ -0,0 +1,172 @@
+r"""
+An alternative version of $P(q)$ for the core-shell ellipsoid
+(see CoreShellEllipsoidModel), having as parameters the core axial ratio X
+and a shell thickness, which are more often what we would like to determine.
+
+This model is also better behaved when polydispersity is applied than the four
+independent radii in CoreShellEllipsoidModel.
+
+Definition
+----------
+
+.. figure:: img/core_shell_ellipsoid_fig1.gif
+
+
+The geometric parameters of this model are
+
+*equat_core =* equatorial core radius *= R_minor_core*
+
+*X_core = polar_core / equat_core = Rmajor_core / Rminor_core*
+
+*T_shell = equat_outer - equat_core = Rminor_outer - Rminor_core*
+
+*XpolarShell = Tpolar_shell / T_shell = (Rmajor_outer - Rmajor_core)/
+(Rminor_outer - Rminor_core)*
+
+In terms of the original radii
+
+*polar_core = equat_core * X_core*
+
+*equat_shell = equat_core + T_shell*
+
+*polar_shell = equat_core * X_core + T_shell * XpolarShell*
+
+(where we note that "shell" perhaps confusingly, relates to the outer radius)
+When *X_core < 1* the core is oblate; when *X_core > 1* it is prolate.
+*X_core = 1* is a spherical core.
+
+For a fixed shell thickness *XpolarShell = 1*, to scale the shell thickness
+pro-rata with the radius *XpolarShell = X_core*.
+
+When including an $S(q)$, the radius in $S(q)$ is calculated to be that of
+a sphere with the same 2nd virial coefficient of the outer surface of the
+ellipsoid. This may have some undesirable effects if the aspect ratio of the
+ellipsoid is large (ie, if $X << 1$ or $X >> 1$ ), when the $S(q)$
+- which assumes spheres - will not in any case be valid.
+
+If SAS data are in absolute units, and the SLDs are correct, then scale should
+be the total volume fraction of the "outer particle". When $S(q)$ is introduced
+this moves to the $S(q)$ volume fraction, and scale should then be 1.0,
+or contain some other units conversion factor (for example, if you have SAXS data).
+
+References
+----------
+
+R K Heenan, *Private communication*
+
+"""
+
+from numpy import inf, sin, cos, pi
+
+name = "core_shell_ellipsoid_xt"
+title = "Form factor for an spheroid ellipsoid particle with a core shell structure."
+description = """
+        [core_shell_ellipsoid_xt] Calculates the form factor for an spheroid
+        ellipsoid particle with a core_shell structure.
+        The form factor is averaged over all possible
+        orientations of the ellipsoid such that P(q)
+        = scale*<f^2>/Vol + bkg, where f is the
+        single particle scattering amplitude.
+        [Parameters]:
+        equat_core = equatorial radius of core,
+        x_core = polar radius of core,
+        t_shell = equatorial radius of outer surface,
+        x_polar_shell = polar radius (revolution axis) of outer surface
+        core_sld = SLD_core
+        shell_sld = SLD_shell
+        solvent_sld = SLD_solvent
+        background = Incoherent bkg
+        scale =scale
+        Note:It is the users' responsibility to ensure
+        that shell radii are larger than core radii.
+        oblate: polar radius < equatorial radius
+        prolate :  polar radius > equatorial radius - this new model will make this easier
+        and polydispersity integrals more logical (as previously the shell could disappear).
+    """
+category = "shape:ellipsoid"
+
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["equat_core",    "Ang",       20,   [0, inf],    "volume",      "Equatorial radius of core"],
+    ["x_core",        "None",       3,   [0, inf],    "volume",      "Polar radius of core"],
+    ["t_shell",       "Ang",       30,   [0, inf],    "volume",      "Equatorial radius of shell"],
+    ["x_polar_shell", "",           1,   [0, inf],    "volume",      "Polar radius of shell"],
+    ["core_sld",      "1e-6/Ang^2", 2,   [-inf, inf], "",            "Core scattering length density"],
+    ["shell_sld",     "1e-6/Ang^2", 1,   [-inf, inf], "",            "Shell scattering length density"],
+    ["solvent_sld",   "1e-6/Ang^2", 6.3, [-inf, inf], "",            "Solvent scattering length density"],
+    ["theta",         "degrees",    0,   [-inf, inf], "orientation", "Oblate orientation wrt incoming beam"],
+    ["phi",           "degrees",    0,   [-inf, inf], "orientation", "Oblate orientation in the plane of the detector"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["lib/sph_j1c.c", "lib/gfn.c", "lib/gauss76.c",
+          "core_shell_ellipsoid_xt.c"]
+
+demo = dict(scale=0.05, background=0.001,
+            equat_core=20.0,
+            x_core=3.0,
+            t_shell=30.0,
+            x_polar_shell=1.0,
+            core_sld=2.0,
+            shell_sld=1.0,
+            solvent_sld=6.3,
+            theta=0,
+            phi=0)
+
+oldname = 'CoreShellEllipsoidXTModel'
+
+oldpars = dict(x_polar_shell='XpolarShell',
+               x_core='X_core',
+               t_shell='T_shell',
+               core_sld='sld_core',
+               shell_sld='sld_shell',
+               solvent_sld='sld_solvent',
+               theta='axis_theta',
+               phi='axis_phi')
+
+q = 0.1
+phi = pi/6
+qx = q*cos(phi)
+qy = q*sin(phi)
+
+tests = [
+    # Accuracy tests based on content in test/utest_coreshellellipsoidXTmodel.py
+    [{'equat_core': 200.0,
+      'x_core': 0.1,
+      't_shell': 50.0,
+      'x_polar_shell': 0.2,
+      'core_sld': 2.0,
+      'shell_sld': 1.0,
+      'solvent_sld': 6.3,
+      'background': 0.001,
+      'scale': 1.0,
+     }, 1.0, 0.00189402],
+
+    # Additional tests with larger range of parameters
+    [{'background': 0.01}, 0.1, 11.6915],
+
+    [{'equat_core': 20.0,
+      'x_core': 200.0,
+      't_shell': 54.0,
+      'x_polar_shell': 3.0,
+      'core_sld': 20.0,
+      'shell_sld': 10.0,
+      'solvent_sld': 6.0,
+      'background': 0.0,
+      'scale': 1.0,
+     }, 0.01, 8688.53],
+
+    [{'background': 0.001}, (0.4, 0.5), 0.00690673],
+
+    [{'equat_core': 20.0,
+      'x_core': 200.0,
+      't_shell': 54.0,
+      'x_polar_shell': 3.0,
+      'core_sld': 20.0,
+      'shell_sld': 10.0,
+      'solvent_sld': 6.0,
+      'background': 0.01,
+      'scale': 0.01,
+     }, (qx, qy), 0.0100002],
+    ]
Index: sasmodels/models/correlation_length.py
===================================================================
--- sasmodels/models/correlation_length.py	(revision 5054e805a7c53aa18f659a0c1613f527f05caab3)
+++ sasmodels/models/correlation_length.py	(revision 5054e805a7c53aa18f659a0c1613f527f05caab3)
@@ -0,0 +1,92 @@
+#correlation length model
+# Note: model title and parameter table are inserted automatically
+r"""
+Definition
+----------
+
+The scattering intensity I(q) is calculated as
+
+.. math::
+    I(Q) = \frac{A}{Q^n} + \frac{C}{1 + (Q\xi)^m} + B
+
+The first term describes Porod scattering from clusters (exponent = n) and the
+second term is a Lorentzian function describing scattering from polymer chains
+(exponent = m). This second term characterizes the polymer/solvent interactions
+and therefore the thermodynamics. The two multiplicative factors A and C, the
+incoherent background B and the two exponents n and m are used as fitting
+parameters. The final parameter &xi; is a correlation length for the polymer
+chains. Note that when m=2 this functional form becomes the familiar Lorentzian
+function.
+
+For 2D data: The 2D scattering intensity is calculated in the same way as 1D,
+where the q vector is defined as
+
+.. math::
+    q = \sqrt{q_x^2 + q_y^2}
+
+.. figure:: img/correlation_length_1d.jpg
+
+    1D plot using the default values (w/500 data points).
+
+REFERENCE
+B Hammouda, D L Ho and S R Kline, Insight into Clustering in
+Poly(ethylene oxide) Solutions, Macromolecules, 37 (2004) 6932-6937
+"""
+
+from numpy import inf, sqrt
+
+name = "correlation_length"
+title = """Calculates an empirical functional form for SAS data characterized
+by a low-Q signal and a high-Q signal."""
+description = """
+"""
+category = "shape-independent"
+# pylint: disable=bad-continuation, line-too-long
+#             ["name", "units", default, [lower, upper], "type","description"],
+parameters = [
+              ["lorentz_scale", "", 10.0, [0, inf], "", "Lorentzian Scaling Factor"],
+              ["porod_scale", "", 1e-06, [0, inf], "", "Porod Scaling Factor"],
+              ["cor_length", "Ang", 50.0, [0, inf], "", "Correlation length"],
+              ["exponent_p", "", 3.0, [0, inf], "", "Porod Exponent"],
+              ["exponent_l", "1/Ang^2", 2.0, [0, inf], "", "Lorentzian Exponent"],
+             ]
+# pylint: enable=bad-continuation, line-too-long
+
+def Iq(q, lorentz_scale, porod_scale, cor_length, exponent_p, exponent_l):
+    """
+    1D calculation of the Correlation length model
+    """
+    porod = porod_scale / pow(q, exponent_p)
+    lorentz = lorentz_scale / (1.0 + pow(q * cor_length, exponent_l))
+    inten = porod + lorentz
+    return inten
+
+def Iqxy(qx, qy, lorentz_scale, porod_scale, cor_length, exponent_p, exponent_l):
+    """
+    2D calculation of the Correlation length model
+    There is no orientation contribution.
+    """
+    q = sqrt(qx ** 2 + qy ** 2)
+    return Iq(q, lorentz_scale, porod_scale, cor_length, exponent_p, exponent_l)
+
+# parameters for demo
+demo = dict(lorentz_scale=10.0, porod_scale=1.0e-06, cor_length=50.0,
+            exponent_p=3.0, exponent_l=2.0, background=0.1,
+           )
+
+# For testing against the old sasview models, include the converted parameter
+# names and the target sasview model name.
+oldname = 'CorrLengthModel'
+# pylint: disable=bad-continuation
+oldpars = dict(
+               lorentz_scale='scale_l', porod_scale='scale_p',
+               cor_length='length_l', exponent_p='exponent_p',
+               exponent_l='exponent_l'
+               )
+
+tests = [
+         [{}, 0.001, 1009.98],
+         [{}, 0.150141, 0.174645],
+         [{}, 0.442528, 0.0203957]
+         ]
+# pylint: enable=bad-continuation
Index: sasmodels/models/fcc.c
===================================================================
--- sasmodels/models/fcc.c	(revision 82d239a5cb4d8005a4b04bf42b1425792a28383d)
+++ sasmodels/models/fcc.c	(revision eeb8bacf06af69341f80d5e76d7b10a7239c4e67)
@@ -101,5 +101,5 @@
 
   double b3_x, b3_y, b1_x, b1_y, b2_x, b2_y; //b3_z,
-  double q_z;
+  // double q_z;
   double cos_val_b3, cos_val_b2, cos_val_b1;
   double a1_dot_q, a2_dot_q,a3_dot_q;
@@ -124,5 +124,5 @@
   const double latticescale = 2.0*(4.0/3.0)*M_PI*(radius*radius*radius)/(s1*s1*s1);
   // q vector
-  q_z = 0.0; // for SANS; assuming qz is negligible
+  // q_z = 0.0; // for SANS; assuming qz is negligible
   /// Angles here are respect to detector coordinate
   ///  instead of against q coordinate(PRB 36(46), 3(6), 1754(3854))
Index: sasmodels/models/fcc.py
===================================================================
--- sasmodels/models/fcc.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/fcc.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -112,4 +112,6 @@
 category = "shape:paracrystal"
 
+single = False
+
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["dnn", "Ang", 220, [-inf, inf], "", "Nearest neighbour distance"],
Index: sasmodels/models/flexible_cylinder.c
===================================================================
--- sasmodels/models/flexible_cylinder.c	(revision f94d8a27ee285627fab8769c64a054dbc75e774f)
+++ sasmodels/models/flexible_cylinder.c	(revision f94d8a27ee285627fab8769c64a054dbc75e774f)
@@ -0,0 +1,64 @@
+double form_volume(double length, double kuhn_length, double radius);
+double Iq(double q, double length, double kuhn_length, double radius,
+          double sld, double solvent_sld);
+double Iqxy(double qx, double qy, double length, double kuhn_length,
+            double radius, double sld, double solvent_sld);
+double flexible_cylinder_kernel(double q, double length, double kuhn_length,
+                                double radius, double sld, double solvent_sld);
+
+
+double form_volume(double length, double kuhn_length, double radius)
+{
+
+      return 0.0;
+}
+
+double flexible_cylinder_kernel(double q,
+          double length,
+          double kuhn_length,
+          double radius,
+          double sld,
+          double solvent_sld)
+{
+
+	double Pi = 4.0*atan(1.0);
+
+	double cont = sld-solvent_sld;
+	double qr = q*radius;
+	double flex = Sk_WR(q,length,kuhn_length);
+    double crossSect = (2.0*J1(qr)/qr)*(2.0*J1(qr)/qr);
+
+	flex *= crossSect;
+	flex *= Pi*radius*radius*length;
+	flex *= cont*cont;
+	flex *= 1.0e-4;
+
+	return flex;
+}
+
+double Iq(double q,
+          double length,
+          double kuhn_length,
+          double radius,
+          double sld,
+          double solvent_sld)
+{
+
+	double result = flexible_cylinder_kernel(q, length, kuhn_length, radius, sld, solvent_sld);
+
+	return result;
+}
+
+double Iqxy(double qx, double qy,
+            double length,
+            double kuhn_length,
+            double radius,
+            double sld,
+            double solvent_sld)
+{
+	double q;
+	q = sqrt(qx*qx+qy*qy);
+	double result = flexible_cylinder_kernel(q, length, kuhn_length, radius, sld, solvent_sld);
+
+	return result;
+}
Index: sasmodels/models/flexible_cylinder.py
===================================================================
--- sasmodels/models/flexible_cylinder.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
+++ sasmodels/models/flexible_cylinder.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -0,0 +1,139 @@
+r"""
+This model provides the form factor, $P(q)$, for a flexible cylinder
+where the form factor is normalized by the volume of the cylinder.
+**Inter-cylinder interactions are NOT provided for.**
+
+.. math::
+
+    P(q) = \text{scale} \left<F^2\right>/V + \text{background}
+
+where the averaging $\left<\ldots\right>$ is applied only for the 1D
+calculation
+
+The 2D scattering intensity is the same as 1D, regardless of the orientation of
+the q vector which is defined as
+
+.. math::
+
+    q = \sqrt{q_x^2 + q_y^2}
+
+Definitions
+-----------
+
+.. figure:: img/flexible_cylinder_geometry.jpg
+
+
+The chain of contour length, $L$, (the total length) can be described as a
+chain of some number of locally stiff segments of length $l_p$, the persistence
+length (the length along the cylinder over which the flexible cylinder can be
+considered a rigid rod).
+The Kuhn length $(b = 2*l_p)$ is also used to describe the stiffness of a chain.
+
+The returned value is in units of $cm^-1$, on absolute scale.
+
+In the parameters, the sldCyl and sldSolv represent the SLD of the chain/cylinder
+and solvent respectively.
+
+
+.. figure:: img/flexible_cylinder_1d.jpg
+
+    1D plot using the default values (w/1000 data point).
+
+
+Our model uses the form factor calculations implemented in a c-library provided
+by the NIST Center for Neutron Research (Kline, 2006).
+
+
+From the reference:
+
+    'Method 3 With Excluded Volume' is used.
+    The model is a parametrization of simulations of a discrete representation
+    of the worm-like chain model of Kratky and Porod applied in the
+    pseudocontinuous limit.
+    See equations (13,26-27) in the original reference for the details.
+
+References
+----------
+
+J S Pedersen and P Schurtenberger. *Scattering functions of semiflexible
+polymers with and without excluded volume effects.* Macromolecules,
+29 (1996) 7602-7612
+
+Correction of the formula can be found in
+
+W R Chen, P D Butler and L J Magid, *Incorporating Intermicellar Interactions
+in the Fitting of SANS Data from Cationic Wormlike Micelles.* Langmuir,
+22(15) 2006 6539-6548
+"""
+from numpy import inf
+
+name = "flexible_cylinder"
+title = "Flexible cylinder where the form factor is normalized by the volume" \
+        "of the cylinder."
+description = """Note : scale and contrast=sld-solvent_sld are both
+                multiplicative factors in the model and are perfectly
+                correlated. One or both of these parameters must be held fixed
+                during model fitting.
+              """
+
+category = "shape:cylinder"
+single = False
+
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["length",      "Ang",       1000.0, [0, inf],    "volume", "Length of the flexible cylinder"],
+    ["kuhn_length", "Ang",        100.0, [0, inf],    "volume", "Kuhn length of the flexible cylinder"],
+    ["radius",      "Ang",         20.0, [0, inf],    "volume", "Radius of the flexible cylinder"],
+    ["sld",         "1e-6/Ang^2",   1.0, [-inf, inf], "",       "Cylinder scattering length density"],
+    ["solvent_sld", "1e-6/Ang^2",   6.3, [-inf, inf], "",       "Solvent scattering length density"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+source = ["lib/J1.c", "lib/wrc_cyl.c", "flexible_cylinder.c"]
+
+demo = dict(scale=1.0, background=0.0001,
+            length=1000.0,
+            kuhn_length=100.0,
+            radius=20.0,
+            sld=1.0,
+            solvent_sld=6.3)
+
+oldname = 'FlexibleCylinderModel'
+oldpars = dict(sld='sldCyl', solvent_sld='sldSolv')
+
+
+tests = [
+    # Accuracy tests based on content in test/utest_other_models.py
+    # Currently fails in OCL
+    # [{'length':     1000.0,
+    #  'kuhn_length': 100.0,
+    #  'radius':       20.0,
+    #  'sld':           1.0,
+    #  'solvent_sld':   6.3,
+    #  'background':    0.0001,
+    #  }, 0.001, 3509.2187],
+
+    # Additional tests with larger range of parameters
+    [{'length':    1000.0,
+      'kuhn_length': 100.0,
+      'radius':       20.0,
+      'sld':           1.0,
+      'solvent_sld':   6.3,
+      'background':    0.0001,
+     }, 1.0, 0.000595345],
+    [{'length':        10.0,
+      'kuhn_length': 800.0,
+      'radius':        2.0,
+      'sld':           6.0,
+      'solvent_sld':  12.3,
+      'background':    0.001,
+     }, 0.1, 1.55228],
+    [{'length':        100.0,
+      'kuhn_length': 800.0,
+      'radius':       50.0,
+      'sld':           0.1,
+      'solvent_sld':   5.1,
+      'background':    0.0,
+     }, 1.0, 0.000938456]
+    ]
+
Index: sasmodels/models/flexible_cylinder_ex.c
===================================================================
--- sasmodels/models/flexible_cylinder_ex.c	(revision 504abeee8bc5d67a14d7598227542961d3d748ed)
+++ sasmodels/models/flexible_cylinder_ex.c	(revision 504abeee8bc5d67a14d7598227542961d3d748ed)
@@ -0,0 +1,103 @@
+double form_volume(double length, double kuhn_length, double radius);
+double Iq(double q, double length, double kuhn_length, double radius,
+          double axis_ratio, double sld, double solvent_sld);
+double Iqxy(double qx, double qy, double length, double kuhn_length,
+            double radius, double axis_ratio, double sld, double solvent_sld);
+double flexible_cylinder_ex_kernel(double q, double length, double kuhn_length,
+                                double radius, double axis_ratio, double sld,
+                                double solvent_sld);
+double elliptical_crosssection(double q, double a, double b);
+
+double form_volume(double length, double kuhn_length, double radius)
+{
+    return 1.0;
+}
+
+double
+elliptical_crosssection(double q, double a, double b)
+{
+    double uplim,lolim,Pi,summ,arg,zi,yyy,answer;
+    int i,nord=76;
+
+    Pi = 4.0*atan(1.0);
+    lolim=0.0;
+    uplim=Pi/2.0;
+    summ=0.0;
+
+    for(i=0;i<nord;i++) {
+		zi = ( Gauss76Z[i]*(uplim-lolim) + uplim + lolim )/2.0;
+		arg = q*sqrt(a*a*sin(zi)*sin(zi)+b*b*cos(zi)*cos(zi));
+		yyy = pow((2.0 * J1(arg) / arg),2);
+		yyy *= Gauss76Wt[i];
+		summ += yyy;
+    }
+    answer = (uplim-lolim)/2.0*summ;
+    answer *= 2.0/Pi;
+    return(answer);
+
+}
+
+double flexible_cylinder_ex_kernel(double q,
+          double length,
+          double kuhn_length,
+          double radius,
+          double axis_ratio,
+          double sld,
+          double solvent_sld)
+{
+
+	double Pi,flex,crossSect, cont;
+
+	Pi = 4.0*atan(1.0);
+	cont = sld - solvent_sld;
+	crossSect = elliptical_crosssection(q,radius,(radius*axis_ratio));
+
+	flex = Sk_WR(q,length,kuhn_length);
+	flex *= crossSect;
+	flex *= Pi*radius*radius*axis_ratio*axis_ratio*length;
+	flex *= cont*cont;
+	flex *= 1.0e-4;
+
+	return flex;
+}
+
+double Iq(double q,
+          double length,
+          double kuhn_length,
+          double radius,
+          double axis_ratio,
+          double sld,
+          double solvent_sld)
+{
+
+	double result = flexible_cylinder_ex_kernel(q,
+	                length,
+	                kuhn_length,
+	                radius,
+	                axis_ratio,
+	                sld,
+	                solvent_sld);
+
+	return result;
+}
+
+double Iqxy(double qx, double qy,
+            double length,
+            double kuhn_length,
+            double radius,
+            double axis_ratio,
+            double sld,
+            double solvent_sld)
+{
+	double q;
+	q = sqrt(qx*qx+qy*qy);
+	double result = flexible_cylinder_ex_kernel(q,
+	                length,
+	                kuhn_length,
+	                radius,
+	                axis_ratio,
+	                sld,
+	                solvent_sld);
+
+	return result;
+}
Index: sasmodels/models/flexible_cylinder_ex.py
===================================================================
--- sasmodels/models/flexible_cylinder_ex.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
+++ sasmodels/models/flexible_cylinder_ex.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -0,0 +1,167 @@
+r"""
+This model calculates the form factor for a flexible cylinder with an
+elliptical cross section and a uniform scattering length density.
+The non-negligible diameter of the cylinder is included by accounting
+for excluded volume interactions within the walk of a single cylinder.
+The form factor is normalized by the particle volume such that
+
+.. math::
+
+    P(q) = \text{scale} \left<F^2\right>/V + \text{background}
+
+where the averaging $\left<\ldots\right>$ is over all possible orientations
+of the flexible cylinder.
+
+The 2D scattering intensity is the same as 1D, regardless of the orientation
+of the q vector which is defined as
+
+.. math::
+
+    q = \sqrt{q_x^2 + q_y^2}
+
+
+Definitions
+-----------
+
+The function calculated is from the reference given below. From that paper,
+"Method 3 With Excluded Volume" is used.
+The model is a parameterization of simulations of a discrete representation of
+the worm-like chain model of Kratky and Porod applied in the pseudo-continuous
+limit. See equations (13, 26-27) in the original reference for the details.
+
+.. note::
+
+    There are several typos in the original reference that have been corrected
+    by WRC. Details of the corrections are in the reference below. Most notably
+
+    - Equation (13): the term $(1 - w(QR))$ should swap position with $w(QR)$
+
+    - Equations (23) and (24) are incorrect; WRC has entered these into
+      Mathematica and solved analytically. The results were then converted to
+      code.
+
+    - Equation (27) should be $q0 = max(a3/sqrt(RgSquare),3)$ instead of
+      $max(a3*b/sqrt(RgSquare),3)$
+
+    - The scattering function is negative for a range of parameter values and
+      q-values that are experimentally accessible. A correction function has been
+      added to give the proper behavior.
+
+.. figure:: img/flexible_cylinder_ex_geometry.jpg
+
+
+The chain of contour length, $L$, (the total length) can be described as a chain
+of some number of locally stiff segments of length $l_p$, the persistence length
+(the length along the cylinder over which the flexible cylinder can be considered
+a rigid rod).
+The Kuhn length $(b = 2*l_p)$ is also used to describe the stiffness of a chain.
+
+The cross section of the cylinder is elliptical, with minor radius $a$ .
+The major radius is larger, so of course, **the axis ratio (parameter 5) must be
+greater than one.** Simple constraints should be applied during curve fitting to
+maintain this inequality.
+
+The returned value is in units of $cm^-1$, on absolute scale.
+
+In the parameters, the $sldCyl$ and $sldSolv$ represent the SLD of the
+chain/cylinder and solvent respectively. The *scale*, and the contrast are both
+multiplicative factors in the model and are perfectly correlated. One or both of
+these parameters must be held fixed during model fitting.
+
+**No inter-cylinder interference effects are included in this calculation.**
+
+.. figure:: img/flexible_cylinder_ex_1d.jpg
+
+    1D plot using the default values (w/1000 data point).
+
+References
+----------
+
+J S Pedersen and P Schurtenberger. *Scattering functions of semiflexible polymers
+with and without excluded volume effects.* Macromolecules, 29 (1996) 7602-7612
+
+Correction of the formula can be found in
+
+W R Chen, P D Butler and L J Magid, *Incorporating Intermicellar Interactions in
+the Fitting of SANS Data from Cationic Wormlike Micelles.* Langmuir,
+22(15) 2006 6539-6548
+"""
+from numpy import inf
+
+name = "flexible_cylinder_ex"
+title = "Flexible cylinder wth an elliptical cross section and a uniform " \
+        "scattering length density."
+description = """Note : scale and contrast=sldCyl-sldSolv are both multiplicative
+        factors in the
+        model and are perfectly correlated. One or
+        both of these parameters must be held fixed
+        during model fitting.
+        """
+single = False
+
+category = "shape:cylinder"
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["length",      "Ang",       1000.0, [0, inf],    "volume", "Length of the flexible cylinder"],
+    ["kuhn_length", "Ang",        100.0, [0, inf],    "volume", "Kuhn length of the flexible cylinder"],
+    ["radius",      "Ang",         20.0, [0, inf],    "volume", "Radius of the flexible cylinder"],
+    ["axis_ratio",  "",             1.5, [0, inf],    "",       "Axis_ratio (major_radius/radius"],
+    ["sld",         "1e-6/Ang^2",   1.0, [-inf, inf], "",       "Cylinder scattering length density"],
+    ["solvent_sld", "1e-6/Ang^2",   6.3, [-inf, inf], "",       "Solvent scattering length density"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["lib/J1.c", "lib/gauss76.c", "lib/wrc_cyl.c", "flexible_cylinder_ex.c"]
+
+demo = dict(scale=1.0, background=0.0001,
+            length=1000.0,
+            kuhn_length=100.0,
+            radius=20.0,
+            axis_ratio=1.5,
+            sld=1.0,
+            solvent_sld=6.3)
+
+oldname = 'FlexCylEllipXModel'
+oldpars = dict(sld='sldCyl', solvent_sld='sldSolv')
+
+
+tests = [
+    # Accuracy tests based on content in test/utest_other_models.py
+    # Currently fails in OCL
+    # [{'length':     1000.0,
+    #  'kuhn_length': 100.0,
+    #  'radius':       20.0,
+    #  'axis_ratio':    1.5,
+    #  'sld':           1.0,
+    #  'solvent_sld':   6.3,
+    #  'background':    0.0001,
+    # }, 0.001, 3509.2187],
+
+    # Additional tests with larger range of parameters
+    [{'length':     1000.0,
+      'kuhn_length': 100.0,
+      'radius':       20.0,
+      'axis_ratio':    1.5,
+      'sld':           1.0,
+      'solvent_sld':   6.3,
+      'background':    0.0001,
+     }, 1.0, 0.00223819],
+    [{'length':        10.0,
+      'kuhn_length': 800.0,
+      'radius':        2.0,
+      'axis_ratio':    0.5,
+      'sld':           6.0,
+      'solvent_sld':  12.3,
+      'background':    0.001,
+     }, 0.1, 0.390281],
+    [{'length':        100.0,
+      'kuhn_length': 800.0,
+      'radius':       50.0,
+      'axis_ratio':    4.5,
+      'sld':           0.1,
+      'solvent_sld':   5.1,
+      'background':    0.0,
+     }, 1.0, 0.0016338264790]
+    ]
+
Index: sasmodels/models/gauss_lorentz_gel.py
===================================================================
--- sasmodels/models/gauss_lorentz_gel.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
+++ sasmodels/models/gauss_lorentz_gel.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -0,0 +1,167 @@
+r"""
+This model calculates the scattering from a gel structure,
+but typically a physical rather than chemical network.
+It is modeled as a sum of a low-q exponential decay plus
+a lorentzian at higher-q values.
+
+Definition
+----------
+
+The scattering intensity I(q) is calculated as (Eqn. 5 from the reference)
+
+.. math::
+
+    I(q) = I_G(0)exp(-q^2\Xi ^2/2) + I_L(0)/(1+q^2\xi^2)
+
+$\Xi$ is the length scale of the static correlations in the gel,
+which can be attributed to the "frozen-in" crosslinks.
+$\xi is the dynamic correlation length, which can be attributed to the
+fluctuating polymer chains between crosslinks.
+$IG(0)$ and $IL(0)$ are the scaling factors for each of these structures.
+Think carefully about how these map to your particular system!
+
+.. note::
+    The peaked structure at higher $q$ values (Figure 2 from the reference)
+    is not reproduced by the model. Peaks can be introduced into the model
+    by summing this model with the PeakGaussModel function.
+
+For 2D data the scattering intensity is calculated in the same way as 1D,
+where the $q$ vector is defined as
+
+.. math::
+
+    q = \sqrt{q_x^2 + q_y^2}
+
+
+.. figure:: img/gauss_lorentz_gel_1d.jpg
+
+    1D plot using the default values (w/500 data point).
+
+
+References
+----------
+
+G Evmenenko, E Theunissen, K Mortensen, H Reynaers, *Polymer*,
+42 (2001) 2907-2913
+
+"""
+
+from numpy import inf, sqrt, exp
+
+name = "gauss_lorentz_gel"
+title = "Gauss Lorentz Gel model of scattering from a gel structure"
+description = """
+            Class that evaluates a GaussLorentzGel model.
+
+            I(q) = scale_g*exp(- q^2*Z^2 / 2)+scale_l/(1+q^2*z^2)
+                    + background
+            List of default parameters:
+                scale_g = Gauss scale factor
+                Z = Static correlation length
+                scale_l = Lorentzian scale factor
+                z = Dynamic correlation length
+                background = Incoherent background
+            """
+category = "shape-independent"
+# pylint: disable=bad-whitespace, line-too-long
+#            ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [["gauss_scale_factor",   "",    100.0,  [-inf, inf], "", "Gauss scale factor"],
+              ["static_cor_length",    "Ang", 100.0,  [0, inf],    "", "Static correlation length"],
+              ["lorentz_scale_factor", "",     50.0,  [-inf, inf], "", "Lorentzian scale factor"],
+              ["dynamic_cor_length",   "Ang",  20.0,  [0, inf],    "", "Dynamic correlation length"],
+             ]
+# pylint: enable=bad-whitespace, line-too-long
+
+def Iq(q,
+       gauss_scale_factor=100.0,
+       static_cor_length=100.0,
+       lorentz_scale_factor=50.0,
+       dynamic_cor_length=20.0):
+    """
+
+    :param q:                    Input q-value
+    :param gauss_scale_factor:   Gauss scale factor
+    :param static_cor_length:    Static correlation length
+    :param lorentz_scale_factor: Lorentzian scale factor
+    :param dynamic_cor_length:   Dynamic correlation length
+    :return:                     1-D intensity
+    """
+
+    term1 = gauss_scale_factor *\
+            exp(-1.0*q*q*static_cor_length*static_cor_length/2.0)
+    term2 = lorentz_scale_factor /\
+            (1.0+(q*dynamic_cor_length)*(q*dynamic_cor_length))
+
+    return term1 + term2
+
+Iq.vectorized = True  # Iq accepts an array of q values
+
+
+def Iqxy(qx, qy, *args):
+    """
+    :param qx:   Input q_x-value
+    :param qy:   Input q_y-value
+    :param args: Remaining aruments
+    :return:     2-D intensity
+    """
+
+    return Iq(sqrt(qx**2 + qy**2), *args)
+
+Iqxy.vectorized = True  # Iqxy accepts an array of qx, qy values
+
+
+demo = dict(scale=1, background=0.1,
+            gauss_scale_factor=100.0,
+            static_cor_length=100.0,
+            lorentz_scale_factor=50.0,
+            dynamic_cor_length=20.0)
+
+oldname = "GaussLorentzGelModel"
+
+oldpars = dict(background='background',
+               gauss_scale_factor='scale_g',
+               static_cor_length='stat_colength',
+               lorentz_scale_factor='scale_l',
+               dynamic_cor_length='dyn_colength')
+
+tests = [
+
+    # Accuracy tests based on content in test/utest_extra_models.py
+    [{'gauss_scale_factor':  100.0,
+      'static_cor_length':   100.0,
+      'lorentz_scale_factor': 50.0,
+      'dynamic_cor_length':   20.0,
+     }, 0.001, 149.481],
+
+    [{'gauss_scale_factor':  100.0,
+      'static_cor_length':   100.0,
+      'lorentz_scale_factor': 50.0,
+      'dynamic_cor_length':   20.0,
+     }, 0.105363, 9.1903],
+
+    [{'gauss_scale_factor':  100.0,
+      'static_cor_length':   100.0,
+      'lorentz_scale_factor': 50.0,
+      'dynamic_cor_length':   20.0,
+     }, 0.441623, 0.632811],
+
+    # Additional tests with larger range of parameters
+    [{'gauss_scale_factor':  10.0,
+      'static_cor_length':  100.0,
+      'lorentz_scale_factor': 3.0,
+      'dynamic_cor_length':   1.0,
+     }, 0.1, 2.9702970297],
+
+    [{'gauss_scale_factor':  10.0,
+      'static_cor_length':  100.0,
+      'lorentz_scale_factor': 3.0,
+      'dynamic_cor_length':   1.0,
+      'background':         100.0
+     }, 5.0, 100.115384615],
+
+    [{'gauss_scale_factor':  10.0,
+      'static_cor_length':  100.0,
+      'lorentz_scale_factor': 3.0,
+      'dynamic_cor_length':   1.0,
+     }, 200., 7.49981250469e-05],
+    ]
Index: sasmodels/models/gaussian_peak.py
===================================================================
--- sasmodels/models/gaussian_peak.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/gaussian_peak.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -42,4 +42,5 @@
 category = "shape-independent"
 
+single = False
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["q0", "1/Ang", 0.05, [-inf, inf], "", "Peak position"],
Index: sasmodels/models/gel_fit.py
===================================================================
--- sasmodels/models/gel_fit.py	(revision 513efc509d218370e6a8ca9cabf24f087a2de902)
+++ sasmodels/models/gel_fit.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -7,6 +7,6 @@
 in the position of the polymer chains that ensure thermodynamic equilibrium,
 and a longer distance (denoted here as $a2$ ) needed to account for the static
-accumulations of polymer pinned down by junction points or clusters of such points.
-The latter is derived from a simple Guinier function.
+accumulations of polymer pinned down by junction points or clusters of such
+points. The latter is derived from a simple Guinier function.
 
 
@@ -40,9 +40,9 @@
 ---------
 
-Mitsuhiro Shibayama, Toyoichi Tanaka, Charles C Han, J. Chem. Phys. 1992, 97 (9),
-6829-6841
+Mitsuhiro Shibayama, Toyoichi Tanaka, Charles C Han,
+*J. Chem. Phys.* 1992, 97 (9), 6829-6841
 
 Simon Mallam, Ferenc Horkay, Anne-Marie Hecht, Adrian R Rennie, Erik Geissler,
-Macromolecules 1991, 24, 543-548
+*Macromolecules* 1991, 24, 543-548
 
 """
@@ -62,4 +62,5 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["guinier_scale",    "cm^{-1}",   1.7, [-inf, inf], "", "Guinier length scale"],
@@ -68,6 +69,6 @@
               ["fractal_exp",      "",          2.0, [0, inf],    "", "Fractal exponent"],
               ["cor_length",       "Ang",      16.0, [0, inf],    "", "Correlation length"]
-              ]
-
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["gel_fit.c"]
@@ -92,5 +93,5 @@
            'fractal_exp': 10.0,
            'cor_length': 20.0
-           }, 0.1, 0.716532],
+          }, 0.1, 0.716532],
 
          [{'guinier_scale': 4.0,
@@ -100,5 +101,4 @@
            'cor_length': 20.0,
            'background': 20.0,
-           }, 5.0, 20.1224653026],
-
-         ]
+          }, 5.0, 20.1224653026],
+        ]
Index: sasmodels/models/hardsphere.py
===================================================================
--- sasmodels/models/hardsphere.py	(revision 7ed702f2d0d4ecbcb0115970cdabf0d05ac08de0)
+++ sasmodels/models/hardsphere.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -55,4 +55,5 @@
                "volume fraction of hard spheres"],
              ]
+single = False
 
 # No volume normalization despite having a volume parameter
@@ -106,2 +107,7 @@
 oldpars = dict()
 
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'effect_radius' : 50.0, 'volfraction' : 0.2,
+           'effect_radius_pd' : 0}, [0.001], [0.209128]]
+        ]
+
Index: sasmodels/models/lamellarCaille.py
===================================================================
--- sasmodels/models/lamellarCaille.py	(revision d18f8a8e10d65f0ace6fdc9360abd60dfcd6dcce)
+++ sasmodels/models/lamellarCaille.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -87,4 +87,6 @@
 category = "shape:lamellae"
 
+single = False
+
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["thickness", "Ang",  30.0, [0, inf], "volume", "sheet thickness"],
@@ -122,2 +124,8 @@
 oldpars = dict(thickness='delta', Nlayers='N_plates', Caille_parameter='caille',
                sld='sld_bi',solvent_sld='sld_sol')
+#
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'thickness' : 30.,'Nlayers' : 20.0, 'spacing' : 400.,
+            'Caille_parameter' : 0.1, 'sld' : 6.3, 'solvent_sld' : 1.0,
+            'thickness_pd' : 0.0, 'spacing_pd' : 0.0 }, [0.001], [28895.13397]]
+        ]
Index: sasmodels/models/lamellarCailleHG.py
===================================================================
--- sasmodels/models/lamellarCailleHG.py	(revision d18f8a8e10d65f0ace6fdc9360abd60dfcd6dcce)
+++ sasmodels/models/lamellarCailleHG.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -91,26 +91,27 @@
 category = "shape:lamellae"
 
+single = False
 parameters = [
-              #   [ "name", "units", default, [lower, upper], "type",
-              #     "description" ],
-              [ "tail_length", "Ang",  10, [0, inf], "volume",
-                "Tail thickness" ],
-              [ "head_length", "Ang",  2, [0, inf], "volume",
-                "head thickness" ],
-              [ "Nlayers", "",  30, [0, inf], "",
-                "Number of layers" ],
-              [ "spacing", "Ang", 40., [0.0,inf], "volume",
-                "d-spacing of Caille S(Q)" ],
-              [ "Caille_parameter", "", 0.001, [0.0,0.8], "",
-                "Caille parameter" ],
-              [ "sld", "1e-6/Ang^2", 0.4, [-inf,inf], "",
-                "Tail scattering length density" ],
-              [ "head_sld", "1e-6/Ang^2", 2.0, [-inf,inf], "",
-                "Head scattering length density" ],
-              [ "solvent_sld", "1e-6/Ang^2", 6, [-inf,inf], "",
-                "Solvent scattering length density" ],
+    #   [ "name", "units", default, [lower, upper], "type",
+    #     "description" ],
+    ["tail_length", "Ang", 10, [0, inf], "volume",
+     "Tail thickness"],
+    ["head_length", "Ang", 2, [0, inf], "volume",
+     "head thickness"],
+    ["Nlayers", "", 30, [0, inf], "",
+     "Number of layers"],
+    ["spacing", "Ang", 40., [0.0, inf], "volume",
+     "d-spacing of Caille S(Q)"],
+    ["Caille_parameter", "", 0.001, [0.0, 0.8], "",
+     "Caille parameter"],
+    ["sld", "1e-6/Ang^2", 0.4, [-inf, inf], "",
+     "Tail scattering length density"],
+    ["head_sld", "1e-6/Ang^2", 2.0, [-inf, inf], "",
+     "Head scattering length density"],
+    ["solvent_sld", "1e-6/Ang^2", 6, [-inf, inf], "",
+     "Solvent scattering length density"],
     ]
 
-source = [ "lamellarCailleHG_kernel.c"]
+source = ["lamellarCailleHG_kernel.c"]
 
 # No volume normalization despite having a volume parameter
@@ -128,15 +129,24 @@
 
 demo = dict(
-            scale=1, background=0,
-            Nlayers=20,
-            spacing=200., Caille_parameter=0.05,
-            tail_length=15,head_length=10,
-            #sld=-1, head_sld=4.0, solvent_sld=6.0,
-            sld=-1, head_sld=4.1, solvent_sld=6.0,
-            tail_length_pd= 0.1, tail_length_pd_n=20,
-            head_length_pd= 0.05, head_length_pd_n=30,
-            spacing_pd= 0.2, spacing_pd_n=40
-           )
+    scale=1, background=0,
+    Nlayers=20, spacing=200., Caille_parameter=0.05,
+    tail_length=15, head_length=10,
+    #sld=-1, head_sld=4.0, solvent_sld=6.0,
+    sld=-1, head_sld=4.1, solvent_sld=6.0,
+    tail_length_pd=0.1, tail_length_pd_n=20,
+    head_length_pd=0.05, head_length_pd_n=30,
+    spacing_pd=0.2, spacing_pd_n=40,
+    )
 
 oldname = 'LamellarPSHGModel'
-oldpars = dict(tail_length='deltaT',head_length='deltaH',Nlayers='n_plates',Caille_parameter='caille', sld='sld_tail', head_sld='sld_head',solvent_sld='sld_solvent')
+
+oldpars = dict(
+    tail_length='deltaT', head_length='deltaH', Nlayers='n_plates',
+    Caille_parameter='caille', sld='sld_tail', head_sld='sld_head',
+    solvent_sld='sld_solvent')
+#
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'tail_length' : 10.0, 'head_length' : 2.0,'Nlayers' : 30.0, 'spacing' : 40.,
+            'Caille_parameter' : 0.001, 'sld' : 0.4, 'head_sld' : 2.0, 'solvent_sld' : 6.0,
+            'tail_length_pd' : 0.0, 'head_length_pd' : 0.0, 'spacing_pd' : 0.0 }, [0.001], [6838238.571488]]
+        ]
Index: sasmodels/models/lamellarFFHG.py
===================================================================
--- sasmodels/models/lamellarFFHG.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/lamellarFFHG.py	(revision 7f47777b1828197366e17d650a92f794a3433fcc)
@@ -121,3 +121,9 @@
 oldpars = dict(head_length='h_thickness', tail_length='t_length',
                sld='sld_tail', head_sld='sld_head', solvent_sld='sld_solvent')
+#
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'tail_length' : 15.0, 'head_length' : 10.0,'sld' : 0.4,
+         'head_sld' : 3.0, 'solvent_sld' : 6.0, }, [0.001], [653143.9209]]
+        ]
 
+
Index: sasmodels/models/lamellarPC.py
===================================================================
--- sasmodels/models/lamellarPC.py	(revision d18f8a8e10d65f0ace6fdc9360abd60dfcd6dcce)
+++ sasmodels/models/lamellarPC.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -111,4 +111,6 @@
 category = "shape:lamellae"
 
+single = False
+
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["thickness", "Ang", 33.0, [0, inf], "volume",
@@ -148,2 +150,8 @@
 oldpars = dict(spacing_polydisp='pd_spacing', sld='sld_layer',
                solvent_sld='sld_solvent')
+#
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'thickness' : 33.,'Nlayers' : 20.0, 'spacing' : 250., 'spacing_polydisp' : 0.2,
+            'sld' : 1.0, 'solvent_sld' : 6.34,
+            'thickness_pd' : 0.0, 'thickness_pd_n' : 40 }, [0.001, 0.215268], [21829.3, 0.00487686]]
+        ]
Index: sasmodels/models/lib/Si.c
===================================================================
--- sasmodels/models/lib/Si.c	(revision 7d256c8e17a297791fce97bf345d7fc77d503e10)
+++ sasmodels/models/lib/Si.c	(revision f12357fd54eaa1b861fbe7a32191f62f1f1d5afd)
@@ -13,6 +13,6 @@
 
 		// Explicitly writing factorial values triples the speed of the calculation
-		out_cos = 1/x - 2/pow(x,3) + 24/pow(x,5) - 720/pow(x,7) + 40320/pow(x,9);
-		out_sin = 1/x - 6/pow(x,4) + 120/pow(x,6) - 5040/pow(x,8) + 362880/pow(x,10);
+		out_cos = 1./x - 2./pow(x,3) + 24./pow(x,5) - 720./pow(x,7);
+		out_sin = 1./pow(x,2) - 6./pow(x,4) + 120./pow(x,6) - 5040./pow(x,8);
 
 		out -= cos(x) * out_cos;
@@ -22,7 +22,6 @@
 
 	// Explicitly writing factorial values triples the speed of the calculation
-	out = x - pow(x, 3)/18 + pow(x,5)/600 - pow(x,7)/35280 + pow(x,9)/3265920;
+	out = x - pow(x, 3)/18. + pow(x,5)/600. - pow(x,7)/35280. + pow(x,9)/3265920. - pow(x,11)/439084800.;
 
-	//printf ("Si=%g %g\n", x, out);
 	return out;
 }
Index: sasmodels/models/lib/gfn.c
===================================================================
--- sasmodels/models/lib/gfn.c	(revision 177c1a138481a9833b4929af687e86583b5c16d5)
+++ sasmodels/models/lib/gfn.c	(revision 177c1a138481a9833b4929af687e86583b5c16d5)
@@ -0,0 +1,36 @@
+//
+//     FUNCTION gfn4:    CONTAINS F(Q,A,B,MU)**2  AS GIVEN
+//                       BY (53) & (58-59) IN CHEN AND
+//                       KOTLARCHYK REFERENCE
+//
+//       <OBLATE ELLIPSOID>
+// function gfn4 for oblate ellipsoids
+double
+gfn4(double xx, double crmaj, double crmin, double trmaj, double trmin, double delpc, double delps, double qq);
+double
+gfn4(double xx, double crmaj, double crmin, double trmaj, double trmin, double delpc, double delps, double qq)
+{
+    // local variables
+    const double pi43=4.0/3.0*M_PI;
+    const double aa = crmaj;
+    const double bb = crmin;
+    const double u2 = (bb*bb*xx*xx + aa*aa*(1.0-xx*xx));
+    const double uq = sqrt(u2)*qq;
+    // changing to more accurate sph_j1c since the following inexplicably fails on Radeon Nano.
+    //const double siq = (uq == 0.0 ? 1.0 : 3.0*(sin(uq)/uq/uq - cos(uq)/uq)/uq);
+    const double siq = sph_j1c(uq);
+    const double vc = pi43*aa*aa*bb;
+    const double gfnc = siq*vc*delpc;
+
+    const double ut2 = (trmin*trmin*xx*xx + trmaj*trmaj*(1.0-xx*xx));
+    const double ut= sqrt(ut2)*qq;
+    const double vt = pi43*trmaj*trmaj*trmin;
+    //const double sit = (ut == 0.0 ? 1.0 : 3.0*(sin(ut)/ut/ut - cos(ut)/ut)/ut);
+    const double sit = sph_j1c(ut);
+    const double gfnt = sit*vt*delps;
+
+    const double tgfn = gfnc + gfnt;
+    const double result = tgfn*tgfn;
+
+    return (result);
+}
Index: sasmodels/models/lib/sph_j1c.c
===================================================================
--- sasmodels/models/lib/sph_j1c.c	(revision 9c461c713e9961727442c844be3e6354d6548c2f)
+++ sasmodels/models/lib/sph_j1c.c	(revision a3e78c304bba2ffdbd8ad1b8da217f22e084346c)
@@ -1,4 +1,4 @@
 /**
-* Spherical Bessel function j1(x)/x
+* Spherical Bessel function 3*j1(x)/x
 *
 * Used for low q to avoid cancellation error.
@@ -8,4 +8,9 @@
 * requires the first 3 terms.  Double precision requires the 4th term.
 * The fifth term is not needed, and is commented out.
+* Taylor expansion:
+*      1.0 + q2*(-3./30. + q2*(3./840.))+ q2*(-3./45360. + q2*(3./3991680.))))
+* Expression returned from Herbie (herbie.uwpise.org/demo):
+*      const double t = ((1. + 3.*q2*q2/5600.) - q2/20.);
+*      return t*t;
 */
 
@@ -18,8 +23,23 @@
     SINCOS(q, sin_q, cos_q);
 
-    const double bessel = (q < 1.e-1) ?
-        1.0 + q2*(-3./30. + q2*(3./840. + q2*(-3./45360.))) // + q2*(3./3991680.)))
+    const double bessel = (q < 0.384038453352533)
+        ? (1.0 + q2*(-3./30. + q2*(3./840.)))
         : 3.0*(sin_q/q - cos_q)/q2;
 
     return bessel;
+
+ /*
+    // Code to test various expressions
+    if (sizeof(q2) > 4) {
+        return 3.0*(sin_q/q - cos_q)/q2;
+    } else if (q < 0.384038453352533) {
+        //const double t = ((1. + 3.*q2*q2/5600.) - q2/20.); return t*t;
+        return 1.0 + q2*q2*(3./840.) - q2*(3./30.);
+        //return 1.0 + q2*(-3./30. + q2*(3./840.));
+        //return 1.0 + q2*(-3./30. + q2*(3./840. + q2*(-3./45360.)));
+        //return 1.0 + q2*(-3./30. + q2*(3./840. + q2*(-3./45360. + q2*(3./3991680.))));
+    } else {
+        return 3.0*(sin_q/q - cos_q)/q2;
+    }
+*/
 }
Index: sasmodels/models/lib/wrc_cyl.c
===================================================================
--- sasmodels/models/lib/wrc_cyl.c	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
+++ sasmodels/models/lib/wrc_cyl.c	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -0,0 +1,434 @@
+/*
+    Functions for WRC implementation of flexible cylinders
+*/
+static double
+AlphaSquare(double x)
+{
+    return pow( (1.0 + (x/3.12)*(x/3.12) +
+         (x/8.67)*(x/8.67)*(x/8.67)),(0.176/3.0) );
+}
+
+//
+static double
+Rgsquarezero(double q, double L, double b)
+{
+    return (L*b/6.0) * (1.0 - 1.5*(b/L) + 1.5*pow((b/L),2) -
+         0.75*pow((b/L),3)*(1.0 - exp(-2.0*(L/b))));
+}
+
+//
+static double
+Rgsquareshort(double q, double L, double b)
+{
+    return AlphaSquare(L/b) * Rgsquarezero(q,L,b);
+}
+
+//
+static double
+Rgsquare(double q, double L, double b)
+{
+    return AlphaSquare(L/b)*L*b/6.0;
+}
+
+static double
+sech_WR(double x)
+{
+    return(1/cosh(x));
+}
+
+static double
+a1long(double q, double L, double b, double p1, double p2, double q0)
+{
+    double yy,C,C1,C2,C3,C4,C5,miu,Rg2;
+    double t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11,t12,t13,t14,t15;
+    double E,pi;
+    double b2,b3,b4,q02,q03,q04,q05,Rg22;
+
+    pi = 4.0*atan(1.0);
+    E = 2.718281828459045091;
+
+    if( L/b > 10.0) {
+        C = 3.06/pow((L/b),0.44);
+    } else {
+        C = 1.0;
+    }
+
+    C1 = 1.22;
+    C2 = 0.4288;
+    C3 = -1.651;
+    C4 = 1.523;
+    C5 = 0.1477;
+    miu = 0.585;
+
+    Rg2 = Rgsquare(q,L,b);
+    Rg22 = Rg2*Rg2;
+    b2 = b*b;
+    b3 = b*b*b;
+    b4 = b3*b;
+    q02 = q0*q0;
+    q03 = q0*q0*q0;
+    q04 = q03*q0;
+    q05 = q04*q0;
+
+    t1 = (b*C*((4.0/15.0 - pow(E,(-((q02*Rg2)/b2)))*((11.0/15.0 +
+        (7.0*b2)/(15.0*q02*Rg2))) + (7.0*b2)/(15.0*q02*Rg2))));
+
+    t2 = (2.0*b4*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*((1.0 + 1.0/2.0*(((-1.0) -
+        tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5))))));
+
+    t3 = ((C3*pow((((sqrt(Rg2)*q0)/b)),((-3.0)/miu)) +
+        C2*pow((((sqrt(Rg2)*q0)/b)),((-2.0)/miu)) +
+        C1*pow((((sqrt(Rg2)*q0)/b)),((-1.0)/miu))));
+
+    t4 = ((1.0 + tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5)));
+
+    t5 = (1.0/(b*p1*pow(q0,((-1.0) - p1 - p2)) -
+        b*p2*pow(q0,((-1.0) - p1 - p2))));
+
+    t6 = (b*C*(((-((14.0*b3)/(15.0*q03*Rg2))) +
+        (14.0*b3*pow(E,(-((q02*Rg2)/b2))))/(15.0*q03*Rg2) +
+        (2.0*pow(E,(-((q02*Rg2)/b2)))*q0*((11.0/15.0 +
+        (7.0*b2)/(15.0*q02*Rg2)))*Rg2)/b)));
+
+    t7 = (sqrt(Rg2)*((C3*pow((((sqrt(Rg2)*q0)/b)),((-3.0)/miu)) +
+        C2*pow((((sqrt(Rg2)*q0)/b)),((-2.0)/miu)) +
+        C1*pow((((sqrt(Rg2)*q0)/b)),((-1.0)/miu))))*pow(sech_WR(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5),2));
+
+    t8 = (b4*sqrt(Rg2)*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*pow(sech_WR(((-C4) + (sqrt(Rg2)*q0)/b)/C5),2));
+
+    t9 = (2.0*b4*(((2.0*q0*Rg2)/b -
+        (2.0*pow(E,(-((q02*Rg2)/b2)))*q0*Rg2)/b))*((1.0 + 1.0/2.0*(((-1.0) -
+        tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5))))));
+
+    t10 = (8.0*b4*b*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*((1.0 + 1.0/2.0*(((-1.0) - tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5))))));
+
+    t11 = (((-((3.0*C3*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),((-1.0) -
+        3.0/miu)))/miu)) - (2.0*C2*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),((-1.0) -
+        2.0/miu)))/miu - (C1*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),((-1.0) -
+        1.0/miu)))/miu));
+
+    t12 = ((1.0 + tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5)));
+
+    t13 = (b*C*((4.0/15.0 - pow(E,(-((q02*Rg2)/b2)))*((11.0/15.0 +
+        (7.0*b2)/(15.0*q02* Rg2))) +
+        (7.0*b2)/(15.0*q02*Rg2))));
+
+    t14 = (2.0*b4*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*((1.0 + 1.0/2.0*(((-1.0) - tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5))))));
+
+    t15 = ((C3*pow((((sqrt(Rg2)*q0)/b)),((-3.0)/miu)) +
+        C2*pow((((sqrt(Rg2)*q0)/b)),((-2.0)/miu)) +
+        C1*pow((((sqrt(Rg2)*q0)/b)),((-1.0)/miu))));
+
+
+    yy = (pow(q0,p1)*(((-((b*pi)/(L*q0))) +t1/L +t2/(q04*Rg22) +
+        1.0/2.0*t3*t4)) + (t5*((pow(q0,(p1 - p2))*
+        (((-pow(q0,(-p1)))*(((b2*pi)/(L*q02) +t6/L +t7/(2.0*C5) -
+        t8/(C5*q04*Rg22) + t9/(q04*Rg22) -t10/(q05*Rg22) + 1.0/2.0*t11*t12)) -
+        b*p1*pow(q0,((-1.0) - p1))*(((-((b*pi)/(L*q0))) + t13/L +
+        t14/(q04*Rg22) + 1.0/2.0*t15*((1.0 + tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5)))))))))));
+
+    return (yy);
+}
+
+static double
+a2long(double q, double L, double b, double p1, double p2, double q0)
+{
+    double yy,C1,C2,C3,C4,C5,miu,C,Rg2;
+    double t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,pi;
+    double E,b2,b3,b4,q02,q03,q04,q05,Rg22;
+
+    pi = 4.0*atan(1.0);
+    E = 2.718281828459045091;
+    if( L/b > 10.0) {
+        C = 3.06/pow((L/b),0.44);
+    } else {
+        C = 1.0;
+    }
+
+    C1 = 1.22;
+    C2 = 0.4288;
+    C3 = -1.651;
+    C4 = 1.523;
+    C5 = 0.1477;
+    miu = 0.585;
+
+    Rg2 = Rgsquare(q,L,b);
+    Rg22 = Rg2*Rg2;
+    b2 = b*b;
+    b3 = b*b*b;
+    b4 = b3*b;
+    q02 = q0*q0;
+    q03 = q0*q0*q0;
+    q04 = q03*q0;
+    q05 = q04*q0;
+
+    t1 = (1.0/(b* p1*pow(q0,((-1.0) - p1 - p2)) -
+        b*p2*pow(q0,((-1.0) - p1 - p2)) ));
+
+    t2 = (b*C*(((-1.0*((14.0*b3)/(15.0*q03*Rg2))) +
+        (14.0*b3*pow(E,(-((q02*Rg2)/b2))))/(15.0*q03*Rg2) +
+        (2.0*pow(E,(-((q02*Rg2)/b2)))*q0*((11.0/15.0 +
+        (7*b2)/(15.0*q02*Rg2)))*Rg2)/b)))/L;
+
+    t3 = (sqrt(Rg2)*((C3*pow((((sqrt(Rg2)*q0)/b)),((-3.0)/miu)) +
+        C2*pow((((sqrt(Rg2)*q0)/b)),((-2.0)/miu)) +
+        C1*pow((((sqrt(Rg2)*q0)/b)),((-1.0)/miu))))*
+        pow(sech_WR(((-C4) +(sqrt(Rg2)*q0)/b)/C5),2.0))/(2.0*C5);
+
+    t4 = (b4*sqrt(Rg2)*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*pow(sech_WR(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5),2))/(C5*q04*Rg22);
+
+    t5 = (2.0*b4*(((2.0*q0*Rg2)/b -
+        (2.0*pow(E,(-((q02*Rg2)/b2)))*q0*Rg2)/b))*
+        ((1.0 + 1.0/2.0*(((-1.0) - tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5))))))/(q04*Rg22);
+
+    t6 = (8.0*b4*b*(((-1.0) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*((1.0 + 1.0/2.0*(((-1) - tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5))))))/(q05*Rg22);
+
+    t7 = (((-((3.0*C3*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),((-1.0) -
+        3.0/miu)))/miu)) - (2.0*C2*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),
+        ((-1.0) - 2.0/miu)))/miu - (C1*sqrt(Rg2)*pow((((sqrt(Rg2)*q0)/b)),
+        ((-1.0) - 1.0/miu)))/miu));
+
+    t8 = ((1.0 + tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5)));
+
+    t9 = (b*C*((4.0/15.0 - pow(E,(-((q02*Rg2)/b2)))*((11.0/15.0 +
+        (7.0    *b2)/(15*q02*Rg2))) + (7.0*b2)/(15.0*q02*Rg2))))/L;
+
+    t10 = (2.0*b4*(((-1) + pow(E,(-((q02*Rg2)/b2))) +
+        (q02*Rg2)/b2))*((1.0 + 1.0/2.0*(((-1) - tanh(((-C4) +
+        (sqrt(Rg2)*q0)/b)/C5))))))/(q04*Rg22);
+
+    yy = ((-1.0*(t1* ((-pow(q0,-p1)*(((b2*pi)/(L*q02) +
+        t2 + t3 - t4 + t5 - t6 + 1.0/2.0*t7*t8)) - b*p1*pow(q0,((-1.0) - p1))*
+        (((-((b*pi)/(L*q0))) + t9 + t10 +
+        1.0/2.0*((C3*pow((((sqrt(Rg2)*q0)/b)),((-3.0)/miu)) +
+        C2*pow((((sqrt(Rg2)*q0)/b)),((-2.0)/miu)) +
+        C1*pow((((sqrt(Rg2)*q0)/b)),((-1.0)/miu))))*
+        ((1.0 + tanh(((-C4) + (sqrt(Rg2)*q0)/b)/C5))))))))));
+
+    return (yy);
+}
+
+//
+static double
+a1short(double q, double L, double b, double p1short, double p2short, double q0)
+{
+    double yy,Rg2_sh;
+    double t1,E,Rg2_sh2,Et1,Emt1,q02,q0p,b3;
+    double pi;
+
+    E = 2.718281828459045091;
+    pi = 4.0*atan(1.0);
+    Rg2_sh = Rgsquareshort(q,L,b);
+    Rg2_sh2 = Rg2_sh*Rg2_sh;
+    b3 = b*b*b;
+    t1 = ((q0*q0*Rg2_sh)/(b*b));
+    Et1 = pow(E,t1);
+    Emt1 =pow(E,-t1);
+    q02 = q0*q0;
+    q0p = pow(q0,(-4.0 + p1short) );
+
+    yy = ((1.0/(L*((p1short - p2short))*Rg2_sh2)*
+        ((b*Emt1*q0p*((8.0*b3*L - 8.0*b3*Et1*L - 2.0*b3*L*p2short +
+        2.0*b3*Et1*L*p2short + 4.0*b*L*q02*Rg2_sh + 4.0*b*Et1*L*q02*Rg2_sh -
+        2.0*b*Et1*L*p2short*q02*Rg2_sh - Et1*pi*q02*q0*Rg2_sh2 +
+        Et1*p2short*pi*q02*q0*Rg2_sh2))))));
+
+    return(yy);
+}
+
+static double
+a2short(double q, double L, double b, double p1short, double p2short, double q0)
+{
+    double yy,Rg2_sh;
+    double t1,E,Rg2_sh2,Et1,Emt1,q02,q0p;
+    double pi;
+
+    E = 2.718281828459045091;
+    pi = 4.0*atan(1.0);
+    Rg2_sh = Rgsquareshort(q,L,b);
+    Rg2_sh2 = Rg2_sh*Rg2_sh;
+    t1 = ((q0*q0*Rg2_sh)/(b*b));
+    Et1 = pow(E,t1);
+    Emt1 =pow(E,-t1);
+    q02 = q0*q0;
+    q0p = pow(q0,(-4.0 + p2short) );
+
+    //E is the number e
+    yy = ((-(1.0/(L*((p1short - p2short))*Rg2_sh2)*
+        ((b*Emt1*q0p*((8.0*b*b*b*L - 8.0*b*b*b*Et1*L - 2.0*b*b*b*L*p1short +
+        2.0*b*b*b*Et1*L*p1short + 4.0*b*L*q02*Rg2_sh + 4.0*b*Et1*L*q02*Rg2_sh -
+        2.0*b*Et1*L*p1short*q02*Rg2_sh - Et1*pi*q02*q0*Rg2_sh2 +
+        Et1*p1short*pi*q02*q0*Rg2_sh2)))))));
+
+    return (yy);
+}
+
+//WR named this w (too generic)
+static double
+w_WR(double x)
+{
+    return 0.5*(1 + tanh((x - 1.523)/0.1477));
+}
+
+//
+static double
+u1(double q, double L, double b)
+{
+    return Rgsquareshort(q,L,b)*q*q;
+}
+
+static double
+u_WR(double q, double L, double b)
+{
+    return Rgsquare(q,L,b)*q*q;
+}
+
+static double
+Sdebye_kernel(double arg)
+{
+    // ORIGINAL
+    double result = 2.0*(exp(-arg) + arg -1.0)/(pow((arg),2));
+
+    // CONVERSION 1 from http://herbie.uwplse.org/
+    //
+    // exhibits discontinuity - needs more investigation
+    //double a1 = 1.0/6.0;
+    //double a2 = 1.0/72.0;
+    //double a3 = 1.0/24.0;
+    //double result = pow((1.0 - a1*arg - (a2+a3)*arg*arg), 2);
+
+    return result;
+}
+static double
+Sdebye(double q, double L, double b)
+{
+    double arg = u_WR(q,L,b);
+    return Sdebye_kernel(arg);
+}
+
+//
+static double
+Sdebye1(double q, double L, double b)
+{
+    double arg = u1(q,L,b);
+    return Sdebye_kernel(arg);
+
+}
+
+//
+static double
+Sexv(double q, double L, double b)
+{
+    double yy,C1,C2,C3,miu,Rg2;
+    C1=1.22;
+    C2=0.4288;
+    C3=-1.651;
+    miu = 0.585;
+
+    Rg2 = Rgsquare(q,L,b);
+
+    yy = (1.0 - w_WR(q*sqrt(Rg2)))*Sdebye(q,L,b) +
+        w_WR(q*sqrt(Rg2))*(C1*pow((q*sqrt(Rg2)),(-1.0/miu)) +
+        C2*pow((q*sqrt(Rg2)),(-2.0/miu)) +
+        C3*pow((q*sqrt(Rg2)),(-3.0/miu)));
+
+    return (yy);
+}
+
+
+static double
+Sexvnew(double q, double L, double b)
+{
+    double yy,C1,C2,C3,miu;
+    double del=1.05,C_star2,Rg2;
+
+    C1=1.22;
+    C2=0.4288;
+    C3=-1.651;
+    miu = 0.585;
+
+    //calculating the derivative to decide on the corection (cutoff) term?
+    // I have modified this from WRs original code
+
+    if( (Sexv(q*del,L,b)-Sexv(q,L,b))/(q*del - q) >= 0.0 ) {
+        C_star2 = 0.0;
+    } else {
+        C_star2 = 1.0;
+    }
+
+    Rg2 = Rgsquare(q,L,b);
+
+    yy = (1.0 - w_WR(q*sqrt(Rg2)))*Sdebye(q,L,b) +
+        C_star2*w_WR(q*sqrt(Rg2))*(C1*pow((q*sqrt(Rg2)),(-1.0/miu)) +
+        C2*pow((q*sqrt(Rg2)),(-2.0/miu)) + C3*pow((q*sqrt(Rg2)),(-3.0/miu)));
+
+    return (yy);
+}
+
+double Sk_WR(double q, double L, double b);
+double Sk_WR(double q, double L, double b)
+{
+  //
+  double p1,p2,p1short,p2short,q0;
+  double C,ans,q0short,Sexvmodify,pi;
+
+  pi = 4.0*atan(1.0);
+
+  p1 = 4.12;
+  p2 = 4.42;
+  p1short = 5.36;
+  p2short = 5.62;
+  q0 = 3.1;
+
+  q0short = fmax(1.9/sqrt(Rgsquareshort(q,L,b)),3.0);
+
+
+  if(L/b > 10.0) {
+    C = 3.06/pow((L/b),0.44);
+  } else {
+    C = 1.0;
+  }
+  //
+
+  //
+  if( L > 4*b ) { // Longer Chains
+    if (q*b <= 3.1) {   //Modified by Yun on Oct. 15,
+
+      Sexvmodify = Sexvnew(q, L, b);
+
+      ans = Sexvmodify + C * (4.0/15.0 + 7.0/(15.0*u_WR(q,L,b)) -
+            (11.0/15.0 + 7.0/(15.0*u_WR(q,L,b)))*exp(-u_WR(q,L,b)))*(b/L);
+
+    } else { //q(i)*b > 3.1
+      ans = a1long(q, L, b, p1, p2, q0)/(pow((q*b),p1)) +
+            a2long(q, L, b, p1, p2, q0)/(pow((q*b),p2)) + pi/(q*L);
+    }
+  } else { //L <= 4*b Shorter Chains
+    if (q*b <= fmax(1.9/sqrt(Rgsquareshort(q,L,b)),3.0) ) {
+      if (q*b<=0.01) {
+        ans = 1.0 - Rgsquareshort(q,L,b)*(q*q)/3.0;
+      } else {
+        ans = Sdebye1(q,L,b);
+      }
+    } else {  //q*b > max(1.9/sqrt(Rgsquareshort(q(i),L,b)),3)
+      ans = a1short(q,L,b,p1short,p2short,q0short)/(pow((q*b),p1short)) +
+            a2short(q,L,b,p1short,p2short,q0short)/(pow((q*b),p2short)) +
+            pi/(q*L);
+    }
+  }
+
+  return(ans);
+}
Index: sasmodels/models/linear_pearls.c
===================================================================
--- sasmodels/models/linear_pearls.c	(revision 577912b439fe119a0663629c075b6b67323269ca)
+++ sasmodels/models/linear_pearls.c	(revision 577912b439fe119a0663629c075b6b67323269ca)
@@ -0,0 +1,123 @@
+double form_volume(double radius, double num_pearls);
+
+double Iq(double q,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld);
+
+double Iqxy(double qx, double qy,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld);
+
+double linear_pearls_kernel(double q,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld);
+
+
+double form_volume(double radius, double num_pearls)
+{
+    // Pearl volume
+    double pearl_vol = 4.0 /3.0 * M_PI * pow(radius, 3.0);
+    // Return total volume
+    return num_pearls * pearl_vol;;
+}
+
+// If used elsewhere - factor out to lib/
+static
+double sinc(double x)
+{
+  if (x==0.0){
+    return 1.0;
+  }
+  return sin(x)/x;
+}
+
+double linear_pearls_kernel(double q,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld)
+{
+    double n_contrib;
+    //relative sld
+    double contrast_pearl = pearl_sld - solvent_sld;
+    //each volume
+    double pearl_vol = 4.0 /3.0 * M_PI * pow(radius, 3.0);
+    //total volume
+    double tot_vol = num_pearls * pearl_vol;
+    //mass
+    double m_s = contrast_pearl * pearl_vol;
+    //center to center distance between the neighboring pearls
+    double separation = edge_sep + 2.0 * radius;
+
+    double x=q*radius;
+
+    // Try Taylor on x*xos(x)
+	// double out_cos = x - pow(x,3)/2 + pow(x,5)/24 - pow(x,7)/720 + pow(x,9)/40320;
+    // psi -= x*out_cos;
+
+    //sine functions of a pearl
+    double psi = sin(q * radius);
+    psi -= x * cos(x);
+    psi /= pow((q * radius), 3.0);
+
+    // N pearls contribution
+    int n_max = num_pearls - 1;
+    n_contrib = num_pearls;
+    for(int num=1; num<=n_max; num++) {
+        n_contrib += (2.0*(num_pearls-num)*sinc(q*separation*num));
+    }
+    // form factor for num_pearls
+    double form_factor = n_contrib;
+    form_factor *= pow((m_s*psi*3.0), 2.0);
+    form_factor /= (tot_vol * 1.0e4);
+
+    return form_factor;
+}
+
+double Iq(double q,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld)
+{
+
+	double result = linear_pearls_kernel(q,
+                    radius,
+                    edge_sep,
+                    num_pearls,
+                    pearl_sld,
+                    solvent_sld);
+
+	return result;
+}
+
+double Iqxy(double qx, double qy,
+            double radius,
+            double edge_sep,
+            double num_pearls,
+            double pearl_sld,
+            double solvent_sld)
+{
+	double q;
+	q = sqrt(qx*qx+qy*qy);
+
+	double result = linear_pearls_kernel(q,
+                    radius,
+                    edge_sep,
+                    num_pearls,
+                    pearl_sld,
+                    solvent_sld);
+
+	return result;
+}
Index: sasmodels/models/linear_pearls.py
===================================================================
--- sasmodels/models/linear_pearls.py	(revision 577912b439fe119a0663629c075b6b67323269ca)
+++ sasmodels/models/linear_pearls.py	(revision 577912b439fe119a0663629c075b6b67323269ca)
@@ -0,0 +1,110 @@
+r"""
+This model provides the form factor for $N$ spherical pearls of radius $R$
+linearly joined by short strings (or segment length or edge separation)
+$l$ $(= A - 2R)$. $A$ is the center-to-center pearl separation distance.
+The thickness of each string is assumed to be negligible.
+
+.. figure:: img/linear_pearls_fig1.jpg
+
+
+Definition
+----------
+
+The output of the scattering intensity function for the linear_pearls model
+is given by (Dobrynin, 1996)
+
+.. math::
+
+    P(Q) = \frac{scale}{V}\left[ m_{p}^2
+    \left(N+2\sum_{n-1}^{N-1}(N-n)\frac{sin(qnl)}{qnl}\right)
+    \left( 3\frac{sin(qR)-qRcos(qR)}{(qr)^3}\right)^2\right]
+
+where the mass $m_p$ is $(SLD_{pearl}-SLD_{solvent})*(volume\ of\ N\ pearls)$.
+V is the total volume.
+
+The 2D scattering intensity is the same as P(q) above,
+regardless of the orientation of the q vector.
+
+.. figure:: img/linear_pearls_1d.jpg
+
+    1D plot using the default values (w/500 data point).
+
+References
+----------
+
+A V Dobrynin, M Rubinstein and S P Obukhov, *Macromol.*,
+29 (1996) 2974-2979
+
+"""
+
+from numpy import inf
+
+name = "linear_pearls"
+title = "Linear pearls model of scattering from spherical pearls."
+description = """
+    Calculate form factor for Pearl Necklace Model
+    [Macromol. 1996, 29, 2974-2979]
+    Parameters:
+
+    sld_pearl: the SLD of the pearl spheres
+    sld_solv: the SLD of the solvent
+    num_pearls: number of the pearls
+    radius: the radius of a pearl
+    edge_separation: the length of string segment; surface to surface
+    """
+category = "shape:sphere"
+
+# pylint: disable=bad-whitespace, line-too-long
+#            ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["radius",      "Ang",       80.0, [0, inf],     "", "Radius of the pearls"],
+    ["edge_sep",    "Ang",      350.0, [0, inf],     "", "Length of the string segment - surface to surface"],
+    ["num_pearls",  "",           3.0, [0, inf],     "", "Number of the pearls"],
+    ["pearl_sld",   "1e-6/Ang^2", 1.0, [-inf, inf],  "", "SLD of the pearl spheres"],
+    ["solvent_sld", "1e-6/Ang^2", 6.3, [-inf, inf],  "", "SLD of the solvent"],
+    ]
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["linear_pearls.c"]
+
+demo = dict(scale=1.0, background=0.0,
+            radius=80.0,
+            edge_sep=350.0,
+            num_pearls=3,
+            pearl_sld=1.0,
+            solvent_sld=6.3)
+
+oldname = "LinearPearlsModel"
+
+oldpars = dict(edge_sep='edge_separation',
+               pearl_sld='sld_pearl',
+               solvent_sld='sld_solv')
+
+"""
+Tests temporarily disabled, until single-double precision accuracy issue solved.
+
+tests = [
+    # Accuracy tests based on content in test/utest_model_pearlnecklace.py
+    [{'radius':      20.0,
+      'num_pearls':   2.0,
+      'pearl_sld':    1.0,
+      'solvent_sld':  6.3,
+      'edge_sep':   400.0,
+     }, 0.001, 185.135],
+
+    # Additional tests with larger range of parameters
+    [{'radius':     120.0,
+      'num_pearls':   5.0,
+      'pearl_sld':    2.0,
+      'solvent_sld':  2.3,
+      'edge_sep':   100.0,
+     }, 0.01, 45.4984],
+
+    [{'radius':       7.0,
+      'num_pearls':   2.0,
+      'pearl_sld':   10.0,
+      'solvent_sld': 99.3,
+      'edge_sep':    20.0,
+     }, 1.0, 0.632811],
+    ]
+"""
Index: sasmodels/models/mass_fractal.py
===================================================================
--- sasmodels/models/mass_fractal.py	(revision 9c461c713e9961727442c844be3e6354d6548c2f)
+++ sasmodels/models/mass_fractal.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -52,5 +52,6 @@
 ---------
 
-D Mildner and P Hall, *J. Phys. D: Appl. Phys.*,  19 (1986) 1535-1545 Equation(9)
+D Mildner and P Hall, *J. Phys. D: Appl. Phys.*,
+19 (1986) 1535-1545 Equation(9)
 
 
@@ -79,10 +80,11 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["radius",        "Ang",  10.0, [0.0, inf], "", "Particle radius"],
               ["mass_dim",      "",      1.9, [1.0, 6.0], "", "Mass fractal dimension"],
               ["cutoff_length", "Ang", 100.0, [0.0, inf], "", "Cut-off length"],
-              ]
-
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["lib/sph_j1c.c", "lib/lanczos_gamma.c", "mass_fractal.c"]
@@ -91,5 +93,5 @@
             radius=10.0,
             mass_dim=1.9,
-            cutoff_length=2.3)
+            cutoff_length=100.0)
 
 oldname = 'MassFractalModel'
@@ -98,19 +100,28 @@
                cutoff_length='co_length')
 
-tests = [[{'radius':        2.0,
-           'mass_dim':      3.3,
-           'cutoff_length': 1.0,
-           }, 0.5, 1.29016774904],
+tests = [
 
-         [{'radius':        1.0,
-           'mass_dim':      1.3,
-           'cutoff_length': 1.0,
-           'background':    0.8,
-           }, 0.001, 1.69747015932],
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'radius':         10.0,
+      'mass_dim':        1.9,
+      'cutoff_length': 100.0,
+     }, 0.05, 279.59322],
 
-         [{'radius':        1.0,
-           'mass_dim':      2.3,
-           'cutoff_length': 1.0,
-           'scale':        10.0,
-           }, 0.051, 11.6227966145],
-         ]
+    # Additional tests with larger range of parameters
+    [{'radius':        2.0,
+      'mass_dim':      3.3,
+      'cutoff_length': 1.0,
+     }, 0.5, 1.29016774904],
+
+    [{'radius':        1.0,
+      'mass_dim':      1.3,
+      'cutoff_length': 1.0,
+      'background':    0.8,
+     }, 0.001, 1.69747015932],
+
+    [{'radius':        1.0,
+      'mass_dim':      2.3,
+      'cutoff_length': 1.0,
+      'scale':        10.0,
+     }, 0.051, 11.6227966145],
+    ]
Index: sasmodels/models/mass_surface_fractal.py
===================================================================
--- sasmodels/models/mass_surface_fractal.py	(revision 9c461c713e9961727442c844be3e6354d6548c2f)
+++ sasmodels/models/mass_surface_fractal.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -54,5 +54,6 @@
 P Schmidt, *J Appl. Cryst.*, 24 (1991) 414-435 Equation(19)
 
-A J Hurd, D W Schaefer, J E Martin, *Phys. Rev. A*, 35 (1987) 2361-2364 Equation(2)
+A J Hurd, D W Schaefer, J E Martin, *Phys. Rev. A*,
+35 (1987) 2361-2364 Equation(2)
 
 """
@@ -79,4 +80,5 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["mass_dim",      "",    1.8, [1e-16, 6.0], "",
@@ -88,6 +90,6 @@
               ["primary_rg", "Ang", 4000.,  [0.0, inf], "",
                "Primary particle radius of gyration"],
-              ]
-
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["mass_surface_fractal.c"]
@@ -106,25 +108,32 @@
                primary_rg='primary_rg')
 
-tests = [[{'radius':        2.0,
-           'mass_dim':      3.3,
-           'surface_dim':   1.0,
-           'cluster_rg':   90.0,
-           'primary_rg': 4000.0,
-           }, 0.001, 0.18462699016],
+tests = [
 
-         [{'radius':        1.0,
-           'mass_dim':      1.3,
-           'surface_dim':   1.0,
-           'cluster_rg':   90.0,
-           'primary_rg': 2000.0,
-           'background':    0.8,
-           }, 0.001, 1.16539753641],
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'mass_dim':      1.8,
+      'surface_dim':   2.3,
+      'cluster_rg':   86.7,
+      'primary_rg': 4000.0,
+     }, 0.05, 1.77537e-05],
 
-         [{'radius':        1.0,
-           'mass_dim':      2.3,
-           'surface_dim':   1.0,
-           'cluster_rg':   90.0,
-           'primary_rg': 1000.0,
-           'scale':        10.0,
-           }, 0.051, 0.000169548800377],
-         ]
+    # Additional tests with larger range of parameters
+    [{'mass_dim':      3.3,
+      'surface_dim':   1.0,
+      'cluster_rg':   90.0,
+      'primary_rg': 4000.0,
+     }, 0.001, 0.18462699016],
+
+    [{'mass_dim':      1.3,
+      'surface_dim':   1.0,
+      'cluster_rg':   90.0,
+      'primary_rg': 2000.0,
+      'background':    0.8,
+     }, 0.001, 1.16539753641],
+
+    [{'mass_dim':      2.3,
+      'surface_dim':   1.0,
+      'cluster_rg':   90.0,
+      'primary_rg': 1000.0,
+      'scale':        10.0,
+     }, 0.051, 0.000169548800377],
+    ]
Index: sasmodels/models/pearl_necklace.c
===================================================================
--- sasmodels/models/pearl_necklace.c	(revision cf404cbf1344577110785a29f1390cbabc7969b2)
+++ sasmodels/models/pearl_necklace.c	(revision f12357fd54eaa1b861fbe7a32191f62f1f1d5afd)
@@ -12,9 +12,4 @@
 	double string_thickness, double number_of_pearls, double sld, 
 	double string_sld, double solvent_sld);
-	
-double ER(double radius, double edge_separation,
-	double string_thickness, double number_of_pearls);
-double VR(double radius, double edge_separation,
-	double string_thickness, double number_of_pearls);
 
 // From Igor library
@@ -22,70 +17,87 @@
 	double num_pearls, double sld_pearl, double sld_string, double sld_solv)
 {
+	//relative slds
 	double contrast_pearl = sld_pearl - sld_solv;
 	double contrast_string = sld_string - sld_solv;
+	
+	// number of string segments
+	num_pearls = floor(num_pearls + 0.5); //Force integer number of pearls
+	double num_strings = num_pearls - 1.0;
+	
+	//Pi
+	double pi = 4.0*atan(1.0);
+	
+	// center to center distance between the neighboring pearls
+	double A_s = edge_separation + 2.0 * radius;
+	
+	// Repeated Calculations
+	double sincasq = sinc(q*A_s);
+	double oneminussinc = 1 - sincasq;
+	double q_r = q * radius;
+	double q_edge = q * edge_separation;
+	
+	// each volume
+	double string_vol = edge_separation * pi * thick_string * thick_string / 4.0;
+	double pearl_vol = 4.0 / 3.0 * pi * radius * radius * radius;
 
 	//total volume
-	double pi = 4.0*atan(1.0);
-	double tot_vol = form_volume(radius, edge_separation, thick_string, num_pearls);
-	double string_vol = edge_separation * pi * pow((thick_string / 2.0), 2);
-	double pearl_vol = 4.0 /3.0 * pi * pow(radius, 3);
-	double num_strings = num_pearls - 1;
-	//each mass
+	double tot_vol;
+	//each masses
 	double m_r= contrast_string * string_vol;
 	double m_s= contrast_pearl * pearl_vol;
 	double psi, gamma, beta;
 	//form factors
-	double sss; //pearls
-	double srr; //strings
-	double srs; //cross
-	double A_s, srr_1, srr_2, srr_3;
+	double sss, srr, srs; //cross
+	double srr_1, srr_2, srr_3;
 	double form_factor;
+	tot_vol = num_strings * string_vol;
+	tot_vol += num_pearls * pearl_vol;
 
 	//sine functions of a pearl
-	psi = sin(q*radius);
-	psi -= q * radius * cos(q * radius);
-	psi /= pow((q * radius), 3);
+	psi = sin(q_r);
+	psi -= q_r * cos(q_r);
 	psi *= 3.0;
+	psi /= q_r * q_r * q_r;
 
 	// Note take only 20 terms in Si series: 10 terms may be enough though.
-	gamma = Si(q* edge_separation);
-	gamma /= (q* edge_separation);
-	beta = Si(q * (edge_separation + radius));
-	beta -= Si(q * radius);
-	beta /= (q* edge_separation);
-
-	// center to center distance between the neighboring pearls
-	A_s = edge_separation + 2.0 * radius;
+	gamma = Si(q_edge);
+	gamma /= (q_edge);
+	beta = Si(q * (A_s - radius));
+	beta -= Si(q_r);
+	beta /= q_edge;
 
 	// form factor for num_pearls
-	sss = 1.0 - pow(sinc(q*A_s), num_pearls );
-	sss /= pow((1.0-sinc(q*A_s)), 2);
-	sss *= -sinc(q*A_s);
-	sss -= num_pearls/2.0;
-	sss += num_pearls/(1.0-sinc(q*A_s));
-	sss *= 2.0 * pow((m_s*psi), 2);
+	sss = 1.0 - pow(sincasq, num_pearls);
+	sss /= oneminussinc * oneminussinc;
+	sss *= -sincasq;
+	sss -= num_pearls / 2.0;
+	sss += num_pearls / oneminussinc;
+	sss *= 2.0 * m_s * psi * m_s * psi;
 
 	// form factor for num_strings (like thin rods)
-	srr_1 = -pow(sinc(q*edge_separation/2.0), 2);
+	srr_1 = -sinc(q_edge/2.0) * sinc(q_edge/2.0);
 
 	srr_1 += 2.0 * gamma;
 	srr_1 *= num_strings;
-	srr_2 = 2.0/(1.0-sinc(q*A_s));
-	srr_2 *= num_strings * pow(beta, 2);
-	srr_3 = 1.0 - pow(sinc(q*A_s), num_strings);
-	srr_3 /= pow((1.0-sinc(q*A_s)), 2);
-	srr_3 *= -2.0 * pow(beta, 2);
+	srr_2 = 2.0/oneminussinc;
+	srr_2 *= num_strings;
+	srr_2 *= beta * beta;
+	srr_3 = 1.0 - pow(sincasq, num_strings);
+	srr_3 /= oneminussinc * oneminussinc;
+	srr_3 *= beta * beta;
+	srr_3 *= -2.0;
 
 	// total srr
 	srr = srr_1 + srr_2 + srr_3;
-	srr *= pow(m_r, 2);
+	srr *= m_r * m_r;
 
 	// form factor for correlations
 	srs = 1.0;
-	srs -= pow(sinc(q*A_s), num_strings);
-	srs /= pow((1.0-sinc(q*A_s)), 2);
-	srs *= -sinc(q*A_s);
-	srs += (num_strings/(1.0-sinc(q*A_s)));
-	srs *= 4.0 * (m_r * m_s * beta * psi);
+	srs -= pow(sincasq, num_strings);
+	srs /= oneminussinc * oneminussinc;
+	srs *= -sincasq;
+	srs += num_strings/oneminussinc;
+	srs *= 4.0;
+	srs *= (m_r * m_s * beta * psi);
 
 	form_factor = sss + srr + srs;
@@ -103,6 +115,6 @@
 	double number_of_strings = number_of_pearls - 1.0;
 	
-	double string_vol = edge_separation * pi * pow((string_thickness / 2.0), 2);
-	double pearl_vol = 4.0 /3.0 * pi * pow(radius, 3);
+	double string_vol = edge_separation * pi * string_thickness * string_thickness / 4.0;
+	double pearl_vol = 4.0 / 3.0 * pi * radius * radius * radius;
 
 	total_vol = number_of_strings * string_vol;
@@ -124,6 +136,9 @@
 	double string_sld, double solvent_sld)
 {
-	double value = 0.0;
-	double tot_vol = 0.0;
+	double value, tot_vol;
+	
+	if (string_thickness >= radius || number_of_pearls <= 0) {
+		return NAN;
+	}
 	
 	value = _pearl_necklace_kernel(q, radius, edge_separation, string_thickness,
@@ -142,19 +157,2 @@
 		sld, string_sld, solvent_sld));
 }
-
-
-double ER(double radius, double edge_separation,
-	double string_thickness, double number_of_pearls)
-{
-	double tot_vol = form_volume(radius, edge_separation, string_thickness, number_of_pearls);
-	double pi = 4.0*atan(1.0);
-
-    double rad_out = pow((3.0*tot_vol/4.0/pi), 0.33333);
-    
-    return rad_out;
-}
-double VR(double radius, double edge_separation,
-	double string_thickness, double number_of_pearls)
-{
-	return 1.0;
-}
Index: sasmodels/models/pearl_necklace.py
===================================================================
--- sasmodels/models/pearl_necklace.py	(revision cf404cbf1344577110785a29f1390cbabc7969b2)
+++ sasmodels/models/pearl_necklace.py	(revision d18582e2d66b12afb03e1c3a7c0af6e4449c6bfe)
@@ -1,7 +1,7 @@
 r"""
-This model provides the form factor for a pearl necklace composed of two 
-elements: *N* pearls (homogeneous spheres of radius *R*) freely jointed by *M* 
+This model provides the form factor for a pearl necklace composed of two
+elements: *N* pearls (homogeneous spheres of radius *R*) freely jointed by *M*
 rods (like strings - with a total mass *Mw* = *M* \* *m*\ :sub:`r` + *N* \* *m*\
-:sub:`s`, and the string segment length (or edge separation) *l* 
+:sub:`s`, and the string segment length (or edge separation) *l*
 (= *A* - 2\ *R*)). *A* is the center-to-center pearl separation distance.
 
@@ -13,5 +13,5 @@
 ----------
 
-The output of the scattering intensity function for the PearlNecklaceModel is 
+The output of the scattering intensity function for the PearlNecklaceModel is
 given by (Schweins, 2004)
 
@@ -37,8 +37,8 @@
     \beta(q) &= \frac{\int_{qR}^{q(A-R)}\frac{sin(t)}{t}dt}{ql}
 
-where the mass *m*\ :sub:`i` is (SLD\ :sub:`i` - SLD\ :sub:`solvent`) \* 
+where the mass *m*\ :sub:`i` is (SLD\ :sub:`i` - SLD\ :sub:`solvent`) \*
 (volume of the *N* pearls/rods). *V* is the total volume of the necklace.
 
-The 2D scattering intensity is the same as $P(q)$ above, regardless of the 
+The 2D scattering intensity is the same as $P(q)$ above, regardless of the
 orientation of the *q* vector.
 
@@ -54,9 +54,9 @@
 REFERENCE
 
-R Schweins and K Huber, *Particle Scattering Factor of Pearl Necklace Chains*, 
+R Schweins and K Huber, *Particle Scattering Factor of Pearl Necklace Chains*,
 *Macromol. Symp.* 211 (2004) 25-42 2004
 """
 
-from numpy import inf
+from numpy import inf, pi
 
 name = "pearl_necklace"
@@ -79,21 +79,42 @@
 
 #             ["name", "units", default, [lower, upper], "type","description"],
-parameters = [["radius", "Angstrom", 80.0, [0, inf], "volume", 
+parameters = [["radius", "Angstrom", 80.0, [0, inf], "volume",
                "Mean radius of the chained spheres"],
-              ["edge_separation", "Angstrom", 350.0, [0, inf], "volume", 
+              ["edge_separation", "Angstrom", 350.0, [0, inf], "volume",
                "Mean separation of chained particles"],
-              ["string_thickness", "Angstrom", 2.5, [0, inf], "volume", 
+              ["string_thickness", "Angstrom", 2.5, [0, inf], "volume",
                "Thickness of the chain linkage"],
-              ["number_of_pearls", "none", 3, [0, inf], "volume", 
+              ["number_of_pearls", "none", 3, [0, inf], "volume",
                "Mean number of pearls in each necklace"],
-              ["sld", "Angstrom^2", 1.0, [-inf, inf], "", 
+              ["sld", "Angstrom^2", 1.0, [-inf, inf], "",
                "Scattering length density of the chained spheres"],
-              ["string_sld", "Angstrom^2", 1.0, [-inf, inf], "", 
+              ["string_sld", "Angstrom^2", 1.0, [-inf, inf], "",
                "Scattering length density of the chain linkage"],
-              ["solvent_sld", "Angstrom^2", 6.3, [-inf, inf], "", 
+              ["solvent_sld", "Angstrom^2", 6.3, [-inf, inf], "",
                "Scattering length density of the solvent"],
-              ]
+             ]
 
 source = ["lib/Si.c", "pearl_necklace.c"]
+single = False  # use double precision unless told otherwise
+
+def volume(radius, edge_separation, string_thickness, number_of_pearls):
+    """
+    Calculates the total particle volume of the necklace.
+    Redundant with form_volume.
+    """
+    number_of_strings = number_of_pearls - 1.0
+    string_vol = edge_separation * pi * pow((string_thickness / 2.0), 2.0)
+    pearl_vol = 4.0 /3.0 * pi * pow(radius, 3.0)
+    total_vol = number_of_strings * string_vol
+    total_vol += number_of_pearls * pearl_vol
+    return total_vol
+
+def ER(radius, edge_separation, string_thickness, number_of_pearls):
+    """
+    Calculation for effective radius.
+    """
+    tot_vol = volume(radius, edge_separation, string_thickness, number_of_pearls)
+    rad_out = pow((3.0*tot_vol/4.0/pi), 0.33333)
+    return rad_out
 
 # parameters for demo
@@ -110,6 +131,8 @@
 # names and the target sasview model name.
 oldname = 'PearlNecklaceModel'
-oldpars = dict(scale='scale',background='background',radius='radius',
+oldpars = dict(scale='scale', background='background', radius='radius',
                number_of_pearls='num_pearls', solvent_sld='sld_solv',
                string_thickness='thick_string', sld='sld_pearl',
                string_sld='sld_string', edge_separation='edge_separation')
+
+tests = [[{}, 0.001, 17380.245], [{}, 'ER', 115.39502]]
Index: sasmodels/models/polymer_excl_volume.py
===================================================================
--- sasmodels/models/polymer_excl_volume.py	(revision 513efc509d218370e6a8ca9cabf24f087a2de902)
+++ sasmodels/models/polymer_excl_volume.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -113,12 +113,15 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type", "description"],
 parameters = [["rg",        "Ang", 60.0, [0, inf],    "", "Radius of Gyration"],
               ["porod_exp", "",     3.0, [-inf, inf], "", "Porod exponent"],
-              ]
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 
-def Iq(q, rg, porod_exp):
-
+def Iq(q,
+       rg=60.0,
+       porod_exp=3.0):
     """
     :param q:         Input q-value (float or [float, float])
@@ -131,5 +134,6 @@
     o2nu = 1.0/(2.0*nu)
 
-    intensity = ((1.0/(nu*power(u, o2nu))) * (gamma(o2nu)*gammainc(o2nu, u) -
+    intensity = ((1.0/(nu*power(u, o2nu))) *
+                 (gamma(o2nu)*gammainc(o2nu, u) -
                   1.0/power(u, o2nu) * gamma(porod_exp) *
                   gammainc(porod_exp, u))) * (q > 0) + 1.0*(q <= 0)
@@ -141,7 +145,12 @@
 
 def Iqxy(qx, qy, *args):
-        iq = Iq(sqrt(qx**2 + qy**2), *args)
+    """
+    :param qx:   Input q_x-value
+    :param qy:   Input q_y-value
+    :param args: Remaining arguments
+    :return:     2D-Intensity
+    """
 
-        return iq
+    return Iq(sqrt(qx**2 + qy**2), *args)
 
 Iqxy.vectorized = True  # Iqxy accepts an array of qx, qy values
@@ -157,9 +166,14 @@
                porod_exp='m')
 
-tests = [[{'rg': 10, 'porod_exp': 4.0}, 0.1, 0.723436675809],
+tests = [
+    # Accuracy tests based on content in test/polyexclvol_default_igor.txt
+    [{'rg': 60, 'porod_exp': 3.0}, 0.001, 0.998801],
+    [{'rg': 60, 'porod_exp': 3.0}, 0.105363, 0.0162751],
+    [{'rg': 60, 'porod_exp': 3.0}, 0.665075, 6.56261e-05],
 
-         [{'rg': 2.2, 'porod_exp': 22.0, 'background': 100.0}, 5.0, 100.0],
-
-         [{'rg': 1.1, 'porod_exp': 1, 'background': 10.0, 'scale': 1.25},
-         20000., 10.0000712097]
-         ]
+    # Additional tests with larger range of parameters
+    [{'rg': 10, 'porod_exp': 4.0}, 0.1, 0.723436675809],
+    [{'rg': 2.2, 'porod_exp': 22.0, 'background': 100.0}, 5.0, 100.0],
+    [{'rg': 1.1, 'porod_exp': 1, 'background': 10.0, 'scale': 1.25},
+     20000., 10.0000712097]
+    ]
Index: sasmodels/models/power_law.py
===================================================================
--- sasmodels/models/power_law.py	(revision eb69cce00f0781ad8f3eec0eb5965a8576d815b5)
+++ sasmodels/models/power_law.py	(revision 841753cbac7824298effa1aec88a7ee977eb257f)
@@ -12,9 +12,9 @@
     I(q) = \text{scale} \cdot q^{-\text{power}} + \text{background}
 
-Note the minus sign in front of the exponent. The exponent *power* 
+Note the minus sign in front of the exponent. The exponent *power*
 should therefore be entered as a **positive** number for fitting.
 
-Also note that unlike many other models, *scale* in this model 
-is NOT explicitly related to a volume fraction. Be careful if 
+Also note that unlike many other models, *scale* in this model
+is NOT explicitly related to a volume fraction. Be careful if
 combining this model with other models.
 
@@ -31,12 +31,12 @@
 from numpy import inf, sqrt
 
-name =  "power_law"
+name = "power_law"
 title = "Simple power law with a flat background"
 
-description = """\
-        Evaluates the function
-        I(q) = scale * q^(-power) + background
-        NB: enter power as a positive number!
-        """
+description = """
+    Evaluates the function
+    I(q) = scale * q^(-power) + background
+    NB: enter power as a positive number!
+    """
 category = "shape-independent"
 
@@ -45,5 +45,6 @@
 
 # NB: Scale and Background are implicit parameters on every model
-def Iq(q,power):
+def Iq(q, power):
+    # pylint: disable=missing-docstring
     inten = (q**-power)
     return inten
@@ -51,4 +52,5 @@
 
 def Iqxy(qx, qy, *args):
+    # pylint: disable=missing-docstring
     return Iq(sqrt(qx ** 2 + qy ** 2), *args)
 Iqxy.vectorized = True # Iqxy accepts an array of qx, qy values
@@ -64,4 +66,5 @@
 
 tests = [
-        [ {'scale': 1.0, 'power': 4.0, 'background' : 0.0}, [0.0106939, 0.469418], [7.64644e+07, 20.5949]]
-        ]
+    [{'scale': 1.0, 'power': 4.0, 'background' : 0.0},
+     [0.0106939, 0.469418], [7.64644e+07, 20.5949]],
+    ]
Index: sasmodels/models/rpa.c
===================================================================
--- sasmodels/models/rpa.c	(revision 82c299fa51ab091ee8ead0a235d4177ccfeecb99)
+++ sasmodels/models/rpa.c	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -205,5 +205,5 @@
   const double Kbb = 0.0;
   const double Kcc = 0.0;
-  const double Kdd = 0.0;
+  //const double Kdd = 0.0;
   const double Zaa = Kaa - Kad - Kad;
   const double Zab = Kab - Kad - Kbd;
@@ -278,9 +278,9 @@
   const double Q12 = (-Mab*Mcc + Mac*Mcb)/DenQ;
   const double Q13 = ( Mab*Mbc - Mac*Mbb)/DenQ;
-  const double Q21 = (-Mba*Mcc + Mbc*Mca)/DenQ;
+  //const double Q21 = (-Mba*Mcc + Mbc*Mca)/DenQ;
   const double Q22 = ( Maa*Mcc - Mac*Mca)/DenQ;
   const double Q23 = (-Maa*Mbc + Mac*Mba)/DenQ;
-  const double Q31 = ( Mba*Mcb - Mbb*Mca)/DenQ;
-  const double Q32 = (-Maa*Mcb + Mab*Mca)/DenQ;
+  //const double Q31 = ( Mba*Mcb - Mbb*Mca)/DenQ;
+  //const double Q32 = (-Maa*Mcb + Mab*Mca)/DenQ;
   const double Q33 = ( Maa*Mbb - Mab*Mba)/DenQ;
 
Index: sasmodels/models/star_polymer.py
===================================================================
--- sasmodels/models/star_polymer.py	(revision 55b283e83d5d634117ba0d673c0f2121cce7ce82)
+++ sasmodels/models/star_polymer.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -55,10 +55,11 @@
         """
 category = "shape-independent"
-
+single = False
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["radius2", "Ang", 100.0, [0.0, inf], "", "Ensemble radius of gyration squared of an arm"],
               ["arms",    "",      3,   [1.0, 6.0], "", "Number of arms in the model"],
-              ]
-
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["star_polymer.c"]
@@ -75,9 +76,9 @@
 tests = [[{'radius2': 2.0,
            'arms':    3.3,
-           }, 0.5, 0.850646091108],
+          }, 0.5, 0.850646091108],
 
          [{'radius2':    1.0,
            'arms':       2.0,
            'background': 1.8,
-           }, 1.0, 2.53575888234],
-         ]
+          }, 1.0, 2.53575888234],
+        ]
Index: sasmodels/models/stickyhardsphere.py
===================================================================
--- sasmodels/models/stickyhardsphere.py	(revision d18f8a8e10d65f0ace6fdc9360abd60dfcd6dcce)
+++ sasmodels/models/stickyhardsphere.py	(revision 13ed84c3c71257d2df9c2ed4b4e98c0bfa4d7d69)
@@ -85,4 +85,5 @@
 category = "structure-factor"
 
+single = False
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [
@@ -182,3 +183,9 @@
 demo = dict(effect_radius=200, volfraction=0.2, perturb=0.05,
             stickiness=0.2, effect_radius_pd=0.1, effect_radius_pd_n=40)
+#
+tests = [
+        [ {'scale': 1.0, 'background' : 0.0, 'effect_radius' : 50.0, 'perturb' : 0.05, 'stickiness' : 0.2, 'volfraction' : 0.1,
+           'effect_radius_pd' : 0}, [0.001], [1.09718]]
+        ]
 
+
Index: sasmodels/models/surface_fractal.py
===================================================================
--- sasmodels/models/surface_fractal.py	(revision 9c461c713e9961727442c844be3e6354d6548c2f)
+++ sasmodels/models/surface_fractal.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -80,4 +80,5 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #             ["name", "units", default, [lower, upper], "type","description"],
 parameters = [["radius",        "Ang", 10.0, [0, inf],   "",
@@ -87,6 +88,6 @@
               ["cutoff_length", "Ang", 500., [0.0, inf], "",
                "Cut-off Length"],
-              ]
-
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 source = ["lib/sph_j1c.c", "lib/lanczos_gamma.c", "surface_fractal.c"]
@@ -100,13 +101,27 @@
                cutoff_length='co_length')
 
-tests = [[{'radius': 1.0, 'surface_dim': 1.0, 'cutoff_length': 10.0,
-           }, 0.332070182643, 1125.00321004],
+tests = [
+    # Accuracy tests based on content in test/utest_other_models.py
+    [{'radius': 10.0,
+      'surface_dim': 2.0,
+      'cutoff_length': 500.0,
+     }, 0.05, 301428.65916],
 
-         [{'radius': 3.5, 'surface_dim': 0.1, 'cutoff_length': 30.0,
-           'background': 0.01,
-           }, 5.0, 0.00999998891322],
+    # Additional tests with larger range of parameters
+    [{'radius': 1.0,
+      'surface_dim': 1.0,
+      'cutoff_length': 10.0,
+     }, 0.332070182643, 1125.00321004],
 
-         [{'radius': 3.0, 'surface_dim': 1.0, 'cutoff_length': 33.0,
-           'scale': 0.1,
-           }, 0.51, 2.50020147004],
-         ]
+    [{'radius': 3.5,
+      'surface_dim': 0.1,
+      'cutoff_length': 30.0,
+      'background': 0.01,
+     }, 5.0, 0.00999998891322],
+
+    [{'radius': 3.0,
+      'surface_dim': 1.0,
+      'cutoff_length': 33.0,
+      'scale': 0.1,
+     }, 0.51, 2.50020147004],
+    ]
Index: sasmodels/models/two_lorentzian.py
===================================================================
--- sasmodels/models/two_lorentzian.py	(revision 513efc509d218370e6a8ca9cabf24f087a2de902)
+++ sasmodels/models/two_lorentzian.py	(revision 168052c5791506d7dfad00173f5636fc5701ad11)
@@ -54,27 +54,23 @@
 category = "shape-independent"
 
+# pylint: disable=bad-whitespace, line-too-long
 #            ["name", "units", default, [lower, upper], "type", "description"],
-parameters = [["lorentz_scale_1",  "",     10.0, [-inf, inf], "",
-               "First power law scale factor"],
-              ["lorentz_length_1", "Ang", 100.0, [-inf, inf], "",
-               "First Lorentzian screening length"],
-              ["lorentz_exp_1",    "",      3.0, [-inf, inf], "",
-               "First exponent of power law"],
-              ["lorentz_scale_2",  "",      1.0, [-inf, inf], "",
-               "Second scale factor for broad Lorentzian peak"],
-              ["lorentz_length_2", "Ang",  10.0, [-inf, inf], "",
-               "Second Lorentzian screening length"],
-              ["lorentz_exp_2",    "",      2.0, [-inf, inf], "",
-               "Second exponent of power law"],
-              ]
+parameters = [["lorentz_scale_1",  "",     10.0, [-inf, inf], "", "First power law scale factor"],
+              ["lorentz_length_1", "Ang", 100.0, [-inf, inf], "", "First Lorentzian screening length"],
+              ["lorentz_exp_1",    "",      3.0, [-inf, inf], "", "First exponent of power law"],
+              ["lorentz_scale_2",  "",      1.0, [-inf, inf], "", "Second scale factor for broad Lorentzian peak"],
+              ["lorentz_length_2", "Ang",  10.0, [-inf, inf], "", "Second Lorentzian screening length"],
+              ["lorentz_exp_2",    "",      2.0, [-inf, inf], "", "Second exponent of power law"],
+             ]
+# pylint: enable=bad-whitespace, line-too-long
 
 
 def Iq(q,
-       lorentz_scale_1,
-       lorentz_length_1,
-       lorentz_exp_1,
-       lorentz_scale_2,
-       lorentz_length_2,
-       lorentz_exp_2):
+       lorentz_scale_1=10.0,
+       lorentz_length_1=100.0,
+       lorentz_exp_1=3.0,
+       lorentz_scale_2=1.0,
+       lorentz_length_2=10.0,
+       lorentz_exp_2=2.0):
 
     """
@@ -88,9 +84,10 @@
     :return:                    Calculated intensity
     """
-
+# pylint: disable=bad-whitespace
     intensity  = lorentz_scale_1/(1.0 +
                                   power(q*lorentz_length_1, lorentz_exp_1))
     intensity += lorentz_scale_2/(1.0 +
                                   power(q*lorentz_length_2, lorentz_exp_2))
+# pylint: enable=bad-whitespace
 
     return intensity
@@ -100,7 +97,12 @@
 
 def Iqxy(qx, qy, *args):
-        iq = Iq(sqrt(qx**2 + qy**2), *args)
+    """
+    :param qx:   Input q_x-value
+    :param qy:   Input q_y-value
+    :param args: Remaining arguments
+    :return:     2D-Intensity
+    """
 
-        return iq
+    return Iq(sqrt(qx**2 + qy**2), *args)
 
 Iqxy.vectorized = True  # Iqxy accepts an array of qx, qy values
@@ -108,36 +110,74 @@
 
 demo = dict(scale=1, background=0.1,
-            lorentz_scale_1=10, lorentz_length_1=100.0, lorentz_exp_1=3.0,
-            lorentz_scale_2=1,  lorentz_length_2=10,    lorentz_exp_2=2.0)
+            lorentz_scale_1=10,
+            lorentz_length_1=100.0,
+            lorentz_exp_1=3.0,
+            lorentz_scale_2=1,
+            lorentz_length_2=10,
+            lorentz_exp_2=2.0)
 
 oldname = "TwoLorentzianModel"
 oldpars = dict(background='background',
-               lorentz_scale_1='scale_1',   lorentz_scale_2='scale_2',
-               lorentz_length_1='length_1', lorentz_length_2='length_2',
-               lorentz_exp_1='exponent_1',  lorentz_exp_2='exponent_2')
+               lorentz_scale_1='scale_1',
+               lorentz_scale_2='scale_2',
+               lorentz_length_1='length_1',
+               lorentz_length_2='length_2',
+               lorentz_exp_1='exponent_1',
+               lorentz_exp_2='exponent_2')
 
-tests = [[{'lorentz_scale_1':   10.0,
-           'lorentz_length_1': 100.0,
-           'lorentz_exp_1':      3.0,
-           'lorentz_scale_2':    1.0,
-           'lorentz_length_2':  10.0,
-           'lorentz_exp_2':      2.0,
-           }, 0.000332070182643, 10.9996228107],
+tests = [
 
-         [{'lorentz_scale_1':  0.0,
-           'lorentz_length_1': 0.0,
-           'lorentz_exp_1':    0.0,
-           'lorentz_scale_2':  0.0,
-           'lorentz_length_2': 0.0,
-           'lorentz_exp_2':    0.0,
-           'background':     100.0
-           }, 5.0, 100.0],
+    # Accuracy tests based on content in test/utest_extra_models.py
+    [{'lorentz_scale_1':   10.0,
+      'lorentz_length_1': 100.0,
+      'lorentz_exp_1':      3.0,
+      'lorentz_scale_2':    1.0,
+      'lorentz_length_2':  10.0,
+      'lorentz_exp_2':      2.0,
+      'background':         0.1,
+     }, 0.001, 11.08991],
 
-         [{'lorentz_scale_1': 200.0,
-           'lorentz_length_1': 10.0,
-           'lorentz_exp_1':     0.1,
-           'lorentz_scale_2':   0.1,
-           'lorentz_length_2':  5.0,
-           'lorentz_exp_2':     2.0
-           }, 20000., 45.5659201896],
-         ]
+    [{'lorentz_scale_1':   10.0,
+      'lorentz_length_1': 100.0,
+      'lorentz_exp_1':      3.0,
+      'lorentz_scale_2':    1.0,
+      'lorentz_length_2':  10.0,
+      'lorentz_exp_2':      2.0,
+      'background':         0.1,
+     }, 0.150141, 0.410245],
+
+    [{'lorentz_scale_1':   10.0,
+      'lorentz_length_1': 100.0,
+      'lorentz_exp_1':      3.0,
+      'lorentz_scale_2':    1.0,
+      'lorentz_length_2':  10.0,
+      'lorentz_exp_2':      2.0,
+      'background':         0.1,
+     }, 0.442528, 0.148699],
+
+    # Additional tests with larger range of parameters
+    [{'lorentz_scale_1':   10.0,
+      'lorentz_length_1': 100.0,
+      'lorentz_exp_1':      3.0,
+      'lorentz_scale_2':    1.0,
+      'lorentz_length_2':  10.0,
+      'lorentz_exp_2':      2.0,
+     }, 0.000332070182643, 10.9996228107],
+
+    [{'lorentz_scale_1':  0.0,
+      'lorentz_length_1': 0.0,
+      'lorentz_exp_1':    0.0,
+      'lorentz_scale_2':  0.0,
+      'lorentz_length_2': 0.0,
+      'lorentz_exp_2':    0.0,
+      'background':     100.0
+     }, 5.0, 100.0],
+
+    [{'lorentz_scale_1': 200.0,
+      'lorentz_length_1': 10.0,
+      'lorentz_exp_1':     0.1,
+      'lorentz_scale_2':   0.1,
+      'lorentz_length_2':  5.0,
+      'lorentz_exp_2':     2.0
+     }, 20000., 45.5659201896],
+    ]
Index: sasmodels/resolution.py
===================================================================
--- sasmodels/resolution.py	(revision 5258859dcf2d6cbd1ba2ea63ce85ff792c2056bb)
+++ sasmodels/resolution.py	(revision 5925e909151e120132e72e91095ab06bad045240)
@@ -5,4 +5,8 @@
 """
 from __future__ import division
+
+from scipy.special import erf
+from numpy import sqrt, log, log10
+import numpy as np
 
 __all__ = ["Resolution", "Perfect1D", "Pinhole1D", "Slit1D",
@@ -10,9 +14,5 @@
            "pinhole_extend_q", "slit_extend_q", "bin_edges",
            "interpolate", "linear_extrapolation", "geometric_extrapolation",
-           ]
-
-from scipy.special import erf
-from numpy import sqrt, log, log10
-import numpy as np
+          ]
 
 MINIMUM_RESOLUTION = 1e-8
@@ -80,8 +80,8 @@
         # 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))
+        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):
@@ -135,5 +135,5 @@
     """
     #print("apply shapes", theory.shape, weight_matrix.shape)
-    Iq = np.dot(theory[None,:], weight_matrix)
+    Iq = np.dot(theory[None, :], weight_matrix)
     #print("result shape",Iq.shape)
     return Iq.flatten()
@@ -153,8 +153,8 @@
     # 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,:] )
+    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,:]
+    weights /= np.sum(weights, axis=0)[None, :]
     return weights
 
@@ -287,5 +287,5 @@
     # 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
+    q_edges[q_edges < 0.0] = 0.0 # clip edges below zero
     weights = np.zeros((len(q), len(q_calc)), 'd')
 
@@ -306,10 +306,10 @@
             #print(qi - h, qi + h)
             #print(in_x + abs_x)
-            weights[i,:] = (in_x + abs_x) * np.diff(q_edges) / (2*h)
+            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
+                weights[i, :] += _q_perp_weights(q_edges, qi+k*h/L, w)
+            weights[i, :] /= 2*L + 1
 
     return weights.T
@@ -358,5 +358,5 @@
     log-scaled data.
     """
-    if len(x) < 2 or (np.diff(x)<0).any():
+    if len(x) < 2 or (np.diff(x) < 0).any():
         raise ValueError("Expected bins to be an increasing set")
     edges = np.hstack([
@@ -373,13 +373,13 @@
     """
     step = np.diff(q)
-    index = step>max_step
+    index = step > max_step
     if np.any(index):
         inserts = []
-        for q_i,step_i in zip(q[:-1][index],step[index]):
+        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))
+            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)))
+        inserts.extend(q[-1] + np.arange(1, 8)*max_step)
+        q_calc = np.sort(np.hstack((q, inserts)))
     else:
         q_calc = q
@@ -399,10 +399,10 @@
     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
+        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
+        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:
@@ -452,5 +452,5 @@
         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]
+        q_low = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1]
     else:
         q_low = []
@@ -470,4 +470,11 @@
 
 def eval_form(q, form, pars):
+    """
+    Return the SAS model evaluated at *q*.
+
+    *form* is the SAS model returned from :fun:`core.load_model`.
+
+    *pars* are the parameter values to use when evaluating.
+    """
     from sasmodels import core
     kernel = core.make_kernel(form, [q])
@@ -478,4 +485,9 @@
 
 def gaussian(q, q0, dq):
+    """
+    Return the Gaussian resolution function.
+
+    *q0* is the center, *dq* is the width and *q* are the points to evaluate.
+    """
     from numpy import exp, pi
     return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq)
@@ -489,5 +501,5 @@
     that make it slow to evaluate but give it good accuracy.
     """
-    from scipy.integrate import romberg, dblquad
+    from scipy.integrate import romberg
 
     if any(k not in form.info['defaults'] for k in pars.keys()):
@@ -520,16 +532,15 @@
             result[i] = r/(2*h)
         else:
-            w_grid = np.linspace(0, w, 21)[None,:]
-            h_grid = np.linspace(-h, h, 23)[:,None]
+            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])
+            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)
-            """
+            # from scipy.integrate import dblquad
+            # 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]),...]
@@ -553,11 +564,14 @@
 
     _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)]
+    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):
+    """
+    Test the resolution calculations.
+    """
 
     def setUp(self):
@@ -595,6 +609,8 @@
         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 ]
+        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)
 
@@ -608,6 +624,8 @@
         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 ]
+        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)
 
@@ -620,5 +638,7 @@
         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 ]
+        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)
 
@@ -632,5 +652,7 @@
         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 ]
+        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)
 
@@ -652,11 +674,16 @@
         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]
+        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):
+    """
+    Test resolution calculations against those returned by Igor.
+    """
 
     def setUp(self):
@@ -666,5 +693,5 @@
         self.model = core.load_model(sphere, dtype='double')
 
-    def Iq_sphere(self, pars, resolution):
+    def _eval_sphere(self, pars, resolution):
         from sasmodels import core
         kernel = core.make_kernel(self.model, [resolution.q_calc])
@@ -674,5 +701,5 @@
         return result
 
-    def compare(self, q, output, answer, tolerance):
+    def _compare(self, q, output, answer, tolerance):
         #err = (output - answer)/answer
         #idx = abs(err) >= tolerance
@@ -691,6 +718,6 @@
         q, width, answer, _ = data
         resolution = Perfect1D(q)
-        output = self.Iq_sphere(pars, resolution)
-        self.compare(q, output, answer, 1e-6)
+        output = self._eval_sphere(pars, resolution)
+        self._compare(q, output, answer, 1e-6)
 
     def test_pinhole(self):
@@ -704,7 +731,7 @@
         q, q_width, answer = data
         resolution = Pinhole1D(q, q_width)
-        output = self.Iq_sphere(pars, resolution)
+        output = self._eval_sphere(pars, resolution)
         # TODO: relative error should be lower
-        self.compare(q, output, answer, 3e-4)
+        self._compare(q, output, answer, 3e-4)
 
     def test_pinhole_romberg(self):
@@ -727,14 +754,14 @@
         q_calc, tol = None, 0.01
         resolution = Pinhole1D(q, q_width, q_calc=q_calc)
-        output = self.Iq_sphere(pars, resolution)
+        output = self._eval_sphere(pars, resolution)
         if 0: # debug plot
             import matplotlib.pyplot as plt
             resolution = Perfect1D(q)
-            source = self.Iq_sphere(pars, resolution)
+            source = self._eval_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)
+        self._compare(q, output, answer, tol)
 
     def test_slit(self):
@@ -748,9 +775,9 @@
         q, delta_qv, _, answer = data
         resolution = Slit1D(q, width=delta_qv, height=0)
-        output = self.Iq_sphere(pars, resolution)
+        output = self._eval_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)
+        self._compare(q, output, answer, 0.5)
 
     def test_slit_romberg(self):
@@ -768,7 +795,7 @@
                                delta_qv[0], 0.)
         resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc)
-        output = self.Iq_sphere(pars, resolution)
+        output = self._eval_sphere(pars, resolution)
         # TODO: relative error should be lower
-        self.compare(q, output, answer, 0.025)
+        self._compare(q, output, answer, 0.025)
 
     def test_ellipsoid(self):
@@ -783,5 +810,5 @@
             }
         form = load_model('ellipsoid', dtype='double')
-        q = np.logspace(log10(4e-5),log10(2.5e-2), 68)
+        q = np.logspace(log10(4e-5), log10(2.5e-2), 68)
         width, height = 0.117, 0.
         resolution = Slit1D(q, width=width, height=height)
@@ -790,5 +817,5 @@
         # TODO: 10% is too much error; use better algorithm
         #print(np.max(abs(answer-output)/answer))
-        self.compare(q, output, answer, 0.1)
+        self._compare(q, output, answer, 0.1)
 
     #TODO: can sas q spacing be too sparse for the resolution calculation?
@@ -804,6 +831,6 @@
         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)
+        output = self._eval_sphere(pars, resolution)
+        self._compare(q, output, answer, 1e-6)
 
 
@@ -1058,4 +1085,7 @@
 
 def demo_pinhole_1d():
+    """
+    Show example of pinhole smearing.
+    """
     q = np.logspace(-4, np.log10(0.2), 400)
     q_width = 0.1*q
@@ -1064,4 +1094,7 @@
 
 def demo_slit_1d():
+    """
+    Show example of slit smearing.
+    """
     q = np.logspace(-4, np.log10(0.2), 100)
     w = h = 0.
@@ -1071,7 +1104,10 @@
     #h = 0.0277790
     resolution = Slit1D(q, w, h)
-    _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w,h))
+    _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w, h))
 
 def demo():
+    """
+    Run the resolution demos.
+    """
     import matplotlib.pyplot as plt
     plt.subplot(121)
Index: sasmodels/resolution2d.py
===================================================================
--- sasmodels/resolution2d.py	(revision 9404dd3f599fcbe72790aae8a6d176e64f292ea6)
+++ sasmodels/resolution2d.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
@@ -2,5 +2,5 @@
 #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. 
+#project funded by the US National Science Foundation.
 #See the license text in license.txt
 """
@@ -19,5 +19,5 @@
 ## Defaults
 NR = {'xhigh':10, 'high':5, 'med':5, 'low':3}
-NPHI ={'xhigh':20, 'high':12, 'med':6, 'low':4}
+NPHI = {'xhigh':20, 'high':12, 'med':6, 'low':4}
 
 class Pinhole2D(Resolution):
@@ -25,10 +25,10 @@
     Gaussian Q smearing class for SAS 2d data
     """
-     
+
     def __init__(self, data=None, index=None,
                  nsigma=NSIGMA, accuracy='Low', coords='polar'):
         """
         Assumption: equally spaced bins in dq_r, dq_phi space.
-        
+
         :param data: 2d data used to set the smearing parameters
         :param index: 1d array with len(data) to define the range
@@ -42,8 +42,8 @@
         ## number of bins in r axis for over-sampling
         self.nr = NR[accuracy.lower()]
-        ## number of bins in phi axis for over-sampling 
+        ## number of bins in phi axis for over-sampling
         self.nphi = NPHI[accuracy.lower()]
         ## maximum nsigmas
-        self.nsigma= nsigma
+        self.nsigma = nsigma
         self.coords = coords
         self._init_data(data, index)
@@ -85,7 +85,7 @@
     def _calc_res(self):
         """
-        Over sampling of r_nbins times phi_nbins, calculate Gaussian weights, 
+        Over sampling of r_nbins times phi_nbins, calculate Gaussian weights,
         then find smeared intensity
-        """    
+        """
         nr, nphi = self.nr, self.nphi
         # Total number of bins = # of bins
@@ -143,11 +143,11 @@
         if self.coords == 'polar':
             q_r = sqrt(qx**2 + qy**2)
-            qx_res = ( (dqx*cos(dphi) + q_r) * cos(-q_phi) +
-                           dqy*sin(dphi) * sin(-q_phi))
-            qy_res = (-(dqx*cos(dphi) + q_r) * sin(-q_phi) +
-                           dqy*sin(dphi) * cos(-q_phi))
+            qx_res = ((dqx*cos(dphi) + q_r) * cos(-q_phi)
+                      + dqy*sin(dphi) * sin(-q_phi))
+            qy_res = (-(dqx*cos(dphi) + q_r) * sin(-q_phi)
+                      + dqy*sin(dphi) * cos(-q_phi))
         else:
-            qx_res = qx +  dqx*cos(dphi)
-            qy_res = qy +  dqy*sin(dphi)
+            qx_res = qx + dqx*cos(dphi)
+            qy_res = qy + dqy*sin(dphi)
 
 
@@ -162,75 +162,7 @@
             theory = np.reshape(theory, (nbins, nq))
             ## Averaging with Gaussian weighting: normalization included.
-            value =np.average(theory, axis=0, weights=self.q_calc_weights)
+            value = np.average(theory, axis=0, weights=self.q_calc_weights)
             ## Return the smeared values in the range of self.index
             return value
         else:
             return theory
-
-"""
-if __name__ == '__main__':
-    ## Test w/ 2D linear function
-    x = 0.001*np.arange(1, 11)
-    dx = np.ones(len(x))*0.0003
-    y = 0.001*np.arange(1, 11)
-    dy = np.ones(len(x))*0.001
-    z = np.ones(10)
-    dz = sqrt(z)
-
-    from sas.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 = sqrt(dx * dx + dy * dy)
-    index = np.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*np.arange(1,11)
-    dx = np.ones(len(x))*0.001
-    y = 0.001*np.arange(1,11)
-    dy = np.ones(len(x))*0.001
-    z = np.ones(10)
-    dz = 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 = np.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: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision 9404dd3f599fcbe72790aae8a6d176e64f292ea6)
+++ sasmodels/sasview_model.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -283,5 +283,5 @@
         """
         q_vectors = [np.asarray(q) for q in args]
-        fn = self._model(self._model.make_input(q_vectors))
+        fn = self._model(q_vectors)
         pars = [self.params[v] for v in fn.fixed_pars]
         pd_pars = [self._get_weights(p) for p in fn.pd_pars]
Index: sasmodels/sesans.py
===================================================================
--- sasmodels/sesans.py	(revision 3c56da87f0995bdde77f4183b68e77b81584dde0)
+++ sasmodels/sesans.py	(revision 190fc2b167d3d89ca9c3b7d3128bdf8bde9f67d0)
@@ -12,40 +12,25 @@
 import numpy as np
 from numpy import pi, exp
-
 from scipy.special import jv as besselj
 
-def make_q(q_zmax, Rmax):
+def make_q(q_max, Rmax):
+    r"""
+    Return a $q$ vector suitable for SESANS covering from $2\pi/ (10 R_{\max})$
+    to $q_max$.
+    """
     q_min = dq = 0.1 * 2*pi / Rmax
-    #q_min = 0.00003
-    return np.arange(q_min, q_zmax, dq)
-
-# TODO: dead code; for now the call to the hankel transform happens in BumpsModel
-class SesansCalculator:
-    def __init__(self, kernel, q_zmax, Rmax, SElength, wavelength, thickness):
-        self._set_kernel(kernel, q_zmax, Rmax)
-        self.SElength = SElength
-        self.wavelength = wavelength
-        self.thickness = thickness
-
-    def _set_kernel(self, kernel, q_zmax, Rmax):
-        kernel_input = kernel.make_input([make_q(q_zmax, Rmax)])
-        self.sans_calculator = kernel(kernel_input)
-
-    def __call__(self, pars, pd_pars, cutoff=1e-5):
-        Iq = self.sans_calculator(pars, pd_pars, cutoff)
-        P = hankel(self.SElength, self.wavelength, self.thickness, self.q, Iq)
-        self.Iq = Iq
-        return P
+    return np.arange(q_min, q_max, dq)
 
 def hankel(SElength, wavelength, thickness, q, Iq):
-    """
+    r"""
     Compute the expected SESANS polarization for a given SANS pattern.
 
-    Uses the hankel transform followed by the exponential.  The values
-    for zz (or spin echo length, or delta), wavelength and sample thickness
-    information should come from the dataset.  *q* should be chosen such
-    that the oscillations in *I(q)* are well sampled (e.g., 5*2*pi/d_max).
+    Uses the hankel transform followed by the exponential.  The values for *zz*
+    (or spin echo length, or delta), wavelength and sample thickness should
+    come from the dataset.  $q$ should be chosen such that the oscillations
+    in $I(q)$ are well sampled (e.g., $5 \cdot 2 \pi/d_{\max}$).
 
-    *SElength* [A] is the set of z points at which to compute the hankel transform
+    *SElength* [A] is the set of $z$ points at which to compute the
+    Hankel transform
 
     *wavelength* [m]  is the wavelength of each individual point *zz*
@@ -53,15 +38,20 @@
     *thickness* [cm] is the sample thickness.
 
-    *q* [A^{-1}] is the set of q points at which the model has been computed.
-    These should be equally spaced.
+    *q* [A$^{-1}$] is the set of $q$ points at which the model has been
+    computed. These should be equally spaced.
 
-    *I* [cm^{-1}] is the value of the SANS model at *q*
+    *I* [cm$^{-1}$] is the value of the SANS model at *q*
     """
-    G = np.zeros(len(SElength), 'd')
-    for i in range(len(SElength)):
-        integr = besselj(0,q*SElength[i])*Iq*q
-        G[i] = np.sum(integr)
-    dq=(q[1]-q[0])*1e10   # [m^-1] step size in q, needed for integration
-    G *= dq*1e10*2*pi # integr step, conver q into [m**-1] and 2 pi circle integr
+    G = np.zeros_like(SElength, 'd')
+    for i, SElength_i in enumerate(SElength):
+        integral = besselj(0, q*SElength_i)*Iq*q
+        G[i] = np.sum(integral)
+
+    # [m^-1] step size in q, needed for integration
+    dq = (q[1]-q[0])*1e10
+
+    # integration step, convert q into [m**-1] and 2 pi circle integration
+    G *= dq*1e10*2*pi
+
     P = exp(thickness*wavelength**2/(4*pi**2)*(G-G[0]))
 
Index: sasmodels/weights.py
===================================================================
--- sasmodels/weights.py	(revision 3c56da87f0995bdde77f4183b68e77b81584dde0)
+++ sasmodels/weights.py	(revision 5c962dff13d0a0a2f6d779be8df775f540958dbc)
@@ -22,4 +22,7 @@
 
     def get_pars(self):
+        """
+        Return the parameters to the disperser as a dictionary.
+        """
         pars = {'type': self.type}
         pars.update(self.__dict__)
@@ -28,4 +31,7 @@
     # pylint: disable=no-self-use
     def set_weights(self, values, weights):
+        """
+        Set the weights on the disperser if it is :class:`ArrayDispersion`.
+        """
         raise RuntimeError("set_weights is only available for ArrayDispersion")
 
@@ -36,5 +42,5 @@
         *center* is the center of the distribution
 
-        *lb*,*ub* are the min and max allowed values
+        *lb*, *ub* are the min and max allowed values
 
         *relative* is True if the distribution width is proportional to the
@@ -63,4 +69,11 @@
 
 class GaussianDispersion(Dispersion):
+    r"""
+    Gaussian dispersion, with 1-\ $\sigma$ width.
+
+    .. math::
+
+        w = \exp\left(-\tfrac12 (x - c)^2/\sigma^2\right)
+    """
     type = "gaussian"
     default = dict(npts=35, width=0, nsigmas=3)
@@ -72,4 +85,11 @@
 
 class RectangleDispersion(Dispersion):
+    r"""
+    Uniform dispersion, with width $\sqrt{3}\sigma$.
+
+    .. math::
+
+        w = 1
+    """
     type = "rectangle"
     default = dict(npts=35, width=0, nsigmas=1.70325)
@@ -81,18 +101,40 @@
 
 class LogNormalDispersion(Dispersion):
+    r"""
+    log Gaussian dispersion, with 1-\ $\sigma$ width.
+
+    .. math::
+
+        w = \frac{\exp\left(-\tfrac12 (\ln x - c)^2/\sigma^2\right)}{x\sigma}
+    """
     type = "lognormal"
     default = dict(npts=80, width=0, nsigmas=8)
     def _weights(self, center, sigma, lb, ub):
-        x = self._linspace(center, sigma, max(lb,1e-8), max(ub,1e-8))
-        px = np.exp(-0.5*(np.log(x)-center)**2)/sigma**2/(x*sigma)
+        x = self._linspace(center, sigma, max(lb, 1e-8), max(ub, 1e-8))
+        px = np.exp(-0.5*(np.log(x)-center)**2/sigma**2)/(x*sigma)
         return x, px
 
 
 class SchulzDispersion(Dispersion):
+    r"""
+    Schultz dispersion, with 1-\ $\sigma$ width.
+
+    .. math::
+
+        w = \frac{z^z\,R^{z-1}}{e^{Rz}\,c \Gamma(z)}
+
+    where $c$ is the center of the distribution, $R = x/c$ and $z=(c/\sigma)^2$.
+
+    This is evaluated using logarithms as
+
+    .. math::
+
+        w = \exp\left(z \ln z + (z-1)\ln R - Rz - \ln c - \ln \Gamma(z) \right)
+    """
     type = "schulz"
     default = dict(npts=80, width=0, nsigmas=8)
     def _weights(self, center, sigma, lb, ub):
-        x = self._linspace(center, sigma, max(lb,1e-8), max(ub,1e-8))
-        R= x/center
+        x = self._linspace(center, sigma, max(lb, 1e-8), max(ub, 1e-8))
+        R = x/center
         z = (center/sigma)**2
         arg = z*np.log(z) + (z-1)*np.log(R) - R*z - np.log(center) - gammaln(z)
@@ -102,4 +144,9 @@
 
 class ArrayDispersion(Dispersion):
+    r"""
+    Empirical dispersion curve.
+
+    Use :meth:`set_weights` to set $w = f(x)$.
+    """
     type = "array"
     default = dict(npts=35, width=0, nsigmas=1)
@@ -110,4 +157,7 @@
 
     def set_weights(self, values, weights):
+        """
+        Set the weights for the given x values.
+        """
         self.values = np.ascontiguousarray(values, 'd')
         self.weights = np.ascontiguousarray(weights, 'd')
@@ -117,5 +167,5 @@
         # TODO: interpolate into the array dispersion using npts
         x = center + self.values*sigma
-        idx = (x>=lb)&(x<=ub)
+        idx = (x >= lb) & (x <= ub)
         x = x[idx]
         px = self.weights[idx]
@@ -124,5 +174,5 @@
 
 # dispersion name -> disperser lookup table.
-models = dict((d.type,d) for d in (
+models = dict((d.type, d) for d in (
     GaussianDispersion, RectangleDispersion,
     ArrayDispersion, SchulzDispersion, LogNormalDispersion
@@ -149,13 +199,13 @@
     of the parameter, and false if it is an absolute width.
 
-    Returns *(value,weight)*, where *value* and *weight* are vectors.
+    Returns *(value, weight)*, where *value* and *weight* are vectors.
     """
     cls = models[disperser]
     obj = cls(n, width, nsigmas)
-    v,w = obj.get_weights(value, limits[0], limits[1], relative)
-    return v,w
+    v, w = obj.get_weights(value, limits[0], limits[1], relative)
+    return v, w
 
 # Hack to allow sasview dispersion objects to interoperate with sasmodels
-dispersers = dict((v.__name__,k) for k,v in models.items())
+dispersers = dict((v.__name__, k) for k, v in models.items())
 dispersers['DispersionModel'] = RectangleDispersion.type
 
