Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/compare.py	(revision 73a3e225dd211075ac3b1df6e80c8ca6ffc95425)
@@ -16,5 +16,5 @@
 from . import core
 from . import kerneldll
-from . import generate
+from . import models
 from .data import plot_theory, empty_data1D, empty_data2D
 from .direct_model import DirectModel
@@ -116,5 +116,5 @@
         return 2*np.random.rand()*(v if v != 0 else 1)
 
-def randomize_model(pars, seed=None):
+def randomize_model(name, pars, seed=None):
     if seed is None:
         seed = np.random.randint(1e9)
@@ -122,20 +122,8 @@
     # Note: the sort guarantees order of calls to random number generator
     pars = dict((p,randomize(p,v)) for p,v in sorted(pars.items()))
-
-    return pars, seed
-
-def constrain_pars(model_definition, pars):
-    name = model_definition.name
+    # The capped cylinder model has a constraint on its parameters
     if name == 'capped_cylinder' and pars['cap_radius'] < pars['radius']:
         pars['radius'],pars['cap_radius'] = pars['cap_radius'],pars['radius']
-
-    # These constraints are only needed for comparison to sasview
-    if name in ('teubner_strey','broad_peak'):
-        del pars['scale']
-    if name in ('guinier',):
-        del pars['background']
-    if getattr(model_definition, 'category', None) == 'structure-factor':
-        del pars['scale'], pars['background']
-
+    return pars, seed
 
 def parlist(pars):
@@ -221,6 +209,4 @@
 
 def compare(name, pars, Ncpu, Nocl, opts, set_pars):
-    model_definition = core.load_model_definition(name)
-
     view = 'linear' if '-linear' in opts else 'log' if '-log' in opts else 'q4' if '-q4' in opts else 'log'
 
@@ -245,6 +231,5 @@
     if '-random' in opts or '-random' in opt_values:
         seed = int(opt_values['-random']) if '-random' in opt_values else None
-        pars, seed = randomize_model(pars, seed=seed)
-        constrain_pars(model_definition, pars)
+        pars, seed = randomize_model(name, pars, seed=seed)
         print "Randomize using -random=%i"%seed
     pars.update(set_pars)  # set value after random to control value
@@ -256,4 +241,5 @@
         print "pars",parlist(pars)
 
+    model_definition = core.load_model_definition(name)
     # OpenCl calculation
     if Nocl > 0:
@@ -261,5 +247,4 @@
                                     dtype=dtype, cutoff=cutoff, Nevals=Nocl)
         print "opencl t=%.1f ms, intensity=%.0f"%(ocl_time, sum(ocl))
-        #print "ocl", ocl
         #print max(ocl), min(ocl)
 
@@ -276,5 +261,4 @@
             #print "ocl/sasview", (ocl-pars['background'])/(cpu-pars['background'])
             print "sasview t=%.1f ms, intensity=%.0f"%(cpu_time, sum(cpu))
-            #print "sasview",cpu
         except ImportError:
             Ncpu = 0
@@ -412,8 +396,9 @@
 
 
-def get_demo_pars(model_definition):
-    info = generate.make_info(model_definition)
-    pars = dict((p[0],p[2]) for p in info['parameters'])
-    pars.update(info['demo'])
+def get_demo_pars(name):
+    __import__('sasmodels.models.'+name)
+    model = getattr(models, name)
+    pars = getattr(model, 'demo', None)
+    if pars is None: pars = dict((p[0],p[2]) for p in model.parameters)
     return pars
 
@@ -440,6 +425,5 @@
     # if model does not define demo parameters
     name = args[0]
-    model_definition = core.load_model_definition(name)
-    pars = get_demo_pars(model_definition)
+    pars = get_demo_pars(name)
 
     Nopencl = int(args[1]) if len(args) > 1 else 5
Index: sasmodels/compare_many.py
===================================================================
--- sasmodels/compare_many.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/compare_many.py	(revision e922c5d4b9d6513548de74d7fa2d4b31e7b3f735)
@@ -9,5 +9,5 @@
 from .compare import (MODELS, randomize_model, suppress_pd, eval_sasview,
                       eval_opencl, eval_ctypes, make_data, get_demo_pars,
-                      columnize, constrain_pars)
+                      columnize)
 
 def get_stats(target, value, index):
@@ -36,30 +36,35 @@
 def compare_instance(name, data, index, N=1, mono=True, cutoff=1e-5):
     model_definition = core.load_model_definition(name)
-    pars = get_demo_pars(model_definition)
+    pars = get_demo_pars(name)
     header = '\n"Model","%s","Count","%d"'%(name, N)
     if not mono: header += ',"Cutoff",%g'%(cutoff,)
     print(header)
 
+    # Stuff the failure flag into a mutable object so we can update it from
+    # within the nested function.  Note that the nested function uses "pars"
+    # which is dynamically scoped, not lexically scoped in this context.  That
+    # is, pars is replaced each time in the loop, so don't assume that it is
+    # the default values defined above.
     def trymodel(fn, *args, **kw):
         try:
-            result, _ = fn(model_definition, pars_i, data, *args, **kw)
-        except KeyboardInterrupt:
-            raise
+            result, _ = fn(model_definition, pars, data, *args, **kw)
         except:
-            print >>sys.stderr, traceback.format_exc()
-            print >>sys.stderr, "when comparing",name,"for seed",seed
-            if hasattr(data, 'qx_data'):
-                result = np.NaN*data.data
-            else:
-                result = np.NaN*data.x
+            result = np.NaN
+            traceback.print_exc()
         return result
 
     num_good = 0
     first = True
-    for k in range(N):
-        print >>sys.stderr, name, k
-        pars_i, seed = randomize_model(pars)
-        constrain_pars(model_definition, pars_i)
-        if mono: suppress_pd(pars_i)
+    for _ in range(N):
+        pars, seed = randomize_model(name, pars)
+        if mono: suppress_pd(pars)
+
+        # Force parameter constraints on a per-model basis.
+        if name in ('teubner_strey','broad_peak'):
+            pars['scale'] = 1.0
+        #if name == 'parallelepiped':
+        #    pars['a_side'],pars['b_side'],pars['c_side'] = \
+        #        sorted([pars['a_side'],pars['b_side'],pars['c_side']])
+
 
         good = True
@@ -92,7 +97,7 @@
             good = good and (stats[0] < 1e-14)
 
-        columns += [v for _,v in sorted(pars_i.items())]
+        columns += [v for _,v in sorted(pars.items())]
         if first:
-            print_column_headers(pars_i, labels)
+            print_column_headers(pars, labels)
             first = False
         if good:
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/generate.py	(revision 4eac427494a8f8fcd648a1d147d8ab575268e6ec)
@@ -99,10 +99,5 @@
 The kernel module must set variables defining the kernel meta data:
 
-    *id* is an implicit variable formed from the filename.  It will be
-    a valid python identifier, and will be used as the reference into
-    the html documentation, with '_' replaced by '-'.
-
-    *name* is the model name as displayed to the user.  If it is missing,
-    it will be constructed from the id.
+    *name* is the model name
 
     *title* is a short description of the model, suitable for a tool tip,
@@ -119,8 +114,4 @@
     the kernel functions.
 
-    *category* is the default category for the model.  Models in the
-    *structure-factor* category do not have *scale* and *background*
-    added.
-
     *source* is the list of C-99 source files that must be joined to
     create the OpenCL kernel functions.  The files defining the functions
@@ -143,9 +134,12 @@
     this step is significant.
 
+An *info* dictionary is constructed from the kernel meta data and
+returned to the caller.
+
+Additional fields can be defined in the kernel definition file that
+are not needed for sas modelling.
+
     *demo* is a dictionary of parameter=value defining a set of
-    parameters to use by default when *compare* is called.  Any
-    parameter not set in *demo* gets the initial value from the
-    parameter list.  *demo* is mostly needed to set the default
-    polydispersity values for tests.
+    parameters to use by default when *compare* is called.
 
     *oldname* is the name of the model in sasview before sasmodels
@@ -155,8 +149,4 @@
     of the new model against the values of the old model before
     you are ready to add the new model to sasmodels.
-
-
-An *info* dictionary is constructed from the kernel meta data and
-returned to the caller.
 
 The model evaluator, function call sequence consists of q inputs and the return vector,
@@ -200,5 +190,5 @@
 
 import sys
-from os.path import abspath, dirname, join as joinpath, exists, basename
+from os.path import abspath, dirname, join as joinpath, exists
 import re
 
@@ -243,7 +233,7 @@
 # description and its parameter table.  The remainder of the doc comes
 # from the module docstring.
-DOC_HEADER = """.. _%(id)s:
-
-%(name)s
+DOC_HEADER = """.. _%(name)s:
+
+%(label)s
 =======================================================
 
@@ -264,4 +254,5 @@
     Generate the parameter table to include in the sphinx documentation.
     """
+    pars = COMMON_PARAMETERS + pars
     column_widths = [
         max(len(p[0]) for p in pars),
@@ -532,30 +523,20 @@
         }
 
-def make_info(kernel_module):
-    """
-    Interpret the model definition file, categorizing the parameters.
-    """
+def make(kernel_module):
+    """
+    Build an OpenCL/ctypes function from the definition in *kernel_module*.
+
+    The module can be loaded with a normal python import statement if you
+    know which module you need, or with __import__('sasmodels.model.'+name)
+    if the name is in a string.
+    """
+    # TODO: allow Iq and Iqxy to be defined in python
     #print kernelfile
-    category = getattr(kernel_module, 'category', None)
-    parameters = COMMON_PARAMETERS + kernel_module.parameters
-    # Default the demo parameters to the starting values for the individual
-    # parameters if an explicit demo parameter set has not been specified.
-    demo_parameters = getattr(kernel_module, 'demo', None)
-    if demo_parameters is None:
-        demo_parameters = dict((p[0],p[2]) for p in parameters)
-    filename = abspath(kernel_module.__file__)
-    kernel_id = basename(filename)[:-3]
-    name = getattr(kernel_module, 'name', None)
-    if name is None:
-        name = " ".join(w.capitalize() for w in kernel_id.split('_'))
     info = dict(
-        id = kernel_id,  # string used to load the kernel
         filename=abspath(kernel_module.__file__),
-        name=name,
+        name=kernel_module.name,
         title=kernel_module.title,
         description=kernel_module.description,
-        category=category,
-        parameters=parameters,
-        demo=demo_parameters,
+        parameters=COMMON_PARAMETERS + kernel_module.parameters,
         source=getattr(kernel_module, 'source', []),
         oldname=kernel_module.oldname,
@@ -569,15 +550,5 @@
     info['partype'] = categorize_parameters(info['parameters'])
     info['defaults'] = dict((p[0], p[2]) for p in info['parameters'])
-    return info
-
-def make(kernel_module):
-    """
-    Build an OpenCL/ctypes function from the definition in *kernel_module*.
-
-    The module can be loaded with a normal python import statement if you
-    know which module you need, or with __import__('sasmodels.model.'+name)
-    if the name is in a string.
-    """
-    info = make_info(kernel_module)
+
     # Assume if one part of the kernel is python then all parts are.
     source = make_model(info) if not callable(info['Iq']) else None
@@ -588,9 +559,8 @@
     Return the documentation for the model.
     """
-    info = make_info(kernel_module)
-    subst = dict(id=info['id'].replace('_', '-'),
-                 name=info['name'],
-                 title=info['title'],
-                 parameters=make_partable(info['parameters']),
+    subst = dict(name=kernel_module.name.replace('_', '-'),
+                 label=" ".join(kernel_module.name.split('_')).capitalize(),
+                 title=kernel_module.title,
+                 parameters=make_partable(kernel_module.parameters),
                  docs=kernel_module.__doc__)
     return DOC_HEADER % subst
Index: sasmodels/models/HayterMSAsq.py
===================================================================
--- sasmodels/models/HayterMSAsq.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/models/HayterMSAsq.py	(revision 63b32bbc85e7dea7a022cf604ac020dee56d7a88)
@@ -75,5 +75,4 @@
                "dielectric constant of solvent (default water), for Debye length"],
              ]
-category = "structure-factor"
 
 # No volume normalization despite having a volume parameter
Index: sasmodels/models/bcc.py
===================================================================
--- sasmodels/models/bcc.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/models/bcc.py	(revision 485aee225e23a0dad14b9acfc70c0c7072e650b9)
@@ -134,5 +134,5 @@
     radius=40,
     theta=60, phi=60, psi=60,
-    radius_pd=.2, radius_pd_n=2,
+    radius_pd=.2, radius_pd_n=0.2,
     theta_pd=15, theta_pd_n=0,
     phi_pd=15, phi_pd_n=0,
Index: sasmodels/models/parallelepiped.py
===================================================================
--- sasmodels/models/parallelepiped.py	(revision cd3dba0c60af7479ce1bae901d065d3d5786c8ea)
+++ sasmodels/models/parallelepiped.py	(revision 9fd094c5d046458fba5a84af0a3e5dbccd0fa10b)
@@ -158,5 +158,5 @@
             a_side_pd=0.1, a_side_pd_n=10,
             b_side_pd=0.1, b_side_pd_n=1,
-            c_side_pd=0.1, c_side_pd_n=1,
+            c_side_pd=0.1, c_side_pd_n=10,
             theta_pd=10, theta_pd_n=1,
             phi_pd=10, phi_pd_n=1,
