Index: doc/ref/gpu/gpu_computations.rst
===================================================================
--- doc/ref/gpu/gpu_computations.rst	(revision 711f17e515fff416ab1aa15ae93c3d245ff21525)
+++ doc/ref/gpu/gpu_computations.rst	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -5,5 +5,5 @@
 ****************
 SasView model evaluations can run on your graphics card (GPU) or they can run
-on the processor (CPU).
+on the processor (CPU). In general, calculations performed on the GPU will run faster.
 
 To run on the GPU, your computer must have OpenCL drivers installed.
@@ -39,6 +39,6 @@
 chose to run the model.
 
-If you don't want to use OpenCL, you can set *SAS_OPENCL=None*
-in the environment, and it will only use normal programs.
+**If you don't want to use OpenCL, you can set** *SAS_OPENCL=None*
+**in your environment settings, and it will only use normal programs.**
 
 If you want to use one of the other devices, you can run the following
Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision bb2d187bb2ab971bfa2283dc7e6225a4764cc649)
+++ sasmodels/compare.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -80,4 +80,5 @@
     -edit starts the parameter explorer
     -default/-demo* use demo vs default parameters
+    -html shows the model docs instead of running the model
 
 Any two calculation engines can be selected for comparison:
@@ -736,5 +737,5 @@
     'linear', 'log', 'q4',
     'hist', 'nohist',
-    'edit',
+    'edit', 'html',
     'demo', 'default',
     ])
@@ -845,4 +846,5 @@
         'use_demo'  : True,
         'zero'      : False,
+        'html'      : False,
     }
     engines = []
@@ -886,4 +888,5 @@
         elif arg == '-demo':    opts['use_demo'] = True
         elif arg == '-default':    opts['use_demo'] = False
+        elif arg == '-html':    opts['html'] = True
     # pylint: enable=bad-whitespace
 
@@ -961,8 +964,20 @@
     return opts
 
+def show_docs(opts):
+    # type: (Dict[str, Any]) -> None
+    """
+    show html docs for the model
+    """
+    import wx  # type: ignore
+    from .generate import view_html_from_info
+    app = wx.App() if wx.GetApp() is None else None
+    view_html_from_info(opts['def'])
+    if app: app.MainLoop()
+
+
 def explore(opts):
     # type: (Dict[str, Any]) -> None
     """
-    Explore the model using the Bumps GUI.
+    explore the model using the bumps gui.
     """
     import wx  # type: ignore
@@ -1057,5 +1072,7 @@
     opts = parse_opts(argv)
     if opts is not None:
-        if opts['explore']:
+        if opts['html']:
+            show_docs(opts)
+        elif opts['explore']:
             explore(opts)
         else:
Index: sasmodels/custom/__init__.py
===================================================================
--- sasmodels/custom/__init__.py	(revision 2a0c7a685e857b3f7d3721e9eaf84f3854af4a75)
+++ sasmodels/custom/__init__.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -8,5 +8,7 @@
 to occur without error.
 """
+from __future__ import division, print_function
 
+import sys
 import os
 from os.path import basename, splitext
@@ -26,4 +28,7 @@
     def load_module_from_path(fullname, path):
         """load module from *path* as *fullname*"""
+        # Clear out old definitions, if any
+        if fullname in sys.modules:
+            del sys.modules[fullname]
         module = imp.load_source(fullname, os.path.expanduser(path))
         #os.unlink(path+"c")  # remove the automatic pyc file
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision 462a115f2a030c6b417271399a8421130e3c21dc)
+++ sasmodels/generate.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -912,8 +912,11 @@
 
 def view_html(model_name):
-    from . import rst2html
     from . import modelinfo
     kernel_module = load_kernel_module(model_name)
     info = modelinfo.make_model_info(kernel_module)
+    view_html_from_info(info)
+
+def view_html_from_info(info):
+    from . import rst2html
     url = "file://"+dirname(info.filename)+"/"
     rst2html.wxview(make_html(info), url=url)
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 897ca7f478f1466ca3d839c8d89b623c240a431a)
+++ sasmodels/model_test.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -121,5 +121,5 @@
 
             # test using dll if desired
-            if 'dll' in loaders:
+            if 'dll' in loaders or not core.HAVE_OPENCL:
                 test_name = "Model: %s, Kernel: dll"%model_name
                 test_method_name = "test_%s_dll" % model_info.id
@@ -270,5 +270,5 @@
 
     # Build a test suite containing just the model
-    loaders = ['opencl']
+    loaders = ['opencl'] if core.HAVE_OPENCL else ['dll']
     models = [model]
     try:
@@ -288,9 +288,18 @@
         stream.writeln(tb)
 
-    # Check if there are user defined tests.
-    # Yes, it is naughty to peek into the structure of the test suite, and
-    # to assume that it contains only one test.
-    if not suite._tests[0].info.tests:
-        stream.writeln("Note: %s has no user defined tests."%model)
+    # Warn if there are no user defined tests.
+    # Note: the test suite constructed above only has one test in it, which
+    # runs through some smoke tests to make sure the model runs, then runs
+    # through the input-output pairs given in the model definition file.  To
+    # check if any such pairs are defined, therefore, we just need to check if
+    # they are in the first test of the test suite.  We do this with an
+    # iterator since we don't have direct access to the list of tests in the
+    # test suite.
+    for test in suite:
+        if not test.info.tests:
+            stream.writeln("Note: %s has no user defined tests."%model)
+        break
+    else:
+        stream.writeln("Note: no test suite created --- this should never happen")
 
 
@@ -325,8 +334,8 @@
         models = models[1:]
     elif models and models[0] == 'opencl_and_dll':
-        loaders = ['opencl', 'dll']
+        loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
         models = models[1:]
     else:
-        loaders = ['opencl', 'dll']
+        loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
     if not models:
         print("""\
@@ -357,7 +366,15 @@
     Run "nosetests sasmodels" on the command line to invoke it.
     """
-    tests = make_suite(['opencl', 'dll'], ['all'])
+    loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
+    tests = make_suite(loaders, ['all'])
     for test_i in tests:
-        yield test_i.run_all
+        # In order for nosetest to see the correct test name, need to set
+        # the description attribute of the returned function.  Since we
+        # can't do this for the returned instance, wrap it in a lambda and
+        # set the description on the lambda.  Otherwise we could just do:
+        #    yield test_i.run_all
+        L = lambda: test_i.run_all()
+        L.description = test_i.test_name
+        yield L
 
 
Index: sasmodels/models/core_shell_bicelle.py
===================================================================
--- sasmodels/models/core_shell_bicelle.py	(revision 416f5c7f5af38d5e5d926a5b6b140a94e7e60502)
+++ sasmodels/models/core_shell_bicelle.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -40,5 +40,5 @@
 .. math::
 
-    I(Q,\alpha) = \frac{\text{scale}}{V} \cdot
+    I(Q,\alpha) = \frac{\text{scale}}{V_t} \cdot
         F(Q,\alpha)^2 + \text{background}
 
Index: sasmodels/models/multilayer_vesicle.py
===================================================================
--- sasmodels/models/multilayer_vesicle.py	(revision 9a4811ac2bdb93459d640d111cba9f1016660790)
+++ sasmodels/models/multilayer_vesicle.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -5,6 +5,7 @@
 This model is a trivial extension of the core_shell_sphere function to include
 *N* shells where the core is filled with solvent and the shells are interleaved
-with layers of solvent. For *N = 1*, this returns the same as the vesicle model.
-The shell thicknessess and SLD are constant across all shells as expected for
+with layers of solvent. For *N = 1*, this returns the same as the vesicle model,
+except for the normalisation, which here is to outermost volume.
+The shell thicknessess and SLD are constant for all shells as expected for
 a multilayer vesicle.
 
@@ -15,4 +16,27 @@
 See the :ref:`core-shell-sphere` model for more documentation.
 
+The 1D scattering intensity is calculated in the following way (Guinier, 1955)
+
+.. math::
+
+    P(q) = \frac{\text{scale.volfraction}}{V_t} F^2(q) + \text{background}
+
+where
+
+.. math::
+
+     F(q) = (\rho_{shell}-\rho_{solv}) \sum_{i=1}^{n\_pairs} \left[
+     3V(R_i)\frac{\sin(qR_i)-qR_i\cos(qR_i)}{(qR_i)^3} \\
+      - 3V(R_i+t_s)\frac{\sin(q(R_i+t_s))-q(R_i+t_s)\cos(q(R_i+t_s))}{(q(R_i+t_s))^3}
+     \right]
+
+
+where $R_i = r_c + (i-1)(t_s + t_w)$
+   
+where $V_t$ is the volume of the whole particle, $V(R)$ is the volume of a sphere
+of radius $R$, $r_c$ is the radius of the core, $\rho_{shell}$ is the scattering length 
+density of a shell, $\rho_{solv}$ is the scattering length density of the solvent.
+
+
 The 2D scattering intensity is the same as 1D, regardless of the orientation
 of the q vector which is defined as:
@@ -22,8 +46,11 @@
     q = \sqrt{q_x^2 + q_y^2}
 
-.. note:
-    The outer most radius
-    $radius + n_pairs * thicn_shell + (n_pairs - 1) * thick_solvent$
-    is used as the effective radius for *S(Q)* when $P(Q) * S(Q)$ is applied.
+
+The outer most radius
+
+$radius + n\_pairs * thick\_shell + (n\_pairs- 1) * thick\_solvent$
+
+is used for both the volume fraction normalization and for the 
+effective radius for *S(Q)* when $P(Q) * S(Q)$ is applied.
 
 For information about polarised and magnetic scattering, see
@@ -62,5 +89,5 @@
     sld_solvent: solvent scattering length density
     sld: shell scattering length density
-    n_pairs:number of pairs of water/shell
+    n_pairs:number of "shell plus solvent" layer pairs
     background: incoherent background
         """
@@ -71,10 +98,10 @@
 parameters = [
     ["volfraction", "",  0.05, [0.0, 1],  "", "volume fraction of vesicles"],
-    ["radius", "Ang", 60.0, [0.0, inf],  "", "Core radius of the multishell"],
-    ["thick_shell", "Ang",        10.0, [0.0, inf],  "", "Shell thickness"],
-    ["thick_solvent", "Ang",        10.0, [0.0, inf],  "", "Water thickness"],
-    ["sld_solvent",    "1e-6/Ang^2",  6.4, [-inf, inf], "sld", "Core scattering length density"],
+    ["radius", "Ang", 60.0, [0.0, inf],  "", "radius of solvent filled core"],
+    ["thick_shell", "Ang",        10.0, [0.0, inf],  "", "thickness of one shell"],
+    ["thick_solvent", "Ang",        10.0, [0.0, inf],  "", "solvent thickness between shells"],
+    ["sld_solvent",    "1e-6/Ang^2",  6.4, [-inf, inf], "sld", "solvent scattering length density"],
     ["sld",   "1e-6/Ang^2",  0.4, [-inf, inf], "sld", "Shell scattering length density"],
-    ["n_pairs",     "",            2.0, [1.0, inf],  "", "Number of pairs of water and shell"],
+    ["n_pairs",     "",            2.0, [1.0, inf],  "", "Number of shell plus solvent layer pairs"],
     ]
 # pylint: enable=bad-whitespace, line-too-long
Index: sasmodels/models/vesicle.py
===================================================================
--- sasmodels/models/vesicle.py	(revision 42356c8890c04b71169941b12a36fc0e07e2034b)
+++ sasmodels/models/vesicle.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -75,5 +75,5 @@
         thickness: the shell thickness
         sld: the shell SLD
-        sld_slovent: the solvent (and core) SLD
+        sld_solvent: the solvent (and core) SLD
         background: incoherent background
         volfraction: shell volume fraction
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision 5b37fd681b84af2a1adc33b956ca441b19da76fe)
+++ sasmodels/sasview_model.py	(revision 1a6cd57bd7b12db89d69b335e984c55413295845)
@@ -115,5 +115,4 @@
     Load a custom model given the model path.
     """
-    #print("load custom", path)
     kernel_module = custom.load_custom_kernel_module(path)
     try:
@@ -124,7 +123,31 @@
         if model.name == "":
             model.name = splitext(basename(path))[0]
+        if not hasattr(model, 'filename'):
+            model.filename = kernel_module.__file__
+            # For old models, treat .pyc and .py files interchangeably.
+            # This is needed because of the Sum|Multi(p1,p2) types of models
+            # and the convoluted way in which they are created.
+            if model.filename.endswith(".py"):
+                logging.info("Loading %s as .pyc", model.filename)
+                model.filename = model.filename+'c'
+        if not hasattr(model, 'id'):
+            model.id = splitext(basename(model.filename))[0]
     except AttributeError:
         model_info = modelinfo.make_model_info(kernel_module)
         model = _make_model_from_info(model_info)
+
+    # If a model name already exists and we are loading a different model,
+    # use the model file name as the model name.
+    if model.name in MODELS and not model.filename == MODELS[model.name].filename:
+        _previous_name = model.name
+        model.name = model.id
+        
+        # If the new model name is still in the model list (for instance,
+        # if we put a cylinder.py in our plug-in directory), then append
+        # an identifier.
+        if model.name in MODELS and not model.filename == MODELS[model.name].filename:
+            model.name = model.id + '_user'
+        logging.info("Model %s already exists: using %s [%s]", _previous_name, model.name, model.filename)
+
     MODELS[model.name] = model
     return model
@@ -154,4 +177,5 @@
     attrs = _generate_model_attributes(model_info)
     attrs['__init__'] = __init__
+    attrs['filename'] = model_info.filename
     ConstructedModel = type(model_info.name, (SasviewModel,), attrs) # type: SasviewModelType
     return ConstructedModel
@@ -311,4 +335,8 @@
         else:
             hidden = set()
+        if self._model_info.structure_factor:
+            hidden.add('scale')
+            hidden.add('background')
+            self._model_info.parameters.defaults['background'] = 0.
 
         self._persistency_dict = {}
@@ -575,8 +603,5 @@
                             magnetic=is_magnetic)
         calculator.release()
-        try:
-            self._model.release()
-        except:
-            pass
+        self._model.release()
         return result
 
@@ -659,5 +684,7 @@
                 return [self.multiplicity], [1.0]
             else:
-                return [np.NaN], [1.0]
+                # For hidden parameters use the default value.
+                value = self._model_info.parameters.defaults.get(par.name, np.NaN)
+                return [value], [1.0]
         elif par.polydisperse:
             dis = self.dispersion[par.name]
@@ -680,4 +707,15 @@
     cylinder = Cylinder()
     return cylinder.evalDistribution([0.1, 0.1])
+
+def test_structure_factor():
+    # type: () -> float
+    """
+    Test that a sasview model (cylinder) can be run.
+    """
+    Model = _make_standard_model('hardsphere')
+    model = Model()
+    value = model.evalDistribution([0.1, 0.1])
+    if np.isnan(value):
+        raise ValueError("hardsphere returns null")
 
 def test_rpa():
