Index: sasmodels/bumps_model.py
===================================================================
--- sasmodels/bumps_model.py	(revision 190fc2b167d3d89ca9c3b7d3128bdf8bde9f67d0)
+++ sasmodels/bumps_model.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -83,10 +83,9 @@
     pars = {}
     for p in model_info['parameters']:
-        name, default, limits = p[0], p[2], p[3]
-        value = kwargs.pop(name, default)
-        pars[name] = Parameter.default(value, name=name, limits=limits)
+        value = kwargs.pop([p.name, p.default])
+        pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits)
     for name in model_info['partype']['pd-2d']:
         for xpart, xdefault, xlimits in [
-                ('_pd', 0., limits),
+                ('_pd', 0., pars[name].limits),
                 ('_pd_n', 35., (0, 1000)),
                 ('_pd_nsigma', 3., (0, 10)),
Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision 91c5fdc1e57978ebef854d27add589c392a93f3a)
+++ sasmodels/compare.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -681,13 +681,13 @@
     """
     # Get the default values for the parameters
-    pars = dict((p[0], p[2]) for p in model_info['parameters'])
+    pars = dict((p.name, p.default) for p in model_info['parameters'])
 
     # Fill in default values for the polydispersity parameters
     for p in model_info['parameters']:
-        if p[4] in ('volume', 'orientation'):
-            pars[p[0]+'_pd'] = 0.0
-            pars[p[0]+'_pd_n'] = 0
-            pars[p[0]+'_pd_nsigma'] = 3.0
-            pars[p[0]+'_pd_type'] = "gaussian"
+        if p.type in ('volume', 'orientation'):
+            pars[p.name+'_pd'] = 0.0
+            pars[p.name+'_pd_n'] = 0
+            pars[p.name+'_pd_nsigma'] = 3.0
+            pars[p.name+'_pd_type'] = "gaussian"
 
     # Plug in values given in demo
@@ -833,4 +833,5 @@
         pars = suppress_pd(pars)
     pars.update(presets)  # set value after random to control value
+    #import pprint; pprint.pprint(model_info)
     constrain_pars(model_info, pars)
     constrain_new_to_old(model_info, pars)
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision 2f0c07deb2cb7a8c0c93476fbf1e0ffbe6c278cb)
+++ sasmodels/generate.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -80,6 +80,6 @@
     is selected, or when an initial value is not otherwise specified.
 
-    [*lb*, *ub*] are the hard limits on the parameter value, used to limit
-    the polydispersity density function.  In the fit, the parameter limits
+    *limits = [lb, ub]* are the hard limits on the parameter value, used to
+    limit the polydispersity density function.  In the fit, the parameter limits
     given to the fit are the limits  on the central value of the parameter.
     If there is polydispersity, it will evaluate parameter values outside
@@ -216,6 +216,10 @@
 import re
 import string
+from collections import namedtuple
 
 import numpy as np
+
+PARAMETER_FIELDS = ['name', 'units', 'default', 'limits', 'type', 'description']
+Parameter = namedtuple('Parameter', PARAMETER_FIELDS)
 
 #TODO: determine which functions are useful outside of generate
@@ -293,7 +297,7 @@
     """
     column_widths = [
-        max(len(p[0]) for p in pars),
-        max(len(p[-1]) for p in pars),
-        max(len(format_units(p[1])) for p in pars),
+        max(len(p.name) for p in pars),
+        max(len(p.description) for p in pars),
+        max(len(format_units(p.units)) for p in pars),
         PARTABLE_VALUE_WIDTH,
         ]
@@ -310,8 +314,8 @@
     for p in pars:
         lines.append(" ".join([
-            "%-*s" % (column_widths[0], p[0]),
-            "%-*s" % (column_widths[1], p[-1]),
-            "%-*s" % (column_widths[2], format_units(p[1])),
-            "%*g" % (column_widths[3], p[2]),
+            "%-*s" % (column_widths[0], p.name),
+            "%-*s" % (column_widths[1], p.description),
+            "%-*s" % (column_widths[2], format_units(p.units)),
+            "%*g" % (column_widths[3], p.default),
             ]))
     lines.append(sep)
@@ -469,13 +473,13 @@
     fixed_2d = partype['fixed-1d']
 
-    iq_parameters = [p[0]
+    iq_parameters = [p.name
                      for p in model_info['parameters'][2:]  # skip scale, background
-                     if p[0] in set(fixed_1d + pd_1d)]
-    iqxy_parameters = [p[0]
+                     if p.name in set(fixed_1d + pd_1d)]
+    iqxy_parameters = [p.name
                        for p in model_info['parameters'][2:]  # skip scale, background
-                       if p[0] in set(fixed_2d + pd_2d)]
-    volume_parameters = [p[0]
+                       if p.name in set(fixed_2d + pd_2d)]
+    volume_parameters = [p.name
                          for p in model_info['parameters']
-                         if p[4] == 'volume']
+                         if p.type == 'volume']
 
     # Fill in defintions for volume parameters
@@ -606,19 +610,18 @@
 
     for p in pars:
-        name, ptype = p[0], p[4]
-        if ptype == 'volume':
-            partype['pd-1d'].append(name)
-            partype['pd-2d'].append(name)
-            partype['pd-rel'].add(name)
-        elif ptype == 'magnetic':
-            partype['fixed-2d'].append(name)
-        elif ptype == 'orientation':
-            partype['pd-2d'].append(name)
-        elif ptype == '':
-            partype['fixed-1d'].append(name)
-            partype['fixed-2d'].append(name)
+        if p.type == 'volume':
+            partype['pd-1d'].append(p.name)
+            partype['pd-2d'].append(p.name)
+            partype['pd-rel'].add(p.name)
+        elif p.type == 'magnetic':
+            partype['fixed-2d'].append(p.name)
+        elif p.type == 'orientation':
+            partype['pd-2d'].append(p.name)
+        elif p.type == '':
+            partype['fixed-1d'].append(p.name)
+            partype['fixed-2d'].append(p.name)
         else:
-            raise ValueError("unknown parameter type %r" % ptype)
-        partype[ptype].append(name)
+            raise ValueError("unknown parameter type %r" % p.type)
+        partype[p.type].append(p.name)
 
     return partype
@@ -628,9 +631,12 @@
     Process parameter block, precalculating parameter details.
     """
+    # convert parameters into named tuples
+    pars = [Parameter(*p) for p in model_info['parameters']]
     # Fill in the derived attributes
-    partype = categorize_parameters(model_info['parameters'])
-    model_info['limits'] = dict((p[0], p[3]) for p in model_info['parameters'])
+    model_info['parameters'] = pars
+    partype = categorize_parameters(pars)
+    model_info['limits'] = dict((p.name, p.limits) for p in pars)
     model_info['partype'] = partype
-    model_info['defaults'] = dict((p[0], p[2]) for p in model_info['parameters'])
+    model_info['defaults'] = dict((p.name, p.default) for p in pars)
     if model_info.get('demo', None) is None:
         model_info['demo'] = model_info['defaults']
@@ -649,4 +655,5 @@
     * *id* is the id of the kernel
     * *name* is the display name of the kernel
+    * *filename* is the full path to the module defining the file (if any)
     * *title* is a short description of the kernel
     * *description* is a long description of the kernel (this doesn't seem
Index: sasmodels/kernelpy.py
===================================================================
--- sasmodels/kernelpy.py	(revision 17bbadde9de73711781f748816b06e6956dc8cfa)
+++ sasmodels/kernelpy.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -111,5 +111,5 @@
 
         # First two fixed pars are scale and background
-        pars = [p[0] for p in model_info['parameters'][2:]]
+        pars = [p.name for p in model_info['parameters'][2:]]
         offset = len(self.q_input.q_vectors)
         self.args = self.q_input.q_vectors + [None] * len(pars)
Index: sasmodels/list_pars.py
===================================================================
--- sasmodels/list_pars.py	(revision 17bbadde9de73711781f748816b06e6956dc8cfa)
+++ sasmodels/list_pars.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -26,7 +26,6 @@
         model_info = load_model_info(name)
         for p in model_info['parameters']:
-            pname = p[0]
-            partable.setdefault(pname, [])
-            partable[pname].append(name)
+            partable.setdefault(p.name, [])
+            partable[p.name].append(name)
     return partable
 
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 17bbadde9de73711781f748816b06e6956dc8cfa)
+++ sasmodels/model_test.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -251,5 +251,6 @@
   python -m sasmodels.model_test [-v] [opencl|dll] model1 model2 ...
 
-If -v is included on the
+If -v is included on the command line, then use verboe output.
+
 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.
Index: sasmodels/product.py
===================================================================
--- sasmodels/product.py	(revision 35b4c47271c19171252212163d34c16621a369ba)
+++ sasmodels/product.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -28,9 +28,9 @@
     s_id, s_name, s_pars = s_info['id'], s_info['name'], s_info['parameters']
     # We require models to start with scale and background
-    assert s_pars[SCALE][0] == 'scale'
-    assert s_pars[BACKGROUND][0] == 'background'
+    assert s_pars[SCALE].name == 'scale'
+    assert s_pars[BACKGROUND].name == 'background'
     # We require structure factors to start with effect radius and volfraction
-    assert s_pars[EFFECT_RADIUS][0] == 'effect_radius'
-    assert s_pars[VOLFRACTION][0] == 'volfraction'
+    assert s_pars[EFFECT_RADIUS].name == 'effect_radius'
+    assert s_pars[VOLFRACTION].name == 'volfraction'
     # Combine the parameter sets.  We are skipping the first three
     # parameters of S since scale, background are defined in P and
@@ -38,5 +38,5 @@
     pars = p_pars + s_pars[3:]
     # check for duplicates; can't use assertion since they may never be checked
-    if len(set(p[0] for p in pars)) != len(pars):
+    if len(set(p.name for p in pars)) != len(pars):
         raise ValueError("Duplicate parameters in %s and %s"%(p_id))
     # For comparison with sasview, determine the old parameters.
@@ -52,10 +52,16 @@
     model_info['title'] = 'Product of %s and structure factor %s'%(p_name, s_name)
     model_info['description'] = model_info['title']
+    model_info['docs'] = model_info['title']
     model_info['category'] = "custom"
     model_info['parameters'] = pars
-    # Remember the component info blocks so we can build the product model
-    model_info['composition'] = ('product', [p_info, s_info])
+    #model_info['single'] = p_info['single'] and s_info['single']
+    model_info['structure_factor'] = False
+    model_info['variant_info'] = None
+    #model_info['tests'] = []
+    #model_info['source'] = []
+    # Iq, Iqxy, form_volume, ER, VR and sesans
     model_info['oldname'] = oldname
     model_info['oldpars'] = oldpars
+    model_info['composition'] = ('product', [p_info, s_info])
     process_parameters(model_info)
     return model_info
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision 28da77d128132a61c3693d27c989d800ea525f89)
+++ sasmodels/sasview_model.py	(revision fcd7bbda85a603b85101ba7ec6495729497e6b3f)
@@ -60,7 +60,7 @@
         self.dispersion = dict()
         partype = model.info['partype']
-        for name, units, default, limits, _, _ in model.info['parameters']:
-            self.params[name] = default
-            self.details[name] = [units] + limits
+        for p in model.info['parameters']:
+            self.params[p.name] = p.default
+            self.details[p.name] = [p.units] + p.limits
 
         for name in partype['pd-2d']:
