Index: README.rst
===================================================================
--- README.rst	(revision 84bc3c1266774b5725b3ade395f50b2910fb50af)
+++ README.rst	(revision e30d645b749d90d70af7bd103c010fae12fede8c)
@@ -46,11 +46,4 @@
 lamellar.py is an example of a single file model with embedded C code.
 
-Magnetism hasn't been implemented yet.  We may want a separate Imagnetic
-calculator with the extra parameters and calculations.   We should
-return all desired spin states together so we can share the work of
-computing the form factors for the different magnetic contrasts.  This
-will mean extending the data handler to support multiple cross sections
-in the same data set.
-
 |TravisStatus|_
 
Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision a0d75cedeb80e006f8c1b78e51a89515b8662b50)
+++ sasmodels/compare.py	(revision fe25edaa0ee387b937ccacc643b1d54f998d9e95)
@@ -33,4 +33,5 @@
 import datetime
 import traceback
+import re
 
 import numpy as np  # type: ignore
@@ -38,12 +39,13 @@
 from . import core
 from . import kerneldll
-from . import weights
+from . import exception
 from .data import plot_theory, empty_data1D, empty_data2D
 from .direct_model import DirectModel
 from .convert import revert_name, revert_pars, constrain_new_to_old
+from .generate import FLOAT_RE
 
 try:
     from typing import Optional, Dict, Any, Callable, Tuple
-except:
+except Exception:
     pass
 else:
@@ -58,5 +60,5 @@
 sasmodels rewrite.
 
-model is the name of the model to compare (see below).
+model or model1,model2 are the names of the models to compare (see below).
 N1 is the number of times to run sasmodels (default=1).
 N2 is the number times to run sasview (default=1).
@@ -71,4 +73,5 @@
     -preset*/-random[=seed] preset or random parameters
     -mono/-poly* force monodisperse/polydisperse
+    -magnetic/-nonmagnetic* suppress magnetism
     -cutoff=1e-5* cutoff value for including a point in polydispersity
     -pars/-nopars* prints the parameter set or not
@@ -81,5 +84,5 @@
     -default/-demo* use demo vs default parameters
     -html shows the model docs instead of running the model
-    -title="graph title" adds a title to the plot
+    -title="note" adds note to the plot title, after the model name
 
 Any two calculation engines can be selected for comparison:
@@ -89,9 +92,11 @@
     -sasview sets the sasview calculation engine
 
-The default is -single -sasview.  Note that the interpretation of quad
+The default is -single -double.  Note that the interpretation of quad
 precision depends on architecture, and may vary from 64-bit to 128-bit,
 with 80-bit floats being common (1e-19 precision).
 
 Key=value pairs allow you to set specific values for the model parameters.
+Key=value1,value2 to compare different values of the same parameter.
+value can be an expression including other parameters
 """
 
@@ -108,4 +113,7 @@
 
 kerneldll.ALLOW_SINGLE_PRECISION_DLLS = True
+
+# list of math functions for use in evaluating parameters
+MATH = dict((k,getattr(math, k)) for k in dir(math) if not k.startswith('_'))
 
 # CRUFT python 2.6
@@ -314,11 +322,14 @@
         name = name.split('*')[0]
 
-    if name == 'capped_cylinder' and pars['radius_cap'] < pars['radius']:
-        pars['radius'], pars['radius_cap'] = pars['radius_cap'], pars['radius']
-    if name == 'barbell' and pars['radius_bell'] < pars['radius']:
-        pars['radius'], pars['radius_bell'] = pars['radius_bell'], pars['radius']
-
-    # Limit guinier to an Rg such that Iq > 1e-30 (single precision cutoff)
-    if name == 'guinier':
+    if name == 'barbell':
+        if pars['radius_bell'] < pars['radius']:
+            pars['radius'], pars['radius_bell'] = pars['radius_bell'], pars['radius']
+
+    elif name == 'capped_cylinder':
+        if pars['radius_cap'] < pars['radius']:
+            pars['radius'], pars['radius_cap'] = pars['radius_cap'], pars['radius']
+
+    elif name == 'guinier':
+        # Limit guinier to an Rg such that Iq > 1e-30 (single precision cutoff)
         #q_max = 0.2  # mid q maximum
         q_max = 1.0  # high q maximum
@@ -326,5 +337,11 @@
         pars['rg'] = min(pars['rg'], rg_max)
 
-    if name == 'rpa':
+    elif name == 'pearl_necklace':
+        if pars['radius'] < pars['thick_string']:
+            pars['radius'], pars['thick_string'] = pars['thick_string'], pars['radius']
+        pars['num_pearls'] = math.ceil(pars['num_pearls'])
+        pass
+
+    elif name == 'rpa':
         # Make sure phi sums to 1.0
         if pars['case_num'] < 2:
@@ -337,4 +354,7 @@
             pars['Phi'+c] /= total
 
+    elif name == 'stacked_disks':
+        pars['n_stacking'] = math.ceil(pars['n_stacking'])
+
 def parlist(model_info, pars, is2d):
     # type: (ModelInfo, ParameterSet, bool) -> str
@@ -344,5 +364,10 @@
     lines = []
     parameters = model_info.parameters
+    magnetic = False
     for p in parameters.user_parameters(pars, is2d):
+        if any(p.id.startswith(x) for x in ('M0:', 'mtheta:', 'mphi:')):
+            continue
+        if p.id.startswith('up:') and not magnetic:
+            continue
         fields = dict(
             value=pars.get(p.id, p.default),
@@ -352,6 +377,10 @@
             pdtype=pars.get(p.id+"_pd_type", 'gaussian'),
             relative_pd=p.relative_pd,
+            M0=pars.get('M0:'+p.id, 0.),
+            mphi=pars.get('mphi:'+p.id, 0.),
+            mtheta=pars.get('mtheta:'+p.id, 0.),
         )
         lines.append(_format_par(p.name, **fields))
+        magnetic = magnetic or fields['M0'] != 0.
     return "\n".join(lines)
 
@@ -359,5 +388,5 @@
 
 def _format_par(name, value=0., pd=0., n=0, nsigma=3., pdtype='gaussian',
-                relative_pd=False):
+                relative_pd=False, M0=0., mphi=0., mtheta=0.):
     # type: (str, float, float, int, float, str) -> str
     line = "%s: %g"%(name, value)
@@ -367,4 +396,6 @@
         line += " +/- %g  (%d points in [-%g,%g] sigma %s)"\
                 % (pd, n, nsigma, nsigma, pdtype)
+    if M0 != 0.:
+        line += "  M0:%.3f  mphi:%.1f  mtheta:%.1f" % (M0, mphi, mtheta)
     return line
 
@@ -380,4 +411,17 @@
     for p in pars:
         if p.endswith("_pd_n"): pars[p] = 0
+    return pars
+
+def suppress_magnetism(pars):
+    # type: (ParameterSet) -> ParameterSet
+    """
+    Suppress theta_pd for now until the normalization is resolved.
+
+    May also suppress complete polydispersity of the model to test
+    models more quickly.
+    """
+    pars = pars.copy()
+    for p in pars:
+        if p.startswith("M0:"): pars[p] = 0
     return pars
 
@@ -466,8 +510,15 @@
             if k.endswith('.type'):
                 par = k[:-5]
+                if v == 'gaussian': continue
                 cls = dispersers[v if v != 'rectangle' else 'rectangula']
                 handle = cls()
                 model[0].disperser_handles[par] = handle
-                model[0].set_dispersion(par, handle)
+                try:
+                    model[0].set_dispersion(par, handle)
+                except Exception:
+                    exception.annotate_exception("while setting %s to %r"
+                                                 %(par, v))
+                    raise
+
 
         #print("sasview pars",oldpars)
@@ -605,6 +656,13 @@
     parameters.
     """
-    n_base, n_comp = opts['n1'], opts['n2']
-    pars = opts['pars']
+    result = run_models(opts, verbose=True)
+    if opts['plot']:  # Note: never called from explore
+        plot_models(opts, result, limits=limits)
+
+def run_models(opts, verbose=False):
+    # type: (Dict[str, Any]) -> Dict[str, Any]
+
+    n_base, n_comp = opts['count']
+    pars, pars2 = opts['pars']
     data = opts['data']
 
@@ -612,4 +670,5 @@
     base = opts['engines'][0] if n_base else None
     comp = opts['engines'][1] if n_comp else None
+
     base_time = comp_time = None
     base_value = comp_value = resid = relerr = None
@@ -620,6 +679,7 @@
             base_raw, base_time = time_calculation(base, pars, n_base)
             base_value = np.ma.masked_invalid(base_raw)
-            print("%s t=%.2f ms, intensity=%.0f"
-                  % (base.engine, base_time, base_value.sum()))
+            if verbose:
+                print("%s t=%.2f ms, intensity=%.0f"
+                      % (base.engine, base_time, base_value.sum()))
             _show_invalid(data, base_value)
         except ImportError:
@@ -630,8 +690,9 @@
     if n_comp > 0:
         try:
-            comp_raw, comp_time = time_calculation(comp, pars, n_comp)
+            comp_raw, comp_time = time_calculation(comp, pars2, n_comp)
             comp_value = np.ma.masked_invalid(comp_raw)
-            print("%s t=%.2f ms, intensity=%.0f"
-                  % (comp.engine, comp_time, comp_value.sum()))
+            if verbose:
+                print("%s t=%.2f ms, intensity=%.0f"
+                      % (comp.engine, comp_time, comp_value.sum()))
             _show_invalid(data, comp_value)
         except ImportError:
@@ -643,36 +704,74 @@
         resid = (base_value - comp_value)
         relerr = resid/np.where(comp_value != 0., abs(comp_value), 1.0)
-        _print_stats("|%s-%s|"
-                     % (base.engine, comp.engine) + (" "*(3+len(comp.engine))),
-                     resid)
-        _print_stats("|(%s-%s)/%s|"
-                     % (base.engine, comp.engine, comp.engine),
-                     relerr)
+        if verbose:
+            _print_stats("|%s-%s|"
+                         % (base.engine, comp.engine) + (" "*(3+len(comp.engine))),
+                         resid)
+            _print_stats("|(%s-%s)/%s|"
+                         % (base.engine, comp.engine, comp.engine),
+                         relerr)
+
+    return dict(base_value=base_value, comp_value=comp_value,
+                base_time=base_time, comp_time=comp_time,
+                resid=resid, relerr=relerr)
+
+
+def _print_stats(label, err):
+    # type: (str, np.ma.ndarray) -> None
+    # work with trimmed data, not the full set
+    sorted_err = np.sort(abs(err.compressed()))
+    if len(sorted_err) == 0.:
+        print(label + "  no valid values")
+        return
+
+    p50 = int((len(sorted_err)-1)*0.50)
+    p98 = int((len(sorted_err)-1)*0.98)
+    data = [
+        "max:%.3e"%sorted_err[-1],
+        "median:%.3e"%sorted_err[p50],
+        "98%%:%.3e"%sorted_err[p98],
+        "rms:%.3e"%np.sqrt(np.mean(sorted_err**2)),
+        "zero-offset:%+.3e"%np.mean(sorted_err),
+        ]
+    print(label+"  "+"  ".join(data))
+
+
+def plot_models(opts, result, limits=None):
+    # type: (Dict[str, Any], Dict[str, Any], Optional[Tuple[float, float]]) -> Tuple[float, float]
+    base_value, comp_value= result['base_value'], result['comp_value']
+    base_time, comp_time = result['base_time'], result['comp_time']
+    resid, relerr = result['resid'], result['relerr']
+
+    have_base, have_comp = (base_value is not None), (comp_value is not None)
+    base = opts['engines'][0] if have_base else None
+    comp = opts['engines'][1] if have_comp else None
+    data = opts['data']
 
     # Plot if requested
-    if not opts['plot'] and not opts['explore']: return
     view = opts['view']
     import matplotlib.pyplot as plt
     if limits is None:
         vmin, vmax = np.Inf, -np.Inf
-        if n_base > 0:
+        if have_base:
             vmin = min(vmin, base_value.min())
             vmax = max(vmax, base_value.max())
-        if n_comp > 0:
+        if have_comp:
             vmin = min(vmin, comp_value.min())
             vmax = max(vmax, comp_value.max())
         limits = vmin, vmax
 
-    if n_base > 0:
-        if n_comp > 0: plt.subplot(131)
+    if have_base:
+        if have_comp: plt.subplot(131)
         plot_theory(data, base_value, view=view, use_data=False, limits=limits)
         plt.title("%s t=%.2f ms"%(base.engine, base_time))
         #cbar_title = "log I"
-    if n_comp > 0:
-        if n_base > 0: plt.subplot(132)
+    if have_comp:
+        if have_base: plt.subplot(132)
+        if not opts['is2d'] and have_base:
+            plot_theory(data, base_value, view=view, use_data=False, limits=limits)
         plot_theory(data, comp_value, view=view, use_data=False, limits=limits)
         plt.title("%s t=%.2f ms"%(comp.engine, comp_time))
         #cbar_title = "log I"
-    if n_comp > 0 and n_base > 0:
+    if have_base and have_comp:
         plt.subplot(133)
         if not opts['rel_err']:
@@ -680,4 +779,8 @@
         else:
             err, errstr, errview = abs(relerr), "rel err", "log"
+        if 0:  # 95% cutoff
+            sorted = np.sort(err.flatten())
+            cutoff = sorted[int(sorted.size*0.95)]
+            err[err>cutoff] = cutoff
         #err,errstr = base/comp,"ratio"
         plot_theory(data, None, resid=err, view=errview, use_data=False)
@@ -691,7 +794,7 @@
     fig = plt.gcf()
     extra_title = ' '+opts['title'] if opts['title'] else ''
-    fig.suptitle(opts['name'] + extra_title)
-
-    if n_comp > 0 and n_base > 0 and '-hist' in opts:
+    fig.suptitle(":".join(opts['name']) + extra_title)
+
+    if have_base and have_comp and opts['show_hist']:
         plt.figure()
         v = relerr
@@ -708,18 +811,4 @@
     return limits
 
-def _print_stats(label, err):
-    # type: (str, np.ma.ndarray) -> None
-    # work with trimmed data, not the full set
-    sorted_err = np.sort(abs(err.compressed()))
-    p50 = int((len(sorted_err)-1)*0.50)
-    p98 = int((len(sorted_err)-1)*0.98)
-    data = [
-        "max:%.3e"%sorted_err[-1],
-        "median:%.3e"%sorted_err[p50],
-        "98%%:%.3e"%sorted_err[p98],
-        "rms:%.3e"%np.sqrt(np.mean(sorted_err**2)),
-        "zero-offset:%+.3e"%np.mean(sorted_err),
-        ]
-    print(label+"  "+"  ".join(data))
 
 
@@ -735,4 +824,5 @@
     'preset', 'random',
     'poly', 'mono',
+    'magnetic', 'nonmagnetic',
     'nopars', 'pars',
     'rel', 'abs',
@@ -791,5 +881,19 @@
     return pars
 
-
+INTEGER_RE = re.compile("^[+-]?[1-9][0-9]*$")
+def isnumber(str):
+    match = FLOAT_RE.match(str)
+    isfloat = (match and not str[match.end():])
+    return isfloat or INTEGER_RE.match(str)
+
+# For distinguishing pairs of models for comparison
+# key-value pair separator =
+# shell characters  | & ; <> $ % ' " \ # `
+# model and parameter names _
+# parameter expressions - + * / . ( )
+# path characters including tilde expansion and windows drive ~ / :
+# not sure about brackets [] {}
+# maybe one of the following @ ? ^ ! ,
+MODEL_SPLIT = ','
 def parse_opts(argv):
     # type: (List[str]) -> Dict[str, Any]
@@ -813,12 +917,4 @@
         print("expected parameters: model N1 N2")
 
-    name = positional_args[0]
-    try:
-        model_info = core.load_model_info(name)
-    except ImportError as exc:
-        print(str(exc))
-        print("Could not find model; use one of:\n    " + models)
-        return None
-
     invalid = [o[1:] for o in flags
                if o[1:] not in NAME_OPTIONS
@@ -828,4 +924,7 @@
         return None
 
+    name = positional_args[0]
+    n1 = int(positional_args[1]) if len(positional_args) > 1 else 1
+    n2 = int(positional_args[2]) if len(positional_args) > 2 else 1
 
     # pylint: disable=bad-whitespace
@@ -842,4 +941,6 @@
         'seed'      : -1,  # default to preset
         'mono'      : False,
+        # Default to magnetic a magnetic moment is set on the command line
+        'magnetic'  : False,
         'show_pars' : False,
         'show_hist' : False,
@@ -875,4 +976,6 @@
         elif arg == '-mono':    opts['mono'] = True
         elif arg == '-poly':    opts['mono'] = False
+        elif arg == '-magnetic':       opts['magnetic'] = True
+        elif arg == '-nonmagnetic':    opts['magnetic'] = False
         elif arg == '-pars':    opts['show_pars'] = True
         elif arg == '-nopars':  opts['show_pars'] = False
@@ -895,8 +998,96 @@
     # pylint: enable=bad-whitespace
 
+    if MODEL_SPLIT in name:
+        name, name2 = name.split(MODEL_SPLIT, 2)
+    else:
+        name2 = name
+    try:
+        model_info = core.load_model_info(name)
+        model_info2 = core.load_model_info(name2) if name2 != name else model_info
+    except ImportError as exc:
+        print(str(exc))
+        print("Could not find model; use one of:\n    " + models)
+        return None
+
+    # Get demo parameters from model definition, or use default parameters
+    # if model does not define demo parameters
+    pars = get_pars(model_info, opts['use_demo'])
+    pars2 = get_pars(model_info2, opts['use_demo'])
+    pars2.update((k, v) for k, v in pars.items() if k in pars2)
+    # randomize parameters
+    #pars.update(set_pars)  # set value before random to control range
+    if opts['seed'] > -1:
+        pars = randomize_pars(model_info, pars, seed=opts['seed'])
+        if model_info != model_info2:
+            pars2 = randomize_pars(model_info2, pars2, seed=opts['seed'])
+            # Share values for parameters with the same name
+            for k, v in pars.items():
+                if k in pars2:
+                    pars2[k] = v
+        else:
+            pars2 = pars.copy()
+        constrain_pars(model_info, pars)
+        constrain_pars(model_info2, pars2)
+        print("Randomize using -random=%i"%opts['seed'])
+    if opts['mono']:
+        pars = suppress_pd(pars)
+        pars2 = suppress_pd(pars2)
+    if not opts['magnetic']:
+        pars = suppress_magnetism(pars)
+        pars2 = suppress_magnetism(pars2)
+
+    # Fill in parameters given on the command line
+    presets = {}
+    presets2 = {}
+    for arg in values:
+        k, v = arg.split('=', 1)
+        if k not in pars and k not in pars2:
+            # 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))))
+            return None
+        v1, v2 = v.split(MODEL_SPLIT, 2) if MODEL_SPLIT in v else (v,v)
+        if v1 and k in pars:
+            presets[k] = float(v1) if isnumber(v1) else v1
+        if v2 and k in pars2:
+            presets2[k] = float(v2) if isnumber(v2) else v2
+
+    # If pd given on the command line, default pd_n to 35
+    for k, v in list(presets.items()):
+        if k.endswith('_pd'):
+            presets.setdefault(k+'_n', 35.)
+    for k, v in list(presets2.items()):
+        if k.endswith('_pd'):
+            presets2.setdefault(k+'_n', 35.)
+
+    # Evaluate preset parameter expressions
+    context = MATH.copy()
+    context['np'] = np
+    context.update(pars)
+    context.update((k,v) for k,v in presets.items() if isinstance(v, float))
+    for k, v in presets.items():
+        if not isinstance(v, float) and not k.endswith('_type'):
+            presets[k] = eval(v, context)
+    context.update(presets)
+    context.update((k,v) for k,v in presets2.items() if isinstance(v, float))
+    for k, v in presets2.items():
+        if not isinstance(v, float) and not k.endswith('_type'):
+            presets2[k] = eval(v, context)
+
+    # update parameters with presets
+    pars.update(presets)  # set value after random to control value
+    pars2.update(presets2)  # set value after random to control value
+    #import pprint; pprint.pprint(model_info)
+
+    same_model = name == name2 and pars == pars
     if len(engines) == 0:
-        engines.extend(['single', 'double'])
+        if same_model:
+            engines.extend(['single', 'double'])
+        else:
+            engines.extend(['single', 'single'])
     elif len(engines) == 1:
-        if engines[0][0] == 'double':
+        if not same_model:
+            engines.append(engines[0])
+        elif engines[0] == 'double':
             engines.append('single')
         else:
@@ -905,39 +1096,18 @@
         del engines[2:]
 
-    n1 = int(positional_args[1]) if len(positional_args) > 1 else 1
-    n2 = int(positional_args[2]) if len(positional_args) > 2 else 1
     use_sasview = any(engine == 'sasview' and count > 0
                       for engine, count in zip(engines, [n1, n2]))
-
-    # Get demo parameters from model definition, or use default parameters
-    # if model does not define demo parameters
-    pars = get_pars(model_info, opts['use_demo'])
-
-
-    # Fill in parameters given on the command line
-    presets = {}
-    for arg in values:
-        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))))
-            return None
-        presets[k] = float(v) if not k.endswith('type') else v
-
-    # randomize parameters
-    #pars.update(set_pars)  # set value before random to control range
-    if opts['seed'] > -1:
-        pars = randomize_pars(model_info, pars, seed=opts['seed'])
-        print("Randomize using -random=%i"%opts['seed'])
-    if opts['mono']:
-        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)
     if use_sasview:
         constrain_new_to_old(model_info, pars)
+        constrain_new_to_old(model_info2, pars2)
+
     if opts['show_pars']:
-        print(str(parlist(model_info, pars, opts['is2d'])))
+        if not same_model:
+            print("==== %s ====="%model_info.name)
+            print(str(parlist(model_info, pars, opts['is2d'])))
+            print("==== %s ====="%model_info2.name)
+            print(str(parlist(model_info2, pars2, opts['is2d'])))
+        else:
+            print(str(parlist(model_info, pars, opts['is2d'])))
 
     # Create the computational engines
@@ -948,5 +1118,5 @@
         base = None
     if n2:
-        comp = make_engine(model_info, data, engines[1], opts['cutoff'])
+        comp = make_engine(model_info2, data, engines[1], opts['cutoff'])
     else:
         comp = None
@@ -955,11 +1125,10 @@
     # Remember it all
     opts.update({
-        'name'      : name,
-        'def'       : model_info,
-        'n1'        : n1,
-        'n2'        : n2,
-        'presets'   : presets,
-        'pars'      : pars,
         'data'      : data,
+        'name'      : [name, name2],
+        'def'       : [model_info, model_info2],
+        'count'     : [n1, n2],
+        'presets'   : [presets, presets2],
+        'pars'      : [pars, pars2],
         'engines'   : [base, comp],
     })
@@ -976,5 +1145,5 @@
     from .generate import view_html_from_info
     app = wx.App() if wx.GetApp() is None else None
-    view_html_from_info(opts['def'])
+    view_html_from_info(opts['def'][0])
     if app: app.MainLoop()
 
@@ -988,9 +1157,11 @@
     from bumps.names import FitProblem  # type: ignore
     from bumps.gui.app_frame import AppFrame  # type: ignore
+    from bumps.gui import signal
 
     is_mac = "cocoa" in wx.version()
     # Create an app if not running embedded
     app = wx.App() if wx.GetApp() is None else None
-    problem = FitProblem(Explore(opts))
+    model = Explore(opts)
+    problem = FitProblem(model)
     frame = AppFrame(parent=None, title="explore", size=(1000,700))
     if not is_mac: frame.Show()
@@ -998,4 +1169,8 @@
     frame.panel.Layout()
     frame.panel.aui.Split(0, wx.TOP)
+    def reset_parameters(event):
+        model.revert_values()
+        signal.update_parameters(problem)
+    frame.Bind(wx.EVT_TOOL, reset_parameters, frame.ToolBar.GetToolByPos(1))
     if is_mac: frame.Show()
     # If running withing an app, start the main loop
@@ -1015,9 +1190,12 @@
         config_matplotlib()
         self.opts = opts
-        model_info = opts['def']
-        pars, pd_types = bumps_model.create_parameters(model_info, **opts['pars'])
+        p1, p2 = opts['pars']
+        m1, m2 = opts['def']
+        self.fix_p2 = m1 != m2 or p1 != p2
+        model_info = m1
+        pars, pd_types = bumps_model.create_parameters(model_info, **p1)
         # Initialize parameter ranges, fixing the 2D parameters for 1D data.
         if not opts['is2d']:
-            for p in model_info.parameters.user_parameters(is2d=False):
+            for p in model_info.parameters.user_parameters({}, is2d=False):
                 for ext in ['', '_pd', '_pd_n', '_pd_nsigma']:
                     k = p.name+ext
@@ -1030,6 +1208,14 @@
 
         self.pars = pars
+        self.starting_values = dict((k, v.value) for k, v in pars.items())
         self.pd_types = pd_types
         self.limits = None
+
+    def revert_values(self):
+        for k, v in self.starting_values.items():
+            self.pars[k].value = v
+
+    def model_update(self):
+        pass
 
     def numpoints(self):
@@ -1062,9 +1248,14 @@
         pars = dict((k, v.value) for k, v in self.pars.items())
         pars.update(self.pd_types)
-        self.opts['pars'] = pars
-        limits = compare(self.opts, limits=self.limits)
+        self.opts['pars'][0] = pars
+        if not self.fix_p2:
+            self.opts['pars'][1] = pars
+        result = run_models(self.opts)
+        limits = plot_models(self.opts, result, limits=self.limits)
         if self.limits is None:
             vmin, vmax = limits
             self.limits = vmax*1e-7, 1.3*vmax
+            import pylab; pylab.clf()
+            plot_models(self.opts, result, limits=self.limits)
 
 
Index: sasmodels/conversion_table.py
===================================================================
--- sasmodels/conversion_table.py	(revision 5f1acda0bb84d51fb552a3fe3066b6f523837731)
+++ sasmodels/conversion_table.py	(revision bb584b3946fe8b5a6ab2d1a751f4b80ad63cf1b8)
@@ -5,9 +5,28 @@
 names for each parameter in sasmodels.  This is used by :mod:`convert` to
 determine the equivalent parameter set when comparing a sasmodels model to
-the models defined in SasView 3.1.
+the models defined in previous versions of SasView and sasmodels. This is now
+versioned based on the version number of SasView.
+
+When any sasmodels parameter or model name is changed, this must be modified to
+account for that.
+
+Usage:
+<old_Sasview_version> : {
+    <new_model_name> : [
+        <old_model_name> ,
+        {
+            <new_param_name_1> : <old_param_name_1>,
+            ...
+            <new_param_name_n> : <old_param_name_n>
+        }
+    ]
+}
+
+Any future parameter and model name changes can and should be given in this
+table for future compatibility.
 """
 
-
 CONVERSION_TABLE = {
+    (3,1,2) : {
     "adsorbed_layer": [
         "Core2ndMomentModel",
@@ -143,16 +162,16 @@
         }
     ],
-    "core_shell_ellipsoid": [
+    "core_shell_ellipsoid:1": [
         "CoreShellEllipsoidModel",
         {
+            "sld_core": "sld_core",
+            "sld_shell": "sld_shell",
+            "sld_solvent": "sld_solvent",
+            "radius_equat_core": "equat_core",
+            "x_core": "polar_core",
+            "thick_shell": "equat_shell",
+            "x_polar_shell": "polar_shell",
+            "theta": "axis_theta",
             "phi": "axis_phi",
-            "sld_core": "sld_core",
-            "polar_shell": "polar_shell",
-            "sld_solvent": "sld_solvent",
-            "equat_shell": "equat_shell",
-            "equat_core": "equat_core",
-            "theta": "axis_theta",
-            "polar_core": "polar_core",
-            "sld_shell": "sld_shell"
         }
     ],
@@ -160,12 +179,13 @@
         "CoreShellEllipsoidXTModel",
         {
-            "phi": "axis_phi",
             "sld_core": "sld_core",
+            "sld_shell": "sld_shell",
+            "sld_solvent": "sld_solvent",
+            "radius_equat_core": "equat_core",
+            "thick_shell": "T_shell",
             "x_core": "X_core",
-            "sld_solvent": "sld_solvent",
-            "thick_shell": "T_shell",
             "x_polar_shell": "XpolarShell",
             "theta": "axis_theta",
-            "sld_shell": "sld_shell"
+            "phi": "axis_phi",
         }
     ],
@@ -173,18 +193,18 @@
         "CSParallelepipedModel",
         {
+            "sld_core": "sld_pcore",
+            "sld_a": "sld_rimA",
+            "sld_b": "sld_rimB",
+            "sld_c": "sld_rimC",
+            "sld_solvent": "sld_solv",
+            "length_a": "shortA",
+            "length_b": "midB",
+            "length_c": "longC",
+            "thick_rim_a": "rimA",
+            "thick_rim_c": "rimC",
+            "thick_rim_b": "rimB",
+            "theta": "parallel_theta",
             "phi": "parallel_phi",
             "psi": "parallel_psi",
-            "sld_core": "sld_pcore",
-            "sld_c": "sld_rimC",
-            "sld_b": "sld_rimB",
-            "sld_solvent": "sld_solv",
-            "length_a": "shortA",
-            "sld_a": "sld_rimA",
-            "length_b": "midB",
-            "thick_rimc": "rimC",
-            "theta": "parallel_theta",
-            "thick_rim_a": "rimA",
-            "length_c": "longC",
-            "thick_rim_b": "rimB"
         }
     ],
@@ -210,5 +230,5 @@
     ],
     "correlation_length": [
-        "CorrLengthModel",
+        "CorrLength",
         {
             "porod_scale": "scale_p",
@@ -217,5 +237,6 @@
             "lorentz_exp": "exponent_l",
             "cor_length": "length_l"
-        }
+        },
+        "CorrLengthModel"
     ],
     "cylinder": [
@@ -240,5 +261,5 @@
         "DABModel",
         {
-            "length": "length"
+            "cor_length": "length"
         }
     ],
@@ -257,10 +278,11 @@
         "EllipticalCylinderModel",
         {
+            "axis_ratio": "r_ratio",
+            "radius_minor": "r_minor",
+            "sld": "sldCyl",
+            "sld_solvent": "sldSolv",
+            "theta": "cyl_theta",
             "phi": "cyl_phi",
             "psi": "cyl_psi",
-            "theta": "cyl_theta",
-            "sld": "sldCyl",
-            "axis_ratio": "r_ratio",
-            "sld_solvent": "sldSolv"
         }
     ],
@@ -297,10 +319,16 @@
     ],
     "fractal_core_shell": [
-        "FractalCoreShellModel",
-        {
+        "FractalCoreShell",
+        {
+            "sld_core": "core_sld",
             "sld_shell": "shell_sld",
             "sld_solvent": "solvent_sld",
-            "sld_core": "core_sld"
-        }
+            "radius": "radius",
+            "thickness": "thickness",
+            "fractal_dim": "frac_dim",
+            "cor_length": "cor_length",
+            "volfraction": "volfraction",
+        },
+        "FractalCoreShellModel"
     ],
     "fuzzy_sphere": [
@@ -314,5 +342,5 @@
     ],
     "gauss_lorentz_gel": [
-        "GaussLorentzGelModel",
+        "GaussLorentzGel",
         {
             "gauss_scale": "scale_g",
@@ -321,11 +349,14 @@
             "background": "background",
             "lorentz_scale": "scale_l"
-        }
+        },
+        "GaussLorentzGelModel"
     ],
     "gaussian_peak": [
+        "Peak Gauss Model",
+        {
+            "peak_pos": "q0",
+            "sigma": "B",
+        },
         "PeakGaussModel",
-        {
-            "sigma": "B"
-        }
     ],
     "gel_fit": [
@@ -334,31 +365,32 @@
             "rg": "radius",
             "lorentz_scale": "lScale",
+            "guinier_scale": "gScale",
             "fractal_dim": "FractalExp",
             "cor_length": "zeta",
-            "guinier_scale": "gScale"
         }
     ],
     "guinier": [
+        "Guinier",
+        {
+            "rg": "rg"
+        },
         "GuinierModel",
-        {
-            "rg": "rg"
-        }
     ],
     "guinier_porod": [
-        "GuinierPorodModel",
+        "GuinierPorod",
         {
             "s": "dim",
             "rg": "rg",
-            "m": "m",
+            "porod_exp": "m",
             "scale": "scale",
             "background": "background"
-        }
+        },
+        "GuinierPorodModel",
     ],
     "hardsphere": [
         "HardsphereStructure",
         {
-            "radius_effective_pd": "effect_radius_pd",
+            "scale": "scale_factor",
             "radius_effective": "effect_radius",
-            "radius_effective_pd_n": "effect_radius_pd_n"
         }
     ],
@@ -366,8 +398,11 @@
         "HayterMSAStructure",
         {
-            "salt_concentration": "saltconc",
-            "radius_effective_pd": "effect_radius_pd",
+            "scale": "scale_factor",
             "radius_effective": "effect_radius",
-            "radius_effective_pd_n": "effect_radius_pd_n"
+            "volfraction": "volfraction",
+            "charge": "charge",
+            "temperature": "temperature",
+            "concentration_salt": "saltconc",
+            "dielectconst": "dielectconst",
         }
     ],
@@ -375,13 +410,11 @@
         "HollowCylinderModel",
         {
+            "sld": "sldCyl",
+            "sld_solvent": "sldSolv",
+            "radius": "core_radius",
+            "thickness": "radius",
+            "length": "length",
+            "theta": "axis_theta",
             "phi": "axis_phi",
-            "scale": "scale",
-            "radius_core": "core_radius",
-            "sld_solvent": "sldSolv",
-            "length": "length",
-            "radius": "radius",
-            "background": "background",
-            "sld": "sldCyl",
-            "theta": "axis_theta"
         }
     ],
@@ -389,10 +422,10 @@
         "RectangularHollowPrismModel",
         {
+            "sld": "sldPipe",
+            "sld_solvent": "sldSolv",
+            "length_a": "short_side",
             "b2a_ratio": "b2a_ratio",
-            "length_a": "short_side",
-            "sld": "sldPipe",
-            "length_c": "c2a_ratio",
-            "sld_solvent": "sldSolv",
-            "thickness": "thickness"
+            "c2a_ratio": "c2a_ratio",
+            "thickness": "thickness",
         }
     ],
@@ -401,8 +434,8 @@
         {
             "sld": "sldPipe",
+            "sld_solvent": "sldSolv",
+            "length_a": "short_side",
             "b2a_ratio": "b2a_ratio",
-            "length_a": "short_side",
-            "length_c": "c2a_ratio",
-            "sld_solvent": "sldSolv"
+            "c2a_ratio": "c2a_ratio",
         }
     ],
@@ -428,11 +461,12 @@
         "LamellarPSHGModel",
         {
+            "sld": "sld_tail",
+            "sld_head": "sld_head",
+            "sld_solvent": "sld_solvent",
+            "length_tail": "deltaT",
+            "length_head": "deltaH",
+            "d_spacing": "spacing",
             "Caille_parameter": "caille",
             "Nlayers": "n_plates",
-            "sld_head": "sld_head",
-            "length_tail": "deltaT",
-            "length_head": "deltaH",
-            "sld": "sld_tail",
-            "sld_solvent": "sld_solvent"
         }
     ],
@@ -441,8 +475,9 @@
         {
             "sld": "sld_bi",
+            "sld_solvent": "sld_sol",
+            "thickness": "delta",
+            "d_spacing": "spacing",
             "Caille_parameter": "caille",
-            "Nlayers": "N_plates",
-            "sld_solvent": "sld_sol",
-            "thickness": "delta"
+            "Nlayers": "n_plates",
         }
     ],
@@ -451,6 +486,9 @@
         {
             "sld": "sld_layer",
+            "sld_solvent": "sld_solvent",
+            "thickness": "thickness",
+            "d_spacing": "spacing",
             "sigma_d": "pd_spacing",
-            "sld_solvent": "sld_solvent"
+            "Nlayers": "Nlayers",
         }
     ],
@@ -473,8 +511,9 @@
     ],
     "lorentz": [
+        "Lorentz",
+        {
+            "cor_length": "length"
+        },
         "LorentzModel",
-        {
-            "cor_length": "length"
-        }
     ],
     "mass_fractal": [
@@ -497,10 +536,11 @@
     ],
     "mono_gauss_coil": [
+        "Debye",
+        {
+            "rg": "rg",
+            "i_zero": "scale",
+            "background": "background",
+        },
         "DebyeModel",
-        {
-            "rg": "rg",
-            "scale": "scale",
-            "background": "background"
-        }
     ],
     "multilayer_vesicle": [
@@ -551,9 +591,10 @@
     ],
     "peak_lorentz": [
-        "PeakLorentzModel",
+        "Peak Lorentz Model",
         {
             "peak_pos": "q0",
             "peak_hwhm": "B"
-        }
+        },
+        "PeakLorentzModel",
     ],
     "pearl_necklace": [
@@ -576,32 +617,39 @@
             "rg": "rg",
             "polydispersity": "poly_m",
+            "i_zero": "scale",
+            "background": "background",
+        }
+    ],
+    "polymer_excl_volume": [
+        "PolymerExclVolume",
+        {
+            "rg": "rg",
+            "scale": "scale",
+            "background": "background",
+            "porod_exp": "m"
+        }
+    ],
+    "polymer_micelle": [
+        "MicelleSphCoreModel",
+        {
+            "sld_corona": "rho_corona",
+            "sld_solvent": "rho_solv",
+            "sld_core": "rho_core",
+            "ndensity": "ndensity",
+            "v_core": "v_core",
+            "v_corona": "v_corona",
+            "radius_core": "radius_core",
+            "rg": "radius_gyr",
+            "d_penetration": "d_penetration",
+            "n_aggreg": "n_aggreg",
+        }
+    ],
+    "porod": [
+        "PorodModel",
+        {
             "scale": "scale",
             "background": "background"
         }
     ],
-    "polymer_excl_volume": [
-        "PolymerExclVolume",
-        {
-            "rg": "rg",
-            "scale": "scale",
-            "background": "background",
-            "porod_exp": "m"
-        }
-    ],
-    "polymer_micelle": [
-        "MicelleSphCoreModel",
-        {
-            "sld_corona": "rho_corona",
-            "sld_solvent": "rho_solv",
-            "sld_core": "rho_core"
-        }
-    ],
-    "porod": [
-        "PorodModel",
-        {
-            "scale": "scale",
-            "background": "background"
-        }
-    ],
     "power_law": [
         "PowerLawAbsModel",
@@ -616,5 +664,5 @@
         {
             "scale": "scale",
-            "solvent_sld": "sld_solvent",
+            "sld_solvent": "sld_solvent",
             "thickness": "thickness",
             "beta": "beta",
@@ -643,7 +691,7 @@
         {
             "sld": "sldPipe",
+            "length_a": "short_side",
             "b2a_ratio": "b2a_ratio",
-            "length_a": "short_side",
-            "length_c": "c2a_ratio",
+            "c2a_ratio": "c2a_ratio",
             "sld_solvent": "sldSolv"
         }
@@ -716,7 +764,8 @@
         "SquareWellStructure",
         {
-            "radius_effective_pd": "effect_radius_pd",
+            "scale": "scale_factor",
             "radius_effective": "effect_radius",
-            "radius_effective_pd_n": "effect_radius_pd_n"
+            "wellwidth": "wellwidth",
+            "welldepth": "welldepth",
         }
     ],
@@ -729,5 +778,7 @@
             "theta": "axis_theta",
             "sld_solvent": "solvent_sld",
-            "n_stacking": "n_stacking"
+            "n_stacking": "n_stacking",
+            "thick_layer": "layer_thick",
+            "thick_core": "core_thick",
         }
     ],
@@ -742,7 +793,6 @@
         "StickyHSStructure",
         {
-            "radius_effective_pd": "effect_radius_pd",
+            "scale": "scale_factor",
             "radius_effective": "effect_radius",
-            "radius_effective_pd_n": "effect_radius_pd_n"
         }
     ],
@@ -758,5 +808,10 @@
         "TeubnerStreyModel",
         {
-            "a2": "scale"
+            # Note: parameters are completely rewritten in convert.py
+            "volfraction_a": "volfraction_a",
+            "sld_a": "sld_a",
+            "sld_b": "sld_b",
+            "d": "d",
+            "xi": "xi",
         }
     ],
@@ -775,5 +830,5 @@
     ],
     "two_lorentzian": [
-        "TwoLorentzianModel",
+        "TwoLorentzian",
         {
             "lorentz_scale_1": "scale_1",
@@ -784,8 +839,9 @@
             "lorentz_length_1": "length_1",
             "background": "background"
-        }
+        },
+        "TwoLorentzianModel",
     ],
     "two_power_law": [
-        "TwoPowerLawModel",
+        "TwoPowerLaw",
         {
             "coefficent_1": "coef_A",
@@ -794,10 +850,20 @@
             "background": "background",
             "crossover": "qc"
-        }
+        },
+        "TwoPowerLawModel",
     ],
     "unified_power_Rg": [
+        "UnifiedPowerRg",
+        dict(((field_new+str(index), field_old+str(index))
+              for field_new, field_old in [("rg", "Rg"),
+                                           ("power", "power"),
+                                           ("G", "G"),
+                                           ("B", "B"),]
+              for index in range(11)),
+             **{
+                   "background": "background",
+                   "scale": "scale",
+               }),
         "UnifiedPowerRgModel",
-        {
-        }
     ],
     "vesicle": [
@@ -808,3 +874,4 @@
         }
     ]
+    }
 }
Index: sasmodels/convert.py
===================================================================
--- sasmodels/convert.py	(revision 51241113b3f18a1a2b9aea3eaf8c2eaad5b8560a)
+++ sasmodels/convert.py	(revision 07c8d460b7b27e8016df36dcd00643a649cd4e71)
@@ -2,11 +2,12 @@
 Convert models to and from sasview.
 """
-from __future__ import print_function
-
-from os.path import join as joinpath, abspath, dirname
+from __future__ import print_function, division
+
+import re
 import math
 import warnings
 
 from .conversion_table import CONVERSION_TABLE
+from .core import load_model_info
 
 # List of models which SasView versions don't contain the explicit 'scale' argument.
@@ -17,5 +18,4 @@
     'two_lorentzian',
     "two_power_law",
-    'gel_fit',
     'gauss_lorentz_gel',
     'be_polyelectrolyte',
@@ -49,10 +49,78 @@
 # Convert new style names for polydispersity info to old style names
 PD_DOT = [
-    ("", ""),
     ("_pd", ".width"),
     ("_pd_n", ".npts"),
     ("_pd_nsigma", ".nsigmas"),
     ("_pd_type", ".type"),
+    (".lower", ".lower"),
+    (".upper", ".upper"),
+    (".fittable", ".fittable"),
+    (".std", ".std"),
+    (".units", ".units"),
+    ("", "")
     ]
+
+def _rescale(par, scale):
+    return [pk*scale for pk in par] if isinstance(par, list) else par*scale
+
+def _is_sld(model_info, id):
+    """
+    Return True if parameter is a magnetic magnitude or SLD parameter.
+    """
+    if id.startswith('M0:'):
+        return True
+    if '_pd' in id or '.' in id:
+        return False
+    for p in model_info.parameters.call_parameters:
+        if p.id == id:
+            return p.type == 'sld'
+    # check through kernel parameters in case it is a named as a vector
+    for p in model_info.parameters.kernel_parameters:
+        if p.id == id:
+            return p.type == 'sld'
+    return False
+
+def _rescale_sld(model_info, pars, scale):
+    """
+    rescale all sld parameters in the new model definition by *scale* so the
+    numbers are nicer.  Relies on the fact that all sld parameters in the
+    new model definition end with sld.  For backward conversion use
+    *scale=1e-6*.  For forward conversion use *scale=1e6*.
+    """
+    return dict((id, (_rescale(v, scale) if _is_sld(model_info, id) else v))
+                for id, v in pars.items())
+
+
+def _get_translation_table(model_info, version=(3,1,2)):
+    conv_param = CONVERSION_TABLE.get(version, {}).get(model_info.id, [None, {}])
+    translation = conv_param[1].copy()
+    for p in model_info.parameters.kernel_parameters:
+        if p.length > 1:
+            newid = p.id
+            oldid = translation.get(p.id, p.id)
+            translation.pop(newid, None)
+            for k in range(1, p.length+1):
+                if newid+str(k) not in translation:
+                    translation[newid+str(k)] = oldid+str(k)
+    # Remove control parameter from the result
+    if model_info.control:
+        translation[model_info.control] = "CONTROL"
+    return translation
+
+# ========= FORWARD CONVERSION sasview 3.x => sasmodels ===========
+def _dot_pd_to_underscore_pd(par):
+    if par.endswith(".width"):
+        return par[:-6]+"_pd"
+    elif par.endswith(".type"):
+        return par[:-5]+"_pd_type"
+    elif par.endswith(".nsigmas"):
+        return par[:-8]+"_pd_nsigma"
+    elif par.endswith(".npts"):
+        return par[:-5]+"_pd_n"
+    else:
+        return par
+
+def _pd_to_underscores(pars):
+    return dict((_dot_pd_to_underscore_pd(k), v) for k, v in pars.items())
 
 def _convert_pars(pars, mapping):
@@ -63,61 +131,210 @@
     for new, old in mapping.items():
         if old == new: continue
+        if old is None: continue
         for underscore, dot in PD_DOT:
-            if old+dot in newpars:
+            source = old+dot
+            if source in newpars:
                 if new is not None:
-                    newpars[new+underscore] = pars[old+dot]
-                del newpars[old+dot]
+                    target = new+dot
+                else:
+                    target = None
+                if source != target:
+                    if target:
+                        newpars[target] = pars[old+dot]
+                    del newpars[source]
     return newpars
 
-def convert_model(name, pars):
+def _conversion_target(model_name, version=(3,1,2)):
+    """
+    Find the sasmodel name which translates into the sasview name.
+
+    Note: *CoreShellEllipsoidModel* translates into *core_shell_ellipsoid:1*.
+    This is necessary since there is only one variant in sasmodels for the
+    two variants in sasview.
+    """
+    for sasmodels_name, sasview_dict in \
+            CONVERSION_TABLE.get(version, {}).items():
+        if sasview_dict[0] == model_name:
+            return sasmodels_name
+    return None
+
+def _hand_convert(name, oldpars, version=(3,1,2)):
+    if version == (3,1,2):
+        oldpars = _hand_convert_3_1_2_to_4_1(name, oldpars)
+    return oldpars
+
+def _hand_convert_3_1_2_to_4_1(name, oldpars):
+    if name == 'core_shell_parallelepiped':
+        # Make sure pd on rim parameters defaults to zero
+        # ... probably not necessary.
+        oldpars['rimA.width'] = 0.0
+        oldpars['rimB.width'] = 0.0
+        oldpars['rimC.width'] = 0.0
+    elif name == 'core_shell_ellipsoid:1':
+        # Reverse translation (from new to old), from core_shell_ellipsoid.c
+        #    equat_shell = equat_core + thick_shell
+        #    polar_core = equat_core * x_core
+        #    polar_shell = equat_core * x_core + thick_shell*x_polar_shell
+        # Forward translation (from old to new), inverting reverse translation:
+        #    thick_shell = equat_shell - equat_core
+        #    x_core = polar_core / equat_core
+        #    x_polar_shell = (polar_shell - polar_core)/(equat_shell - equat_core)
+        # Auto translation (old <=> new) happens after hand_convert
+        #    equat_shell <=> thick_shell
+        #    polar_core <=> x_core
+        #    polar_shell <=> x_polar_shell
+        # So...
+        equat_core, equat_shell = oldpars['equat_core'], oldpars['equat_shell']
+        polar_core, polar_shell = oldpars['polar_core'], oldpars['polar_shell']
+        oldpars['equat_shell'] = equat_shell - equat_core
+        oldpars['polar_core'] = polar_core / equat_core
+        oldpars['polar_shell'] = (polar_shell-polar_core)/(equat_shell-equat_core)
+    elif name == 'hollow_cylinder':
+        # now uses radius and thickness
+        thickness = oldpars['radius'] - oldpars['core_radius']
+        oldpars['radius'] = thickness
+        if 'radius.width' in oldpars:
+            pd = oldpars['radius.width']*oldpars['radius']/thickness
+            oldpars['radius.width'] = pd
+    elif name == 'multilayer_vesicle':
+        if 'scale' in oldpars:
+            oldpars['volfraction'] = oldpars['scale']
+            oldpars['scale'] = 1.0
+        if 'scale.lower' in oldpars:
+            oldpars['volfraction.lower'] = oldpars['scale.lower']
+        if 'scale.upper' in oldpars:
+            oldpars['volfraction.upper'] = oldpars['scale.upper']
+        if 'scale.fittable' in oldpars:
+            oldpars['volfraction.fittable'] = oldpars['scale.fittable']
+        if 'scale.std' in oldpars:
+            oldpars['volfraction.std'] = oldpars['scale.std']
+        if 'scale.units' in oldpars:
+            oldpars['volfraction.units'] = oldpars['scale.units']
+    elif name == 'pearl_necklace':
+        pass
+        #_remove_pd(oldpars, 'num_pearls', name)
+        #_remove_pd(oldpars, 'thick_string', name)
+    elif name == 'polymer_micelle':
+        if 'ndensity' in oldpars:
+            oldpars['ndensity'] /= 1e15
+        if 'ndensity.lower' in oldpars:
+            oldpars['ndensity.lower'] /= 1e15
+        if 'ndensity.upper' in oldpars:
+            oldpars['ndensity.upper'] /= 1e15
+    elif name == 'rpa':
+        # convert scattering lengths from femtometers to centimeters
+        for p in "L1", "L2", "L3", "L4":
+            if p in oldpars:
+                oldpars[p] /= 1e-13
+            if p + ".lower" in oldpars:
+                oldpars[p + ".lower"] /= 1e-13
+            if p + ".upper" in oldpars:
+                oldpars[p + ".upper"] /= 1e-13
+    elif name == 'spherical_sld':
+        j = 0
+        while "func_inter" + str(j) in oldpars:
+            name = "func_inter" + str(j)
+            new_name = "shape" + str(j + 1)
+            if oldpars[name] == 'Erf(|nu|*z)':
+                oldpars[new_name] = int(0)
+            elif oldpars[name] == 'RPower(z^|nu|)':
+                oldpars[new_name] = int(1)
+            elif oldpars[name] == 'LPower(z^|nu|)':
+                oldpars[new_name] = int(2)
+            elif oldpars[name] == 'RExp(-|nu|*z)':
+                oldpars[new_name] = int(3)
+            elif oldpars[name] == 'LExp(-|nu|*z)':
+                oldpars[new_name] = int(4)
+            else:
+                oldpars[new_name] = int(0)
+            oldpars.pop(name)
+            oldpars['n_shells'] = str(j + 1)
+            j += 1
+    elif name == 'teubner_strey':
+        # basically undoing the entire Teubner-Strey calculations here.
+        #    drho = (sld_a - sld_b)
+        #    k = 2.0*math.pi*xi/d
+        #    a2 = (1.0 + k**2)**2
+        #    c1 = 2.0 * xi**2 * (1.0 - k**2)
+        #    c2 = xi**4
+        #    prefactor = 8.0*math.pi*phi*(1.0-phi)*drho**2*c2/xi
+        #    scale = 1e-4*prefactor
+        #    oldpars['scale'] = a2/scale
+        #    oldpars['c1'] = c1/scale
+        #    oldpars['c2'] = c2/scale
+
+        # need xi, d, sld_a, sld_b, phi=volfraction_a
+        # assume contrast is 1.0e-6, scale=1, background=0
+        sld_a, sld_b = 1.0, 0.
+        drho = sld_a - sld_b
+
+        # find xi
+        p_scale = oldpars['scale']
+        p_c1 = oldpars['c1']
+        p_c2= oldpars['c2']
+        i_1 = 0.5*p_c1/p_c2
+        i_2 = math.sqrt(math.fabs(p_scale/p_c2))
+        i_3 = 2/(i_1 + i_2)
+        xi = math.sqrt(math.fabs(i_3))
+
+        # find d from xi
+        k = math.sqrt(math.fabs(1 - 0.5*p_c1/p_c2*xi**2))
+        d = 2*math.pi*xi/k
+
+        # solve quadratic phi (1-phi) = xi/(1e-4 8 pi drho^2 c2)
+        # favour volume fraction in [0, 0.5]
+        c = xi / (1e-4 * 8.0 * math.pi * drho**2 * p_c2)
+        phi = 0.5 - math.sqrt(0.25 - c)
+
+        # scale sld_a by 1e-6 because the translator will scale it back
+        oldpars.update(volfraction_a=phi, xi=xi, d=d, sld_a=sld_a*1e-6,
+                       sld_b=sld_b, scale=1.0)
+        oldpars.pop('c1')
+        oldpars.pop('c2')
+
+    return oldpars
+
+def convert_model(name, pars, use_underscore=False, model_version=(3,1,2)):
     """
     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(par, scale):
-    return [pk*scale for pk in par] if isinstance(par, list) else par*scale
-
-def _is_sld(modelinfo, id):
-    if id.startswith('M0:'):
-        return True
-    if (id.endswith('_pd') or id.endswith('_pd_n') or id.endswith('_pd_nsigma')
-            or id.endswith('_pd_width') or id.endswith('_pd_type')):
-        return False
-    for p in modelinfo.parameters.call_parameters:
-        if p.id == id:
-            return p.type == 'sld'
-    # check through kernel parameters in case it is a named as a vector
-    for p in modelinfo.parameters.kernel_parameters:
-        if p.id == id:
-            return p.type == 'sld'
-    raise ValueError("unknown parameter %r in conversion"%id)
-
-def _unscale_sld(modelinfo, 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((id, (_unscale(v, 1e-6) if _is_sld(modelinfo, id) else v))
-                for id, v in pars.items())
-
-def _remove_pd(pars, key, name):
-    """
-    Remove polydispersity from the parameter list.
-
-    Note: operates in place
-    """
-    # Bumps style parameter names
-    width = pars.pop(key+".width", 0.0)
-    n_points = pars.pop(key+".npts", 0)
-    if width != 0.0 and n_points != 0:
-        warnings.warn("parameter %s not polydisperse in sasview %s"%(key, name))
-    pars.pop(key+".nsigmas", None)
-    pars.pop(key+".type", None)
-    return pars
+    newpars = pars
+    keys = sorted(CONVERSION_TABLE.keys())
+    for i, version in enumerate(keys):
+        # Don't allow indices outside list
+        next_i = i + 1
+        if next_i == len(keys):
+            next_i = i
+        # If the save state is from a later version, skip the check
+        if model_version <= keys[next_i]:
+            newname = _conversion_target(name, version)
+        else:
+            newname = None
+        # If no conversion is found, move on
+        if newname is None:
+            newname = name
+            continue
+        if ':' in newname:   # core_shell_ellipsoid:1
+            model_info = load_model_info(newname[:-2])
+            # Know the table exists and isn't multiplicity so grab it directly
+            # Can't use _get_translation_table since that will return the 'bare'
+            # version.
+            translation = CONVERSION_TABLE.get(version, {})[newname][1]
+        else:
+            model_info = load_model_info(newname)
+            translation = _get_translation_table(model_info, version)
+        newpars = _hand_convert(newname, newpars, version)
+        newpars = _convert_pars(newpars, translation)
+        # TODO: Still not convinced this is the best check
+        if not model_info.structure_factor and version == (3,1,2):
+            newpars = _rescale_sld(model_info, newpars, 1e6)
+        newpars.setdefault('scale', 1.0)
+        newpars.setdefault('background', 0.0)
+        if use_underscore:
+            newpars = _pd_to_underscores(newpars)
+        name = newname
+    return newname, newpars
+
+# ========= BACKWARD CONVERSION sasmodels => sasview 3.x ===========
 
 def _revert_pars(pars, mapping):
@@ -146,19 +363,18 @@
     return oldname
 
-def _get_translation_table(model_info):
-    _, translation = CONVERSION_TABLE.get(model_info.id, [None, {}])
-    translation = translation.copy()
-    for p in model_info.parameters.kernel_parameters:
-        if p.length > 1:
-            newid = p.id
-            oldid = translation.get(p.id, p.id)
-            translation.pop(newid, None)
-            for k in range(1, p.length+1):
-                if newid+str(k) not in translation:
-                    translation[newid+str(k)] = oldid+str(k)
-    # Remove control parameter from the result
-    if model_info.control:
-        translation[model_info.control] = "CONTROL"
-    return translation
+def _remove_pd(pars, key, name):
+    """
+    Remove polydispersity from the parameter list.
+
+    Note: operates in place
+    """
+    # Bumps style parameter names
+    width = pars.pop(key+".width", 0.0)
+    n_points = pars.pop(key+".npts", 0)
+    if width != 0.0 and n_points != 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 _trim_vectors(model_info, pars, oldpars):
@@ -180,6 +396,6 @@
         composition_type, parts = model_info.composition
         if composition_type == 'product':
-            translation = {'scale':'scale_factor'}
-            translation.update(_get_translation_table(parts[0]))
+            translation = _get_translation_table(parts[0])
+            # structure factor models include scale:scale_factor mapping
             translation.update(_get_translation_table(parts[1]))
         else:
@@ -187,5 +403,5 @@
     else:
         translation = _get_translation_table(model_info)
-    oldpars = _revert_pars(_unscale_sld(model_info, pars), translation)
+    oldpars = _revert_pars(_rescale_sld(model_info, pars, 1e-6), translation)
     oldpars = _trim_vectors(model_info, pars, oldpars)
 
@@ -215,29 +431,4 @@
         if name in MODELS_WITHOUT_VOLFRACTION:
             del oldpars['volfraction']
-        if name == 'stacked_disks':
-            _remove_pd(oldpars, 'n_stacking', name)
-        elif name == 'pearl_necklace':
-            _remove_pd(oldpars, 'num_pearls', name)
-            _remove_pd(oldpars, 'thick_string', name)
-        elif name == 'core_shell_parallelepiped':
-            _remove_pd(oldpars, 'rimA', name)
-            _remove_pd(oldpars, 'rimB', name)
-            _remove_pd(oldpars, 'rimC', name)
-        elif name == 'polymer_micelle':
-            if 'ndensity' in oldpars:
-                oldpars['ndensity'] *= 1e15
-        elif name == 'spherical_sld':
-            oldpars["CONTROL"] -= 1
-            # remove polydispersity from shells
-            for k in range(1, 11):
-                _remove_pd(oldpars, 'thick_flat'+str(k), 'thickness')
-                _remove_pd(oldpars, 'thick_inter'+str(k), 'interface')
-            # remove extra shells
-            for k in range(int(pars['n_shells']), 11):
-                oldpars.pop('sld_flat'+str(k), 0)
-                oldpars.pop('thick_flat'+str(k), 0)
-                oldpars.pop('thick_inter'+str(k), 0)
-                oldpars.pop('func_inter'+str(k), 0)
-                oldpars.pop('nu_inter'+str(k), 0)
         elif name == 'core_multi_shell':
             # kill extra shells
@@ -252,8 +443,21 @@
         elif name == 'core_shell_parallelepiped':
             _remove_pd(oldpars, 'rimA', name)
-        elif name in ['mono_gauss_coil', 'poly_gauss_coil']:
-            del oldpars['i_zero']
+            _remove_pd(oldpars, 'rimB', name)
+            _remove_pd(oldpars, 'rimC', name)
+        elif name == 'hollow_cylinder':
+            # now uses radius and thickness
+            thickness = oldpars['core_radius']
+            oldpars['radius'] += thickness
+            oldpars['radius.width'] *= thickness/oldpars['radius']
+        #elif name in ['mono_gauss_coil', 'poly_gauss_coil']:
+        #    del oldpars['i_zero']
         elif name == 'onion':
             oldpars.pop('n_shells', None)
+        elif name == 'pearl_necklace':
+            _remove_pd(oldpars, 'num_pearls', name)
+            _remove_pd(oldpars, 'thick_string', name)
+        elif name == 'polymer_micelle':
+            if 'ndensity' in oldpars:
+                oldpars['ndensity'] *= 1e15
         elif name == 'rpa':
             # convert scattering lengths from femtometers to centimeters
@@ -272,4 +476,36 @@
                 for k in "Kab,Kac,Kad".split(','):
                     oldpars.pop(k, None)
+        elif name == 'spherical_sld':
+            oldpars["CONTROL"] -= 1
+            # remove polydispersity from shells
+            for k in range(1, 11):
+                _remove_pd(oldpars, 'thick_flat'+str(k), 'thickness')
+                _remove_pd(oldpars, 'thick_inter'+str(k), 'interface')
+            # remove extra shells
+            for k in range(int(pars['n_shells']), 11):
+                oldpars.pop('sld_flat'+str(k), 0)
+                oldpars.pop('thick_flat'+str(k), 0)
+                oldpars.pop('thick_inter'+str(k), 0)
+                oldpars.pop('func_inter'+str(k), 0)
+                oldpars.pop('nu_inter'+str(k), 0)
+        elif name == 'stacked_disks':
+            _remove_pd(oldpars, 'n_stacking', name)
+        elif name == 'teubner_strey':
+            # basically redoing the entire Teubner-Strey calculations here.
+            volfraction = oldpars.pop('volfraction_a')
+            xi = oldpars.pop('xi')
+            d = oldpars.pop('d')
+            sld_a = oldpars.pop('sld_a')
+            sld_b = oldpars.pop('sld_b')
+            drho = 1e6*(sld_a - sld_b)  # conversion autoscaled these
+            k = 2.0*math.pi*xi/d
+            a2 = (1.0 + k**2)**2
+            c1 = 2.0 * xi**2 * (1.0 - k**2)
+            c2 = xi**4
+            prefactor = 8.0*math.pi*volfraction*(1.0-volfraction)*drho**2*c2/xi
+            scale = 1e-4*prefactor
+            oldpars['scale'] = a2/scale
+            oldpars['c1'] = c1/scale
+            oldpars['c2'] = c2/scale
 
     #print("convert from",list(sorted(pars)))
@@ -315,20 +551,22 @@
         if name in MODELS_WITHOUT_VOLFRACTION:
             pars['volfraction'] = 1
-        if name == 'pearl_necklace':
-            pars['string_thickness_pd_n'] = 0
-            pars['number_of_pearls_pd_n'] = 0
+        if name == 'core_multi_shell':
+            pars['n'] = min(math.ceil(pars['n']), 4)
+        elif name == 'gel_fit':
+            pars['scale'] = 1
         elif name == 'line':
             pars['scale'] = 1
             pars['background'] = 0
+        elif name == 'mono_gauss_coil':
+            pars['scale'] = 1
+        elif name == 'onion':
+            pars['n_shells'] = math.ceil(pars['n_shells'])
+        elif name == 'pearl_necklace':
+            pars['string_thickness_pd_n'] = 0
+            pars['number_of_pearls_pd_n'] = 0
+        elif name == 'poly_gauss_coil':
+            pars['scale'] = 1
         elif name == 'rpa':
             pars['case_num'] = int(pars['case_num'])
-        elif name == 'mono_gauss_coil':
-            pars['i_zero'] = 1
-        elif name == 'poly_gauss_coil':
-            pars['i_zero'] = 1
-        elif name == 'core_multi_shell':
-            pars['n'] = min(math.ceil(pars['n']), 4)
-        elif name == 'onion':
-            pars['n_shells'] = math.ceil(pars['n_shells'])
         elif name == 'spherical_sld':
             pars['n_shells'] = math.ceil(pars['n_shells'])
@@ -339,3 +577,56 @@
                 pars['thickness%d_pd_n'%k] = 0
                 pars['interface%d_pd_n'%k] = 0
-
+        elif name == 'teubner_strey':
+            pars['scale'] = 1
+            if pars['volfraction_a'] > 0.5:
+                pars['volfraction_a'] = 1.0 - pars['volfraction_a']
+        elif name == 'unified_power_Rg':
+            pars['level'] = int(pars['level'])
+
+def _check_one(name, seed=None):
+    """
+    Generate a random set of parameters for *name*, and check that they can
+    be converted back to SasView 3.x and forward again to sasmodels.  Raises
+    an error if the parameters are changed.
+    """
+    from . import compare
+
+    model_info = load_model_info(name)
+
+    old_name = revert_name(model_info)
+    if old_name is None:
+        return
+
+    pars = compare.get_pars(model_info, use_demo=False)
+    pars = compare.randomize_pars(model_info, pars, seed=seed)
+    if name == "teubner_strey":
+        # T-S model is underconstrained, so fix the assumptions.
+        pars['sld_a'], pars['sld_b'] = 1.0, 0.0
+    compare.constrain_pars(model_info, pars)
+    constrain_new_to_old(model_info, pars)
+    old_pars = revert_pars(model_info, pars)
+    new_name, new_pars = convert_model(old_name, old_pars, use_underscore=True)
+    if 1:
+        print("==== %s in ====="%name)
+        print(str(compare.parlist(model_info, pars, True)))
+        print("==== %s ====="%old_name)
+        for k, v in sorted(old_pars.items()):
+            print(k, v)
+        print("==== %s out ====="%new_name)
+        print(str(compare.parlist(model_info, new_pars, True)))
+    assert name==new_name, "%r != %r"%(name, new_name)
+    for k, v in new_pars.items():
+        assert k in pars, "%s: %r appeared from conversion"%(name, k)
+        if isinstance(v, float):
+            assert abs(v-pars[k])<=abs(1e-12*v), "%s: %r  %s != %s"%(name, k, v, pars[k])
+        else:
+            assert v == pars[k], "%s: %r  %s != %s"%(name, k, v, pars[k])
+    for k, v in pars.items():
+        assert k in pars, "%s: %r not converted"%(name, k)
+
+def test_backward_forward():
+    from .core import list_models
+    for name in list_models('all'):
+        L = lambda: _check_one(name, seed=1)
+        L.description = name
+        yield L
Index: sasmodels/core.py
===================================================================
--- sasmodels/core.py	(revision 1875f4ec2447bb528be359e5369f6574d604860f)
+++ sasmodels/core.py	(revision 52e9a450543ba1ed18c9b30bfe89ce5288d4a831)
@@ -53,5 +53,5 @@
          "nonmagnetic", "magnetic")
 def list_models(kind=None):
-    # type: () -> List[str]
+    # type: (str) -> List[str]
     """
     Return the list of available models on the model path.
Index: sasmodels/details.py
===================================================================
--- sasmodels/details.py	(revision 6dc78e4926d9f0e39bbb55c42a8f11b256f166fd)
+++ sasmodels/details.py	(revision ccd5f018ad394bf78c2726b92d428f6903233b21)
@@ -230,5 +230,5 @@
     npars = kernel.info.parameters.npars
     nvalues = kernel.info.parameters.nvalues
-    scalars = [v[0][0] for v in pairs]
+    scalars = [(v[0] if len(v) else np.NaN) for v, w in pairs]
     values, weights = zip(*pairs[2:npars+2]) if npars else ((),())
     length = np.array([len(w) for w in weights])
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision 234c53299996393b4b998f41d2eea755356d9a75)
+++ sasmodels/generate.py	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -139,5 +139,5 @@
     square(x) = x*x
     cube(x) = x*x*x
-    sinc(x) = sin(x)/x, with sin(0)/0 -> 1
+    sas_sinx_x(x) = sin(x)/x, with sin(0)/0 -> 1
     all double precision constants must include the decimal point
     all double declarations may be converted to half, float, or long double
@@ -166,4 +166,5 @@
 import re
 import string
+from zlib import crc32
 
 import numpy as np  # type: ignore
@@ -356,4 +357,11 @@
     return newest
 
+def tag_source(source):
+    # type: (str) -> str
+    """
+    Return a unique tag for the source code.
+    """
+    # Note: need 0xffffffff&val to force an unsigned 32-bit number
+    return "%08X"%(0xffffffff&crc32(source))
 
 def convert_type(source, dtype):
Index: sasmodels/kernel_header.c
===================================================================
--- sasmodels/kernel_header.c	(revision e1d6983831831051b424a8d021b337435d87f0f3)
+++ sasmodels/kernel_header.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -146,4 +146,36 @@
 inline double square(double x) { return x*x; }
 inline double cube(double x) { return x*x*x; }
-inline double sinc(double x) { return x==0 ? 1.0 : sin(x)/x; }
+inline double sas_sinx_x(double x) { return x==0 ? 1.0 : sin(x)/x; }
 
+#if 1
+//think cos(theta) should be sin(theta) in new coords, RKH 11Jan2017
+#define ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sn, cn) do { \
+    SINCOS(phi*M_PI_180, sn, cn); \
+    q = sqrt(qx*qx + qy*qy); \
+    cn  = (q==0. ? 1.0 : (cn*qx + sn*qy)/q * sin(theta*M_PI_180));  \
+    sn = sqrt(1 - cn*cn); \
+    } while (0)
+#else
+// SasView 3.x definition of orientation
+#define ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sn, cn) do { \
+    SINCOS(theta*M_PI_180, sn, cn); \
+    q = sqrt(qx*qx + qy*qy);\
+    cn = (q==0. ? 1.0 : (cn*cos(phi*M_PI_180)*qx + sn*qy)/q); \
+    sn = sqrt(1 - cn*cn); \
+    } while (0)
+#endif
+
+#define ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_alpha, cos_mu, cos_nu) do { \
+    q = sqrt(qx*qx + qy*qy); \
+    const double qxhat = qx/q; \
+    const double qyhat = qy/q; \
+    double sin_theta, cos_theta; \
+    double sin_phi, cos_phi; \
+    double sin_psi, cos_psi; \
+    SINCOS(theta*M_PI_180, sin_theta, cos_theta); \
+    SINCOS(phi*M_PI_180, sin_phi, cos_phi); \
+    SINCOS(psi*M_PI_180, sin_psi, cos_psi); \
+    cos_alpha = cos_theta*cos_phi*qxhat + sin_theta*qyhat; \
+    cos_mu = (-sin_theta*cos_psi*cos_phi - sin_psi*sin_phi)*qxhat + cos_theta*cos_psi*qyhat; \
+    cos_nu = (-cos_phi*sin_psi*sin_theta + sin_phi*cos_psi)*qxhat + sin_psi*cos_theta*qyhat; \
+    } while (0)
Index: sasmodels/kernel_template.c
===================================================================
--- sasmodels/kernel_template.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/kernel_template.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -133,5 +133,5 @@
 inline double square(double x) { return x*x; }
 inline double cube(double x) { return x*x*x; }
-inline double sinc(double x) { return x==0 ? 1.0 : sin(x)/x; }
+inline double sas_sinx_x(double x) { return x==0 ? 1.0 : sin(x)/x; }
 
 
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision b8ddf2ee294b766f2a52d72f50c588327b403bb7)
+++ sasmodels/kernelcl.py	(revision b8ddf2ee294b766f2a52d72f50c588327b403bb7)
@@ -294,5 +294,6 @@
         # so we don't really need to cache things for ourselves.  I'll do so
         # anyway just to save some data munging time.
-        key = "%s-%s%s"%(name, dtype, ("-fast" if fast else ""))
+        tag = generate.tag_source(source)
+        key = "%s-%s-%s%s"%(name, dtype, tag, ("-fast" if fast else ""))
         # Check timestamp on program
         program, program_timestamp = self.compiled.get(key, (None, np.inf))
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision ca55aa04dd10f098b6cec84f2f11c026f1949bbe)
+++ sasmodels/modelinfo.py	(revision 85fe7f81bca37e67a39808fbf8e07e7d8be316b1)
@@ -548,5 +548,5 @@
         return full_list
 
-    def user_parameters(self, pars={}, is2d=True):
+    def user_parameters(self, pars, is2d=True):
         # type: (Dict[str, float], bool) -> List[Parameter]
         """
Index: sasmodels/models/barbell.c
===================================================================
--- sasmodels/models/barbell.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/barbell.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -33,5 +33,5 @@
         const double t = Gauss76Z[i]*zm + zb;
         const double radical = 1.0 - t*t;
-        const double bj = sas_J1c(qrst*sqrt(radical));
+        const double bj = sas_2J1x_x(qrst*sqrt(radical));
         const double Fq = cos(m*t + b) * radical * bj;
         total += Gauss76Wt[i] * Fq;
@@ -39,7 +39,21 @@
     // translate dx in [-1,1] to dx in [lower,upper]
     const double integral = total*zm;
-    const double bell_Fq = 2*M_PI*cube(radius_bell)*integral;
-    return bell_Fq;
+    const double bell_fq = 2.0*M_PI*cube(radius_bell)*integral;
+    return bell_fq;
 }
+
+static double
+_fq(double q, double h,
+    double radius_bell, double radius, double half_length,
+    double sin_alpha, double cos_alpha)
+{
+    const double bell_fq = _bell_kernel(q, h, radius_bell, half_length, sin_alpha, cos_alpha);
+    const double bj = sas_2J1x_x(q*radius*sin_alpha);
+    const double si = sas_sinx_x(q*half_length*cos_alpha);
+    const double cyl_fq = 2.0*M_PI*radius*radius*half_length*bj*si;
+    const double Aq = bell_fq + cyl_fq;
+    return Aq;
+}
+
 
 double form_volume(double radius_bell,
@@ -47,5 +61,4 @@
         double length)
 {
-
     // bell radius should never be less than radius when this is called
     const double hdist = sqrt(square(radius_bell) - square(radius));
@@ -71,10 +84,5 @@
         double sin_alpha, cos_alpha; // slots to hold sincos function output
         SINCOS(alpha, sin_alpha, cos_alpha);
-
-        const double bell_Fq = _bell_kernel(q, h, radius_bell, half_length, sin_alpha, cos_alpha);
-        const double bj = sas_J1c(q*radius*sin_alpha);
-        const double si = sinc(q*half_length*cos_alpha);
-        const double cyl_Fq = M_PI*radius*radius*length*bj*si;
-        const double Aq = bell_Fq + cyl_Fq;
+        const double Aq = _fq(q, h, radius_bell, radius, half_length, sin_alpha, cos_alpha);
         total += Gauss76Wt[i] * Aq * Aq * sin_alpha;
     }
@@ -93,21 +101,9 @@
         double theta, double phi)
 {
-    // Compute angle alpha between q and the cylinder axis
-    double sn, cn; // slots to hold sincos function output
-    SINCOS(phi*M_PI_180, sn, cn);
-    const double q = sqrt(qx*qx + qy*qy);
-    const double cos_val = (q==0. ? 1.0 : (cn*qx + sn*qy)*sin(theta*M_PI_180)/q);
-    const double alpha = acos(cos_val); // rod angle relative to q
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
 
     const double h = -sqrt(square(radius_bell) - square(radius));
-    const double half_length = 0.5*length;
-
-    double sin_alpha, cos_alpha; // slots to hold sincos function output
-    SINCOS(alpha, sin_alpha, cos_alpha);
-    const double bell_Fq = _bell_kernel(q, h, radius_bell, half_length, sin_alpha, cos_alpha);
-    const double bj = sas_J1c(q*radius*sin_alpha);
-    const double si = sinc(q*half_length*cos_alpha);
-    const double cyl_Fq = M_PI*radius*radius*length*bj*si;
-    const double Aq = cyl_Fq + bell_Fq;
+    const double Aq = _fq(q, h, radius_bell, radius, 0.5*length, sin_alpha, cos_alpha);
 
     // Multiply by contrast^2 and convert to cm-1
Index: sasmodels/models/barbell.py
===================================================================
--- sasmodels/models/barbell.py	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/barbell.py	(revision fcb33e4672814b4a8fe1ee865d430090b5fc03e6)
@@ -20,23 +20,23 @@
 .. math::
 
-    I(q) = \frac{\Delta \rho^2}{V} \left<A^2(q)\right>
+    I(q) = \frac{\Delta \rho^2}{V} \left<A^2(q,\alpha).sin(\alpha)\right>
 
-where the amplitude $A(q)$ is given as
+where the amplitude $A(q,\alpha)$ with the rod axis at angle $\alpha$ to $q$ is given as
 
 .. math::
 
     A(q) =&\ \pi r^2L
-        \frac{\sin\left(\tfrac12 qL\cos\theta\right)}
-             {\tfrac12 qL\cos\theta}
-        \frac{2 J_1(qr\sin\theta)}{qr\sin\theta} \\
+        \frac{\sin\left(\tfrac12 qL\cos\alpha\right)}
+             {\tfrac12 qL\cos\alpha}
+        \frac{2 J_1(qr\sin\alpha)}{qr\sin\alpha} \\
         &\ + 4 \pi R^3 \int_{-h/R}^1 dt
-        \cos\left[ q\cos\theta
+        \cos\left[ q\cos\alpha
             \left(Rt + h + {\tfrac12} L\right)\right]
         \times (1-t^2)
-        \frac{J_1\left[qR\sin\theta \left(1-t^2\right)^{1/2}\right]}
-             {qR\sin\theta \left(1-t^2\right)^{1/2}}
+        \frac{J_1\left[qR\sin\alpha \left(1-t^2\right)^{1/2}\right]}
+             {qR\sin\alpha \left(1-t^2\right)^{1/2}}
 
 The $\left<\ldots\right>$ brackets denote an average of the structure over
-all orientations. $\left<A^2(q)\right>$ is then the form factor, $P(q)$.
+all orientations. $\left<A^2(q,\alpha)\right>$ is then the form factor, $P(q)$.
 The scale factor is equivalent to the volume fraction of cylinders, each of
 volume, $V$. Contrast $\Delta\rho$ is the difference of scattering length
@@ -85,5 +85,5 @@
 * **Author:** NIST IGOR/DANSE **Date:** pre 2010
 * **Last Modified by:** Paul Butler **Date:** March 20, 2016
-* **Last Reviewed by:** Paul Butler **Date:** March 20, 2016
+* **Last Reviewed by:** Richard Heenan **Date:** January 4, 2017
 """
 from numpy import inf
Index: sasmodels/models/bcc_paracrystal.c
===================================================================
--- sasmodels/models/bcc_paracrystal.c	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/bcc_paracrystal.c	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -31,6 +31,7 @@
 	const double temp8 = -sin_theta*cos_phi - sin_theta*sin_phi + cos_theta;
 	const double temp9 = -sin_theta*cos_phi + sin_theta*sin_phi - cos_theta;
+
 	const double temp10 = exp((-1.0/8.0)*temp1*(temp7*temp7 + temp8*temp8 + temp9*temp9));
-	result = pow(1.0-(temp10*temp10),3)*temp6
+	result = cube(1.0 - (temp10*temp10))*temp6
 	    / ( (1.0 - 2.0*temp10*cos(0.5*temp3*temp7) + temp10*temp10)
 	      * (1.0 - 2.0*temp10*cos(0.5*temp3*temp8) + temp10*temp10)
@@ -81,90 +82,30 @@
 
     return answer;
-
-
 }
 
 
-double Iqxy(double qx, double qy, double dnn,
-    double d_factor, double radius,double sld, double solvent_sld,
-    double theta, double phi, double psi){
+double Iqxy(double qx, double qy,
+    double dnn, double d_factor, double radius,
+    double sld, double solvent_sld,
+    double theta, double phi, double psi)
+{
+    double q, cos_a1, cos_a2, cos_a3;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_a3, cos_a2, cos_a1);
 
-  double b3_x, b3_y, b1_x, b1_y, b2_x, b2_y; //b3_z,
-  //double q_z;
-  double cos_val_b3, cos_val_b2, cos_val_b1;
-  double a1_dot_q, a2_dot_q,a3_dot_q;
-  double answer;
-  double Zq, Fkq, Fkq_2;
+    const double a1 = +cos_a3 - cos_a1 + cos_a2;
+    const double a2 = +cos_a3 + cos_a1 - cos_a2;
+    const double a3 = -cos_a3 + cos_a1 + cos_a2;
 
-  //convert to q and make scaled values
-  double q = sqrt(qx*qx+qy*qy);
-  double q_x = qx/q;
-  double q_y = qy/q;
+    const double qd = 0.5*q*dnn;
+    const double arg = 0.5*square(qd*d_factor)*(a1*a1 + a2*a2 + a3*a3);
+    const double tanh_qd = tanh(arg);
+    const double cosh_qd = cosh(arg);
+    const double Zq = tanh_qd/(1. - cos(qd*a1)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*a2)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*a3)/cosh_qd);
 
-  //convert angle degree to radian
-  theta = theta * M_PI_180;
-  phi = phi * M_PI_180;
-  psi = psi * M_PI_180;
-
-  const double Da = d_factor*dnn;
-  const double s1 = dnn/sqrt(0.75);
-
-
-  //the occupied volume of the lattice
-  const double latticescale = 2.0*sphere_volume(radius/s1);
-  // q vector
-  //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))
-    // b3 axis orientation
-    b3_x = cos(theta) * cos(phi);
-    b3_y = sin(theta);
-    //b3_z = -cos(theta) * sin(phi);
-    cos_val_b3 =  b3_x*q_x + b3_y*q_y;// + b3_z*q_z;
-
-    //alpha = acos(cos_val_b3);
-    // b1 axis orientation
-    b1_x = -cos(phi)*sin(psi) * sin(theta)+sin(phi)*cos(psi);
-    b1_y = sin(psi)*cos(theta);
-    cos_val_b1 = b1_x*q_x + b1_y*q_y;
-    // b2 axis orientation
-    b2_x = -sin(theta)*cos(psi)*cos(phi)-sin(psi)*sin(phi);
-  	b2_y = cos(theta)*cos(psi);
-    cos_val_b2 = b2_x*q_x + b2_y*q_y;
-
-    // The following test should always pass
-    if (fabs(cos_val_b3)>1.0) {
-      //printf("bcc_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b3 = 1.0;
-    }
-    if (fabs(cos_val_b2)>1.0) {
-      //printf("bcc_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b2 = 1.0;
-    }
-    if (fabs(cos_val_b1)>1.0) {
-      //printf("bcc_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b1 = 1.0;
-    }
-    // Compute the angle btw vector q and the a3 axis
-    a3_dot_q = 0.5*dnn*q*(cos_val_b2+cos_val_b1-cos_val_b3);
-
-    // a1 axis
-    a1_dot_q = 0.5*dnn*q*(cos_val_b3+cos_val_b2-cos_val_b1);
-
-    // a2 axis
-    a2_dot_q = 0.5*dnn*q*(cos_val_b3+cos_val_b1-cos_val_b2);
-
-
-    // Get Fkq and Fkq_2
-    Fkq = exp(-0.5*pow(Da/dnn,2.0)*(a1_dot_q*a1_dot_q+a2_dot_q*a2_dot_q+a3_dot_q*a3_dot_q));
-    Fkq_2 = Fkq*Fkq;
-    // Call Zq=Z1*Z2*Z3
-    Zq = (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a1_dot_q)+Fkq_2);
-    Zq *= (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a2_dot_q)+Fkq_2);
-    Zq *= (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a3_dot_q)+Fkq_2);
-
-  // Use SphereForm directly from libigor
-  answer = sphere_form(q,radius,sld,solvent_sld)*Zq*latticescale;
-
-  return answer;
- }
+    const double Fq = sphere_form(q,radius,sld,solvent_sld)*Zq;
+    //the occupied volume of the lattice
+    const double lattice_scale = 2.0*sphere_volume(sqrt(0.75)*radius/dnn);
+    return lattice_scale * Fq;
+}
Index: sasmodels/models/bcc_paracrystal.py
===================================================================
--- sasmodels/models/bcc_paracrystal.py	(revision b0c42719ac9096c35137bf644db59e1c5915b06b)
+++ sasmodels/models/bcc_paracrystal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -128,5 +128,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/gauss150.c", "lib/sphere_form.c", "bcc_paracrystal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/gauss150.c", "lib/sphere_form.c", "bcc_paracrystal.c"]
 
 # parameters for demo
Index: sasmodels/models/be_polyelectrolyte.py
===================================================================
--- sasmodels/models/be_polyelectrolyte.py	(revision 5df888c6540361f7d5cf02845ce9d6a422974042)
+++ sasmodels/models/be_polyelectrolyte.py	(revision bf9de5336d958bc710b80a3f5618dde9593b4e94)
@@ -67,5 +67,6 @@
 * **Author:** NIST IGOR/DANSE **Date:** pre 2010
 * **Last Modified by:** Paul Kienzle **Date:** July 24, 2016
-* **Last Reviewed by:** Piotr rozyczko **Date:** January 27, 2016
+* **Last Reviewed by:** Paul Butler and Richard Heenan **Date:** 
+  October 07, 2016
 """
 
Index: sasmodels/models/binary_hard_sphere.c
===================================================================
--- sasmodels/models/binary_hard_sphere.c	(revision e481a39647a6ec1b21e8f541d254d8c65855ae34)
+++ sasmodels/models/binary_hard_sphere.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -7,10 +7,4 @@
     );
     
-double Iqxy(double qx, double qy,
-    double lg_radius, double sm_radius,
-    double lg_vol_frac, double sm_vol_frac,
-    double lg_sld, double sm_sld, double solvent_sld
-    );
-
 void calculate_psfs(double qval,
     double r2, double nf2,
@@ -55,6 +49,6 @@
     // /* do form factor calculations  */
     
-    v1 = 4.0*M_PI/3.0*r1*r1*r1;
-    v2 = 4.0*M_PI/3.0*r2*r2*r2;
+    v1 = M_4PI_3*r1*r1*r1;
+    v2 = M_4PI_3*r2*r2*r2;
     
     n1 = phi1/v1;
@@ -64,18 +58,8 @@
     qr2 = r2*q;
 
-    //if (qr1 == 0){
-        //sc1 = 1.0/3.0;
-    //}else{
-        //sc1 = (sin(qr1)-qr1*cos(qr1))/qr1/qr1/qr1;
-    //}
-    //if (qr2 == 0){
-        //sc2 = 1.0/3.0;
-    //}else{
-        //sc2 = (sin(qr2)-qr2*cos(qr2))/qr2/qr2/qr2;
-    //}
-    sc1 = sph_j1c(qr1);
-    sc2 = sph_j1c(qr2);
-    b1 = r1*r1*r1*(rho1-rhos)*4.0/3.0*M_PI*sc1;
-    b2 = r2*r2*r2*(rho2-rhos)*4.0/3.0*M_PI*sc2;
+    sc1 = sas_3j1x_x(qr1);
+    sc2 = sas_3j1x_x(qr2);
+    b1 = r1*r1*r1*(rho1-rhos)*M_4PI_3*sc1;
+    b2 = r2*r2*r2*(rho2-rhos)*M_4PI_3*sc2;
     inten = n1*b1*b1*psf11;
     inten += sqrt(n1*n2)*2.0*b1*b2*psf12;
@@ -88,17 +72,4 @@
 }
 
-
-double Iqxy(double qx, double qy,
-    double lg_radius, double sm_radius,
-    double lg_vol_frac, double sm_vol_frac,
-    double lg_sld, double sm_sld, double solvent_sld)
-    
-{
-    double q = sqrt(qx*qx + qy*qy);
-    return Iq(q,
-        lg_radius, sm_radius,
-        lg_vol_frac, sm_vol_frac,
-        lg_sld, sm_sld, solvent_sld);
-}
 
 void calculate_psfs(double qval,
Index: sasmodels/models/binary_hard_sphere.py
===================================================================
--- sasmodels/models/binary_hard_sphere.py	(revision b0c42719ac9096c35137bf644db59e1c5915b06b)
+++ sasmodels/models/binary_hard_sphere.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -108,5 +108,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "binary_hard_sphere.c"]
+source = ["lib/sas_3j1x_x.c", "binary_hard_sphere.c"]
 
 # parameters for demo and documentation
Index: sasmodels/models/capped_cylinder.c
===================================================================
--- sasmodels/models/capped_cylinder.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/capped_cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -39,5 +39,5 @@
         const double t = Gauss76Z[i]*zm + zb;
         const double radical = 1.0 - t*t;
-        const double bj = sas_J1c(qrst*sqrt(radical));
+        const double bj = sas_2J1x_x(qrst*sqrt(radical));
         const double Fq = cos(m*t + b) * radical * bj;
         total += Gauss76Wt[i] * Fq;
@@ -45,6 +45,18 @@
     // translate dx in [-1,1] to dx in [lower,upper]
     const double integral = total*zm;
-    const double cap_Fq = 2*M_PI*cube(radius_cap)*integral;
+    const double cap_Fq = 2.0*M_PI*cube(radius_cap)*integral;
     return cap_Fq;
+}
+
+static double
+_fq(double q, double h, double radius_cap, double radius, double half_length,
+    double sin_alpha, double cos_alpha)
+{
+    const double cap_Fq = _cap_kernel(q, h, radius_cap, half_length, sin_alpha, cos_alpha);
+    const double bj = sas_2J1x_x(q*radius*sin_alpha);
+    const double si = sas_sinx_x(q*half_length*cos_alpha);
+    const double cyl_Fq = 2.0*M_PI*radius*radius*half_length*bj*si;
+    const double Aq = cap_Fq + cyl_Fq;
+    return Aq;
 }
 
@@ -93,10 +105,7 @@
         SINCOS(alpha, sin_alpha, cos_alpha);
 
-        const double cap_Fq = _cap_kernel(q, h, radius_cap, half_length, sin_alpha, cos_alpha);
-        const double bj = sas_J1c(q*radius*sin_alpha);
-        const double si = sinc(q*half_length*cos_alpha);
-        const double cyl_Fq = M_PI*radius*radius*length*bj*si;
-        const double Aq = cap_Fq + cyl_Fq;
-        total += Gauss76Wt[i] * Aq * Aq * sin_alpha; // sin_alpha for spherical coord integration
+        const double Aq = _fq(q, h, radius_cap, radius, half_length, sin_alpha, cos_alpha);
+        // sin_alpha for spherical coord integration
+        total += Gauss76Wt[i] * Aq * Aq * sin_alpha;
     }
     // translate dx in [-1,1] to dx in [lower,upper]
@@ -114,21 +123,9 @@
     double theta, double phi)
 {
-    // Compute angle alpha between q and the cylinder axis
-    double sn, cn;
-    SINCOS(phi*M_PI_180, sn, cn);
-    const double q = sqrt(qx*qx+qy*qy);
-    const double cos_val = (q==0. ? 1.0 : (cn*qx + sn*qy)*sin(theta*M_PI_180)/q);
-    const double alpha = acos(cos_val); // rod angle relative to q
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
 
     const double h = sqrt(radius_cap*radius_cap - radius*radius);
-    const double half_length = 0.5*length;
-
-    double sin_alpha, cos_alpha; // slots to hold sincos function output
-    SINCOS(alpha, sin_alpha, cos_alpha);
-    const double cap_Fq = _cap_kernel(q, h, radius_cap, half_length, sin_alpha, cos_alpha);
-    const double bj = sas_J1c(q*radius*sin_alpha);
-    const double si = sinc(q*half_length*cos_alpha);
-    const double cyl_Fq = M_PI*radius*radius*length*bj*si;
-    const double Aq = cap_Fq + cyl_Fq;
+    const double Aq = _fq(q, h, radius_cap, radius, 0.5*length, sin_alpha, cos_alpha);
 
     // Multiply by contrast^2 and convert to cm-1
Index: sasmodels/models/capped_cylinder.py
===================================================================
--- sasmodels/models/capped_cylinder.py	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/capped_cylinder.py	(revision fcb33e4672814b4a8fe1ee865d430090b5fc03e6)
@@ -21,20 +21,20 @@
 .. math::
 
-    I(q) = \frac{\Delta \rho^2}{V} \left<A^2(q)\right>
+    I(q) = \frac{\Delta \rho^2}{V} \left<A^2(q,\alpha).sin(\alpha)\right>
 
-where the amplitude $A(q)$ is given as
+where the amplitude $A(q,\alpha)$ with the rod axis at angle $\alpha$ to $q$ is given as
 
 .. math::
 
     A(q) =&\ \pi r^2L
-        \frac{\sin\left(\tfrac12 qL\cos\theta\right)}
-            {\tfrac12 qL\cos\theta}
-        \frac{2 J_1(qr\sin\theta)}{qr\sin\theta} \\
+        \frac{\sin\left(\tfrac12 qL\cos\alpha\right)}
+            {\tfrac12 qL\cos\alpha}
+        \frac{2 J_1(qr\sin\alpha)}{qr\sin\alpha} \\
         &\ + 4 \pi R^3 \int_{-h/R}^1 dt
-        \cos\left[ q\cos\theta
+        \cos\left[ q\cos\alpha
             \left(Rt + h + {\tfrac12} L\right)\right]
         \times (1-t^2)
-        \frac{J_1\left[qR\sin\theta \left(1-t^2\right)^{1/2}\right]}
-             {qR\sin\theta \left(1-t^2\right)^{1/2}}
+        \frac{J_1\left[qR\sin\alpha \left(1-t^2\right)^{1/2}\right]}
+             {qR\sin\alpha \left(1-t^2\right)^{1/2}}
 
 The $\left<\ldots\right>$ brackets denote an average of the structure over
@@ -88,5 +88,6 @@
 * **Author:** NIST IGOR/DANSE **Date:** pre 2010
 * **Last Modified by:** Paul Butler **Date:** September 30, 2016
-* **Last Reviewed by:** Richard Heenan **Date:** March 19, 2016
+* **Last Reviewed by:** Richard Heenan **Date:** January 4, 2017
+
 """
 from numpy import inf
Index: sasmodels/models/core_multi_shell.c
===================================================================
--- sasmodels/models/core_multi_shell.c	(revision c5ac2b2355ae3705fd04e87490d99bfe4140d545)
+++ sasmodels/models/core_multi_shell.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -3,5 +3,5 @@
 f_constant(double q, double r, double sld)
 {
-  const double bes = sph_j1c(q * r);
+  const double bes = sas_3j1x_x(q * r);
   const double vol = M_4PI_3 * cube(r);
   return sld * vol * bes;
@@ -33,9 +33,9 @@
   f = 0.;
   for (int i=0; i<n; i++) {
-    f += M_4PI_3 * cube(r) * (sld[i] - last_sld) * sph_j1c(q*r);
+    f += M_4PI_3 * cube(r) * (sld[i] - last_sld) * sas_3j1x_x(q*r);
     last_sld = sld[i];
     r += thickness[i];
   }
-  f += M_4PI_3 * cube(r) * (solvent_sld - last_sld) * sph_j1c(q*r);
+  f += M_4PI_3 * cube(r) * (solvent_sld - last_sld) * sas_3j1x_x(q*r);
   return f * f * 1.0e-4;
 }
Index: sasmodels/models/core_multi_shell.py
===================================================================
--- sasmodels/models/core_multi_shell.py	(revision b0c42719ac9096c35137bf644db59e1c5915b06b)
+++ sasmodels/models/core_multi_shell.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -13,5 +13,5 @@
 
 The 2D scattering intensity is the same as $P(q)$ above, regardless of the
-orientation of the $q$ vector which is defined as
+orientation of the $\vec q$ vector which is defined as
 
 .. math::
@@ -29,5 +29,5 @@
 
 Our model uses the form factor calculations implemented in a c-library provided
-by the NIST Center for Neutron Research (Kline, 2006).
+by the NIST Center for Neutron Research (Kline, 2006) [#kline]_.
 
 References
@@ -35,6 +35,7 @@
 
 .. [#] See the :ref:`core-shell-sphere` model documentation.
-.. [#] L A Feigin and D I Svergun, *Structure Analysis by Small-Angle X-Ray and Neutron Scattering*,
-   Plenum Press, New York, 1987.
+.. [#kline] S R Kline, *J Appl. Cryst.*, 39 (2006) 895
+.. [#] L A Feigin and D I Svergun, *Structure Analysis by Small-Angle X-Ray and
+   Neutron Scattering*, Plenum Press, New York, 1987.
 
 Authorship and Verification
@@ -43,5 +44,5 @@
 * **Author:** NIST IGOR/DANSE **Date:** pre 2010
 * **Last Modified by:** Paul Kienzle **Date:** September 12, 2016
-* **Last Reviewed by:** Under Review **Date:** as of October 5, 2016
+* **Last Reviewed by:** Paul Kienzle **Date:** September 12, 2016
 """
 
@@ -100,5 +101,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "core_multi_shell.c"]
+source = ["lib/sas_3j1x_x.c", "core_multi_shell.c"]
 
 def profile(sld_core, radius, sld_solvent, n, sld, thickness):
Index: sasmodels/models/core_shell_bicelle.c
===================================================================
--- sasmodels/models/core_shell_bicelle.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/core_shell_bicelle.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -26,5 +26,5 @@
 double form_volume(double radius, double thick_rim, double thick_face, double length)
 {
-    return M_PI*(radius+thick_rim)*(radius+thick_rim)*(length+2*thick_face);
+    return M_PI*(radius+thick_rim)*(radius+thick_rim)*(length+2.0*thick_face);
 }
 
@@ -39,5 +39,6 @@
               double rhor,
               double rhosolv,
-              double dum)
+              double sin_alpha,
+              double cos_alpha)
 {
     double si1,si2,be1,be2;
@@ -49,15 +50,13 @@
     const double vol2 = M_PI*(rad+radthick)*(rad+radthick)*2.0*(length+facthick);
     const double vol3 = M_PI*rad*rad*2.0*(length+facthick);
-    double sn,cn;
-    SINCOS(dum, sn, cn);
-    double besarg1 = qq*rad*sn;
-    double besarg2 = qq*(rad+radthick)*sn;
-    double sinarg1 = qq*length*cn;
-    double sinarg2 = qq*(length+facthick)*cn;
+    double besarg1 = qq*rad*sin_alpha;
+    double besarg2 = qq*(rad+radthick)*sin_alpha;
+    double sinarg1 = qq*length*cos_alpha;
+    double sinarg2 = qq*(length+facthick)*cos_alpha;
 
-    be1 = sas_J1c(besarg1);
-    be2 = sas_J1c(besarg2);
-    si1 = sinc(sinarg1);
-    si2 = sinc(sinarg2);
+    be1 = sas_2J1x_x(besarg1);
+    be2 = sas_2J1x_x(besarg2);
+    si1 = sas_sinx_x(sinarg1);
+    si2 = sas_sinx_x(sinarg2);
 
     const double t = vol1*dr1*si1*be1 +
@@ -65,7 +64,7 @@
                      vol3*dr3*si2*be1;
 
-    const double retval = t*t*sn;
+    const double retval = t*t*sin_alpha;
 
-    return(retval);
+    return retval;
 
 }
@@ -83,12 +82,15 @@
 {
     // set up the integration end points
-    const double uplim = M_PI/4;
-    const double halfheight = length/2.0;
+    const double uplim = M_PI_4;
+    const double halfheight = 0.5*length;
 
     double summ = 0.0;
     for(int i=0;i<N_POINTS_76;i++) {
-        double zi = (Gauss76Z[i] + 1.0)*uplim;
+        double alpha = (Gauss76Z[i] + 1.0)*uplim;
+        double sin_alpha, cos_alpha; // slots to hold sincos function output
+        SINCOS(alpha, sin_alpha, cos_alpha);
         double yyy = Gauss76Wt[i] * bicelle_kernel(qq, rad, radthick, facthick,
-                             halfheight, rhoc, rhoh, rhor,rhosolv, zi);
+                             halfheight, rhoc, rhoh, rhor, rhosolv,
+                             sin_alpha, cos_alpha);
         summ += yyy;
     }
@@ -96,9 +98,9 @@
     // calculate value of integral to return
     double answer = uplim*summ;
-    return(answer);
+    return answer;
 }
 
 static double
-bicelle_kernel_2d(double q, double q_x, double q_y,
+bicelle_kernel_2d(double qx, double qy,
           double radius,
           double thick_rim,
@@ -112,20 +114,10 @@
           double phi)
 {
-    //convert angle degree to radian
-    theta *= M_PI_180;
-    phi *= M_PI_180;
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
 
-    // Cylinder orientation
-    const double cyl_x = sin(theta) * cos(phi);
-    const double cyl_y = sin(theta) * sin(phi);
-
-    // Compute the angle btw vector q and the axis of the cylinder
-    const double cos_val = cyl_x*q_x + cyl_y*q_y;
-    const double alpha = acos( cos_val );
-
-    // Get the kernel
     double answer = bicelle_kernel(q, radius, thick_rim, thick_face,
-                           length/2.0, core_sld, face_sld, rim_sld,
-                           solvent_sld, alpha) / fabs(sin(alpha));
+                           0.5*length, core_sld, face_sld, rim_sld,
+                           solvent_sld, sin_alpha, cos_alpha) / fabs(sin_alpha);
 
     answer *= 1.0e-4;
@@ -162,7 +154,5 @@
           double phi)
 {
-    double q;
-    q = sqrt(qx*qx+qy*qy);
-    double intensity = bicelle_kernel_2d(q, qx/q, qy/q,
+    double intensity = bicelle_kernel_2d(qx, qy,
                       radius,
                       thick_rim,
Index: sasmodels/models/core_shell_bicelle.py
===================================================================
--- sasmodels/models/core_shell_bicelle.py	(revision a23639a5c9b5f1b36f3c84424cf447ebd4f6285a)
+++ sasmodels/models/core_shell_bicelle.py	(revision 8afefaee3d363dde220aa55fe83181a73b621f98)
@@ -41,5 +41,5 @@
 
     I(Q,\alpha) = \frac{\text{scale}}{V_t} \cdot
-        F(Q,\alpha)^2 + \text{background}
+        F(Q,\alpha)^2.sin(\alpha) + \text{background}
 
 where
@@ -85,5 +85,5 @@
 * **Author:** NIST IGOR/DANSE **Date:** pre 2010
 * **Last Modified by:** Paul Butler **Date:** September 30, 2016
-* **Last Reviewed by:** Richard Heenan **Date:** October 5, 2016
+* **Last Reviewed by:** Richard Heenan **Date:** January 4, 2017
 """
 
@@ -141,5 +141,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c",
+source = ["lib/sas_Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c",
           "core_shell_bicelle.c"]
 
@@ -156,3 +156,3 @@
             phi=0)
 
-qx, qy = 0.4 * cos(90), 0.5 * sin(0)
+#qx, qy = 0.4 * cos(pi/2.0), 0.5 * sin(0)
Index: sasmodels/models/core_shell_bicelle_elliptical.c
===================================================================
--- sasmodels/models/core_shell_bicelle_elliptical.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
+++ sasmodels/models/core_shell_bicelle_elliptical.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -0,0 +1,139 @@
+double form_volume(double radius, double x_core, double thick_rim, double thick_face, double length);
+double Iq(double q,
+          double radius,
+          double x_core,
+          double thick_rim,
+          double thick_face,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld);
+
+
+double Iqxy(double qx, double qy,
+          double radius,
+          double x_core,
+          double thick_rim,
+          double thick_face,
+          double length,
+          double core_sld,
+          double face_sld,
+          double rim_sld,
+          double solvent_sld,
+          double theta,
+          double phi,
+          double psi);
+
+// NOTE that "length" here is the full height of the core!
+double form_volume(double radius, double x_core, double thick_rim, double thick_face, double length)
+{
+    return M_PI*(radius+thick_rim)*(radius*x_core+thick_rim)*(length+2.0*thick_face);
+}
+
+double 
+                Iq(double qq,
+                   double rad,
+                   double x_core,
+                   double radthick,
+                   double facthick,
+                   double length,
+                   double rhoc,
+                   double rhoh,
+                   double rhor,
+                   double rhosolv)
+{
+    double si1,si2,be1,be2;
+     // core_shell_bicelle_elliptical, RKH Dec 2016, based on elliptical_cylinder and core_shell_bicelle
+     // tested against limiting cases of cylinder, elliptical_cylinder and core_shell_bicelle
+     //    const double uplim = M_PI_4;
+    const double halfheight = 0.5*length;
+    //const double va = 0.0;
+    //const double vb = 1.0;
+    // inner integral limits
+    //const double vaj=0.0;
+    //const double vbj=M_PI;
+
+    const double radius_major = rad * x_core;
+    const double rA = 0.5*(square(radius_major) + square(rad));
+    const double rB = 0.5*(square(radius_major) - square(rad));
+    const double dr1 = (rhoc-rhoh)   *M_PI*rad*radius_major*(2.0*halfheight);;
+    const double dr2 = (rhor-rhosolv)*M_PI*(rad+radthick)*(radius_major+radthick)*2.0*(halfheight+facthick);
+    const double dr3 = (rhoh-rhor)   *M_PI*rad*radius_major*2.0*(halfheight+facthick);
+    //const double vol1 = M_PI*rad*radius_major*(2.0*halfheight);
+    //const double vol2 = M_PI*(rad+radthick)*(radius_major+radthick)*2.0*(halfheight+facthick);
+    //const double vol3 = M_PI*rad*radius_major*2.0*(halfheight+facthick);
+
+    //initialize integral
+    double outer_sum = 0.0;
+    for(int i=0;i<76;i++) {
+        //setup inner integral over the ellipsoidal cross-section
+        // since we generate these lots of times, why not store them somewhere?
+        //const double cos_alpha = ( Gauss76Z[i]*(vb-va) + va + vb )/2.0;
+        const double cos_alpha = ( Gauss76Z[i] + 1.0 )/2.0;
+        const double sin_alpha = sqrt(1.0 - cos_alpha*cos_alpha);
+        double inner_sum=0;
+        double sinarg1 = qq*halfheight*cos_alpha;
+        double sinarg2 = qq*(halfheight+facthick)*cos_alpha;
+        si1 = sas_sinx_x(sinarg1);
+        si2 = sas_sinx_x(sinarg2);
+        for(int j=0;j<76;j++) {
+            //76 gauss points for the inner integral (WAS 20 points,so this may make unecessarily slow, but playing safe)
+            //const double beta = ( Gauss76Z[j]*(vbj-vaj) + vaj + vbj )/2.0;
+            const double beta = ( Gauss76Z[j] +1.0)*M_PI_2;
+            const double rr = sqrt(rA - rB*cos(beta));
+            double besarg1 = qq*rr*sin_alpha;
+            double besarg2 = qq*(rr+radthick)*sin_alpha;
+            be1 = sas_2J1x_x(besarg1);
+            be2 = sas_2J1x_x(besarg2);
+            inner_sum += Gauss76Wt[j] *square(dr1*si1*be1 +
+                                              dr2*si2*be2 +
+                                              dr3*si2*be1);
+        }
+        //now calculate outer integral
+        outer_sum += Gauss76Wt[i] * inner_sum;
+    }
+
+    return outer_sum*2.5e-05;
+}
+
+double 
+Iqxy(double qx, double qy,
+          double rad,
+          double x_core,
+          double radthick,
+          double facthick,
+          double length,
+          double rhoc,
+          double rhoh,
+          double rhor,
+          double rhosolv,
+          double theta,
+          double phi,
+          double psi)
+{
+       // THIS NEEDS TESTING
+    double qq, cos_val, cos_mu, cos_nu;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, qq, cos_val, cos_mu, cos_nu);
+    const double dr1 = rhoc-rhoh;
+    const double dr2 = rhor-rhosolv;
+    const double dr3 = rhoh-rhor;
+    const double radius_major = rad*x_core;
+    const double halfheight = 0.5*length;
+    const double vol1 = M_PI*rad*radius_major*length;
+    const double vol2 = M_PI*(rad+radthick)*(radius_major+radthick)*2.0*(halfheight+facthick);
+    const double vol3 = M_PI*rad*radius_major*2.0*(halfheight+facthick);
+
+    // Compute:  r = sqrt((radius_major*cos_nu)^2 + (radius_minor*cos_mu)^2)
+    // Given:    radius_major = r_ratio * radius_minor  
+    // ASSUME the sin_alpha is included in the separate integration over orientation of rod angle
+    const double r = rad*sqrt(square(x_core*cos_nu) + cos_mu*cos_mu);
+    const double be1 = sas_2J1x_x( qq*r );
+    const double be2 = sas_2J1x_x( qq*(r + radthick ) );
+    const double si1 = sas_sinx_x( qq*halfheight*cos_val );
+    const double si2 = sas_sinx_x( qq*(halfheight + facthick)*cos_val );
+    const double Aq = square( vol1*dr1*si1*be1 + vol2*dr2*si2*be2 +  vol3*dr3*si2*be1);
+    //const double vol = form_volume(radius_minor, r_ratio, length);
+    return 1.0e-4 * Aq;
+}
+
Index: sasmodels/models/core_shell_bicelle_elliptical.py
===================================================================
--- sasmodels/models/core_shell_bicelle_elliptical.py	(revision 8afefaee3d363dde220aa55fe83181a73b621f98)
+++ sasmodels/models/core_shell_bicelle_elliptical.py	(revision 8afefaee3d363dde220aa55fe83181a73b621f98)
@@ -0,0 +1,161 @@
+r"""
+Definition
+----------
+
+This model provides the form factor for an elliptical cylinder with a
+core-shell scattering length density profile. Thus this is a variation
+of the core-shell bicelle model, but with an elliptical cylinder for the core.
+Outer shells on the rims and flat ends may be of different thicknesses and 
+scattering length densities. The form factor is normalized by the total particle volume.
+
+
+.. figure:: img/core_shell_bicelle_geometry.png
+
+    Schematic cross-section of bicelle. Note however that the model here
+    calculates for rectangular, not curved, rims as shown below.
+
+.. figure:: img/core_shell_bicelle_parameters.png
+
+   Cross section of model used here. Users will have 
+   to decide how to distribute "heads" and "tails" between the rim, face 
+   and core regions in order to estimate appropriate starting parameters.
+
+Given the scattering length densities (sld) $\rho_c$, the core sld, $\rho_f$,
+the face sld, $\rho_r$, the rim sld and $\rho_s$ the solvent sld, the
+scattering length density variation along the bicelle axis is:
+
+.. math::
+
+    \rho(r) = 
+      \begin{cases} 
+      &\rho_c \text{ for } 0 \lt r \lt R; -L \lt z\lt L \\[1.5ex]
+      &\rho_f \text{ for } 0 \lt r \lt R; -(L+2t) \lt z\lt -L;
+      L \lt z\lt (L+2t) \\[1.5ex]
+      &\rho_r\text{ for } 0 \lt r \lt R; -(L+2t) \lt z\lt -L; L \lt z\lt (L+2t)
+      \end{cases}
+
+The form factor for the bicelle is calculated in cylindrical coordinates, where
+$\alpha$ is the angle between the $Q$ vector and the cylinder axis, and $\psi$ is the angle
+for the ellipsoidal cross section core, to give:
+
+.. math::
+
+    I(Q,\alpha,\psi) = \frac{\text{scale}}{V_t} \cdot
+        F(Q,\alpha, \psi)^2.sin(\alpha) + \text{background}
+
+where a numerical integration of $F(Q,\alpha, \psi)^2.sin(\alpha)$ is carried out over \alpha and \psi for:
+
+.. math::
+
+        \begin{align}    
+    F(Q,\alpha,\psi) = &\bigg[ 
+    (\rho_c - \rho_f) V_c \frac{2J_1(QR'sin \alpha)}{QR'sin\alpha}\frac{sin(QLcos\alpha/2)}{Q(L/2)cos\alpha} \\
+    &+(\rho_f - \rho_r) V_{c+f} \frac{2J_1(QR'sin\alpha)}{QR'sin\alpha}\frac{sin(Q(L/2+t_f)cos\alpha)}{Q(L/2+t_f)cos\alpha} \\
+    &+(\rho_r - \rho_s) V_t \frac{2J_1(Q(R'+t_r)sin\alpha)}{Q(R'+t_r)sin\alpha}\frac{sin(Q(L/2+t_f)cos\alpha)}{Q(L/2+t_f)cos\alpha}
+    \bigg]
+    \end{align} 
+
+where
+
+.. math::
+
+    R'=\frac{R}{\sqrt{2}}\sqrt{(1+X_{core}^{2}) + (1-X_{core}^{2})cos(\psi)}
+    
+    
+and $V_t = \pi.(R+t_r)(Xcore.R+t_r)^2.(L+2.t_f)$ is the total volume of the bicelle, 
+$V_c = \pi.Xcore.R^2.L$ the volume of the core, $V_{c+f} = \pi.Xcore.R^2.(L+2.t_f)$ 
+the volume of the core plus the volume of the faces, $R$ is the radius
+of the core, $Xcore$ is the axial ratio of the core, $L$ the length of the core, 
+$t_f$ the thickness of the face, $t_r$ the thickness of the rim and $J_1$ the usual 
+first order bessel function. The core has radii $R$ and $Xcore.R$ so is circular, 
+as for the core_shell_bicelle model, for $Xcore$ =1. Note that you may need to 
+limit the range of $Xcore$, especially if using the Monte-Carlo algorithm, as 
+setting radius to $R/Xcore$ and axial ratio to $1/Xcore$ gives an equivalent solution!
+
+The output of the 1D scattering intensity function for randomly oriented
+bicelles is then given by integrating over all possible $\alpha$ and $\psi$.
+
+For oriented bicellles the *theta*, *phi* and *psi* orientation parameters only appear when fitting 2D data, 
+see the :ref:`elliptical-cylinder` model for further information.
+
+
+.. figure:: img/elliptical_cylinder_angle_definition.jpg
+
+    Definition of the angles for the oriented core_shell_bicelle_elliptical model.
+    Note that *theta* and *phi* are currently defined differently to those for the core_shell_bicelle model.
+
+
+References
+----------
+
+.. [#]
+
+Authorship and Verification
+----------------------------
+
+* **Author:** Richard Heenan **Date:** December 14, 2016
+* **Last Modified by:**  Richard Heenan **Date:** December 14, 2016
+* **Last Reviewed by:**  Richard Heenan BEWARE 2d data yet to be checked **Date:** December 14, 2016
+"""
+
+from numpy import inf, sin, cos
+
+name = "core_shell_bicelle_elliptical"
+title = "Elliptical cylinder with a core-shell scattering length density profile.."
+description = """
+    core_shell_bicelle_elliptical
+    Elliptical cylinder core, optional shell on the two flat faces, and shell of 
+    uniform thickness on its rim (extending around the end faces).    
+    Please see full documentation for equations and further details.
+    Involves a double numerical integral around the ellipsoid diameter
+    and the angle of the cylinder axis to Q.
+    Compare also the core_shell_bicelle and elliptical_cylinder models.
+      """
+category = "shape:cylinder"
+
+# pylint: disable=bad-whitespace, line-too-long
+#             ["name", "units", default, [lower, upper], "type", "description"],
+parameters = [
+    ["radius",         "Ang",       30, [0, inf],    "volume",      "Cylinder core radius"],
+    ["x_core",        "None",       3,  [0, inf],    "volume",      "axial ratio of core, X = r_polar/r_equatorial"],
+    ["thick_rim",  "Ang",        8, [0, inf],    "volume",      "Rim shell thickness"],
+    ["thick_face", "Ang",       14, [0, inf],    "volume",      "Cylinder face thickness"],
+    ["length",         "Ang",      50, [0, inf],    "volume",      "Cylinder length"],
+    ["sld_core",       "1e-6/Ang^2", 4, [-inf, inf], "sld",         "Cylinder core scattering length density"],
+    ["sld_face",       "1e-6/Ang^2", 7, [-inf, inf], "sld",         "Cylinder face scattering length density"],
+    ["sld_rim",        "1e-6/Ang^2", 1, [-inf, inf], "sld",         "Cylinder rim scattering length density"],
+    ["sld_solvent",    "1e-6/Ang^2", 6, [-inf, inf], "sld",         "Solvent scattering length density"],
+    ["theta",          "degrees",   90, [-360, 360], "orientation", "In plane angle"],
+    ["phi",            "degrees",    0, [-360, 360], "orientation", "Out of plane angle"],
+    ["psi",            "degrees",    0, [-360, 360], "orientation", "Major axis angle relative to Q"],
+    ]
+
+# pylint: enable=bad-whitespace, line-too-long
+
+source = ["lib/sas_Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c",
+          "core_shell_bicelle_elliptical.c"]
+
+demo = dict(scale=1, background=0,
+            radius=30.0,
+            x_core=3.0,
+            thick_rim=8.0,
+            thick_face=14.0,
+            length=50.0,
+            sld_core=4.0,
+            sld_face=7.0,
+            sld_rim=1.0,
+            sld_solvent=6.0,
+            theta=90,
+            phi=0,
+            psi=0)
+
+#qx, qy = 0.4 * cos(pi/2.0), 0.5 * sin(0)
+
+tests = [
+    [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'ER', 1],
+    [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'VR', 1],
+
+    [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0,
+    'sld_core':4.0, 'sld_face':7.0, 'sld_rim':1.0, 'sld_solvent':6.0, 'background':0.0},
+    0.015, 286.540286],
+]
Index: sasmodels/models/core_shell_cylinder.c
===================================================================
--- sasmodels/models/core_shell_cylinder.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/core_shell_cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -5,18 +5,16 @@
     double radius, double thickness, double length, double theta, double phi);
 
-// twovd = 2 * volume * delta_rho
+// vd = volume * delta_rho
 // besarg = q * R * sin(alpha)
 // siarg = q * L/2 * cos(alpha)
-double _cyl(double twovd, double besarg, double siarg);
-double _cyl(double twovd, double besarg, double siarg)
+double _cyl(double vd, double besarg, double siarg);
+double _cyl(double vd, double besarg, double siarg)
 {
-    const double bj = (besarg == 0.0 ? 0.5 : 0.5*sas_J1c(besarg));
-    const double si = (siarg == 0.0 ? 1.0 : sin(siarg)/siarg);
-    return twovd*si*bj;
+    return vd * sas_sinx_x(siarg) * sas_2J1x_x(besarg);
 }
 
 double form_volume(double radius, double thickness, double length)
 {
-    return M_PI*(radius+thickness)*(radius+thickness)*(length+2*thickness);
+    return M_PI*(radius+thickness)*(radius+thickness)*(length+2.0*thickness);
 }
 
@@ -32,10 +30,8 @@
     const double core_qr = q*radius;
     const double core_qh = q*0.5*length;
-    const double core_twovd = 2.0 * form_volume(radius,0,length)
-                            * (core_sld-shell_sld);
+    const double core_vd = form_volume(radius,0,length) * (core_sld-shell_sld);
     const double shell_qr = q*(radius + thickness);
     const double shell_qh = q*(0.5*length + thickness);
-    const double shell_twovd = 2.0 * form_volume(radius,thickness,length)
-                             * (shell_sld-solvent_sld);
+    const double shell_vd = form_volume(radius,thickness,length) * (shell_sld-solvent_sld);
     double total = 0.0;
     // double lower=0, upper=M_PI_2;
@@ -46,6 +42,6 @@
         const double alpha = 0.5*(Gauss76Z[i]*M_PI_2 + M_PI_2);
         SINCOS(alpha, sn, cn);
-        const double fq = _cyl(core_twovd, core_qr*sn, core_qh*cn)
-            + _cyl(shell_twovd, shell_qr*sn, shell_qh*cn);
+        const double fq = _cyl(core_vd, core_qr*sn, core_qh*cn)
+            + _cyl(shell_vd, shell_qr*sn, shell_qh*cn);
         total += Gauss76Wt[i] * fq * fq * sn;
     }
@@ -66,27 +62,16 @@
     double phi)
 {
-    double sn, cn; // slots to hold sincos function output
-
-    // Compute angle alpha between q and the cylinder axis
-    SINCOS(phi*M_PI_180, sn, cn);
-    // # The following correction factor exists in sasview, but it can't be
-    // # right, so we are leaving it out for now.
-    // const double correction = fabs(cn)*M_PI_2;
-    const double q = sqrt(qx*qx+qy*qy);
-    const double cos_val = (q==0. ? 1.0 : (cn*qx + sn*qy)*sin(theta*M_PI_180)/q);
-    const double alpha = acos(cos_val);
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
 
     const double core_qr = q*radius;
     const double core_qh = q*0.5*length;
-    const double core_twovd = 2.0 * form_volume(radius,0,length)
-                            * (core_sld-shell_sld);
+    const double core_vd = form_volume(radius,0,length) * (core_sld-shell_sld);
     const double shell_qr = q*(radius + thickness);
     const double shell_qh = q*(0.5*length + thickness);
-    const double shell_twovd = 2.0 * form_volume(radius,thickness,length)
-                             * (shell_sld-solvent_sld);
+    const double shell_vd = form_volume(radius,thickness,length) * (shell_sld-solvent_sld);
 
-    SINCOS(alpha, sn, cn);
-    const double fq = _cyl(core_twovd, core_qr*sn, core_qh*cn)
-        + _cyl(shell_twovd, shell_qr*sn, shell_qh*cn);
+    const double fq = _cyl(core_vd, core_qr*sin_alpha, core_qh*cos_alpha)
+        + _cyl(shell_vd, shell_qr*sin_alpha, shell_qh*cos_alpha);
     return 1.0e-4 * fq * fq;
 }
Index: sasmodels/models/core_shell_cylinder.py
===================================================================
--- sasmodels/models/core_shell_cylinder.py	(revision 40a87fa5096610e24c3ead10299f609e99f093f2)
+++ sasmodels/models/core_shell_cylinder.py	(revision 8e68ea06a7246c4bd95ce82c755719ced6fa5a60)
@@ -1,16 +1,13 @@
-# core shell cylinder model
-# Note: model title and parameter table are inserted automatically
 r"""
-The form factor is normalized by the particle volume.
-
 Definition
 ----------
 
 The output of the 2D scattering intensity function for oriented core-shell
-cylinders is given by (Kline, 2006)
+cylinders is given by (Kline, 2006 [#kline]_). The form factor is normalized
+by the particle volume.
 
 .. math::
 
-    I(q,\alpha) = \frac{\text{scale}}{V_s} F^2(q) + \text{background}
+    I(q,\alpha) = \frac{\text{scale}}{V_s} F^2(q,\alpha).sin(\alpha) + \text{background}
 
 where
@@ -18,5 +15,5 @@
 .. math::
 
-    F(q) = &\ (\rho_c - \rho_s) V_c
+    F(q,\alpha) = &\ (\rho_c - \rho_s) V_c
            \frac{\sin \left( q \tfrac12 L\cos\alpha \right)}
                 {q \tfrac12 L\cos\alpha}
@@ -61,21 +58,17 @@
 The $\theta$ and $\phi$ parameters are not used for the 1D output.
 
-Validation
-----------
-
-Validation of our code was done by comparing the output of the 1D model to
-the output of the software provided by the NIST (Kline, 2006).
-
-Averaging over a distribution of orientation is done by evaluating the
-equation above. Since we have no other software to compare the
-implementation of the intensity for fully oriented cylinders, we
-compared the result of averaging our 2D output using a uniform
-distribution $p(\theta,\phi) = 1.0$.
-
 Reference
 ---------
-see, for example, Ian Livsey  J. Chem. Soc., Faraday Trans. 2, 1987,83, 1445-1452
 
-2016/03/18 - Description reviewed by RKH
+.. [#] see, for example, Ian Livsey  J. Chem. Soc., Faraday Trans. 2, 1987,83,
+   1445-1452
+.. [#kline] S R Kline, *J Appl. Cryst.*, 39 (2006) 895
+
+Authorship and Verification
+----------------------------
+
+* **Author:** NIST IGOR/DANSE **Date:** pre 2010
+* **Last Modified by:** Paul Kienzle **Date:** Aug 8, 2016
+* **Last Reviewed by:** Richard Heenan **Date:** March 18, 2016
 """
 
@@ -158,3 +151,3 @@
             theta_pd=15, theta_pd_n=45,
             phi_pd=15, phi_pd_n=1)
-# ADDED by:  RKH  ON: 18Mar2016 renamed sld's etc
+
Index: sasmodels/models/core_shell_ellipsoid.c
===================================================================
--- sasmodels/models/core_shell_ellipsoid.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/core_shell_ellipsoid.c	(revision 0a3d9b2c366d93b98a02e5c9077f26098e630bd0)
@@ -32,5 +32,5 @@
     const double equat_shell = radius_equat_core + thick_shell;
     const double polar_shell = radius_equat_core*x_core + thick_shell*x_polar_shell;
-    double vol = 4.0*M_PI/3.0*equat_shell*equat_shell*polar_shell;
+    double vol = M_4PI_3*equat_shell*equat_shell*polar_shell;
     return vol;
 }
@@ -49,5 +49,4 @@
     const double uplim = 1.0;
 
-    double summ = 0.0;	 //initialize intergral
 
     const double delpc = core_sld - shell_sld; //core - shell
@@ -59,26 +58,19 @@
     const double polar_shell = radius_equat_core*x_core + thick_shell*x_polar_shell;
 
-    for(int i=0;i<N_POINTS_76;i++) {
-        double zi = ( Gauss76Z[i]*(uplim-lolim) + uplim + lolim )/2.0;
-        double yyy = Gauss76Wt[i] * gfn4(zi,
-                                  radius_equat_core,
-                                  polar_core,
-                                  equat_shell,
-                                  polar_shell,
-                                  delpc,
-                                  delps,
-                                  q);
-        summ += yyy;
+    double summ = 0.0;	 //initialize intergral
+    for(int i=0;i<76;i++) {
+        double zi = 0.5*( Gauss76Z[i]*(uplim-lolim) + uplim + lolim );
+        double yyy = gfn4(zi, radius_equat_core, polar_core, equat_shell,
+                          polar_shell, delpc, delps, q);
+        summ += Gauss76Wt[i] * yyy;
     }
+    summ *= 0.5*(uplim-lolim);
 
-    double answer = (uplim-lolim)/2.0*summ;
-    //convert to [cm-1]
-    answer *= 1.0e-4;
-
-    return answer;
+    // convert to [cm-1]
+    return 1.0e-4 * summ;
 }
 
 static double
-core_shell_ellipsoid_xt_kernel_2d(double q, double q_x, double q_y,
+core_shell_ellipsoid_xt_kernel_2d(double qx, double qy,
           double radius_equat_core,
           double x_core,
@@ -91,18 +83,9 @@
           double phi)
 {
-    //convert angle degree to radian
-    theta = theta * M_PI_180;
-    phi = phi * M_PI_180;
-
-    // ellipsoid orientation, the axis of the rotation is consistent with the ploar axis.
-    const double cyl_x = sin(theta) * cos(phi);
-    const double cyl_y = sin(theta) * sin(phi);
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
 
     const double sldcs = core_sld - shell_sld;
     const double sldss = shell_sld- solvent_sld;
-
-    // Compute the angle btw vector q and the
-    // axis of the cylinder
-    const double cos_val = cyl_x*q_x + cyl_y*q_y;
 
     const double polar_core = radius_equat_core*x_core;
@@ -112,5 +95,5 @@
     // Call the IGOR library function to get the kernel:
     // MUST use gfn4 not gf2 because of the def of params.
-    double answer = gfn4(cos_val,
+    double answer = gfn4(cos_alpha,
                   radius_equat_core,
                   polar_core,
@@ -160,7 +143,5 @@
           double phi)
 {
-    double q;
-    q = sqrt(qx*qx+qy*qy);
-    double intensity = core_shell_ellipsoid_xt_kernel_2d(q, qx/q, qy/q,
+    double intensity = core_shell_ellipsoid_xt_kernel_2d(qx, qy,
                        radius_equat_core,
                        x_core,
Index: sasmodels/models/core_shell_ellipsoid.py
===================================================================
--- sasmodels/models/core_shell_ellipsoid.py	(revision ef5a3145c555427a3626384f3a1ca07763f4d48f)
+++ sasmodels/models/core_shell_ellipsoid.py	(revision 8e68ea06a7246c4bd95ce82c755719ced6fa5a60)
@@ -125,5 +125,5 @@
 #             ["name", "units", default, [lower, upper], "type", "description"],
 parameters = [
-    ["radius_equat_core","Ang",     20,   [0, inf],    "volume",      "Equatorial radius of core"],
+    ["radius_equat_core","Ang",     20,   [0, inf],   "volume",      "Equatorial radius of core"],
     ["x_core",        "None",       3,   [0, inf],    "volume",      "axial ratio of core, X = r_polar/r_equatorial"],
     ["thick_shell",   "Ang",       30,   [0, inf],    "volume",      "thickness of shell at equator"],
@@ -137,5 +137,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/gfn.c", "lib/gauss76.c",
+source = ["lib/sas_3j1x_x.c", "lib/gfn.c", "lib/gauss76.c",
           "core_shell_ellipsoid.c"]
 
@@ -162,46 +162,52 @@
 
 q = 0.1
-phi = pi/6
-qx = q*cos(phi)
-qy = q*sin(phi)
-# After redefinition of angles find new reasonable values for unit test
-#tests = [
-#    # Accuracy tests based on content in test/utest_coreshellellipsoidXTmodel.py
-#    [{'radius_equat_core': 200.0,
-#      'x_core': 0.1,
-#      'thick_shell': 50.0,
-#      'x_polar_shell': 0.2,
-#      'sld_core': 2.0,
-#      'sld_shell': 1.0,
-#      'sld_solvent': 6.3,
-#      'background': 0.001,
-#      'scale': 1.0,
-#     }, 1.0, 0.00189402],
+# tests had in old coords theta=0, phi=0; new coords theta=90, phi=0
+qx = q*cos(pi/6.0)
+qy = q*sin(pi/6.0)
+# 11Jan2017 RKH sorted tests after redefinition of angles
+tests = [
+     # Accuracy tests based on content in test/utest_coreshellellipsoidXTmodel.py
+    [{'radius_equat_core': 200.0,
+      'x_core': 0.1,
+      'thick_shell': 50.0,
+      'x_polar_shell': 0.2,
+      'sld_core': 2.0,
+      'sld_shell': 1.0,
+      'sld_solvent': 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],
-
-#    [{'radius_equat_core': 20.0,
-#      'x_core': 200.0,
-#      'thick_shell': 54.0,
-#      'x_polar_shell': 3.0,
-#      'sld_core': 20.0,
-#      'sld_shell': 10.0,
-#      'sld_solvent': 6.0,
-#      'background': 0.0,
-#      'scale': 1.0,
-#     }, 0.01, 8688.53],
-
-#   [{'background': 0.001}, (0.4, 0.5), 0.00690673],
-
-#   [{'radius_equat_core': 20.0,
-#      'x_core': 200.0,
-#      'thick_shell': 54.0,
-#      'x_polar_shell': 3.0,
-#      'sld_core': 20.0,
-#      'sld_shell': 10.0,
-#      'sld_solvent': 6.0,
-#      'background': 0.01,
-#      'scale': 0.01,
-#     }, (qx, qy), 0.0100002],
-#    ]
+    [{'background': 0.01}, 0.1, 11.6915],
+
+    [{'radius_equat_core': 20.0,
+      'x_core': 200.0,
+      'thick_shell': 54.0,
+      'x_polar_shell': 3.0,
+      'sld_core': 20.0,
+      'sld_shell': 10.0,
+      'sld_solvent': 6.0,
+      'background': 0.0,
+      'scale': 1.0,
+     }, 0.01, 8688.53],
+
+   # 2D tests
+   [{'background': 0.001,
+     'theta': 90.0,
+     'phi': 0.0,
+     }, (0.4, 0.5), 0.00690673],
+
+   [{'radius_equat_core': 20.0,
+      'x_core': 200.0,
+      'thick_shell': 54.0,
+      'x_polar_shell': 3.0,
+      'sld_core': 20.0,
+      'sld_shell': 10.0,
+      'sld_solvent': 6.0,
+      'background': 0.01,
+      'scale': 0.01,
+      'theta': 90.0,
+      'phi': 0.0,
+     }, (qx, qy), 0.01000025],
+    ]
Index: sasmodels/models/core_shell_parallelepiped.c
===================================================================
--- sasmodels/models/core_shell_parallelepiped.c	(revision 22221346d45e39a667fe231ce7ca5ecfc79b0ac5)
+++ sasmodels/models/core_shell_parallelepiped.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -35,9 +35,8 @@
     // Did not understand the code completely, it should be rechecked (Miguel Gonzalez)
     
-    double t1, t2, t3, t4, tmp, answer;   
-    double mu = q * length_b;
+    const double mu = 0.5 * q * length_b;
     
     //calculate volume before rescaling (in original code, but not used)
-    //double vol = form_volume(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c);		
+    //double vol = form_volume(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c);        
     //double vol = length_a * length_b * length_c + 
     //       2.0 * thick_rim_a * length_b * length_c + 
@@ -46,76 +45,54 @@
     
     // Scale sides by B
-    double a_scaled = length_a / length_b;
-    double c_scaled = length_c / length_b;
+    const double a_scaled = length_a / length_b;
+    const double c_scaled = length_c / length_b;
 
-    // DelRho values (note that drC is not used later)       
-	double dr0 = core_sld-solvent_sld;
-	double drA = arim_sld-solvent_sld;
-	double drB = brim_sld-solvent_sld;
-	//double drC = crim_sld-solvent_sld;
+    // ta and tb correspond to the definitions in CSPPKernel, but they don't make sense to me (MG)
+    // the a_scaled in the definition of tb was present in CSPPKernel in libCylinder.c,
+    // while in cspkernel in csparallelepiped.cpp (used for the 2D), all the definitions
+    // for ta, tb, tc use also A + 2*rim_thickness (but not scaled by B!!!)
+    double ta = (a_scaled + 2.0*thick_rim_a)/length_b;
+    double tb = (a_scaled + 2.0*thick_rim_b)/length_b;
 
-    //Order of integration
-    int nordi=76;			        
-    int nordj=76;
-    
-    // outer integral (with nordi gauss points), integration limits = 0, 1
-    double summ = 0; //initialize integral
+    double Vin = length_a * length_b * length_c;
+    //double Vot = (length_a * length_b * length_c +
+    //            2.0 * thick_rim_a * length_b * length_c +
+    //            2.0 * length_a * thick_rim_b * length_c +
+    //            2.0 * length_a * length_b * thick_rim_c);
+    double V1 = (2.0 * thick_rim_a * length_b * length_c);    // incorrect V1 (aa*bb*cc+2*ta*bb*cc)
+    double V2 = (2.0 * length_a * thick_rim_b * length_c);    // incorrect V2(aa*bb*cc+2*aa*tb*cc)
 
-    for( int i=0; i<nordi; i++) {
-		
-        // inner integral (with nordj gauss points), integration limits = 0, 1
-	
-        double summj = 0.0;
-	    double sigma = 0.5 * ( Gauss76Z[i] + 1.0 );		
-		
-	    for(int j=0; j<nordj; j++) {
+    // Scale factors (note that drC is not used later)
+    const double drho0 = (core_sld-solvent_sld);
+    const double drhoA = (arim_sld-solvent_sld);
+    const double drhoB = (brim_sld-solvent_sld);
+    //const double drC_Vot = (crim_sld-solvent_sld)*Vot;
 
-            double uu = 0.5 * ( Gauss76Z[j] + 1.0 );
-            double mudum = mu * sqrt(1.0-sigma*sigma);
+    // Precompute scale factors for combining cross terms from the shape
+    const double scale23 = drhoA*V1;
+    const double scale14 = drhoB*V2;
+    const double scale12 = drho0*Vin - scale23 - scale14;
 
-	        double Vin = length_a * length_b * length_c;
-	        //double Vot = (length_a * length_b * length_c +
-            //            2.0 * thick_rim_a * length_b * length_c +
-            //            2.0 * length_a * thick_rim_b * length_c +
-            //            2.0 * length_a * length_b * thick_rim_c);
-	        double V1 = (2.0 * thick_rim_a * length_b * length_c);    // incorrect V1 (aa*bb*cc+2*ta*bb*cc)
-	        double V2 = (2.0 * length_a * thick_rim_b * length_c);    // incorrect V2(aa*bb*cc+2*aa*tb*cc)
-	
-            // ta and tb correspond to the definitions in CSPPKernel, but they don't make sense to me (MG)
-            // the a_scaled in the definition of tb was present in CSPPKernel in libCylinder.c,
-            // while in cspkernel in csparallelepiped.cpp (used for the 2D), all the definitions
-            // for ta, tb, tc use also A + 2*rim_thickness (but not scaled by B!!!)            
-            double ta = (a_scaled+2.0*thick_rim_a)/length_b; 
-            double tb = (a_scaled+2.0*thick_rim_b)/length_b;
-    
-	        double arg1 = (0.5*mudum*a_scaled) * sin(0.5*M_PI*uu);
-	        double arg2 = (0.5*mudum) * cos(0.5*M_PI*uu);
-	        double arg3=  (0.5*mudum*ta) * sin(0.5*M_PI*uu);
-	        double arg4=  (0.5*mudum*tb) * cos(0.5*M_PI*uu);
+    // outer integral (with gauss points), integration limits = 0, 1
+    double outer_total = 0; //initialize integral
 
-	        if(arg1==0.0){
-		        t1 = 1.0;
-	        } else {
-		        t1 = (sin(arg1)/arg1);                //defn for CSPP model sin(arg1)/arg1    test:  (sin(arg1)/arg1)*(sin(arg1)/arg1)   
-	        }
-	        if(arg2==0.0){
-		        t2 = 1.0;
-	        } else {
-		        t2 = (sin(arg2)/arg2);           //defn for CSPP model sin(arg2)/arg2   test: (sin(arg2)/arg2)*(sin(arg2)/arg2)    
-	        }	
-	        if(arg3==0.0){
-		        t3 = 1.0;
-	        } else {
-		        t3 = sin(arg3)/arg3;
-	        }
-	        if(arg4==0.0){
-		        t4 = 1.0;
-	        } else {
-		        t4 = sin(arg4)/arg4;
-	        }
-            
+    for( int i=0; i<76; i++) {
+        double sigma = 0.5 * ( Gauss76Z[i] + 1.0 );
+        double mu_proj = mu * sqrt(1.0-sigma*sigma);
+
+        // inner integral (with gauss points), integration limits = 0, 1
+        double inner_total = 0.0;
+        for(int j=0; j<76; j++) {
+            const double uu = 0.5 * ( Gauss76Z[j] + 1.0 );
+            double sin_uu, cos_uu;
+            SINCOS(M_PI_2*uu, sin_uu, cos_uu);
+            const double si1 = sas_sinx_x(mu_proj * sin_uu * a_scaled);
+            const double si2 = sas_sinx_x(mu_proj * cos_uu);
+            const double si3 = sas_sinx_x(mu_proj * sin_uu * ta);
+            const double si4 = sas_sinx_x(mu_proj * cos_uu * tb);
+
             // Expression in libCylinder.c (neither drC nor Vot are used)
-	        tmp =( dr0*t1*t2*Vin + drA*(t3-t1)*t2*V1+ drB*t1*(t4-t2)*V2 )*( dr0*t1*t2*Vin + drA*(t3-t1)*t2*V1+ drB*t1*(t4-t2)*V2 );   //  correct FF : square of sum of phase factors            
-            
+            const double form = scale12*si1*si2 + scale23*si2*si3 + scale14*si1*si4;
+
             // To note also that in csparallelepiped.cpp, there is a function called
             // cspkernel, which seems to make more sense and has the following comment:
@@ -126,30 +103,17 @@
             // while CSParallelepipedModel calls CSParallelepiped in libCylinder.c        
             
-            summj += Gauss76Wt[j] * tmp;
+            //  correct FF : sum of square of phase factors
+            inner_total += Gauss76Wt[j] * form * form;
+        }
+        inner_total *= 0.5;
 
-        }
-		
-        // value of the inner integral
-        answer = 0.5 * summj;
+        // now sum up the outer integral
+        const double si = sas_sinx_x(mu * c_scaled * sigma);
+        outer_total += Gauss76Wt[i] * inner_total * si * si;
+    }
+    outer_total *= 0.5;
 
-		// finish the outer integral 
-		double arg = 0.5 * mu* c_scaled *sigma;
-		if ( arg == 0.0 ) {
-			answer *= 1.0;
-		} else {
-	        answer *= sin(arg)*sin(arg)/arg/arg;
-		}
-		
-		// now sum up the outer integral
-		summ += Gauss76Wt[i] * answer;
-
-    }
-    
-	answer = 0.5 * summ;
-
-	//convert from [1e-12 A-1] to [cm-1]
-	answer *= 1.0e-4;
-	
-	return answer;
+    //convert from [1e-12 A-1] to [cm-1]
+    return 1.0e-4 * outer_total;
 }
 
@@ -170,109 +134,44 @@
     double psi)
 {
-    double tmp1, tmp2, tmp3, tmpt1, tmpt2, tmpt3;   
+    double q, cos_val_a, cos_val_b, cos_val_c;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_val_c, cos_val_b, cos_val_a);
 
-    double q = sqrt(qx*qx+qy*qy);
-    double qx_scaled = qx/q;
-    double qy_scaled = qy/q;
+    // cspkernel in csparallelepiped recoded here
+    const double dr0 = core_sld-solvent_sld;
+    const double drA = arim_sld-solvent_sld;
+    const double drB = brim_sld-solvent_sld;
+    const double drC = crim_sld-solvent_sld;
 
-    // Convert angles given in degrees to radians
-    theta *= M_PI_180;
-    phi   *= M_PI_180;
-    psi   *= M_PI_180;
-    
-    // Parallelepiped c axis orientation
-    double cparallel_x = cos(theta) * cos(phi);
-    double cparallel_y = sin(theta);
-    
-    // Compute angle between q and parallelepiped axis
-    double cos_val_c = cparallel_x*qx_scaled + cparallel_y*qy_scaled;// + cparallel_z*qz;
-
-    // Parallelepiped a axis orientation
-    double parallel_x = -cos(phi)*sin(psi) * sin(theta)+sin(phi)*cos(psi);
-    double parallel_y = sin(psi)*cos(theta);
-    double cos_val_a = parallel_x*qx_scaled + parallel_y*qy_scaled;
-
-    // Parallelepiped b axis orientation
-    double bparallel_x = -sin(theta)*cos(psi)*cos(phi)-sin(psi)*sin(phi);
-    double bparallel_y = cos(theta)*cos(psi);
-    double cos_val_b = bparallel_x*qx_scaled + bparallel_y*qy_scaled;
-
-    // The following tests should always pass
-    if (fabs(cos_val_c)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_c = 1.0;
-    }
-    if (fabs(cos_val_a)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_a = 1.0;
-    }
-    if (fabs(cos_val_b)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_b = 1.0;
-    }
-    
-    // cspkernel in csparallelepiped recoded here 
-    double dr0 = core_sld-solvent_sld;
-	double drA = arim_sld-solvent_sld;
-	double drB = brim_sld-solvent_sld;
-	double drC = crim_sld-solvent_sld;
-	double Vin = length_a * length_b * length_c;
+    double Vin = length_a * length_b * length_c;
+    double V1 = 2.0 * thick_rim_a * length_b * length_c;    // incorrect V1(aa*bb*cc+2*ta*bb*cc)
+    double V2 = 2.0 * length_a * thick_rim_b * length_c;    // incorrect V2(aa*bb*cc+2*aa*tb*cc)
+    double V3 = 2.0 * length_a * length_b * thick_rim_c;
     // As for the 1D case, Vot is not used
-	//double Vot = (length_a * length_b * length_c +
+    //double Vot = (length_a * length_b * length_c +
     //              2.0 * thick_rim_a * length_b * length_c +
     //              2.0 * length_a * thick_rim_b * length_c +
     //              2.0 * length_a * length_b * thick_rim_c);
-	double V1 = (2.0 * thick_rim_a * length_b * length_c);    // incorrect V1 (aa*bb*cc+2*ta*bb*cc)
-	double V2 = (2.0 * length_a * thick_rim_b * length_c);    // incorrect V2(aa*bb*cc+2*aa*tb*cc)
-    double V3 = (2.0 * length_a * length_b * thick_rim_c);
+
     // The definitions of ta, tb, tc are not the same as in the 1D case because there is no
     // the scaling by B. The use of length_a for the 3 of them seems clearly a mistake to me,
     // but for the moment I let it like this until understanding better the code.
-	double ta = length_a + 2.0*thick_rim_a;
+    double ta = length_a + 2.0*thick_rim_a;
     double tb = length_a + 2.0*thick_rim_b;
     double tc = length_a + 2.0*thick_rim_c;
     //handle arg=0 separately, as sin(t)/t -> 1 as t->0
-    double argA = 0.5*q*length_a*cos_val_a;
-    double argB = 0.5*q*length_b*cos_val_b;
-    double argC = 0.5*q*length_c*cos_val_c;
-    double argtA = 0.5*q*ta*cos_val_a;
-    double argtB = 0.5*q*tb*cos_val_b;
-    double argtC = 0.5*q*tc*cos_val_c;
+    double siA = sas_sinx_x(0.5*q*length_a*cos_val_a);
+    double siB = sas_sinx_x(0.5*q*length_b*cos_val_b);
+    double siC = sas_sinx_x(0.5*q*length_c*cos_val_c);
+    double siAt = sas_sinx_x(0.5*q*ta*cos_val_a);
+    double siBt = sas_sinx_x(0.5*q*tb*cos_val_b);
+    double siCt = sas_sinx_x(0.5*q*tc*cos_val_c);
     
-    if(argA==0.0) {
-        tmp1 = 1.0;
-    } else {
-        tmp1 = sin(argA)/argA;
-    }
-    if (argB==0.0) {
-        tmp2 = 1.0;
-    } else {
-        tmp2 = sin(argB)/argB;
-    }
-    if (argC==0.0) {
-        tmp3 = 1.0;
-    } else {
-        tmp3 = sin(argC)/argC;
-    }
-    if(argtA==0.0) {
-        tmpt1 = 1.0;
-    } else {
-        tmpt1 = sin(argtA)/argtA;
-    }
-    if (argtB==0.0) {
-        tmpt2 = 1.0;
-    } else {
-        tmpt2 = sin(argtB)/argtB;
-    }
-    if (argtC==0.0) {
-        tmpt3 = 1.0;
-    } else {
-        tmpt3 = sin(argtC)*sin(argtC)/argtC/argtC;
-    }
 
     // f uses Vin, V1, V2, and V3 and it seems to have more sense than the value computed
     // in the 1D code, but should be checked!
-    double f = ( dr0*tmp1*tmp2*tmp3*Vin + drA*(tmpt1-tmp1)*tmp2*tmp3*V1 + 
-               drB*tmp1*(tmpt2-tmp2)*tmp3*V2 + drC*tmp1*tmp2*(tmpt3-tmp3)*V3);
+    double f = ( dr0*siA*siB*siC*Vin
+               + drA*(siAt-siA)*siB*siC*V1
+               + drB*siA*(siBt-siB)*siC*V2
+               + drC*siA*siB*(siCt*siCt-siC)*V3);
    
     return 1.0e-4 * f * f;
Index: sasmodels/models/core_shell_parallelepiped.py
===================================================================
--- sasmodels/models/core_shell_parallelepiped.py	(revision 22221346d45e39a667fe231ce7ca5ecfc79b0ac5)
+++ sasmodels/models/core_shell_parallelepiped.py	(revision 5810f00b5c1f61f1386ab0be011468bf48fbf8c0)
@@ -1,5 +1,6 @@
-# core_shell_parallelepiped model
-# Note: model title and parameter table are inserted automatically
 r"""
+Definition
+----------
+
 Calculates the form factor for a rectangular solid with a core-shell structure.
 **The thickness and the scattering length density of the shell or "rim"
@@ -15,9 +16,4 @@
 of the rectangular solid.
 
-An instrument resolution smeared version of the model is also provided.
-
-
-Definition
-----------
 
 The function calculated is the form factor of the rectangular solid below.
@@ -41,17 +37,29 @@
 **meaning that there are "gaps" at the corners of the solid.**
 
-The intensity calculated follows the :ref:`parallelepiped` model, with the core-shell
-intensity being calculated as the square of the sum of the amplitudes of the
-core and shell, in the same manner as a core-shell model.
-
-**For the calculation of the form factor to be valid, the sides of the solid
-MUST be chosen such that** $A < B < C$.
-**If this inequality is not satisfied, the model will not report an error,
-and the calculation will not be correct.**
+The intensity calculated follows the :ref:`parallelepiped` model, with the
+core-shell intensity being calculated as the square of the sum of the
+amplitudes of the core and shell, in the same manner as a core-shell model.
+
+.. math::
+
+    F_{a}(Q,\alpha,\beta)=
+    \Bigg(\frac{sin(Q(L_A+2t_A)/2sin\alpha sin\beta)}{Q(L_A+2t_A)/2sin\alpha
+    sin\beta)}
+    - \frac{sin(QL_A/2sin\alpha sin\beta)}{QL_A/2sin\alpha sin\beta)} \Bigg)
+    + \frac{sin(QL_B/2sin\alpha sin\beta)}{QL_B/2sin\alpha sin\beta)}
+    + \frac{sin(QL_C/2sin\alpha sin\beta)}{QL_C/2sin\alpha sin\beta)}
+
+.. note::
+
+    For the calculation of the form factor to be valid, the sides of the solid
+    MUST be chosen such that** $A < B < C$.
+    If this inequality is not satisfied, the model will not report an error,
+    but the calculation will not be correct and thus the result wrong.
 
 FITTING NOTES
 If the scale is set equal to the particle volume fraction, |phi|, the returned
 value is the scattered intensity per unit volume, $I(q) = \phi P(q)$.
-However, **no interparticle interference effects are included in this calculation.**
+However, **no interparticle interference effects are included in this
+calculation.**
 
 There are many parameters in this model. Hold as many fixed as possible with
@@ -68,8 +76,4 @@
 and length $(C+2t_C)$ values, and used as the effective radius
 for $S(Q)$ when $P(Q) * S(Q)$ is applied.
-
-.. Comment by Miguel Gonzalez:
-   The later seems to contradict the previous statement that interparticle interference
-   effects are not included.
 
 To provide easy access to the orientation of the parallelepiped, we define the
@@ -98,7 +102,17 @@
 ----------
 
-P Mittelbach and G Porod, *Acta Physica Austriaca*, 14 (1961) 185-211
-Equations (1), (13-14). (in German)
-
+.. [#] P Mittelbach and G Porod, *Acta Physica Austriaca*, 14 (1961) 185-211
+    Equations (1), (13-14). (in German)
+.. [#] D Singh (2009). *Small angle scattering studies of self assembly in
+   lipid mixtures*, John's Hopkins University Thesis (2009) 223-225. `Available
+   from Proquest <http://search.proquest.com/docview/304915826?accountid
+   =26379>`_
+
+Authorship and Verification
+----------------------------
+
+* **Author:** NIST IGOR/DANSE **Date:** pre 2010
+* **Last Modified by:** Paul Butler **Date:** September 30, 2016
+* **Last Reviewed by:** Miguel Gonzales **Date:** March 21, 2016
 """
 
@@ -164,6 +178,5 @@
 # parameters for demo
 demo = dict(scale=1, background=0.0,
-            sld_core=1e-6, sld_a=2e-6, sld_b=4e-6,
-            sld_c=2e-6, sld_solvent=6e-6,
+            sld_core=1, sld_a=2, sld_b=4, sld_c=2, sld_solvent=6,
             length_a=35, length_b=75, length_c=400,
             thick_rim_a=10, thick_rim_b=10, thick_rim_c=10,
@@ -177,5 +190,5 @@
             theta_pd=10, theta_pd_n=1,
             phi_pd=10, phi_pd_n=1,
-            psi_pd=10, psi_pd_n=10)
+            psi_pd=10, psi_pd_n=1)
 
 qx, qy = 0.2 * np.cos(2.5), 0.2 * np.sin(2.5)
Index: sasmodels/models/core_shell_sphere.c
===================================================================
--- sasmodels/models/core_shell_sphere.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/core_shell_sphere.c	(revision 3a48772f136eb2795903c63707474e7527b4a325)
@@ -16,4 +16,4 @@
 double form_volume(double radius, double thickness)
 {
-    return 4.0 * M_PI / 3.0 * pow((radius + thickness), 3);
+    return M_4PI_3 * cube(radius + thickness);
 }
Index: sasmodels/models/core_shell_sphere.py
===================================================================
--- sasmodels/models/core_shell_sphere.py	(revision 9a4811ac2bdb93459d640d111cba9f1016660790)
+++ sasmodels/models/core_shell_sphere.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -75,5 +75,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/core_shell.c", "core_shell_sphere.c"]
+source = ["lib/sas_3j1x_x.c", "lib/core_shell.c", "core_shell_sphere.c"]
 
 demo = dict(scale=1, background=0, radius=60, thickness=10,
@@ -95,6 +95,6 @@
     """
     return (1, 1)
-    whole = 4.0 * pi / 3.0 * pow((radius + thickness), 3)
-    core = 4.0 * pi / 3.0 * radius * radius * radius
+    whole = 4.0/3.0 * pi * (radius + thickness)**3
+    core = 4.0/3.0 * pi * radius**3
     return whole, whole - core
 
Index: sasmodels/models/cylinder.c
===================================================================
--- sasmodels/models/cylinder.c	(revision 9cc7fca27a737c4942602927eb564d8c20db58e0)
+++ sasmodels/models/cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -18,5 +18,5 @@
     const double qr = q*radius;
     const double qh = q*0.5*length; 
-    return  sas_J1c(qr*sn) * sinc(qh*cn) ;
+    return sas_2J1x_x(qr*sn) * sas_sinx_x(qh*cn);
 }
 
@@ -58,15 +58,9 @@
     double phi)
 {
-    double sn, cn; // slots to hold sincos function output
-
-    // Compute angle alpha between q and the cylinder axis
-    SINCOS(phi*M_PI_180, sn, cn);
-    const double q = sqrt(qx*qx + qy*qy);
-    const double cos_val = (q==0. ? 1.0 : (cn*qx + sn*qy)*sin(theta*M_PI_180)/q);
-
-    const double alpha = acos(cos_val);
-
-    SINCOS(alpha, sn, cn);
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
+    //printf("sn: %g cn: %g\n", sin_alpha, cos_alpha);
     const double s = (sld-solvent_sld) * form_volume(radius, length);
-    return 1.0e-4 * square(s * fq(q, sn, cn, radius, length));
+    const double form = fq(q, sin_alpha, cos_alpha, radius, length);
+    return 1.0e-4 * square(s * form);
 }
Index: sasmodels/models/cylinder.py
===================================================================
--- sasmodels/models/cylinder.py	(revision 4cdd0cc83e644cfa2cf6045c86fd32ccd5ccf2ef)
+++ sasmodels/models/cylinder.py	(revision b7e8b94b477e6d9a7e4e2666bb218d3568ee66d2)
@@ -2,5 +2,5 @@
 # Note: model title and parameter table are inserted automatically
 r"""
-The form factor is normalized by the particle volume V = \piR^2L.
+
 For information about polarised and magnetic scattering, see
 the :ref:`magnetism` documentation.
@@ -14,5 +14,5 @@
 .. math::
 
-    P(q,\alpha) = \frac{\text{scale}}{V} F^2(q,\alpha) + \text{background}
+    P(q,\alpha) = \frac{\text{scale}}{V} F^2(q,\alpha).sin(\alpha) + \text{background}
 
 where
@@ -25,5 +25,5 @@
            \frac{J_1 \left(q R \sin \alpha\right)}{q R \sin \alpha}
 
-and $\alpha$ is the angle between the axis of the cylinder and $\vec q$, $V$
+and $\alpha$ is the angle between the axis of the cylinder and $\vec q$, $V =\pi R^2L$
 is the volume of the cylinder, $L$ is the length of the cylinder, $R$ is the
 radius of the cylinder, and $\Delta\rho$ (contrast) is the scattering length
@@ -35,9 +35,26 @@
 .. math::
 
-    F^2(q)=\int_{0}^{\pi/2}{F^2(q,\theta)\sin(\theta)d\theta}
+    F^2(q)=\int_{0}^{\pi/2}{F^2(q,\alpha)\sin(\alpha)d\alpha}=\int_{0}^{1}{F^2(q,u)du}
 
 
-To provide easy access to the orientation of the cylinder, we define the
-axis of the cylinder using two angles $\theta$ and $\phi$. Those angles
+Numerical integration is simplified by a change of variable to $u = cos(\alpha)$ with 
+$sin(\alpha)=\sqrt{1-u^2}$. 
+
+The output of the 1D scattering intensity function for randomly oriented
+cylinders is thus given by
+
+.. math::
+
+    P(q) = \frac{\text{scale}}{V}
+        \int_0^{\pi/2} F^2(q,\alpha) \sin \alpha\ d\alpha + \text{background}
+
+
+NB: The 2nd virial coefficient of the cylinder is calculated based on the
+radius and length values, and used as the effective radius for $S(q)$
+when $P(q) \cdot S(q)$ is applied.
+
+For oriented cylinders, we define the direction of the
+axis of the cylinder using two angles $\theta$ (note this is not the
+same as the scattering angle used in q) and $\phi$. Those angles
 are defined in :numref:`cylinder-angle-definition` .
 
@@ -48,18 +65,5 @@
     Definition of the angles for oriented cylinders.
 
-
-NB: The 2nd virial coefficient of the cylinder is calculated based on the
-radius and length values, and used as the effective radius for $S(q)$
-when $P(q) \cdot S(q)$ is applied.
-
-The output of the 1D scattering intensity function for randomly oriented
-cylinders is then given by
-
-.. math::
-
-    P(q) = \frac{\text{scale}}{V}
-        \int_0^{\pi/2} F^2(q,\alpha) \sin \alpha\ d\alpha + \text{background}
-
-The $\theta$ and $\phi$ parameters are not used for the 1D output.
+The $\theta$ and $\phi$ parameters only appear in the model when fitting 2d data.
 
 Validation
@@ -74,9 +78,9 @@
 
     P(q) = \int_0^{\pi/2} d\phi
-        \int_0^\pi p(\alpha) P_0(q,\alpha) \sin \alpha\ d\alpha
+        \int_0^\pi p(\theta) P_0(q,\theta) \sin \theta\ d\theta
 
 
 where $p(\theta,\phi) = 1$ is the probability distribution for the orientation
-and $P_0(q,\alpha)$ is the scattering intensity for the fully oriented
+and $P_0(q,\theta)$ is the scattering intensity for the fully oriented
 system, and then comparing to the 1D result.
 
@@ -145,10 +149,13 @@
 
 qx, qy = 0.2 * np.cos(2.5), 0.2 * np.sin(2.5)
-# After redefinition of angles, find new tests values 
-#tests = [[{}, 0.2, 0.042761386790780453],
-#         [{}, [0.2], [0.042761386790780453]],
-#         [{'theta':10.0, 'phi':10.0}, (qx, qy), 0.03514647218513852],
-#         [{'theta':10.0, 'phi':10.0}, [(qx, qy)], [0.03514647218513852]],
-#        ]
+# After redefinition of angles, find new tests values.  Was 10 10 in old coords
+tests = [[{}, 0.2, 0.042761386790780453],
+        [{}, [0.2], [0.042761386790780453]],
+#  new coords    
+        [{'theta':80.1534480601659, 'phi':10.1510817110481}, (qx, qy), 0.03514647218513852],
+        [{'theta':80.1534480601659, 'phi':10.1510817110481}, [(qx, qy)], [0.03514647218513852]],
+# old coords   [{'theta':10.0, 'phi':10.0}, (qx, qy), 0.03514647218513852],
+#              [{'theta':10.0, 'phi':10.0}, [(qx, qy)], [0.03514647218513852]],
+        ]
 del qx, qy  # not necessary to delete, but cleaner
 # ADDED by:  RKH  ON: 18Mar2016 renamed sld's etc
Index: sasmodels/models/dab.py
===================================================================
--- sasmodels/models/dab.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/dab.py	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -59,6 +59,6 @@
 
 Iq = """
-    double numerator   = pow(cor_length, 3);
-    double denominator = pow(1 + pow(q*cor_length,2), 2);
+    double numerator   = cube(cor_length);
+    double denominator = square(1 + square(q*cor_length));
     
     return numerator / denominator ;
Index: sasmodels/models/ellipsoid.c
===================================================================
--- sasmodels/models/ellipsoid.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/ellipsoid.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -8,7 +8,15 @@
 {
     double ratio = radius_polar/radius_equatorial;
-    const double u = q*radius_equatorial*sqrt(1.0
-                   + sin_alpha*sin_alpha*(ratio*ratio - 1.0));
-    const double f = sph_j1c(u);
+    // Given the following under the radical:
+    //     1 + sin^2(T) (v^2 - 1)
+    // we can expand to match the form given in Guinier (1955)
+    //     = (1 - sin^2(T)) + v^2 sin^2(T) = cos^2(T) + sin^2(T)
+    // Instead of using pythagoras we could pass in sin and cos; this may be
+    // slightly better for 2D which has already computed it, but it introduces
+    // an extra sqrt and square for 1-D not required by the current form, so
+    // leave it as is.
+    const double r = radius_equatorial
+                     * sqrt(1.0 + sin_alpha*sin_alpha*(ratio*ratio - 1.0));
+    const double f = sas_3j1x_x(q*r);
 
     return f*f;
@@ -49,12 +57,7 @@
     double phi)
 {
-    double sn, cn;
-
-    const double q = sqrt(qx*qx + qy*qy);
-    SINCOS(phi*M_PI_180, sn, cn);
-    const double cos_alpha = (q==0. ? 1.0 : (cn*qx + sn*qy)*sin(theta*M_PI_180)/q);
-    const double alpha = acos(cos_alpha);
-    SINCOS(alpha, sn, cn);
-    const double form = _ellipsoid_kernel(q, radius_polar, radius_equatorial, sn);
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
+    const double form = _ellipsoid_kernel(q, radius_polar, radius_equatorial, sin_alpha);
     const double s = (sld - sld_solvent) * form_volume(radius_polar, radius_equatorial);
 
Index: sasmodels/models/ellipsoid.py
===================================================================
--- sasmodels/models/ellipsoid.py	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/ellipsoid.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -135,5 +135,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "lib/gauss76.c", "ellipsoid.c"]
+source = ["lib/sas_3j1x_x.c", "lib/gauss76.c", "ellipsoid.c"]
 
 def ER(radius_polar, radius_equatorial):
Index: sasmodels/models/elliptical_cylinder.c
===================================================================
--- sasmodels/models/elliptical_cylinder.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/elliptical_cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -6,158 +6,76 @@
 
 
-double _elliptical_cylinder_kernel(double q, double radius_minor, double r_ratio, double theta);
-
-double _elliptical_cylinder_kernel(double q, double radius_minor, double r_ratio, double theta)
-{
-    // This is the function LAMBDA1^2 in Feigin's notation
-    // q is the q-value for the calculation (1/A)
-    // radius_minor is the transformed radius"a" in Feigin's notation
-    // r_ratio is the ratio (major radius)/(minor radius) of the Ellipsoid [=] ---
-    // theta is the dummy variable of the integration
-
-    double retval,arg;
-
-    arg = q*radius_minor*sqrt((1.0+r_ratio*r_ratio)/2+(1.0-r_ratio*r_ratio)*cos(theta)/2);
-    //retval = 2.0*J1(arg)/arg;
-    retval = sas_J1c(arg);
-    return retval*retval ;
-}
-
-
-double form_volume(double radius_minor, double r_ratio, double length)
+double
+form_volume(double radius_minor, double r_ratio, double length)
 {
     return M_PI * radius_minor * radius_minor * r_ratio * length;
 }
 
-double Iq(double q, double radius_minor, double r_ratio, double length,
-          double sld, double solvent_sld) {
+double
+Iq(double q, double radius_minor, double r_ratio, double length,
+   double sld, double solvent_sld)
+{
+    // orientational average limits
+    const double va = 0.0;
+    const double vb = 1.0;
+    // inner integral limits
+    const double vaj=0.0;
+    const double vbj=M_PI;
 
-    const int nordi=76; //order of integration
-    const int nordj=20;
-    double va,vb;       //upper and lower integration limits
-    double summ,zi,yyy,answer;         //running tally of integration
-    double summj,vaj,vbj,zij,arg,si;            //for the inner integration
-
-    // orientational average limits
-    va = 0.0;
-    vb = 1.0;
-    // inner integral limits
-    vaj=0.0;
-    vbj=M_PI;
+    const double radius_major = r_ratio * radius_minor;
+    const double rA = 0.5*(square(radius_major) + square(radius_minor));
+    const double rB = 0.5*(square(radius_major) - square(radius_minor));
 
     //initialize integral
-    summ = 0.0;
-
-    const double delrho = sld - solvent_sld;
-
-    for(int i=0;i<nordi;i++) {
+    double outer_sum = 0.0;
+    for(int i=0;i<76;i++) {
         //setup inner integral over the ellipsoidal cross-section
-        summj=0;
-        zi = ( Gauss76Z[i]*(vb-va) + va + vb )/2.0;     //the "x" dummy
-        arg = radius_minor*sqrt(1.0-zi*zi);
-        for(int j=0;j<nordj;j++) {
+        const double cos_val = ( Gauss76Z[i]*(vb-va) + va + vb )/2.0;
+        const double sin_val = sqrt(1.0 - cos_val*cos_val);
+        //const double arg = radius_minor*sin_val;
+        double inner_sum=0;
+        for(int j=0;j<20;j++) {
             //20 gauss points for the inner integral
-            zij = ( Gauss20Z[j]*(vbj-vaj) + vaj + vbj )/2.0;        //the "y" dummy
-            yyy = Gauss20Wt[j] * _elliptical_cylinder_kernel(q, arg, r_ratio, zij);
-            summj += yyy;
+            const double theta = ( Gauss20Z[j]*(vbj-vaj) + vaj + vbj )/2.0;
+            const double r = sin_val*sqrt(rA - rB*cos(theta));
+            const double be = sas_2J1x_x(q*r);
+            inner_sum += Gauss20Wt[j] * be * be;
         }
         //now calculate the value of the inner integral
-        answer = (vbj-vaj)/2.0*summj;
+        inner_sum *= 0.5*(vbj-vaj);
 
         //now calculate outer integral
-        arg = q*length*zi/2.0;
-        si = square(sinc(arg));
-        yyy = Gauss76Wt[i] * answer * si;
-        summ += yyy;
+        const double si = sas_sinx_x(q*0.5*length*cos_val);
+        outer_sum += Gauss76Wt[i] * inner_sum * si * si;
     }
+    outer_sum *= 0.5*(vb-va);
 
     //divide integral by Pi
-    answer = (vb-va)/2.0*summ/M_PI;
-    // Multiply by contrast^2
-    answer *= delrho*delrho;
+    const double form = outer_sum/M_PI;
 
+    // scale by contrast and volume, and convert to to 1/cm units
     const double vol = form_volume(radius_minor, r_ratio, length);
-    return answer*vol*vol*1.0e-4;
+    const double delrho = sld - solvent_sld;
+    return 1.0e-4*square(delrho*vol)*form;
 }
 
 
-double Iqxy(double qx, double qy, double radius_minor, double r_ratio, double length,
-            double sld, double solvent_sld, double theta, double phi, double psi) {
-    const double _theta = theta * M_PI / 180.0;
-    const double _phi = phi * M_PI / 180.0;
-    const double _psi = psi * M_PI / 180.0;
-    const double q = sqrt(qx*qx+qy*qy);
-    const double q_x = qx/q;
-    const double q_y = qy/q;
+double
+Iqxy(double qx, double qy,
+     double radius_minor, double r_ratio, double length,
+     double sld, double solvent_sld,
+     double theta, double phi, double psi)
+{
+    double q, cos_val, cos_mu, cos_nu;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_val, cos_mu, cos_nu);
 
-    //Cylinder orientation
-    double cyl_x = cos(_theta) * cos(_phi);
-    double cyl_y = sin(_theta);
-
-    //cyl_z = -cos(_theta) * sin(_phi);
-
-    // q vector
-    //q_z = 0;
-
-    // Note: cos(alpha) = 0 and 1 will get an
-    // undefined value from CylKernel
-    //alpha = acos( cos_val );
-
-    //ellipse orientation:
-    // the elliptical corss section was transformed and projected
-    // into the detector plane already through sin(alpha)and furthermore psi remains as same
-    // on the detector plane.
-    // So, all we need is to calculate the angle (nu) of the minor axis of the ellipse wrt
-    // the wave vector q.
-
-    //x- y- component on the detector plane.
-    const double ella_x =  -cos(_phi)*sin(_psi) * sin(_theta)+sin(_phi)*cos(_psi);
-    const double ella_y =  sin(_psi)*cos(_theta);
-    const double ellb_x =  -sin(_theta)*cos(_psi)*cos(_phi)-sin(_psi)*sin(_phi);
-    const double ellb_y =  cos(_theta)*cos(_psi);
-
-    // Compute the angle btw vector q and the
-    // axis of the cylinder
-    double cos_val = cyl_x*q_x + cyl_y*q_y;// + cyl_z*q_z;
-
-    // calculate the axis of the ellipse wrt q-coord.
-    double cos_nu = ella_x*q_x + ella_y*q_y;
-    double cos_mu = ellb_x*q_x + ellb_y*q_y;
-
-    // The following test should always pass
-    if (fabs(cos_val)>1.0) {
-      //printf("cyl_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val = 1.0;
-    }
-    if (fabs(cos_nu)>1.0) {
-      //printf("cyl_ana_2D: Unexpected error: cos(nu)>1\n");
-      cos_nu = 1.0;
-    }
-    if (fabs(cos_mu)>1.0) {
-      //printf("cyl_ana_2D: Unexpected error: cos(nu)>1\n");
-      cos_mu = 1.0;
-    }
-
-    const double r_major = r_ratio * radius_minor;
-    const double qr = q*sqrt( r_major*r_major*cos_nu*cos_nu + radius_minor*radius_minor*cos_mu*cos_mu );
-    const double qL = q*length*cos_val/2.0;
-
-    double Be;
-    if (qr==0){
-      Be = 0.5;
-    }else{
-      //Be = NR_BessJ1(qr)/qr;
-      Be = 0.5*sas_J1c(qr);
-    }
-
-    double Si;
-    if (qL==0){
-      Si = 1.0;
-    }else{
-      Si = sin(qL)/qL;
-    }
-
-    const double k = 2.0 * Be * Si;
+    // Compute:  r = sqrt((radius_major*cos_nu)^2 + (radius_minor*cos_mu)^2)
+    // Given:    radius_major = r_ratio * radius_minor
+    const double r = radius_minor*sqrt(square(r_ratio*cos_nu) + cos_mu*cos_mu);
+    const double be = sas_2J1x_x(q*r);
+    const double si = sas_sinx_x(q*0.5*length*cos_val);
+    const double Aq = be * si;
+    const double delrho = sld - solvent_sld;
     const double vol = form_volume(radius_minor, r_ratio, length);
-    return (sld - solvent_sld) * (sld - solvent_sld) * k * k *vol*vol*1.0e-4;
+    return 1.0e-4 * square(delrho * vol * Aq);
 }
Index: sasmodels/models/elliptical_cylinder.py
===================================================================
--- sasmodels/models/elliptical_cylinder.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/elliptical_cylinder.py	(revision fcb33e4672814b4a8fe1ee865d430090b5fc03e6)
@@ -20,5 +20,5 @@
 
     I(\vec q)=\frac{1}{V_\text{cyl}}\int{d\psi}\int{d\phi}\int{
-        p(\theta,\phi,\psi)F^2(\vec q,\alpha,\psi)\sin(\theta)d\theta}
+        p(\theta,\phi,\psi)F^2(\vec q,\alpha,\psi)\sin(\alpha)d\alpha}
 
 with the functions
@@ -26,5 +26,5 @@
 .. math::
 
-    F(\vec q,\alpha,\psi) = 2\frac{J_1(a)\sin(b)}{ab}
+    F(q,\alpha,\psi) = 2\frac{J_1(a)\sin(b)}{ab}
 
 where
@@ -32,10 +32,12 @@
 .. math::
 
-    a &= \vec q\sin(\alpha)\left[
-        r^2_\text{major}\sin^2(\psi)+r^2_\text{minor}\cos(\psi) \right]^{1/2}
+    a = qr'\sin(\alpha)
+    
+    b = q\frac{L}{2}\cos(\alpha)
+    
+    r'=\frac{r_{minor}}{\sqrt{2}}\sqrt{(1+\nu^{2}) + (1-\nu^{2})cos(\psi)}
 
-    b &= \vec q\frac{L}{2}\cos(\alpha)
 
-and the angle $\Psi$ is defined as the orientation of the major axis of the
+and the angle $\psi$ is defined as the orientation of the major axis of the
 ellipse with respect to the vector $\vec q$. The angle $\alpha$ is the angle
 between the axis of the cylinder and $\vec q$.
@@ -95,5 +97,13 @@
 
 L A Feigin and D I Svergun, *Structure Analysis by Small-Angle X-Ray and
-Neutron Scattering*, Plenum, New York, (1987)
+Neutron Scattering*, Plenum, New York, (1987) [see table 3.4]
+
+Authorship and Verification
+----------------------------
+
+* **Author:**
+* **Last Modified by:** 
+* **Last Reviewed by:**  Richard Heenan - corrected equation in docs **Date:** December 21, 2016
+
 """
 
Index: sasmodels/models/fcc_paracrystal.c
===================================================================
--- sasmodels/models/fcc_paracrystal.c	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/fcc_paracrystal.c	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -32,5 +32,5 @@
 
 	const double temp10 = exp((-1.0/8.0)*temp1*((temp7*temp7)+(temp8*temp8)+(temp9*temp9)));
-	result = pow((1.0-(temp10*temp10)),3)*temp6
+	result = cube(1.0-(temp10*temp10))*temp6
 	    / ( (1.0 - 2.0*temp10*cos(0.5*temp3*temp7) + temp10*temp10)
 	      * (1.0 - 2.0*temp10*cos(0.5*temp3*temp8) + temp10*temp10)
@@ -85,86 +85,28 @@
 }
 
+double Iqxy(double qx, double qy,
+    double dnn, double d_factor, double radius,
+    double sld, double solvent_sld,
+    double theta, double phi, double psi)
+{
+    double q, cos_a1, cos_a2, cos_a3;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_a3, cos_a2, cos_a1);
 
-double Iqxy(double qx, double qy, double dnn,
-    double d_factor, double radius,double sld, double solvent_sld,
-    double theta, double phi, double psi){
+    const double a1 = cos_a2 + cos_a3;
+    const double a2 = cos_a3 + cos_a1;
+    const double a3 = cos_a2 + cos_a1;
+    const double qd = 0.5*q*dnn;
+    const double arg = 0.5*square(qd*d_factor)*(a1*a1 + a2*a2 + a3*a3);
+    const double tanh_qd = tanh(arg);
+    const double cosh_qd = cosh(arg);
+    const double Zq = tanh_qd/(1. - cos(qd*a1)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*a2)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*a3)/cosh_qd);
 
-  double b3_x, b3_y, b1_x, b1_y, b2_x, b2_y; //b3_z,
-  // double q_z;
-  double cos_val_b3, cos_val_b2, cos_val_b1;
-  double a1_dot_q, a2_dot_q,a3_dot_q;
-  double answer;
-  double Zq, Fkq, Fkq_2;
+    //if (isnan(Zq)) printf("q:(%g,%g) qd: %g a1: %g a2: %g a3: %g arg: %g\n", qx, qy, qd, a1, a2, a3, arg);
 
-  //convert to q and make scaled values
-  double q = sqrt(qx*qx+qy*qy);
-  double q_x = qx/q;
-  double q_y = qy/q;
-
-  //convert angle degree to radian
-  theta = theta * M_PI_180;
-  phi = phi * M_PI_180;
-  psi = psi * M_PI_180;
-
-  const double Da = d_factor*dnn;
-  const double s1 = dnn/sqrt(0.75);
-
-
-  //the occupied volume of the lattice
-  const double latticescale = 2.0*sphere_volume(radius/s1);
-  // q vector
-  // 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))
-    // b3 axis orientation
-    b3_x = cos(theta) * cos(phi);
-    b3_y = sin(theta);
-    //b3_z = -cos(theta) * sin(phi);
-    cos_val_b3 =  b3_x*q_x + b3_y*q_y;// + b3_z*q_z;
-
-    //alpha = acos(cos_val_b3);
-    // b1 axis orientation
-    b1_x = -cos(phi)*sin(psi) * sin(theta)+sin(phi)*cos(psi);
-    b1_y = sin(psi)*cos(theta);
-    cos_val_b1 = b1_x*q_x + b1_y*q_y;
-    // b2 axis orientation
-    b2_x = -sin(theta)*cos(psi)*cos(phi)-sin(psi)*sin(phi);
-  	b2_y = cos(theta)*cos(psi);
-    cos_val_b2 = b2_x*q_x + b2_y*q_y;
-
-    // The following test should always pass
-    if (fabs(cos_val_b3)>1.0) {
-      //printf("FCC_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b3 = 1.0;
-    }
-    if (fabs(cos_val_b2)>1.0) {
-      //printf("FCC_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b2 = 1.0;
-    }
-    if (fabs(cos_val_b1)>1.0) {
-      //printf("FCC_ana_2D: Unexpected error: cos()>1\n");
-      cos_val_b1 = 1.0;
-    }
-    // Compute the angle btw vector q and the a3 axis
-    a3_dot_q = 0.5*dnn*q*(cos_val_b2+cos_val_b1-cos_val_b3);
-
-    // a1 axis
-    a1_dot_q = 0.5*dnn*q*(cos_val_b3+cos_val_b2-cos_val_b1);
-
-    // a2 axis
-    a2_dot_q = 0.5*dnn*q*(cos_val_b3+cos_val_b1-cos_val_b2);
-
-
-    // Get Fkq and Fkq_2
-    Fkq = exp(-0.5*pow(Da/dnn,2.0)*(a1_dot_q*a1_dot_q+a2_dot_q*a2_dot_q+a3_dot_q*a3_dot_q));
-    Fkq_2 = Fkq*Fkq;
-    // Call Zq=Z1*Z2*Z3
-    Zq = (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a1_dot_q)+Fkq_2);
-    Zq *= (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a2_dot_q)+Fkq_2);
-    Zq *= (1.0-Fkq_2)/(1.0-2.0*Fkq*cos(a3_dot_q)+Fkq_2);
-
-  // Use SphereForm directly from libigor
-  answer = sphere_form(q,radius,sld,solvent_sld)*Zq*latticescale;
-
-  return answer;
- }
+    const double Fq = sphere_form(q,radius,sld,solvent_sld)*Zq;
+    //the occupied volume of the lattice
+    const double lattice_scale = 4.0*sphere_volume(M_SQRT1_2*radius/dnn);
+    return lattice_scale * Fq;
+}
Index: sasmodels/models/fcc_paracrystal.py
===================================================================
--- sasmodels/models/fcc_paracrystal.py	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/fcc_paracrystal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -116,5 +116,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/gauss150.c", "lib/sphere_form.c", "fcc_paracrystal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/gauss150.c", "lib/sphere_form.c", "fcc_paracrystal.c"]
 
 # parameters for demo
Index: sasmodels/models/flexible_cylinder.c
===================================================================
--- sasmodels/models/flexible_cylinder.c	(revision e6408d049d3598925e345d8adec1cdb0bb0c6adb)
+++ sasmodels/models/flexible_cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -14,5 +14,5 @@
 {
     const double contrast = sld - solvent_sld;
-    const double cross_section = sas_J1c(q*radius);
+    const double cross_section = sas_2J1x_x(q*radius);
     const double volume = M_PI*radius*radius*length;
     const double flex = Sk_WR(q, length, kuhn_length);
Index: sasmodels/models/flexible_cylinder_elliptical.c
===================================================================
--- sasmodels/models/flexible_cylinder_elliptical.c	(revision b66d38efa49109d61182ada21c4f30e06f75d4cd)
+++ sasmodels/models/flexible_cylinder_elliptical.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -2,6 +2,4 @@
 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,
@@ -17,18 +15,16 @@
 elliptical_crosssection(double q, double a, double b)
 {
-    double summ=0.0;
+    double sum=0.0;
 
     for(int i=0;i<N_POINTS_76;i++) {
-        double zi = ( Gauss76Z[i] + 1.0 )*M_PI/4.0;
+        const double zi = ( Gauss76Z[i] + 1.0 )*M_PI_4;
         double sn, cn;
         SINCOS(zi, sn, cn);
-        double arg = q*sqrt(a*a*sn*sn+b*b*cn*cn);
-        double yyy = pow((double)sas_J1c(arg),2);
-        yyy *= Gauss76Wt[i];
-        summ += yyy;
+        const double arg = q*sqrt(a*a*sn*sn + b*b*cn*cn);
+        const double yyy = sas_2J1x_x(arg);
+        sum += Gauss76Wt[i] * yyy * yyy;
     }
-
-    summ /= 2.0;
-    return(summ);
+    sum *= 0.5;
+    return(sum);
 
 }
@@ -77,22 +73,2 @@
 }
 
-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/fractal.c
===================================================================
--- sasmodels/models/fractal.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/fractal.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -1,54 +1,25 @@
-double Iq(double q,
-          double volfraction,
-          double radius,
-          double fractal_dim,
-          double cor_length,
-          double sld_block,
-          double sld_solvent);
+#define INVALID(p) (p.fractal_dim < 0.0)
 
-double Iq(double q,
-          double volfraction,
-          double radius,
-          double fractal_dim,
-          double cor_length,
-          double sld_block,
-          double sld_solvent)
+static double
+Iq(double q,
+   double volfraction,
+   double radius,
+   double fractal_dim,
+   double cor_length,
+   double sld_block,
+   double sld_solvent)
 {
-    double qr,r0,Df,corr,phi,sldp,sldm;
-    double pq,sq,inten;
-    
-     // Actively check the argument - needed for mass fractal - is it needie
-     //here?
-    if (fractal_dim <= 0.0){
-       return 0.0;
-    }
-   
-    phi = volfraction;        // volume fraction of building block spheres...
-    r0 = radius;     //  radius of building block
-    Df = fractal_dim;     //  fractal dimension
-    corr = cor_length;       //  correlation length of fractal-like aggregates
-    sldp = sld_block;       // SLD of building block
-    sldm = sld_solvent;       // SLD of matrix or solution
- 
-     qr=q*r0;
-    
+    const double sq = fractal_sq(q, radius, fractal_dim, cor_length);
+
     //calculate P(q) for the spherical subunits
-    pq = phi*M_4PI_3*r0*r0*r0*(sldp-sldm)*(sldp-sldm)*sph_j1c(qr)*sph_j1c(qr);
-    
-    //calculate S(q)
-    sq = Df*sas_gamma(Df-1.0)*sin((Df-1.0)*atan(q*corr));
-    sq /= pow(qr,Df) * pow((1.0 + 1.0/(q*corr)/(q*corr)),((Df-1.0)/2.0));
-    sq += 1.0;
-    
-    //combine, scale to units cm-1 sr-1 (assuming data on absolute scale)
-    //and return
-    inten = pq*sq;
-    // convert I(1/A) to (1/cm)
-    inten *= 1.0e8;
-    //convert rho^2 in 10^-6 1/A to 1/A
-    inten *= 1.0e-12;    
-    
-    
-    return(inten);
+    const double V = M_4PI_3*cube(radius);
+    const double pq = V * square((sld_block-sld_solvent)*sas_3j1x_x(q*radius));
+
+    // scale to units cm-1 sr-1 (assuming data on absolute scale)
+    //    convert I(1/A) to (1/cm)  => 1e8 * I(q)
+    //    convert rho^2 in 10^-6 1/A to 1/A  => 1e-12 * I(q)
+    //    combined: 1e-4 * I(q)
+
+    return 1.e-4 * volfraction * sq * pq;
 }
 
Index: sasmodels/models/fractal.py
===================================================================
--- sasmodels/models/fractal.py	(revision d1cfa86b03d71f574114b242dc91eefade357ba2)
+++ sasmodels/models/fractal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -97,5 +97,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/sas_gamma.c", "fractal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sas_gamma.c", "lib/fractal_sq.c", "fractal.c"]
 
 demo = dict(volfraction=0.05,
Index: sasmodels/models/fractal_core_shell.c
===================================================================
--- sasmodels/models/fractal_core_shell.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/fractal_core_shell.c	(revision bdd08df2ecb671a5503c0699fe24a2bc3f2a8a7a)
@@ -1,47 +1,28 @@
-double form_volume(double radius, double thickness);
-
-double Iq(double q,
-          double radius,
-          double thickness,
-          double core_sld,
-          double shell_sld,
-          double solvent_sld,
-          double volfraction,
-          double fractal_dim,
-          double cor_length);
-
-double form_volume(double radius, double thickness)
+static double
+form_volume(double radius, double thickness)
 {
-    return 4.0 * M_PI / 3.0 * pow((radius + thickness), 3);
+    return M_4PI_3 * cube(radius + thickness);
 }
 
-double Iq(double q,
-          double radius,
-          double thickness,
-          double core_sld,
-          double shell_sld,
-          double solvent_sld,
-          double volfraction,
-          double fractal_dim,
-          double cor_length) {
+static double
+Iq(double q,
+   double radius,
+   double thickness,
+   double core_sld,
+   double shell_sld,
+   double solvent_sld,
+   double volfraction,
+   double fractal_dim,
+   double cor_length)
+{
+    //The radius for the building block of the core shell particle that is 
+    //needed by the Teixeira fractal S(q) is the radius of the whole particle.
+    const double cs_radius = radius + thickness;
+    const double sq = fractal_sq(q, cs_radius, fractal_dim, cor_length);
+    const double pq = core_shell_kernel(q, radius, thickness,
+                                        core_sld, shell_sld, solvent_sld);
 
-
-   double intensity = core_shell_kernel(q,
-                              radius,
-                              thickness,
-                              core_sld,
-                              shell_sld,
-                              solvent_sld);
-    //calculate S(q)
-    double frac_1 = fractal_dim-1.0;
-    double qr = q*radius;
-
-    double t1 = fractal_dim*sas_gamma(frac_1)*sin(frac_1*atan(q*cor_length));
-    double t2 = (1.0 + 1.0/(q*cor_length)/(q*cor_length));
-    double t3 = pow(qr, fractal_dim) * pow(t2, (frac_1/2.0));
-    double sq = t1/t3;
-    sq += 1.0;
-
-    return sq*intensity*volfraction;
+    // Note: core_shell_kernel already performs the 1e-4 unit conversion
+    return volfraction * sq * pq;
 }
 
Index: sasmodels/models/fractal_core_shell.py
===================================================================
--- sasmodels/models/fractal_core_shell.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/fractal_core_shell.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -1,42 +1,41 @@
 r"""
+Definition
+----------
 Calculates the scattering from a fractal structure with a primary building
 block of core-shell spheres, as opposed to just homogeneous spheres in
-the fractal model.
-This model could find use for aggregates of coated particles, or aggregates
-of vesicles.
-
-Definition
-----------
+the fractal model. It is an extension of the well known Teixeira\ [#teixeira]_
+fractal model replacing the $P(q)$ of a solid sphere with that of a core-shell
+sphere. This model could find use for aggregates of coated particles, or
+aggregates of vesicles for example.
 
 .. math::
 
-    I(q) = \text{background} + P(q)S(q)
+    I(q) = P(q)S(q) + \text{background}
 
-The form factor $P(q)$ is that from core_shell model with $bkg$ = 0
-
+Where $P(q)$ is the core-shell form factor and $S(q)$ is the
+Teixeira\ [#teixeira]_ fractal structure factor both of which are given again
+below:
 
 .. math::
 
-    P(q)=\frac{scale}{V_s}\left[3V_c(\rho_c-\rho_s)
+    P(q) &= \frac{\phi}{V_s}\left[3V_c(\rho_c-\rho_s)
     \frac{\sin(qr_c)-qr_c\cos(qr_c)}{(qr_c)^3}+
     3V_s(\rho_s-\rho_{solv})
     \frac{\sin(qr_s)-qr_s\cos(qr_s)}{(qr_s)^3}\right]^2
 
+    S(q) &= 1 + \frac{D_f\ \Gamma\!(D_f-1)}{[1+1/(q\xi)^2]^{(D_f-1)/2}} 
+    \frac{\sin[(D_f-1)\tan^{-1}(q\xi)]}{(qr_s)^{D_f}}
 
-while the fractal structure factor $S(q)$ is
+where $\phi$ is the volume fraction of particles, $V_s$ is the volume of the
+whole particle, $V_c$ is the volume of the core, $\rho_c$, $\rho_s$, and
+$\rho_{solv}$ are the scattering length densities of the core, shell, and
+solvent respectively, $r_c$ and $r_s$ are the radius of the core and the radius
+of the whole particle respectively, $D_f$ is the fractal dimension, and |xi| the
+correlation length.
+ 
+Polydispersity of radius and thickness are also provided for.
 
-.. math::
-
-    S(q) = \frac{D_f\Gamma(D_f-1)\sin((D_f-1)\tan^{-1}(q\xi))}
-    {(qr_c)^{D_f}\left(1+\frac{1}{q^2\xi ^2} \right)^{\frac{D_f-1}{2}}}
-
-where $D_f$ = fractal_dim, |xi| = cor_length, $r_c$ = (core) radius, and
-$scale$ = volume fraction.
-
-The fractal structure is as documented in the fractal model.
-Polydispersity of radius and thickness is provided for.
-
-For 2D data: The 2D scattering intensity is calculated in the same way as 1D,
-where the $q$ vector is defined as
+This model does not allow for anisotropy and thus the 2D scattering intensity
+is calculated in the same way as 1D, where the $q$ vector is defined as
 
 .. math::
@@ -44,8 +43,19 @@
     q = \sqrt{q_x^2 + q_y^2}
 
+Our model is derived from the form factor calculations implemented in IGOR
+macros by the NIST Center for Neutron Research\ [#Kline]_
+
 References
 ----------
 
-See the core_shell and fractal model descriptions
+.. [#teixeira] J Teixeira, *J. Appl. Cryst.*, 21 (1988) 781-785
+.. [#Kline]  S R Kline, *J Appl. Cryst.*, 39 (2006) 895
+
+Authorship and Verification
+----------------------------
+
+* **Author:** NIST IGOR/DANSE **Date:** pre 2010
+* **Last Modified by:** Paul Butler and Paul Kienzle **on:** November 27, 2016
+* **Last Reviewed by:** Paul Butler and Paul Kienzle **on:** November 27, 2016
 
 """
@@ -54,8 +64,19 @@
 
 name = "fractal_core_shell"
-title = ""
-description = """
+title = "Scattering from a fractal structure formed from core shell spheres"
+description = """\
+    Model for fractal aggregates of core-shell primary particles. It is based on
+    the Teixeira model for the S(q) of a fractal * P(q) for a core-shell sphere
 
-"""
+    radius =  the radius of the core
+    thickness = thickness of the shell
+    thick_layer = thickness of a layer
+    sld_core = the SLD of the core
+    sld_shell = the SLD of the shell
+    sld_solvent = the SLD of the solvent
+    volfraction = volume fraction of core-shell particles
+    fractal_dim = fractal dimension
+    cor_length = correlation length of the fractal like aggretates
+    """
 category = "shape-independent"
 
@@ -63,15 +84,17 @@
 #   ["name", "units", default, [lower, upper], "type","description"],
 parameters = [
-    ["radius",      "Ang",        60.0, [0, inf],    "volume", "Sphere core radius"],
-    ["thickness",   "Ang",        10.0, [0, inf],    "volume", "Sphere shell thickness"],
+    ["radius",      "Ang",        60.0, [0.0, inf],  "volume", "Sphere core radius"],
+    ["thickness",   "Ang",        10.0, [0.0, inf],  "volume", "Sphere shell thickness"],
     ["sld_core",    "1e-6/Ang^2", 1.0,  [-inf, inf], "sld",    "Sphere core scattering length density"],
     ["sld_shell",   "1e-6/Ang^2", 2.0,  [-inf, inf], "sld",    "Sphere shell scattering length density"],
     ["sld_solvent", "1e-6/Ang^2", 3.0,  [-inf, inf], "sld",    "Solvent scattering length density"],
-    ["volfraction", "",           1.0,  [0, inf],    "",       "Volume fraction of building block spheres"],
-    ["fractal_dim",    "",           2.0,  [-inf, inf], "",       "Fractal dimension"],
-    ["cor_length",  "Ang",      100.0,  [0, inf],    "",       "Correlation length of fractal-like aggregates"]]
+    ["volfraction", "",           1.0,  [0.0, inf],  "",       "Volume fraction of building block spheres"],
+    ["fractal_dim",    "",        2.0,  [0.0, 6.0],  "",       "Fractal dimension"],
+    ["cor_length",  "Ang",      100.0,  [0.0, inf],  "",       "Correlation length of fractal-like aggregates"],
+]
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/sas_gamma.c", "lib/core_shell.c", "fractal_core_shell.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sas_gamma.c", "lib/core_shell.c",
+          "lib/fractal_sq.c", "fractal_core_shell.c"]
 
 demo = dict(scale=0.05,
@@ -100,17 +123,21 @@
         @param thickness: shell thickness
     """
-    whole = 4.0 * pi / 3.0 * pow((radius + thickness), 3)
-    core = 4.0 * pi / 3.0 * radius * radius * radius
+    whole = 4.0/3.0 * pi * (radius + thickness)**3
+    core = 4.0/3.0 * pi * radius**3
     return whole, whole-core
 
 tests = [[{'radius': 20.0, 'thickness': 10.0}, 'ER', 30.0],
-         [{'radius': 20.0, 'thickness': 10.0}, 'VR', 0.703703704],
+         [{'radius': 20.0, 'thickness': 10.0}, 'VR', 0.703703704]]
 
-         # The SasView test result was 0.00169, with a background of 0.001
-         [{'radius': 60.0,
-           'thickness': 10.0,
-           'sld_core': 1.0,
-           'sld_shell': 2.0,
-           'sld_solvent': 3.0,
-           'background': 0.0
-          }, 0.4, 0.00070126]]
+#         # The SasView test result was 0.00169, with a background of 0.001
+#         # They are however wrong as we now know.  IGOR might be a more
+#         # appropriate source. Otherwise will just have to assume this is now
+#         # correct and self generate a correct answer for the future. Until we
+#         # figure it out leave the tests commented out
+#         [{'radius': 60.0,
+#           'thickness': 10.0,
+#           'sld_core': 1.0,
+#           'sld_shell': 2.0,
+#           'sld_solvent': 3.0,
+#           'background': 0.0
+#          }, 0.015211, 692.84]]
Index: sasmodels/models/fuzzy_sphere.py
===================================================================
--- sasmodels/models/fuzzy_sphere.py	(revision 9a4811ac2bdb93459d640d111cba9f1016660790)
+++ sasmodels/models/fuzzy_sphere.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -81,15 +81,15 @@
 # pylint: enable=bad-whitespace,line-too-long
 
-source = ["lib/sph_j1c.c"]
+source = ["lib/sas_3j1x_x.c"]
 
 # No volume normalization despite having a volume parameter
 # This should perhaps be volume normalized?
 form_volume = """
-    return 1.333333333333333*M_PI*radius*radius*radius;
+    return M_4PI_3*cube(radius);
     """
 
 Iq = """
     const double qr = q*radius;
-    const double bes = sph_j1c(qr);
+    const double bes = sas_3j1x_x(qr);
     const double qf = q*fuzziness;
     const double fq = bes * (sld - sld_solvent) * form_volume(radius) * exp(-0.5*qf*qf);
Index: sasmodels/models/guinier_porod.py
===================================================================
--- sasmodels/models/guinier_porod.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/guinier_porod.py	(revision cdcebf1033e85ca2bb11488372c82a9050ec942a)
@@ -4,5 +4,6 @@
 and dimensionality of scattering objects, including asymmetric objects
 such as rods or platelets, and shapes intermediate between spheres
-and rods or between rods and platelets.
+and rods or between rods and platelets, and overcomes some of the 
+deficiencies of the (Beaucage) Unified_Power_Rg model (see Hammouda, 2010).
 
 Definition
@@ -59,9 +60,8 @@
 ---------
 
-A Guinier, G Fournet, *Small-Angle Scattering of X-Rays*,
-John Wiley and Sons, New York, (1955)
+B Hammouda, *A new Guinier-Porod model, J. Appl. Cryst.*, (2010), 43, 716-719
 
-O Glatter, O Kratky, *Small-Angle X-Ray Scattering*, Academic Press (1982)
-Check out Chapter 4 on Data Treatment, pages 155-156.
+B Hammouda, *Analysis of the Beaucage model, J. Appl. Cryst.*, (2010), 43, 1474-1478
+
 """
 
Index: sasmodels/models/hayter_msa.c
===================================================================
--- sasmodels/models/hayter_msa.c	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/hayter_msa.c	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -23,9 +23,7 @@
 	double SIdiam, diam, Kappa, cs, IonSt;
 	double  Perm, Beta;
-	double pi, charge;
+	double charge;
 	int ierr;
 	
-	pi = M_PI;
-
 	diam=2*radius_effective;		//in A
 
@@ -38,5 +36,5 @@
 	charge=zz*Elcharge;		//in Coulomb (C)
 	SIdiam = diam*1.0E-10;		//in m
-	Vp=4.0*pi/3.0*(SIdiam/2.0)*(SIdiam/2.0)*(SIdiam/2.0);	//in m^3
+	Vp=M_4PI_3*cube(SIdiam/2.0);	//in m^3
 	cs=csalt*6.022E23*1.0E3;	//# salt molecules/m^3
 	
@@ -50,5 +48,5 @@
 	Kappa=sqrt(2*Beta*IonSt/Perm);     //Kappa calc from Ionic strength
 									   //	Kappa=2/SIdiam					// Use to compare with HP paper
-	gMSAWave[5]=Beta*charge*charge/(pi*Perm*SIdiam*pow((2.0+Kappa*SIdiam),2));
+	gMSAWave[5]=Beta*charge*charge/(M_PI*Perm*SIdiam*square(2.0+Kappa*SIdiam));
 	
 	//         Finally set up dimensionless parameters 
Index: sasmodels/models/hollow_cylinder.c
===================================================================
--- sasmodels/models/hollow_cylinder.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/hollow_cylinder.c	(revision 592343f7d47ec3aad7ffa1ca242acb1346553ede)
@@ -1,4 +1,3 @@
 double form_volume(double radius, double thickness, double length);
-
 double Iq(double q, double radius, double thickness, double length, double sld,
 	double solvent_sld);
@@ -9,107 +8,67 @@
 
 // From Igor library
-static double hollow_cylinder_scaling(
-    double integrand, double delrho, double volume)
+static double
+_hollow_cylinder_scaling(double integrand, double delrho, double volume)
 {
-    double answer;
-    // Multiply by contrast^2
-    answer = integrand*delrho*delrho;
-
-    //normalize by cylinder volume
-    answer *= volume*volume;
-
-    //convert to [cm-1]
-    answer *= 1.0e-4;
-
-    return answer;
+    return 1.0e-4 * square(volume * delrho) * integrand;
 }
 
 
-static double _hollow_cylinder_kernel(
-    double q, double radius, double thickness, double length, double dum)
+static double
+_hollow_cylinder_kernel(double q,
+    double radius, double thickness, double length, double sin_val, double cos_val)
 {
-    const double qs = q*sqrt(1.0-dum*dum);
-    const double lam1 = sas_J1c((radius+thickness)*qs);
-    const double lam2 = sas_J1c(radius*qs);
+    const double qs = q*sin_val;
+    const double lam1 = sas_2J1x_x((radius+thickness)*qs);
+    const double lam2 = sas_2J1x_x(radius*qs);
     const double gamma_sq = square(radius/(radius+thickness));
-    //Note: lim_{r -> r_c} psi = J0(radius_core*qs)
+    //Note: lim_{thickness -> 0} psi = sas_J0(radius*qs)
+    //Note: lim_{radius -> 0} psi = sas_2J1x_x(thickness*qs)
     const double psi = (lam1 - gamma_sq*lam2)/(1.0 - gamma_sq);	//SRK 10/19/00
-    const double t2 = sinc(q*length*dum/2.0);
-    return square(psi*t2);
+    const double t2 = sas_sinx_x(0.5*q*length*cos_val);
+    return psi*t2;
+}
+
+double
+form_volume(double radius, double thickness, double length)
+{
+    double v_shell = M_PI*length*(square(radius+thickness) - radius*radius);
+    return v_shell;
 }
 
 
-static double hollow_cylinder_analytical_2D_scaled(
-    double q, double q_x, double q_y, double radius, double thickness,
-    double length, double sld, double solvent_sld, double theta, double phi)
+double
+Iq(double q, double radius, double thickness, double length,
+    double sld, double solvent_sld)
 {
-    double cyl_x, cyl_y; //, cyl_z
-    //double q_z;
-    double vol, cos_val, delrho;
-    double answer;
-    //convert angle degree to radian
-    theta = theta * M_PI_180;
-    phi = phi * M_PI_180;
-    delrho = solvent_sld - sld;
+    const double lower = 0.0;
+    const double upper = 1.0;		//limits of numerical integral
 
-    // Cylinder orientation
-    cyl_x = sin(theta) * cos(phi);
-    cyl_y = sin(theta) * sin(phi);
-    //cyl_z = -cos(theta) * sin(phi);
+    double summ = 0.0;			//initialize intergral
+    for (int i=0;i<76;i++) {
+        const double cos_val = 0.5*( Gauss76Z[i] * (upper-lower) + lower + upper );
+        const double sin_val = sqrt(1.0 - cos_val*cos_val);
+        const double inter = _hollow_cylinder_kernel(q, radius, thickness, length,
+                                                     sin_val, cos_val);
+        summ += Gauss76Wt[i] * inter * inter;
+    }
 
-    // q vector
-    //q_z = 0;
-
-    // Compute the angle btw vector q and the
-    // axis of the cylinder
-    cos_val = cyl_x*q_x + cyl_y*q_y;// + cyl_z*q_z;
-
-    answer = _hollow_cylinder_kernel(q, radius, thickness, length, cos_val);
-
-    vol = form_volume(radius, thickness, length);
-    answer = hollow_cylinder_scaling(answer, delrho, vol);
-
-    return answer;
+    const double Aq = 0.5*summ*(upper-lower);
+    const double volume = form_volume(radius, thickness, length);
+    return _hollow_cylinder_scaling(Aq, solvent_sld - sld, volume);
 }
 
+double
+Iqxy(double qx, double qy,
+    double radius, double thickness, double length,
+    double sld, double solvent_sld, double theta, double phi)
+{
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
+    const double Aq = _hollow_cylinder_kernel(q, radius, thickness, length,
+        sin_alpha, cos_alpha);
 
-double form_volume(double radius, double thickness, double length)
-{
-    double v_shell = M_PI*length*((radius+thickness)*(radius+thickness)-radius*radius);
-    return(v_shell);
+    const double vol = form_volume(radius, thickness, length);
+    return _hollow_cylinder_scaling(Aq*Aq, solvent_sld-sld, vol);
 }
 
-
-double Iq(double q, double radius, double thickness, double length,
-    double sld, double solvent_sld)
-{
-    int i;
-    double lower,upper,zi, inter;		//upper and lower integration limits
-    double summ,answer,delrho;			//running tally of integration
-    double norm,volume;	//final calculation variables
-
-    lower = 0.0;
-    upper = 1.0;		//limits of numerical integral
-
-    summ = 0.0;			//initialize intergral
-    for (i=0;i<76;i++) {
-        zi = ( Gauss76Z[i] * (upper-lower) + lower + upper )/2.0;
-        inter = Gauss76Wt[i] * _hollow_cylinder_kernel(q, radius, thickness, length, zi);
-        summ += inter;
-    }
-
-    norm = summ*(upper-lower)/2.0;
-    volume = form_volume(radius, thickness, length);
-    delrho = solvent_sld - sld;
-    answer = hollow_cylinder_scaling(norm, delrho, volume);
-
-    return(answer);
-}
-
-
-double Iqxy(double qx, double qy, double radius, double thickness,
-    double length, double sld, double solvent_sld, double theta, double phi)
-{
-    const double q = sqrt(qx*qx+qy*qy);
-    return hollow_cylinder_analytical_2D_scaled(q, qx/q, qy/q, radius, thickness, length, sld, solvent_sld, theta, phi);
-}
Index: sasmodels/models/hollow_rectangular_prism.c
===================================================================
--- sasmodels/models/hollow_rectangular_prism.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/hollow_rectangular_prism.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -2,16 +2,14 @@
 double Iq(double q, double sld, double solvent_sld, double length_a, 
           double b2a_ratio, double c2a_ratio, double thickness);
-double Iqxy(double qx, double qy, double sld, double solvent_sld, 
-            double length_a, double b2a_ratio, double c2a_ratio, double thickness);
 
 double form_volume(double length_a, double b2a_ratio, double c2a_ratio, double thickness)
 {
-    double b_side = length_a * b2a_ratio;
-    double c_side = length_a * c2a_ratio;
+    double length_b = length_a * b2a_ratio;
+    double length_c = length_a * c2a_ratio;
     double a_core = length_a - 2.0*thickness;
-    double b_core = b_side - 2.0*thickness;
-    double c_core = c_side - 2.0*thickness;
+    double b_core = length_b - 2.0*thickness;
+    double c_core = length_c - 2.0*thickness;
     double vol_core = a_core * b_core * c_core;
-    double vol_total = length_a * b_side * c_side;
+    double vol_total = length_a * length_b * length_c;
     double vol_shell = vol_total - vol_core;
     return vol_shell;
@@ -26,90 +24,62 @@
     double thickness)
 {
-    double termA1, termA2, termB1, termB2, termC1, termC2;
+    const double length_b = length_a * b2a_ratio;
+    const double length_c = length_a * c2a_ratio;
+    const double a_half = 0.5 * length_a;
+    const double b_half = 0.5 * length_b;
+    const double c_half = 0.5 * length_c;
+    const double vol_total = length_a * length_b * length_c;
+    const double vol_core = 8.0 * (a_half-thickness) * (b_half-thickness) * (c_half-thickness);
+
+    //Integration limits to use in Gaussian quadrature
+    const double v1a = 0.0;
+    const double v1b = M_PI_2;  //theta integration limits
+    const double v2a = 0.0;
+    const double v2b = M_PI_2;  //phi integration limits
     
-    double b_side = length_a * b2a_ratio;
-    double c_side = length_a * c2a_ratio;
-    double a_half = 0.5 * length_a;
-    double b_half = 0.5 * b_side;
-    double c_half = 0.5 * c_side;
+    double outer_sum = 0.0;
+    for(int i=0; i<76; i++) {
 
-   //Integration limits to use in Gaussian quadrature
-    double v1a = 0.0;
-    double v1b = 0.5 * M_PI;  //theta integration limits
-    double v2a = 0.0;
-    double v2b = 0.5 * M_PI;  //phi integration limits
-    
-    //Order of integration
-    int nordi=76;			        
-    int nordj=76;
+        const double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );
+        double sin_theta, cos_theta;
+        SINCOS(theta, sin_theta, cos_theta);
 
-    double sumi = 0.0;
-    
-    for(int i=0; i<nordi; i++) {
+        const double termC1 = sas_sinx_x(q * c_half * cos(theta));
+        const double termC2 = sas_sinx_x(q * (c_half-thickness)*cos(theta));
 
-	    double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );	
+        double inner_sum = 0.0;
+        for(int j=0; j<76; j++) {
 
-	    double arg = q * c_half * cos(theta);
-	    if (fabs(arg) > 1.e-16) {termC1 = sin(arg)/arg;} else {termC1 = 1.0;}
-	    arg = q * (c_half-thickness)*cos(theta);
-	    if (fabs(arg) > 1.e-16) {termC2 = sin(arg)/arg;} else {termC2 = 1.0;}
-
-	    double sumj = 0.0;
-        
-	    for(int j=0; j<nordj; j++) {
-
-            double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b ); 
+            const double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b );
+            double sin_phi, cos_phi;
+            SINCOS(phi, sin_phi, cos_phi);
 
             // Amplitude AP from eqn. (13), rewritten to avoid round-off effects when arg=0
 
-	        arg = q * a_half * sin(theta) * sin(phi);
-	        if (fabs(arg) > 1.e-16) {termA1 = sin(arg)/arg;} else {termA1 = 1.0;}
-	        arg = q * (a_half-thickness) * sin(theta) * sin(phi);
-	        if (fabs(arg) > 1.e-16) {termA2 = sin(arg)/arg;} else {termA2 = 1.0;}
+            const double termA1 = sas_sinx_x(q * a_half * sin_theta * sin_phi);
+            const double termA2 = sas_sinx_x(q * (a_half-thickness) * sin_theta * sin_phi);
 
-	        arg = q * b_half * sin(theta) * cos(phi);
-	        if (fabs(arg) > 1.e-16) {termB1 = sin(arg)/arg;} else {termB1 = 1.0;}
-	        arg = q * (b_half-thickness) * sin(theta) * cos(phi);
-	        if (fabs(arg) > 1.e-16) {termB2 = sin(arg)/arg;} else {termB2 = 1.0;}
+            const double termB1 = sas_sinx_x(q * b_half * sin_theta * cos_phi);
+            const double termB2 = sas_sinx_x(q * (b_half-thickness) * sin_theta * cos_phi);
 
-            double AP1 = (length_a*b_side*c_side) * termA1 * termB1 * termC1;
-            double AP2 = 8.0 * (a_half-thickness) * (b_half-thickness) * (c_half-thickness) * termA2 * termB2 * termC2;
-            double AP = AP1 - AP2;
+            const double AP1 = vol_total * termA1 * termB1 * termC1;
+            const double AP2 = vol_core * termA2 * termB2 * termC2;
 
-	        sumj += Gauss76Wt[j] * (AP*AP);
+            inner_sum += Gauss76Wt[j] * square(AP1-AP2);
+        }
+        inner_sum *= 0.5 * (v2b-v2a);
 
-	    }
-
-	    sumj = 0.5 * (v2b-v2a) * sumj;
-	    sumi += Gauss76Wt[i] * sumj * sin(theta);
-
+        outer_sum += Gauss76Wt[i] * inner_sum * sin(theta);
     }
-
-    double answer = 0.5*(v1b-v1a)*sumi;
+    outer_sum *= 0.5*(v1b-v1a);
 
     // Normalize as in Eqn. (15) without the volume factor (as cancels with (V*DelRho)^2 normalization)
     // The factor 2 is due to the different theta integration limit (pi/2 instead of pi)
-    answer *= (2.0/M_PI);
+    const double form = outer_sum/M_PI_2;
 
     // Multiply by contrast^2. Factor corresponding to volume^2 cancels with previous normalization.
-    answer *= (sld-solvent_sld)*(sld-solvent_sld);
+    const double delrho = sld - solvent_sld;
 
     // Convert from [1e-12 A-1] to [cm-1]
-    answer *= 1.0e-4;
-
-    return answer;
-    
+    return 1.0e-4 * delrho * delrho * form;
 }
-
-double Iqxy(double qx, double qy,
-    double sld,
-    double solvent_sld,
-    double length_a,
-    double b2a_ratio,
-    double c2a_ratio,
-    double thickness)
-{
-    double q = sqrt(qx*qx + qy*qy);
-    double intensity = Iq(q, sld, solvent_sld, length_a, b2a_ratio, c2a_ratio, thickness); 
-    return intensity;    
-}
Index: sasmodels/models/hollow_rectangular_prism.py
===================================================================
--- sasmodels/models/hollow_rectangular_prism.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/hollow_rectangular_prism.py	(revision ab2aea81f7fb392e5f24f1c76dd989914d8f7022)
@@ -13,5 +13,5 @@
 the difference of the amplitudes of two massive parallelepipeds
 differing in their outermost dimensions in each direction by the
-same length increment :math:`2\Delta` (Nayuk, 2012).
+same length increment $2\Delta$ (Nayuk, 2012).
 
 As in the case of the massive parallelepiped model (:ref:`rectangular-prism`),
@@ -58,9 +58,9 @@
 
 .. math::
-  I(q) = \text{scale} \times V \times (\rho_{\text{p}} -
-  \rho_{\text{solvent}})^2 \times P(q) + \text{background}
+  I(q) = \text{scale} \times V \times (\rho_\text{p} -
+  \rho_\text{solvent})^2 \times P(q) + \text{background}
 
-where $\rho_{\text{p}}$ is the scattering length of the parallelepiped,
-$\rho_{\text{solvent}}$ is the scattering length of the solvent,
+where $\rho_\text{p}$ is the scattering length of the parallelepiped,
+$\rho_\text{solvent}$ is the scattering length of the solvent,
 and (if the data are in absolute units) *scale* represents the volume fraction
 (which is unitless).
@@ -148,5 +148,5 @@
 # parameters for demo
 demo = dict(scale=1, background=0,
-            sld=6.3e-6, sld_solvent=1.0e-6,
+            sld=6.3, sld_solvent=1.0,
             length_a=35, b2a_ratio=1, c2a_ratio=1, thickness=1,
             length_a_pd=0.1, length_a_pd_n=10,
Index: sasmodels/models/hollow_rectangular_prism_thin_walls.c
===================================================================
--- sasmodels/models/hollow_rectangular_prism_thin_walls.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/hollow_rectangular_prism_thin_walls.c	(revision ab2aea81f7fb392e5f24f1c76dd989914d8f7022)
@@ -2,12 +2,10 @@
 double Iq(double q, double sld, double solvent_sld, double length_a, 
           double b2a_ratio, double c2a_ratio);
-double Iqxy(double qx, double qy, double sld, double solvent_sld, 
-            double length_a, double b2a_ratio, double c2a_ratio);
 
 double form_volume(double length_a, double b2a_ratio, double c2a_ratio)
 {
-    double b_side = length_a * b2a_ratio;
-    double c_side = length_a * c2a_ratio;
-    double vol_shell = 2.0 * (length_a*b_side + length_a*c_side + b_side*c_side);
+    double length_b = length_a * b2a_ratio;
+    double length_c = length_a * c2a_ratio;
+    double vol_shell = 2.0 * (length_a*length_b + length_a*length_c + length_b*length_c);
     return vol_shell;
 }
@@ -20,61 +18,63 @@
     double c2a_ratio)
 {
-    double b_side = length_a * b2a_ratio;
-    double c_side = length_a * c2a_ratio;
-    double a_half = 0.5 * length_a;
-    double b_half = 0.5 * b_side;
-    double c_half = 0.5 * c_side;
+    const double length_b = length_a * b2a_ratio;
+    const double length_c = length_a * c2a_ratio;
+    const double a_half = 0.5 * length_a;
+    const double b_half = 0.5 * length_b;
+    const double c_half = 0.5 * length_c;
 
    //Integration limits to use in Gaussian quadrature
-    double v1a = 0.0;
-    double v1b = 0.5 * M_PI;  //theta integration limits
-    double v2a = 0.0;
-    double v2b = 0.5 * M_PI;  //phi integration limits
+    const double v1a = 0.0;
+    const double v1b = M_PI_2;  //theta integration limits
+    const double v2a = 0.0;
+    const double v2b = M_PI_2;  //phi integration limits
     
-    //Order of integration
-    int nordi=76;			        
-    int nordj=76;
+    double outer_sum = 0.0;
+    for(int i=0; i<76; i++) {
+        const double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );
 
-    double sumi = 0.0;
-    
-    for(int i=0; i<nordi; i++) {
+        double sin_theta, cos_theta;
+        double sin_c, cos_c;
+        SINCOS(theta, sin_theta, cos_theta);
+        SINCOS(q*c_half*cos_theta, sin_c, cos_c);
 
-	    double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );	
-        
         // To check potential problems if denominator goes to zero here !!!
-        double termAL_theta = 8.0*cos(q*c_half*cos(theta)) / (q*q*sin(theta)*sin(theta));
-        double termAT_theta = 8.0*sin(q*c_half*cos(theta)) / (q*q*sin(theta)*cos(theta));
+        const double termAL_theta = 8.0 * cos_c / (q*q*sin_theta*sin_theta);
+        const double termAT_theta = 8.0 * sin_c / (q*q*sin_theta*cos_theta);
 
-	    double sumj = 0.0;
-        
-	    for(int j=0; j<nordj; j++) {
+        double inner_sum = 0.0;
+        for(int j=0; j<76; j++) {
+            const double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b );
 
-            double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b ); 
-            
+            double sin_phi, cos_phi;
+            double sin_a, cos_a;
+            double sin_b, cos_b;
+            SINCOS(phi, sin_phi, cos_phi);
+            SINCOS(q*a_half*sin_theta*sin_phi, sin_a, cos_a);
+            SINCOS(q*b_half*sin_theta*cos_phi, sin_b, cos_b);
+
             // Amplitude AL from eqn. (7c)
-            double AL = termAL_theta * sin(q*a_half*sin(theta)*sin(phi)) * 
-                sin(q*b_half*sin(theta)*cos(phi)) / (sin(phi)*cos(phi));
+            const double AL = termAL_theta
+                * sin_a*sin_b / (sin_phi*cos_phi);
 
             // Amplitude AT from eqn. (9)
-            double AT = termAT_theta * (  (cos(q*a_half*sin(theta)*sin(phi))*sin(q*b_half*sin(theta)*cos(phi))/cos(phi)) 
-                + (cos(q*b_half*sin(theta)*cos(phi))*sin(q*a_half*sin(theta)*sin(phi))/sin(phi)) );
+            const double AT = termAT_theta
+                * ( cos_a*sin_b/cos_phi + cos_b*sin_a/sin_phi );
 
-            sumj += Gauss76Wt[j] * (AL+AT)*(AL+AT);
+            inner_sum += Gauss76Wt[j] * square(AL+AT);
+        }
 
-	    }
-
-	    sumj = 0.5 * (v2b-v2a) * sumj;
-	    sumi += Gauss76Wt[i] * sumj * sin(theta);
-
+        inner_sum *= 0.5 * (v2b-v2a);
+        outer_sum += Gauss76Wt[i] * inner_sum * sin_theta;
     }
 
-    double answer = 0.5*(v1b-v1a)*sumi;
+    outer_sum *= 0.5*(v1b-v1a);
 
     // Normalize as in Eqn. (15) without the volume factor (as cancels with (V*DelRho)^2 normalization)
     // The factor 2 is due to the different theta integration limit (pi/2 instead of pi)
-    answer *= (2.0/M_PI);
+    double answer = outer_sum/M_PI_2;
 
     // Multiply by contrast^2. Factor corresponding to volume^2 cancels with previous normalization.
-    answer *= (sld-solvent_sld)*(sld-solvent_sld);
+    answer *= square(sld-solvent_sld);
 
     // Convert from [1e-12 A-1] to [cm-1]
@@ -82,16 +82,3 @@
 
     return answer;
-    
 }
-
-double Iqxy(double qx, double qy,
-    double sld,
-    double solvent_sld,
-    double length_a,
-    double b2a_ratio,
-    double c2a_ratio)
-{
-    double q = sqrt(qx*qx + qy*qy);
-    double intensity = Iq(q, sld, solvent_sld, length_a, b2a_ratio, c2a_ratio); 
-    return intensity;    
-}
Index: sasmodels/models/hollow_rectangular_prism_thin_walls.py
===================================================================
--- sasmodels/models/hollow_rectangular_prism_thin_walls.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/hollow_rectangular_prism_thin_walls.py	(revision ab2aea81f7fb392e5f24f1c76dd989914d8f7022)
@@ -3,5 +3,5 @@
 r"""
 
-This model provides the form factor, *P(q)*, for a hollow rectangular
+This model provides the form factor, $P(q)$, for a hollow rectangular
 prism with infinitely thin walls. It computes only the 1D scattering, not the 2D.
 
@@ -14,36 +14,37 @@
 
 Assuming a hollow parallelepiped with infinitely thin walls, edge lengths
-:math:`A \le B \le C` and presenting an orientation with respect to the
-scattering vector given by |theta| and |phi|, where |theta| is the angle
-between the *z* axis and the longest axis of the parallelepiped *C*, and
-|phi| is the angle between the scattering vector (lying in the *xy* plane)
-and the *y* axis, the form factor is given by
+$A \le B \le C$ and presenting an orientation with respect to the
+scattering vector given by $\theta$ and $\phi$, where $\theta$ is the angle
+between the $z$ axis and the longest axis of the parallelepiped $C$, and
+$\phi$ is the angle between the scattering vector (lying in the $xy$ plane)
+and the $y$ axis, the form factor is given by
 
 .. math::
-  P(q) =  \frac{1}{V^2} \frac{2}{\pi} \int_0^{\frac{\pi}{2}}
-  \int_0^{\frac{\pi}{2}} [A_L(q)+A_T(q)]^2 \sin\theta d\theta d\phi
+
+    P(q) = \frac{1}{V^2} \frac{2}{\pi} \int_0^{\frac{\pi}{2}}
+           \int_0^{\frac{\pi}{2}} [A_L(q)+A_T(q)]^2 \sin\theta\,d\theta\,d\phi
 
 where
 
 .. math::
-  V = 2AB + 2AC + 2BC
 
-.. math::
-  A_L(q) =  8 \times \frac{ \sin \bigl( q \frac{A}{2} \sin\phi \sin\theta \bigr)
-                              \sin \bigl( q \frac{B}{2} \cos\phi \sin\theta \bigr)
-                              \cos \bigl( q \frac{C}{2} \cos\theta \bigr) }
-                            {q^2 \, \sin^2\theta \, \sin\phi \cos\phi}
-
-.. math::
-  A_T(q) =  A_F(q) \times \frac{2 \, \sin \bigl( q \frac{C}{2} \cos\theta \bigr)}{q \, \cos\theta}
+    V &= 2AB + 2AC + 2BC \\
+    A_L(q) &=  8 \times \frac{
+            \sin \left( \tfrac{1}{2} q A \sin\phi \sin\theta \right)
+            \sin \left( \tfrac{1}{2} q B \cos\phi \sin\theta \right)
+            \cos \left( \tfrac{1}{2} q C \cos\theta \right)
+        }{q^2 \, \sin^2\theta \, \sin\phi \cos\phi} \\
+    A_T(q) &=  A_F(q) \times
+      \frac{2\,\sin \left( \tfrac{1}{2} q C \cos\theta \right)}{q\,\cos\theta}
 
 and
 
 .. math::
-  A_F(q) =  4 \frac{ \cos \bigl( q \frac{A}{2} \sin\phi \sin\theta \bigr)
-                       \sin \bigl( q \frac{B}{2} \cos\phi \sin\theta \bigr) }
+
+  A_F(q) =  4 \frac{ \cos \left( \tfrac{1}{2} q A \sin\phi \sin\theta \right)
+                       \sin \left( \tfrac{1}{2} q B \cos\phi \sin\theta \right) }
                      {q \, \cos\phi \, \sin\theta} +
-              4 \frac{ \sin \bigl( q \frac{A}{2} \sin\phi \sin\theta \bigr)
-                       \cos \bigl( q \frac{B}{2} \cos\phi \sin\theta \bigr) }
+              4 \frac{ \sin \left( \tfrac{1}{2} q A \sin\phi \sin\theta \right)
+                       \cos \left( \tfrac{1}{2} q B \cos\phi \sin\theta \right) }
                      {q \, \sin\phi \, \sin\theta}
 
@@ -51,8 +52,9 @@
 
 .. math::
-  I(q) = \mbox{scale} \times V \times (\rho_{\mbox{p}} - \rho_{\mbox{solvent}})^2 \times P(q)
 
-where *V* is the volume of the rectangular prism, :math:`\rho_{\mbox{p}}`
-is the scattering length of the parallelepiped, :math:`\rho_{\mbox{solvent}}`
+  I(q) = \text{scale} \times V \times (\rho_\text{p} - \rho_\text{solvent})^2 \times P(q)
+
+where $V$ is the volume of the rectangular prism, $\rho_\text{p}$
+is the scattering length of the parallelepiped, $\rho_\text{solvent}$
 is the scattering length of the solvent, and (if the data are in absolute
 units) *scale* represents the volume fraction (which is unitless).
@@ -127,5 +129,5 @@
 # parameters for demo
 demo = dict(scale=1, background=0,
-            sld=6.3e-6, sld_solvent=1.0e-6,
+            sld=6.3, sld_solvent=1.0,
             length_a=35, b2a_ratio=1, c2a_ratio=1,
             length_a_pd=0.1, length_a_pd_n=10,
Index: sasmodels/models/lamellar_stack_paracrystal.c
===================================================================
--- sasmodels/models/lamellar_stack_paracrystal.c	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/lamellar_stack_paracrystal.c	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -67,5 +67,5 @@
 	double Snq;
 
-	Snq = an/( (double)Nlayers*pow((1.0+ww*ww-2.0*ww*cos(qval*davg)),2) );
+	Snq = an/( (double)Nlayers*square(1.0+ww*ww-2.0*ww*cos(qval*davg)) );
 	
 	return(Snq);
Index: smodels/models/lib/Si.c
===================================================================
--- sasmodels/models/lib/Si.c	(revision 0278e3fec71d4a97458c09903fd5e3bd354166c1)
+++ 	(revision )
@@ -1,25 +1,0 @@
-// integral of sin(x)/x Taylor series approximated to w/i 0.1%
-double Si(double x);
-double Si(double x)
-{
-    double out;
-
-    if (x >= M_PI*6.2/4.0){
-        double out_sin = 0.0;
-        double out_cos = 0.0;
-        out = M_PI/2.0;
-
-        // 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);
-        out_sin = 1./pow(x,2) - 6./pow(x,4) + 120./pow(x,6) - 5040./pow(x,8);
-
-        out -= cos(x) * out_cos;
-        out -= sin(x) * out_sin;
-        return out;
-    }
-
-    // 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. - pow(x,11)/439084800.;
-
-    return out;
-}
Index: sasmodels/models/lib/core_shell.c
===================================================================
--- sasmodels/models/lib/core_shell.c	(revision 7d4b2ae2af901813f3ff60e60aa0eae98f4dfd5e)
+++ sasmodels/models/lib/core_shell.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -16,6 +16,6 @@
     const double core_qr = q * radius;
     const double core_contrast = core_sld - shell_sld;
-    const double core_bes = sph_j1c(core_qr);
-    const double core_volume = 4.0 * M_PI / 3.0 * radius * radius * radius;
+    const double core_bes = sas_3j1x_x(core_qr);
+    const double core_volume = M_4PI_3 * cube(radius);
     double f = core_volume * core_bes * core_contrast;
 
@@ -23,6 +23,6 @@
     const double shell_qr = q * (radius + thickness);
     const double shell_contrast = shell_sld - solvent_sld;
-    const double shell_bes = sph_j1c(shell_qr);
-    const double shell_volume = 4.0 * M_PI / 3.0 * pow((radius + thickness), 3);
+    const double shell_bes = sas_3j1x_x(shell_qr);
+    const double shell_volume = M_4PI_3 * cube(radius + thickness);
     f += shell_volume * shell_bes * shell_contrast;
     return f * f * 1.0e-4;
Index: sasmodels/models/lib/fractal_sq.c
===================================================================
--- sasmodels/models/lib/fractal_sq.c	(revision 217590bce5ffcf2c4de704a4770a9cab003dd287)
+++ sasmodels/models/lib/fractal_sq.c	(revision 217590bce5ffcf2c4de704a4770a9cab003dd287)
@@ -0,0 +1,28 @@
+static double
+fractal_sq(double q, double radius, double fractal_dim, double cor_length)
+{
+    //calculate S(q),  using Teixeira, Eq(15)
+    // mathematica query to check limiting conditions:
+    //    lim x->0 of [ x gamma(x-1) sin(arctan(q c (x-1))) (q r)^(-x) (1 + 1/(q c)^2)^((1-x)/2) ]
+    // Note: gamma(x) may be unreliable for x<0, so the gamma(D-1) is risky.
+    // We instead transform D*gamma(D-1) into gamma(D+1)/(D-1).
+    double term;
+    if (q == 0.) {
+        const double D = fractal_dim;
+        term = pow(cor_length/radius, D)*sas_gamma(D+1.);
+    } else if (fractal_dim == 0.) {
+        term = 1.0;
+    } else if (fractal_dim == 1.) {
+        term = atan(q*cor_length)/(q*radius);
+    } else {
+        // q>0, D>0
+        const double D = fractal_dim;
+        const double Dm1 = fractal_dim - 1.0;
+        // Note: for large Dm1, sin(Dm1*atan(q*cor_length) can go negative
+        const double t1 = sas_gamma(D+1.)/Dm1*sin(Dm1*atan(q*cor_length));
+        const double t2 = pow(q*radius, -D);
+        const double t3 = pow(1.0 + 1.0/square(q*cor_length), -0.5*Dm1);
+        term = t1 * t2 * t3;
+    }
+    return 1.0 + term;
+}
Index: sasmodels/models/lib/gfn.c
===================================================================
--- sasmodels/models/lib/gfn.c	(revision 177c1a138481a9833b4929af687e86583b5c16d5)
+++ sasmodels/models/lib/gfn.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -12,5 +12,4 @@
 {
     // local variables
-    const double pi43=4.0/3.0*M_PI;
     const double aa = crmaj;
     const double bb = crmin;
@@ -19,13 +18,13 @@
     // 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 siq = sas_3j1x_x(uq);
+    const double vc = M_4PI_3*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 vt = M_4PI_3*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 sit = sas_3j1x_x(ut);
     const double gfnt = sit*vt*delps;
 
@@ -33,4 +32,4 @@
     const double result = tgfn*tgfn;
 
-    return (result);
+    return result;
 }
Index: sasmodels/models/lib/polevl.c
===================================================================
--- sasmodels/models/lib/polevl.c	(revision 0278e3fec71d4a97458c09903fd5e3bd354166c1)
+++ sasmodels/models/lib/polevl.c	(revision 447e9aab67fe022330aad0a4c8873028603d4b87)
@@ -28,5 +28,5 @@
  *            N                   0
  *
- *  The function p1evl() assumes that coef[N] = 1.0 and is
+ * The function p1evl() assumes that C_N = 1.0 and is
  * omitted from the array.  Its calling arguments are
  * otherwise the same as polevl().
Index: sasmodels/models/lib/sas_3j1x_x.c
===================================================================
--- sasmodels/models/lib/sas_3j1x_x.c	(revision 473a9f131b0c8e4ae5fb862b046d8e30d4eddedb)
+++ sasmodels/models/lib/sas_3j1x_x.c	(revision 473a9f131b0c8e4ae5fb862b046d8e30d4eddedb)
@@ -0,0 +1,56 @@
+/**
+* Spherical Bessel function 3*j1(x)/x
+*
+* Used for low q to avoid cancellation error.
+* Note that the values differ from sasview ~ 5e-12 rather than 5e-14, but
+* in this case it is likely cancellation errors in the original expression
+* using double precision that are the source.
+*/
+double sas_3j1x_x(double q);
+
+// The choice of the number of terms in the series and the cutoff value for
+// switching between series and direct calculation depends on the numeric
+// precision.
+//
+// Point where direct calculation reaches machine precision:
+//
+//   single machine precision eps 3e-8 at qr=1.1 **
+//   double machine precision eps 4e-16 at qr=1.1
+//
+// Point where Taylor series reaches machine precision (eps), where taylor
+// series matches direct calculation (cross) and the error at that point:
+//
+//   prec   n eps  cross  error
+//   single 3 0.28  0.4   6.2e-7
+//   single 4 0.68  0.7   2.3e-7
+//   single 5 1.18  1.2   7.5e-8
+//   double 3 0.01  0.03  2.3e-13
+//   double 4 0.06  0.1   3.1e-14
+//   double 5 0.16  0.2   5.0e-15
+//
+// ** Note: relative error on single precision starts increase on the direct
+// method at qr=1.1, rising from 3e-8 to 5e-5 by qr=1e3.  This should be
+// safe for the sans range, with objects of 100 nm supported to a q of 0.1
+// while maintaining 5 digits of precision.  For usans/sesans, the objects
+// are larger but the q is smaller, so again it should be fine.
+//
+// See explore/sph_j1c.py for code to explore these ranges.
+
+// Use 4th order series
+#if FLOAT_SIZE>4
+#define SPH_J1C_CUTOFF 0.1
+#else
+#define SPH_J1C_CUTOFF 0.7
+#endif
+
+double sas_3j1x_x(double q)
+{
+    if (q < SPH_J1C_CUTOFF) {
+        const double q2 = q*q;
+        return (1.0 + q2*(-3./30. + q2*(3./840. + q2*(-3./45360.))));// + q2*(3./3991680.)))));
+    } else {
+        double sin_q, cos_q;
+        SINCOS(q, sin_q, cos_q);
+        return 3.0*(sin_q/q - cos_q)/(q*q);
+    }
+}
Index: sasmodels/models/lib/sas_J0.c
===================================================================
--- sasmodels/models/lib/sas_J0.c	(revision 1596de320c8f962693aa46eb70d0b5bec0e6c601)
+++ sasmodels/models/lib/sas_J0.c	(revision c8902ac8c2f740f2cd6d59d72c25f8957b7bdd8e)
@@ -55,5 +55,5 @@
 #if FLOAT_SIZE>4
 //Cephes double precission
-double j0(double x);
+double cephes_j0(double x);
 
  constant double PPJ0[8] = {
@@ -147,5 +147,5 @@
   };
 
-double j0(double x)
+double cephes_j0(double x)
 {
     double w, z, p, q, xn;
@@ -186,5 +186,5 @@
 #else
 //Cephes single precission
-float j0f(float x);
+float cephes_j0f(float x);
 
  constant float MOJ0[8] = {
@@ -221,5 +221,5 @@
  };
 
-float j0f(float x)
+float cephes_j0f(float x)
 {
     float xx, w, z, p, q, xn;
@@ -257,6 +257,6 @@
 
 #if FLOAT_SIZE>4
-#define sas_J0 j0
+#define sas_J0 cephes_j0
 #else
-#define sas_J0 j0f
+#define sas_J0 cephes_j0f
 #endif
Index: sasmodels/models/lib/sas_J1.c
===================================================================
--- sasmodels/models/lib/sas_J1.c	(revision 1596de320c8f962693aa46eb70d0b5bec0e6c601)
+++ sasmodels/models/lib/sas_J1.c	(revision 473a9f131b0c8e4ae5fb862b046d8e30d4eddedb)
@@ -42,5 +42,5 @@
 #if FLOAT_SIZE>4
 //Cephes double pression function
-double j1(double x);
+double cephes_j1(double x);
 
 constant double RPJ1[8] = {
@@ -106,5 +106,5 @@
     0.0 };
 
-double j1(double x)
+double cephes_j1(double x)
 {
 
@@ -144,5 +144,5 @@
 #else
 //Single precission version of cephes
-float j1f(float x);
+float cephes_j1f(float x);
 
 constant float JPJ1[8] = {
@@ -179,5 +179,5 @@
     };
 
-float j1f(float x)
+float cephes_j1f(float x)
 {
 
@@ -211,12 +211,12 @@
 
 #if FLOAT_SIZE>4
-#define sas_J1 j1
+#define sas_J1 cephes_j1
 #else
-#define sas_J1 j1f
+#define sas_J1 cephes_j1f
 #endif
 
 //Finally J1c function that equals 2*J1(x)/x
-double sas_J1c(double x);
-double sas_J1c(double x)
+double sas_2J1x_x(double x);
+double sas_2J1x_x(double x)
 {
     return (x != 0.0 ) ? 2.0*sas_J1(x)/x : 1.0;
Index: sasmodels/models/lib/sas_JN.c
===================================================================
--- sasmodels/models/lib/sas_JN.c	(revision 1596de320c8f962693aa46eb70d0b5bec0e6c601)
+++ sasmodels/models/lib/sas_JN.c	(revision c8902ac8c2f740f2cd6d59d72c25f8957b7bdd8e)
@@ -50,6 +50,6 @@
 #if FLOAT_SIZE > 4
 
-double jn( int n, double x );
-double jn( int n, double x ) {
+double cephes_jn( int n, double x );
+double cephes_jn( int n, double x ) {
 
     // PAK: seems to be machine epsilon/2
@@ -75,9 +75,9 @@
 
     if( n == 0 )
-        return( sign * j0(x) );
+        return( sign * cephes_j0(x) );
     if( n == 1 )
-        return( sign * j1(x) );
+        return( sign * cephes_j1(x) );
     if( n == 2 )
-        return( sign * (2.0 * j1(x) / x  -  j0(x)) );
+        return( sign * (2.0 * cephes_j1(x) / x  -  cephes_j0(x)) );
 
     if( x < MACHEP )
@@ -112,7 +112,7 @@
 
     if( fabs(pk) > fabs(pkm1) )
-        ans = j1(x)/pk;
+        ans = cephes_j1(x)/pk;
     else
-        ans = j0(x)/pkm1;
+        ans = cephes_j0(x)/pkm1;
 
     return( sign * ans );
@@ -121,6 +121,6 @@
 #else
 
-float jnf(int n, float x);
-float jnf(int n, float x)
+float cephes_jnf(int n, float x);
+float cephes_jnf(int n, float x)
 {
     // PAK: seems to be machine epsilon/2
@@ -146,9 +146,9 @@
 
     if( n == 0 )
-        return( sign * j0f(x) );
+        return( sign * cephes_j0f(x) );
     if( n == 1 )
-        return( sign * j1f(x) );
+        return( sign * cephes_j1f(x) );
     if( n == 2 )
-        return( sign * (2.0 * j1f(x) / x  -  j0f(x)) );
+        return( sign * (2.0 * cephes_j1f(x) / x  -  cephes_j0f(x)) );
 
     if( x < MACHEP )
@@ -189,7 +189,7 @@
 
     if( r > ans )  /* if( fabs(pk) > fabs(pkm1) ) */
-        ans = sign * j1f(x)/pk;
+        ans = sign * cephes_j1f(x)/pk;
     else
-        ans = sign * j0f(x)/pkm1;
+        ans = sign * cephes_j0f(x)/pkm1;
     return( ans );
 }
@@ -197,6 +197,6 @@
 
 #if FLOAT_SIZE>4
-#define sas_JN jn
+#define sas_JN cephes_jn
 #else
-#define sas_JN jnf
+#define sas_JN cephes_jnf
 #endif
Index: sasmodels/models/lib/sas_Si.c
===================================================================
--- sasmodels/models/lib/sas_Si.c	(revision 473a9f131b0c8e4ae5fb862b046d8e30d4eddedb)
+++ sasmodels/models/lib/sas_Si.c	(revision 473a9f131b0c8e4ae5fb862b046d8e30d4eddedb)
@@ -0,0 +1,24 @@
+// integral of sin(x)/x Taylor series approximated to w/i 0.1%
+double sas_Si(double x);
+double sas_Si(double x)
+{
+    if (x >= M_PI*6.2/4.0) {
+        const double xxinv = 1./(x*x);
+        // Explicitly writing factorial values triples the speed of the calculation
+        const double out_cos = (((-720.*xxinv + 24.)*xxinv - 2.)*xxinv + 1.)/x;
+        const double out_sin = (((-5040.*xxinv + 120.)*xxinv - 6.)*xxinv + 1)*xxinv;
+
+        double sin_x, cos_x;
+        SINCOS(x, sin_x, cos_x);
+        return M_PI_2 - cos_x*out_cos - sin_x*out_sin;
+    } else {
+        const double xx = x*x;
+        // Explicitly writing factorial values triples the speed of the calculation
+        return (((((-1./439084800.*xx
+            + 1./3265920.)*xx
+            - 1./35280.)*xx
+            + 1./600.)*xx
+            - 1./18.)*xx
+            + 1.)*x;
+    }
+}
Index: sasmodels/models/lib/sas_gamma.c
===================================================================
--- sasmodels/models/lib/sas_gamma.c	(revision 1596de320c8f962693aa46eb70d0b5bec0e6c601)
+++ sasmodels/models/lib/sas_gamma.c	(revision 97f9b46b6cdd9fb99c9a5802c207f0492945d67f)
@@ -5,4 +5,5 @@
 We use gamma definition Gamma(t + 1) = t * Gamma(t) to compute
 to function for values lower than 1.0. Namely Gamma(t) = 1/t * Gamma(t + 1)
+For t < 0, we use Gamma(t) = pi / ( Gamma(1 - t) * sin(pi * t) )
 */
 
@@ -137,3 +138,15 @@
 
 
-inline double sas_gamma( double x) { return tgamma(x+1)/x; }
+inline double sas_gamma(double x)
+{
+    // Note: the builtin tgamma can give slow and unreliable results for x<1.
+    // The following transform extends it to zero and to negative values.
+    // It should return NaN for zero and negative integers but doesn't.
+    // The accuracy is okay but not wonderful for negative numbers, maybe
+    // one or two digits lost in the calculation. If higher accuracy is
+    // needed, you could test the following loop:
+    //    double norm = 1.;
+    //    while (x<1.) { norm*=x; x+=1.; }
+    //    return tgamma(x)/norm;
+    return (x<0. ? M_PI/tgamma(1.-x)/sin(M_PI*x) : tgamma(x+1)/x);
+}
Index: smodels/models/lib/sph_j1c.c
===================================================================
--- sasmodels/models/lib/sph_j1c.c	(revision ba32cddebeff04b66d828ca58b1843244d4425fb)
+++ 	(revision )
@@ -1,56 +1,0 @@
-/**
-* Spherical Bessel function 3*j1(x)/x
-*
-* Used for low q to avoid cancellation error.
-* Note that the values differ from sasview ~ 5e-12 rather than 5e-14, but
-* in this case it is likely cancellation errors in the original expression
-* using double precision that are the source.
-*/
-double sph_j1c(double q);
-
-// The choice of the number of terms in the series and the cutoff value for
-// switching between series and direct calculation depends on the numeric
-// precision.
-//
-// Point where direct calculation reaches machine precision:
-//
-//   single machine precision eps 3e-8 at qr=1.1 **
-//   double machine precision eps 4e-16 at qr=1.1
-//
-// Point where Taylor series reaches machine precision (eps), where taylor
-// series matches direct calculation (cross) and the error at that point:
-//
-//   prec   n eps  cross  error
-//   single 3 0.28  0.4   6.2e-7
-//   single 4 0.68  0.7   2.3e-7
-//   single 5 1.18  1.2   7.5e-8
-//   double 3 0.01  0.03  2.3e-13
-//   double 4 0.06  0.1   3.1e-14
-//   double 5 0.16  0.2   5.0e-15
-//
-// ** Note: relative error on single precision starts increase on the direct
-// method at qr=1.1, rising from 3e-8 to 5e-5 by qr=1e3.  This should be
-// safe for the sans range, with objects of 100 nm supported to a q of 0.1
-// while maintaining 5 digits of precision.  For usans/sesans, the objects
-// are larger but the q is smaller, so again it should be fine.
-//
-// See explore/sph_j1c.py for code to explore these ranges.
-
-// Use 4th order series
-#if FLOAT_SIZE>4
-#define SPH_J1C_CUTOFF 0.1
-#else
-#define SPH_J1C_CUTOFF 0.7
-#endif
-
-double sph_j1c(double q)
-{
-    if (q < SPH_J1C_CUTOFF) {
-        const double q2 = q*q;
-        return (1.0 + q2*(-3./30. + q2*(3./840. + q2*(-3./45360.))));// + q2*(3./3991680.)))));
-    } else {
-        double sin_q, cos_q;
-        SINCOS(q, sin_q, cos_q);
-        return 3.0*(sin_q/q - cos_q)/(q*q);
-    }
-}
Index: sasmodels/models/lib/sphere_form.c
===================================================================
--- sasmodels/models/lib/sphere_form.c	(revision ba32cddebeff04b66d828ca58b1843244d4425fb)
+++ sasmodels/models/lib/sphere_form.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -9,5 +9,5 @@
 double sphere_form(double q, double radius, double sld, double solvent_sld)
 {
-    const double fq = sphere_volume(radius) * sph_j1c(q*radius);
+    const double fq = sphere_volume(radius) * sas_3j1x_x(q*radius);
     const double contrast = (sld - solvent_sld);
     return 1.0e-4*square(contrast * fq);
Index: sasmodels/models/lib/wrc_cyl.c
===================================================================
--- sasmodels/models/lib/wrc_cyl.c	(revision ba32cddebeff04b66d828ca58b1843244d4425fb)
+++ sasmodels/models/lib/wrc_cyl.c	(revision 92ce163837b3c51ecc732713d4a5ea719c037b29)
@@ -14,6 +14,5 @@
     //return t;
 
-    return pow( (1.0 + (x/3.12)*(x/3.12) +
-         (x/8.67)*(x/8.67)*(x/8.67)),(0.176/3.0) );
+    return pow(1.0+square(x/3.12)+cube(x/8.67), 0.176/3.0);
 }
 
@@ -23,6 +22,6 @@
 {
     const double r = b/L;
-    return (L*b/6.0) *
-           (1.0 - r*1.5  + 1.5*r*r - 0.75*r*r*r*(1.0 - exp(-2.0/r)));
+    return (L*b/6.0) * (1.0 + r*(-1.5 + r*(1.5 + r*0.75*expm1(-2.0/r))));
+
 }
 
@@ -41,5 +40,5 @@
 }
 
-static inline double
+static double
 sech_WR(double x)
 {
@@ -51,5 +50,4 @@
 {
     double C;
-    const double onehalf = 1.0/2.0;
 
     if( L/b > 10.0) {
@@ -86,5 +84,5 @@
 
     const double t2 = (2.0*b4*(((-1.0) + pow((double)M_E,(-(Rg02/b2))) +
-         Rg02/b2))*((1.0 + onehalf*(((-1.0) -
+         Rg02/b2))*((1.0 + 0.5*(((-1.0) -
          tanh((-C4 + Rgb/C5)))))));
 
@@ -112,9 +110,9 @@
 
     const double t9 = (2.0*b4*(((2.0*q0*Rg2)/b -
-         (2.0*pow((double)M_E,(-(Rg02/b2)))*q0*Rg2)/b))*((1.0 + onehalf*(((-1.0) -
+         (2.0*pow((double)M_E,(-(Rg02/b2)))*q0*Rg2)/b))*((1.0 + 0.5*(((-1.0) -
          tanh(((-C4) + Rgb)/C5))))));
 
     const double t10 = (8.0*b4*b*(((-1.0) + pow((double)M_E,(-(Rg02/b2))) +
-         Rg02/b2))*((1.0 + onehalf*(((-1.0) - tanh(((-C4) +
+         Rg02/b2))*((1.0 + 0.5*(((-1.0) - tanh(((-C4) +
          Rgb)/C5))))));
 
@@ -131,5 +129,5 @@
 
     const double t14 = (2.0*b4*(((-1.0) + pow((double)M_E,(-(Rg02/b2))) +
-          Rg02/b2))*((1.0 + onehalf*(((-1.0) - tanh(((-C4) +
+          Rg02/b2))*((1.0 + 0.5*(((-1.0) - tanh(((-C4) +
           Rgb)/C5))))));
 
@@ -140,9 +138,9 @@
 
     double yy = (pow(q0,p1)*(((-((b*M_PI)/(L*q0))) +t1/L +t2/(q04*Rg22) +
-        onehalf*t3*t4)) + (t5*((pow(q0,(p1 - p2))*
+        0.5*t3*t4)) + (t5*((pow(q0,(p1 - p2))*
         (((-pow(q0,(-p1)))*(((b2*M_PI)/(L*q02) +t6/L +t7/(2.0*C5) -
-        t8/(C5*q04*Rg22) + t9/(q04*Rg22) -t10/(q05*Rg22) + onehalf*t11*t12)) -
+        t8/(C5*q04*Rg22) + t9/(q04*Rg22) -t10/(q05*Rg22) + 0.5*t11*t12)) -
         b*p1*pow(q0,((-1.0) - p1))*(((-((b*M_PI)/(L*q0))) + t13/L +
-        t14/(q04*Rg22) + onehalf*t15*((1.0 + tanh(((-C4) +
+        t14/(q04*Rg22) + 0.5*t15*((1.0 + tanh(((-C4) +
         Rgb)/C5)))))))))));
 
@@ -154,5 +152,4 @@
 {
     double C;
-    const double onehalf = 1.0/2.0;
 
     if( L/b > 10.0) {
@@ -201,9 +198,9 @@
     const double t5 = (2.0*b4*(((2.0*q0*Rg2)/b -
          (2.0*pow((double)M_E,(-(Rg02/b2)))*q0*Rg2)/b))*
-         ((1.0 + onehalf*(((-1.0) - tanh(((-C4) +
+         ((1.0 + 0.5*(((-1.0) - tanh(((-C4) +
          Rgb)/C5))))))/(q04*Rg22);
 
     const double t6 = (8.0*b4*b*(((-1.0) + pow((double)M_E,(-(Rg02/b2))) +
-         Rg02/b2))*((1.0 + onehalf*(((-1) - tanh(((-C4) +
+         Rg02/b2))*((1.0 + 0.5*(((-1) - tanh(((-C4) +
          Rgb)/C5))))))/(q05*Rg22);
 
@@ -219,11 +216,11 @@
 
     const double t10 = (2.0*b4*(((-1) + pow((double)M_E,(-(Rg02/b2))) +
-          Rg02/b2))*((1.0 + onehalf*(((-1) - tanh(((-C4) +
+          Rg02/b2))*((1.0 + 0.5*(((-1) - tanh(((-C4) +
           Rgb)/C5))))))/(q04*Rg22);
 
     const double yy = ((-1.0*(t1* ((-pow(q0,-p1)*(((b2*M_PI)/(L*q02) +
-         t2 + t3 - t4 + t5 - t6 + onehalf*t7*t8)) - b*p1*pow(q0,((-1.0) - p1))*
+         t2 + t3 - t4 + t5 - t6 + 0.5*t7*t8)) - b*p1*pow(q0,((-1.0) - p1))*
          (((-((b*M_PI)/(L*q0))) + t9 + t10 +
-         onehalf*((C3*pow(((Rgb)),((-3.0)/miu)) +
+         0.5*((C3*pow(((Rgb)),((-3.0)/miu)) +
          C2*pow(((Rgb)),((-2.0)/miu)) +
          C1*pow(((Rgb)),((-1.0)/miu))))*
Index: sasmodels/models/linear_pearls.c
===================================================================
--- sasmodels/models/linear_pearls.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/linear_pearls.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -19,5 +19,5 @@
 {
     // Pearl volume
-    double pearl_vol = 4.0 /3.0 * M_PI * pow(radius, 3.0);
+    double pearl_vol = M_4PI_3 * cube(radius);
     // Return total volume
     return num_pearls * pearl_vol;;
@@ -35,5 +35,5 @@
     double contrast_pearl = pearl_sld - solvent_sld;
     //each volume
-    double pearl_vol = 4.0 /3.0 * M_PI * pow(radius, 3.0);
+    double pearl_vol = M_4PI_3 * cube(radius);
     //total volume
     double tot_vol = num_pearls * pearl_vol;
@@ -43,14 +43,6 @@
     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);
+    double psi = sas_3j1x_x(q * radius);
 
     // N pearls contribution
@@ -58,10 +50,8 @@
     n_contrib = num_pearls;
     for(int num=1; num<=n_max; num++) {
-        n_contrib += (2.0*(num_pearls-num)*sinc(q*separation*num));
+        n_contrib += (2.0*(num_pearls-num)*sas_sinx_x(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);
+    double form_factor = 1.0e-4 * n_contrib * square(m_s*psi) / tot_vol;
 
     return form_factor;
Index: sasmodels/models/linear_pearls.py
===================================================================
--- sasmodels/models/linear_pearls.py	(revision 40a87fa5096610e24c3ead10299f609e99f093f2)
+++ sasmodels/models/linear_pearls.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -63,5 +63,5 @@
 single = False
 
-source = ["linear_pearls.c"]
+source = ["lib/sas_3j1x_x.c", "linear_pearls.c"]
 
 demo = dict(scale=1.0, background=0.0,
Index: sasmodels/models/mass_fractal.c
===================================================================
--- sasmodels/models/mass_fractal.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/mass_fractal.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -1,48 +1,31 @@
-double form_volume(double radius);
+#define INVALID(p) (p.fractal_dim_mass < 1.0)
 
-double Iq(double q,
-          double radius,
-          double fractal_dim_mass,
-          double cutoff_length);
+static double
+Iq(double q, double radius, double fractal_dim_mass, double cutoff_length)
+{
+    //calculate P(q)
+    const double pq = square(sas_3j1x_x(q*radius));
 
-static double _mass_fractal_kernel(double q,
-          double radius,
-          double fractal_dim_mass,
-          double cutoff_length)
-{
-    // Actively check the argument.
-    if (fractal_dim_mass <= 1.0){
-       return 0.0;
+    //calculate S(q)
+    // S(q) = gamma(D-1) sin((D-1)atan(q c))/q c^(D-1) (1+(q c)^2)^(-(D-1)/2)
+    // lim D->1 [gamma(D-1) sin((D-1) a)] = a
+    // lim q->0 [sin((D-1) atan(q c))/q] = (D-1) c
+    // lim q,D->0,1 [gamma(D-1) sin((D-1)atan(q c))/q] = c
+    double sq;
+    if (q > 0. && fractal_dim_mass > 1.) {
+        const double Dm1 = fractal_dim_mass - 1.0;
+        const double t1 = sas_gamma(Dm1)*sin(Dm1*atan(q*cutoff_length));
+        const double t2 = pow(cutoff_length, Dm1);
+        const double t3 = pow(1.0 + square(q*cutoff_length), -0.5*Dm1);
+        sq = t1 * t2 * t3 / q;
+    } else if (q > 0.) {
+        sq = atan(q*cutoff_length)/q;
+    } else if (fractal_dim_mass > 1.) {
+        const double D = fractal_dim_mass;
+        sq = pow(cutoff_length, D) * sas_gamma(D);
+    } else {
+        sq = cutoff_length;
     }
 
-    //calculate P(q)
-    double pq = sph_j1c(q*radius);
-    pq = pq*pq;
-
-    //calculate S(q)
-    double mmo = fractal_dim_mass-1.0;
-    double sq = sas_gamma(mmo)*sin((mmo)*atan(q*cutoff_length));
-    sq *= pow(cutoff_length, mmo);
-    sq /= pow((1.0 + (q*cutoff_length)*(q*cutoff_length)),(mmo/2.0));
-    sq /= q;
-
-    //combine and return
-    double result = pq * sq;
-
-    return result;
+    return pq * sq;
 }
-double form_volume(double radius){
-
-    return 1.333333333333333*M_PI*radius*radius*radius;
-}
-
-double Iq(double q,
-          double radius,
-          double fractal_dim_mass,
-          double cutoff_length)
-{
-    return _mass_fractal_kernel(q,
-           radius,
-           fractal_dim_mass,
-           cutoff_length);
-}
Index: sasmodels/models/mass_fractal.py
===================================================================
--- sasmodels/models/mass_fractal.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/mass_fractal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -78,12 +78,13 @@
 
 # pylint: disable=bad-whitespace, line-too-long
-#             ["name", "units", default, [lower, upper], "type","description"],
-parameters = [["radius",        "Ang",  10.0, [0.0, inf], "", "Particle radius"],
-              ["fractal_dim_mass",      "",      1.9, [1.0, 6.0], "", "Mass fractal dimension"],
-              ["cutoff_length", "Ang", 100.0, [0.0, inf], "", "Cut-off length"],
-             ]
+#   ["name", "units", default, [lower, upper], "type","description"],
+parameters = [
+    ["radius",           "Ang",  10.0, [0.0, inf], "", "Particle radius"],
+    ["fractal_dim_mass", "",      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/sas_gamma.c", "mass_fractal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sas_gamma.c", "mass_fractal.c"]
 
 demo = dict(scale=1, background=0,
Index: sasmodels/models/mass_surface_fractal.c
===================================================================
--- sasmodels/models/mass_surface_fractal.c	(revision 30fbe2eda4d6f97acc840bd5e605de3829caf3c7)
+++ sasmodels/models/mass_surface_fractal.c	(revision 6d96b66e1e58166fbc135a8df1ddf8d37f64b54a)
@@ -1,11 +1,5 @@
-double form_volume(double radius);
-
-double Iq(double q,
-          double fractal_dim_mass,
-          double fractal_dim_surf,
-          double rg_cluster,
-          double rg_primary);
-
-static double _mass_surface_fractal_kernel(double q,
+#define INVALID(p) (p.fractal_dim_mass + p.fractal_dim_surf > 6.)
+static double
+Iq(double q,
           double fractal_dim_mass,
           double fractal_dim_surf,
@@ -14,40 +8,11 @@
 {
      //computation
-    double tot_dim = 6.0 - fractal_dim_surf - fractal_dim_mass;
-    fractal_dim_mass /= 2.0;
-    tot_dim /= 2.0;
+    const double Dm = 0.5*fractal_dim_mass;
+    const double Dt = 0.5*(6.0 - (fractal_dim_mass + fractal_dim_surf));
 
-    double rc_norm = rg_cluster * rg_cluster / (3.0 * fractal_dim_mass);
-    double rp_norm = rg_primary * rg_primary / (3.0 * tot_dim);
+    const double t1 = Dm==0. ? 1.0 : pow(1.0 + square(q*rg_cluster)/(3.0*Dm), -Dm);
+    const double t2 = Dt==0. ? 1.0 : pow(1.0 + square(q*rg_primary)/(3.0*Dt), -Dt);
+    const double form = t1*t2;
 
-    //x for P
-    double x_val1 = 1.0 +  q * q * rc_norm;
-    double x_val2 = 1.0 +  q * q * rp_norm;
-
-    double inv_form = pow(x_val1, fractal_dim_mass) * pow(x_val2, tot_dim);
-
-    //another singular
-    if (inv_form == 0.0) return 0.0;
-
-    double form_factor = 1.0;
-    form_factor /= inv_form;
-
-    return (form_factor);
+    return form;
 }
-double form_volume(double radius){
-
-    return 1.333333333333333*M_PI*radius*radius*radius;
-}
-
-double Iq(double q,
-          double fractal_dim_mass,
-          double fractal_dim_surf,
-          double rg_cluster,
-          double rg_primary)
-{
-    return _mass_surface_fractal_kernel(q,
-            fractal_dim_mass,
-            fractal_dim_surf,
-            rg_cluster,
-            rg_primary);
-}
Index: sasmodels/models/mass_surface_fractal.py
===================================================================
--- sasmodels/models/mass_surface_fractal.py	(revision 30fbe2eda4d6f97acc840bd5e605de3829caf3c7)
+++ sasmodels/models/mass_surface_fractal.py	(revision 6d96b66e1e58166fbc135a8df1ddf8d37f64b54a)
@@ -78,14 +78,11 @@
 
 # pylint: disable=bad-whitespace, line-too-long
-#             ["name", "units", default, [lower, upper], "type","description"],
-parameters = [["fractal_dim_mass",      "",    1.8, [1e-16, 6.0], "",
-               "Mass fractal dimension"],
-              ["fractal_dim_surf",   "",    2.3, [1e-16, 6.0], "",
-               "Surface fractal dimension"],
-              ["rg_cluster", "Ang",   86.7, [0.0, inf], "",
-               "Cluster radius of gyration"],
-              ["rg_primary", "Ang", 4000.,  [0.0, inf], "",
-               "Primary particle radius of gyration"],
-             ]
+#   ["name", "units", default, [lower, upper], "type","description"],
+parameters = [
+    ["fractal_dim_mass", "",      1.8, [0.0, 6.0], "", "Mass fractal dimension"],
+    ["fractal_dim_surf", "",      2.3, [0.0, 6.0], "", "Surface fractal dimension"],
+    ["rg_cluster",       "Ang",  86.7, [0.0, inf], "", "Cluster radius of gyration"],
+    ["rg_primary",       "Ang", 4000., [0.0, inf], "", "Primary particle radius of gyration"],
+]
 # pylint: enable=bad-whitespace, line-too-long
 
Index: sasmodels/models/multilayer_vesicle.c
===================================================================
--- sasmodels/models/multilayer_vesicle.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/multilayer_vesicle.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -19,12 +19,12 @@
 
         // layer 1
-        voli = 4.0*M_PI/3.0*ri*ri*ri;
-        fval += voli*sldi*sph_j1c(ri*q);
+        voli = M_4PI_3*ri*ri*ri;
+        fval += voli*sldi*sas_3j1x_x(ri*q);
 
         ri += thick_shell;
 
         // layer 2
-        voli = 4.0*M_PI/3.0*ri*ri*ri;
-        fval -= voli*sldi*sph_j1c(ri*q);
+        voli = M_4PI_3*ri*ri*ri;
+        fval -= voli*sldi*sas_3j1x_x(ri*q);
 
         //do 2 layers at a time
Index: sasmodels/models/multilayer_vesicle.py
===================================================================
--- sasmodels/models/multilayer_vesicle.py	(revision 041bc75e8cfa1c5f0d3188c31fb26e04c4deeebb)
+++ sasmodels/models/multilayer_vesicle.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -107,5 +107,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "multilayer_vesicle.c"]
+source = ["lib/sas_3j1x_x.c", "multilayer_vesicle.c"]
 
 polydispersity = ["radius", "n_pairs"]
Index: sasmodels/models/onion.c
===================================================================
--- sasmodels/models/onion.c	(revision d119f34af4e9929175fc6628b476183ede452a76)
+++ sasmodels/models/onion.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -6,5 +6,5 @@
   const double vol = M_4PI_3 * cube(r);
   const double qr = q * r;
-  const double bes = sph_j1c(qr);
+  const double bes = sas_3j1x_x(qr);
   const double alpha = A * r/thickness;
   double result;
@@ -30,8 +30,8 @@
 
 static double
-form_volume(double core_radius, double n, double thickness[])
+form_volume(double radius_core, double n, double thickness[])
 {
   int i;
-  double r = core_radius;
+  double r = radius_core;
   for (i=0; i < n; i++) {
     r += thickness[i];
@@ -41,10 +41,10 @@
 
 static double
-Iq(double q, double sld_core, double core_radius, double sld_solvent,
+Iq(double q, double sld_core, double radius_core, double sld_solvent,
     double n_shells, double sld_in[], double sld_out[], double thickness[],
     double A[])
 {
   int n = (int)(n_shells+0.5);
-  double r_out = core_radius;
+  double r_out = radius_core;
   double f = f_exp(q, r_out, sld_core, 0.0, 0.0, 0.0, 0.0);
   for (int i=0; i < n; i++){
Index: sasmodels/models/onion.py
===================================================================
--- sasmodels/models/onion.py	(revision 7b68dc576b7995dbf041602384c63c354ea857b5)
+++ sasmodels/models/onion.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -314,5 +314,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "onion.c"]
+source = ["lib/sas_3j1x_x.c", "onion.c"]
 single = False
 
@@ -366,12 +366,12 @@
     return np.asarray(z), np.asarray(rho)
 
-def ER(core_radius, n, thickness):
+def ER(radius_core, n, thickness):
     """Effective radius"""
-    return np.sum(thickness[:int(n[0])], axis=0) + core_radius
+    return np.sum(thickness[:int(n[0])], axis=0) + radius_core
 
 demo = {
     "sld_solvent": 2.2,
     "sld_core": 1.0,
-    "core_radius": 100,
+    "radius_core": 100,
     "n_shells": 4,
     "sld_in": [0.5, 1.5, 0.9, 2.0],
@@ -381,6 +381,6 @@
     # Could also specify them individually as
     # "A1": 0, "A2": -1, "A3": 1e-4, "A4": 1,
-    #"core_radius_pd_n": 10,
-    #"core_radius_pd": 0.4,
+    #"radius_core_pd_n": 10,
+    #"radius_core_pd": 0.4,
     #"thickness4_pd_n": 10,
     #"thickness4_pd": 0.4,
Index: sasmodels/models/parallelepiped.c
===================================================================
--- sasmodels/models/parallelepiped.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/parallelepiped.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -1,34 +1,8 @@
 double form_volume(double length_a, double length_b, double length_c);
-double Iq(double q, double sld, double solvent_sld, double length_a, double length_b, double length_c);
+double Iq(double q, double sld, double solvent_sld,
+    double length_a, double length_b, double length_c);
 double Iqxy(double qx, double qy, double sld, double solvent_sld,
-    double length_a, double length_b, double length_c, double theta, double phi, double psi);
-
-// From Igor library
-double _pkernel(double a, double b,double c, double ala, double alb, double alc);
-double _pkernel(double a, double b,double c, double ala, double alb, double alc){
-    double argA,argB,argC,tmp1,tmp2,tmp3;
-    //handle arg=0 separately, as sin(t)/t -> 1 as t->0
-    argA = 0.5*a*ala;
-    argB = 0.5*b*alb;
-    argC = 0.5*c*alc;
-    if(argA==0.0) {
-        tmp1 = 1.0;
-    } else {
-        tmp1 = sin(argA)*sin(argA)/argA/argA;
-    }
-    if (argB==0.0) {
-        tmp2 = 1.0;
-    } else {
-        tmp2 = sin(argB)*sin(argB)/argB/argB;
-    }
-    if (argC==0.0) {
-        tmp3 = 1.0;
-    } else {
-        tmp3 = sin(argC)*sin(argC)/argC/argC;
-    }
-    return (tmp1*tmp2*tmp3);
-
-}
-
+    double length_a, double length_b, double length_c,
+    double theta, double phi, double psi);
 
 double form_volume(double length_a, double length_b, double length_c)
@@ -45,66 +19,39 @@
     double length_c)
 {
-    double tmp1, tmp2;
-    
-    double mu = q * length_b;
+    const double mu = 0.5 * q * length_b;
     
     // Scale sides by B
-    double a_scaled = length_a / length_b;
-    double c_scaled = length_c / length_b;
+    const double a_scaled = length_a / length_b;
+    const double c_scaled = length_c / length_b;
         
-    //Order of integration
-    int nordi=76;			        
-    int nordj=76;
+    // outer integral (with gauss points), integration limits = 0, 1
+    double outer_total = 0; //initialize integral
 
-    // outer integral (with nordi gauss points), integration limits = 0, 1
-    double summ = 0; //initialize integral
+    for( int i=0; i<76; i++) {
+        const double sigma = 0.5 * ( Gauss76Z[i] + 1.0 );
+        const double mu_proj = mu * sqrt(1.0-sigma*sigma);
 
-    for( int i=0; i<nordi; i++) {
-		
-        // inner integral (with nordj gauss points), integration limits = 0, 1
-	
-        double summj = 0.0;
-	    double sigma = 0.5 * ( Gauss76Z[i] + 1.0 );		
-		
-	    for(int j=0; j<nordj; j++) {
+        // inner integral (with gauss points), integration limits = 0, 1
+        // corresponding to angles from 0 to pi/2.
+        double inner_total = 0.0;
+        for(int j=0; j<76; j++) {
+            const double uu = 0.5 * ( Gauss76Z[j] + 1.0 );
+            double sin_uu, cos_uu;
+            SINCOS(M_PI_2*uu, sin_uu, cos_uu);
+            const double si1 = sas_sinx_x(mu_proj * sin_uu * a_scaled);
+            const double si2 = sas_sinx_x(mu_proj * cos_uu);
+            inner_total += Gauss76Wt[j] * square(si1 * si2);
+        }
+        inner_total *= 0.5;
 
-            double uu = 0.5 * ( Gauss76Z[j] + 1.0 );
-            double mudum = mu * sqrt(1.0-sigma*sigma);
-	        double arg1 = 0.5 * mudum * cos(0.5*M_PI*uu);
-	        double arg2 = 0.5 * mudum * a_scaled * sin(0.5*M_PI*uu);
-            if(arg1==0.0) {
-	        tmp1 = 1.0;
-            } else {
-                tmp1 = sin(arg1)*sin(arg1)/arg1/arg1;
-            }
-            if (arg2==0.0) {
-                tmp2 = 1.0;
-            } else {
-                tmp2 = sin(arg2)*sin(arg2)/arg2/arg2;
-            }
+        const double si = sas_sinx_x(mu * c_scaled * sigma);
+        outer_total += Gauss76Wt[i] * inner_total * si * si;
+    }
+    outer_total *= 0.5;
 
-            summj += Gauss76Wt[j] * tmp1 * tmp2;
-        }
-		
-        // value of the inner integral
-        double answer = 0.5 * summj;
-
-        double arg = 0.5 * mu * c_scaled * sigma;
-        if ( arg == 0.0 ) {
-            answer *= 1.0;
-        } else {
-            answer *= sin(arg)*sin(arg)/arg/arg;
-        }
-		
-	    // sum of outer integral
-        summ += Gauss76Wt[i] * answer;
-        
-    }	
-   
-    const double vd = (sld-solvent_sld) * form_volume(length_a, length_b, length_c);
-    
-    // convert from [1e-12 A-1] to [cm-1] and 0.5 factor for outer integral
-    return 1.0e-4 * 0.5 * vd * vd * summ;
-    
+    // Multiply by contrast^2 and convert from [1e-12 A-1] to [cm-1]
+    const double V = form_volume(length_a, length_b, length_c);
+    const double drho = (sld-solvent_sld);
+    return 1.0e-4 * square(drho * V) * outer_total;
 }
 
@@ -120,49 +67,14 @@
     double psi)
 {
-    double q = sqrt(qx*qx+qy*qy);
-    double qx_scaled = qx/q;
-    double qy_scaled = qy/q;
+    double q, cos_val_a, cos_val_b, cos_val_c;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_val_c, cos_val_b, cos_val_a);
 
-    // Convert angles given in degrees to radians
-    theta *= M_PI_180;
-    phi   *= M_PI_180;
-    psi   *= M_PI_180;
-    
-    // Parallelepiped c axis orientation
-    double cparallel_x = cos(theta) * cos(phi);
-    double cparallel_y = sin(theta);
-    
-    // Compute angle between q and parallelepiped axis
-    double cos_val_c = cparallel_x*qx_scaled + cparallel_y*qy_scaled;// + cparallel_z*qz;
-
-    // Parallelepiped a axis orientation
-    double parallel_x = -cos(phi)*sin(psi) * sin(theta)+sin(phi)*cos(psi);
-    double parallel_y = sin(psi)*cos(theta);
-    double cos_val_a = parallel_x*qx_scaled + parallel_y*qy_scaled;
-
-    // Parallelepiped b axis orientation
-    double bparallel_x = -sin(theta)*cos(psi)*cos(phi)-sin(psi)*sin(phi);
-    double bparallel_y = cos(theta)*cos(psi);
-    double cos_val_b = bparallel_x*qx_scaled + bparallel_y*qy_scaled;
-
-    // The following tests should always pass
-    if (fabs(cos_val_c)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_c = 1.0;
-    }
-    if (fabs(cos_val_a)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_a = 1.0;
-    }
-    if (fabs(cos_val_b)>1.0) {
-      //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-      cos_val_b = 1.0;
-    }
-    
-    // Call the IGOR library function to get the kernel
-    double form = _pkernel( q*length_a, q*length_b, q*length_c, cos_val_a, cos_val_b, cos_val_c);
-  
-    // Multiply by contrast^2
-    const double vd = (sld - solvent_sld) * form_volume(length_a, length_b, length_c);
-    return 1.0e-4 * vd * vd * form;
+    const double siA = sas_sinx_x(0.5*q*length_a*cos_val_a);
+    const double siB = sas_sinx_x(0.5*q*length_b*cos_val_b);
+    const double siC = sas_sinx_x(0.5*q*length_c*cos_val_c);
+    const double V = form_volume(length_a, length_b, length_c);
+    const double drho = (sld - solvent_sld);
+    const double form = V * drho * siA * siB * siC;
+    // Square and convert from [1e-12 A-1] to [cm-1]
+    return 1.0e-4 * form * form;
 }
Index: sasmodels/models/parallelepiped.py
===================================================================
--- sasmodels/models/parallelepiped.py	(revision 416f5c7f5af38d5e5d926a5b6b140a94e7e60502)
+++ sasmodels/models/parallelepiped.py	(revision 8e68ea06a7246c4bd95ce82c755719ced6fa5a60)
@@ -221,5 +221,5 @@
 # parameters for demo
 demo = dict(scale=1, background=0,
-            sld=6.3e-6, sld_solvent=1.0e-6,
+            sld=6.3, sld_solvent=1.0,
             length_a=35, length_b=75, length_c=400,
             theta=45, phi=30, psi=15,
Index: sasmodels/models/pearl_necklace.c
===================================================================
--- sasmodels/models/pearl_necklace.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/pearl_necklace.c	(revision 4b541acdec7c4de7cd7f18ff5911816123ce8333)
@@ -1,135 +1,91 @@
-double _pearl_necklace_kernel(double q, double radius, double edge_sep,
-	double thick_string, double num_pearls, double sld_pearl,
-	double sld_string, double sld_solv);
 double form_volume(double radius, double edge_sep,
-	double thick_string, double num_pearls);
-
+    double thick_string, double num_pearls);
 double Iq(double q, double radius, double edge_sep,
-	double thick_string, double num_pearls, double sld, 
-	double string_sld, double solvent_sld);
+    double thick_string, double num_pearls, double sld, 
+    double string_sld, double solvent_sld);
 
 #define INVALID(v) (v.thick_string >= v.radius || v.num_pearls <= 0)
 
 // From Igor library
-double _pearl_necklace_kernel(double q, double radius, double edge_sep, double thick_string,
-	double num_pearls, double sld_pearl, double sld_string, double sld_solv)
+static double
+_pearl_necklace_kernel(double q, double radius, double edge_sep, double thick_string,
+    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_sep + 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_sep;
-	
-	// each volume
-	double string_vol = edge_sep * pi * thick_string * thick_string / 4.0;
-	double pearl_vol = 4.0 / 3.0 * pi * radius * radius * radius;
+    // number of string segments
+    num_pearls = floor(num_pearls + 0.5); //Force integer number of pearls
+    const double num_strings = num_pearls - 1.0;
 
-	//total volume
-	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, 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;
+    //each masses: contrast * volume
+    const double contrast_pearl = sld_pearl - sld_solv;
+    const double contrast_string = sld_string - sld_solv;
+    const double string_vol = edge_sep * M_PI_4 * thick_string * thick_string;
+    const double pearl_vol = M_4PI_3 * radius * radius * radius;
+    const double m_string = contrast_string * string_vol;
+    const double m_pearl = contrast_pearl * pearl_vol;
 
-	//sine functions of a pearl
-	psi = sin(q_r);
-	psi -= q_r * cos(q_r);
-	psi *= 3.0;
-	psi /= q_r * q_r * q_r;
+    // center to center distance between the neighboring pearls
+    const double A_s = edge_sep + 2.0 * radius;
 
-	// Note take only 20 terms in Si series: 10 terms may be enough though.
-	gamma = Si(q_edge);
-	gamma /= (q_edge);
-	beta = Si(q * (A_s - radius));
-	beta -= Si(q_r);
-	beta /= q_edge;
+    //sine functions of a pearl
+    // Note: lim_(q->0) Si(q*a)/(q*b) = a/b
+    // So therefore:
+    //    beta = q==0. ? 1.0 : (Si(q*(A_s-radius)) - Si(q*radius))/q_edge;
+    //    gamma = q==0. ? 1.0 : Si(q_edge)/q_edge;
+    // But there is a 1/(1-sinc) term below which blows up so don't bother
+    const double q_edge = q * edge_sep;
+    const double beta = (sas_Si(q*(A_s-radius)) - sas_Si(q*radius)) / q_edge;
+    const double gamma = sas_Si(q_edge) / q_edge;
+    const double psi = sas_3j1x_x(q*radius);
 
-	// form factor for num_pearls
-	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;
+    // Precomputed sinc terms
+    const double si = sas_sinx_x(q*A_s);
+    const double omsi = 1.0 - si;
+    const double pow_si = pow(si, num_pearls);
 
-	// form factor for num_strings (like thin rods)
-	srr_1 = -sinc(q_edge/2.0) * sinc(q_edge/2.0);
+    // form factor for num_pearls
+    const double sss = 2.0*square(m_pearl*psi) * (
+        - si * (1.0 - pow_si) / (omsi*omsi)
+        + num_pearls / omsi
+        - 0.5 * num_pearls
+        );
 
-	srr_1 += 2.0 * gamma;
-	srr_1 *= num_strings;
-	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;
+    // form factor for num_strings (like thin rods)
+    const double srr = m_string * m_string * (
+        - 2.0 * (1.0 - pow_si/si)*beta*beta / (omsi*omsi)
+        + 2.0 * num_strings*beta*beta / omsi
+        + num_strings * (2.0*gamma - square(sas_sinx_x(q_edge/2.0)))
+        );
 
-	// total srr
-	srr = srr_1 + srr_2 + srr_3;
-	srr *= m_r * m_r;
+    // form factor for correlations
+    const double srs = 4.0 * m_string * m_pearl * beta * psi * (
+        - si * (1.0 - pow_si/si) / (omsi*omsi)
+        + num_strings / omsi
+        );
 
-	// form factor for correlations
-	srs = 1.0;
-	srs -= pow(sincasq, num_strings);
-	srs /= oneminussinc * oneminussinc;
-	srs *= -sincasq;
-	srs += num_strings/oneminussinc;
-	srs *= 4.0;
-	srs *= (m_r * m_s * beta * psi);
+    const double form = sss + srr + srs;
 
-	form_factor = sss + srr + srs;
-	form_factor /= (tot_vol * 1.0e4); // norm by volume and A^-1 to cm^-1
-
-	return (form_factor);
+    return 1.0e-4 * form;
 }
 
 double form_volume(double radius, double edge_sep,
-	double thick_string, double num_pearls)
+    double thick_string, double num_pearls)
 {
-	double total_vol;
+    num_pearls = floor(num_pearls + 0.5); //Force integer number of pearls
 
-	double pi = 4.0*atan(1.0);
-	double number_of_strings = num_pearls - 1.0;
-	
-	double string_vol = edge_sep * pi * thick_string * thick_string / 4.0;
-	double pearl_vol = 4.0 / 3.0 * pi * radius * radius * radius;
+    const double num_strings = num_pearls - 1.0;
+    const double string_vol = edge_sep * M_PI_4 * thick_string * thick_string;
+    const double pearl_vol = M_4PI_3 * radius * radius * radius;
+    const double volume = num_strings*string_vol + num_pearls*pearl_vol;
 
-	total_vol = number_of_strings * string_vol;
-	total_vol += num_pearls * pearl_vol;
-
-	return(total_vol);
+    return volume;
 }
 
 double Iq(double q, double radius, double edge_sep,
-	double thick_string, double num_pearls, double sld, 
-	double string_sld, double solvent_sld)
+    double thick_string, double num_pearls, double sld, 
+    double string_sld, double solvent_sld)
 {
-	double value, tot_vol;
-	
-	value = _pearl_necklace_kernel(q, radius, edge_sep, thick_string,
-		num_pearls, sld, string_sld, solvent_sld);
-	tot_vol = form_volume(radius, edge_sep, thick_string, num_pearls);
+    const double form = _pearl_necklace_kernel(q, radius, edge_sep,
+        thick_string, num_pearls, sld, string_sld, solvent_sld);
 
-	return value*tot_vol;
+    return form;
 }
Index: sasmodels/models/pearl_necklace.py
===================================================================
--- sasmodels/models/pearl_necklace.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/pearl_necklace.py	(revision 4b541acdec7c4de7cd7f18ff5911816123ce8333)
@@ -92,5 +92,5 @@
              ]
 
-source = ["lib/Si.c", "pearl_necklace.c"]
+source = ["lib/sas_Si.c", "lib/sas_3j1x_x.c", "pearl_necklace.c"]
 single = False  # use double precision unless told otherwise
 
@@ -112,5 +112,5 @@
     """
     tot_vol = volume(radius, edge_sep, thick_string, num_pearls)
-    rad_out = pow((3.0*tot_vol/4.0/pi), 0.33333)
+    rad_out = (tot_vol/(4.0/3.0*pi)) ** (1./3.)
     return rad_out
 
Index: sasmodels/models/poly_gauss_coil.py
===================================================================
--- sasmodels/models/poly_gauss_coil.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/poly_gauss_coil.py	(revision f0afad25082efbba5303001a106a13ea6a36c1c1)
@@ -59,5 +59,6 @@
 """
 
-from numpy import inf, exp, power
+import numpy as np
+from numpy import inf, expm1, power
 
 name = "poly_gauss_coil"
@@ -83,14 +84,27 @@
     # pylint: disable = missing-docstring
     u = polydispersity - 1.0
-    z = (q*rg)**2 / (1.0 + 2.0*u)
+    z = q**2 * (rg**2 / (1.0 + 2.0*u))
+
     # need to trap the case of the polydispersity being 1 (ie, monodisperse!)
     if polydispersity == 1.0:
-        inten = i_zero * 2.0 * (exp(-z) + z - 1.0)
+        result = 2.0 * (expm1(-z) + z)
+        index = q != 0.
+        result[index] /= z[index]**2
+        result[~index] = 1.0
     else:
-        inten = i_zero * 2.0 * (power(1.0 + u*z, -1.0/u) + z - 1.0) / (1.0 + u)
-    index = q != 0.
-    inten[~index] = i_zero
-    inten[index] /= z[index]**2
-    return inten
+        # Taylor series around z=0 of (2*(1+uz)^(-1/u) + z - 1) / (z^2(u+1))
+        p = [
+            #(-1 - 20*u - 155*u**2 - 580*u**3 - 1044*u**4 - 720*u**5) / 2520.,
+            #( 1 + 14*u + 71*u**2 + 154*u**3 + 120*u**4) / 360.,
+            #(-1 - 9*u - 26*u**2 - 24*u**3) / 60.,
+            ( 1 + 5*u + 6*u**2) / 12.,
+            (-1 - 2*u) / 3.,
+            ( 1 ),
+            ]
+        result = 2.0 * (power(1.0 + u*z, -1.0/u) + z - 1.0) / (1.0 + u)
+        index = z > 1e-4
+        result[index] /= z[index]**2
+        result[~index] = np.polyval(p, z[~index])
+    return i_zero * result
 Iq.vectorized = True  # Iq accepts an array of q values
 
Index: sasmodels/models/polymer_micelle.c
===================================================================
--- sasmodels/models/polymer_micelle.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/polymer_micelle.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -31,9 +31,9 @@
 
     // Self-correlation term of the core
-    const double bes_core = sph_j1c(q*radius_core);
-    const double term1 = n_aggreg*n_aggreg*beta_core*beta_core*bes_core*bes_core;
+    const double bes_core = sas_3j1x_x(q*radius_core);
+    const double term1 = square(n_aggreg*beta_core*bes_core);
 
     // Self-correlation term of the chains
-    const double qrg2 = q*rg*q*rg;
+    const double qrg2 = square(q*rg);
     const double debye_chain = (qrg2 == 0.0) ? 1.0 : 2.0*(expm1(-qrg2)+qrg2)/(qrg2*qrg2);
     const double term2 = n_aggreg * beta_corona * beta_corona * debye_chain;
@@ -41,11 +41,11 @@
     // Interference cross-term between core and chains
     const double chain_ampl = (qrg2 == 0.0) ? 1.0 : -expm1(-qrg2)/qrg2;
-    const double bes_corona = sinc(q*(radius_core + d_penetration * rg));
-    const double term3 = 2 * n_aggreg * n_aggreg * beta_core * beta_corona *
+    const double bes_corona = sas_sinx_x(q*(radius_core + d_penetration * rg));
+    const double term3 = 2.0 * n_aggreg * n_aggreg * beta_core * beta_corona *
                  bes_core * chain_ampl * bes_corona;
 
     // Interference cross-term between chains
-    const double term4 = n_aggreg * (n_aggreg - 1.0) * beta_corona * beta_corona *
-                 chain_ampl * chain_ampl * bes_corona * bes_corona;
+    const double term4 = n_aggreg * (n_aggreg - 1.0)
+                 * square(beta_corona * chain_ampl * bes_corona);
 
     // I(q)_micelle : Sum of 4 terms computed above
Index: sasmodels/models/polymer_micelle.py
===================================================================
--- sasmodels/models/polymer_micelle.py	(revision bba9361c45987d76f3673d7d0a4eb16d28a8b701)
+++ sasmodels/models/polymer_micelle.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -104,5 +104,5 @@
 single = False
 
-source = ["lib/sph_j1c.c", "polymer_micelle.c"]
+source = ["lib/sas_3j1x_x.c", "polymer_micelle.c"]
 
 demo = dict(scale=1, background=0,
Index: sasmodels/models/porod.py
===================================================================
--- sasmodels/models/porod.py	(revision 40a87fa5096610e24c3ead10299f609e99f093f2)
+++ sasmodels/models/porod.py	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -41,5 +41,5 @@
     """
     with errstate(divide='ignore'):
-        return power(q, -4)
+        return q**-4
 
 Iq.vectorized = True  # Iq accepts an array of q values
Index: sasmodels/models/pringle.c
===================================================================
--- sasmodels/models/pringle.c	(revision 30fbe2eda4d6f97acc840bd5e605de3829caf3c7)
+++ sasmodels/models/pringle.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -91,5 +91,5 @@
         SINCOS(psi, sin_psi, cos_psi);
         double bessel_term = _sum_bessel_orders(radius, alpha, beta, q*sin_psi, q*cos_psi);
-        double sinc_term = square(sinc(q * thickness * cos_psi / 2.0));
+        double sinc_term = square(sas_sinx_x(q * thickness * cos_psi / 2.0));
         double pringle_kernel = 4.0 * sin_psi * bessel_term * sinc_term;
         sum += Gauss76Wt[i] * pringle_kernel;
Index: sasmodels/models/raspberry.c
===================================================================
--- sasmodels/models/raspberry.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/raspberry.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -51,11 +51,11 @@
 
     //Form factors for each particle
-    psiL = sph_j1c(q*rL);
-    psiS = sph_j1c(q*rS);
+    psiL = sas_3j1x_x(q*rL);
+    psiS = sas_3j1x_x(q*rS);
 
     //Cross term between large and small particles
-    sfLS = psiL*psiS*sinc(q*(rL+deltaS*rS));
+    sfLS = psiL*psiS*sas_sinx_x(q*(rL+deltaS*rS));
     //Cross term between small particles at the surface
-    sfSS = psiS*psiS*sinc(q*(rL+deltaS*rS))*sinc(q*(rL+deltaS*rS));
+    sfSS = psiS*psiS*sas_sinx_x(q*(rL+deltaS*rS))*sas_sinx_x(q*(rL+deltaS*rS));
 
     //Large sphere form factor term
Index: sasmodels/models/raspberry.py
===================================================================
--- sasmodels/models/raspberry.py	(revision 40a87fa5096610e24c3ead10299f609e99f093f2)
+++ sasmodels/models/raspberry.py	(revision 8e68ea06a7246c4bd95ce82c755719ced6fa5a60)
@@ -129,5 +129,5 @@
             Ref: J. coll. inter. sci. (2010) vol. 343 (1) pp. 36-41."""
 category = "shape:sphere"
-#single = False
+
 
 #             [ "name", "units", default, [lower, upper], "type", "description"],
@@ -152,5 +152,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "raspberry.c"]
+source = ["lib/sas_3j1x_x.c", "raspberry.c"]
 
 # parameters for demo
Index: sasmodels/models/rectangular_prism.c
===================================================================
--- sasmodels/models/rectangular_prism.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/rectangular_prism.c	(revision 1e7b0db0b7b4315c62b59e1d659332bcbc962b8f)
@@ -2,6 +2,4 @@
 double Iq(double q, double sld, double solvent_sld, double length_a, 
           double b2a_ratio, double c2a_ratio);
-double Iqxy(double qx, double qy, double sld, double solvent_sld, 
-            double length_a, double b2a_ratio, double c2a_ratio);
 
 double form_volume(double length_a, double b2a_ratio, double c2a_ratio)
@@ -17,58 +15,41 @@
     double c2a_ratio)
 {
-    double termA, termB, termC;
-    
-    double b_side = length_a * b2a_ratio;
-    double c_side = length_a * c2a_ratio;
-    double volume = length_a * b_side * c_side;
-    double a_half = 0.5 * length_a;
-    double b_half = 0.5 * b_side;
-    double c_half = 0.5 * c_side;
+    const double length_b = length_a * b2a_ratio;
+    const double length_c = length_a * c2a_ratio;
+    const double a_half = 0.5 * length_a;
+    const double b_half = 0.5 * length_b;
+    const double c_half = 0.5 * length_c;
 
    //Integration limits to use in Gaussian quadrature
-    double v1a = 0.0;
-    double v1b = 0.5 * M_PI;  //theta integration limits
-    double v2a = 0.0;
-    double v2b = 0.5 * M_PI;  //phi integration limits
+    const double v1a = 0.0;
+    const double v1b = M_PI_2;  //theta integration limits
+    const double v2a = 0.0;
+    const double v2b = M_PI_2;  //phi integration limits
     
-    //Order of integration
-    int nordi=76;			        
-    int nordj=76;
+    double outer_sum = 0.0;
+    for(int i=0; i<76; i++) {
+        const double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );
+        double sin_theta, cos_theta;
+        SINCOS(theta, sin_theta, cos_theta);
 
-    double sumi = 0.0;
-    
-    for(int i=0; i<nordi; i++) {
+        const double termC = sas_sinx_x(q * c_half * cos_theta);
 
-	    double theta = 0.5 * ( Gauss76Z[i]*(v1b-v1a) + v1a + v1b );	
+        double inner_sum = 0.0;
+        for(int j=0; j<76; j++) {
+            double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b );
+            double sin_phi, cos_phi;
+            SINCOS(phi, sin_phi, cos_phi);
 
-	    double arg = q * c_half * cos(theta);
-	    if (fabs(arg) > 1.e-16) {termC = sin(arg)/arg;} else {termC = 1.0;}  
-
-	    double sumj = 0.0;
-        
-	    for(int j=0; j<nordj; j++) {
-
-            double phi = 0.5 * ( Gauss76Z[j]*(v2b-v2a) + v2a + v2b ); 
-
-	        // Amplitude AP from eqn. (12), rewritten to avoid round-off effects when arg=0
-
-	        arg = q * a_half * sin(theta) * sin(phi); 
-	        if (fabs(arg) > 1.e-16) {termA = sin(arg)/arg;} else {termA = 1.0;}
-	       
-	        arg = q * b_half * sin(theta) * cos(phi); 
-	        if (fabs(arg) > 1.e-16) {termB = sin(arg)/arg;} else {termB = 1.0;}	  
-               
-	        double AP = termA * termB * termC;  
-
-	        sumj += Gauss76Wt[j] * (AP*AP);
-
-	    }
-
-	    sumj = 0.5 * (v2b-v2a) * sumj;
-	    sumi += Gauss76Wt[i] * sumj * sin(theta);
-
+            // Amplitude AP from eqn. (12), rewritten to avoid round-off effects when arg=0
+            const double termA = sas_sinx_x(q * a_half * sin_theta * sin_phi);
+            const double termB = sas_sinx_x(q * b_half * sin_theta * cos_phi);
+            const double AP = termA * termB * termC;
+            inner_sum += Gauss76Wt[j] * AP * AP;
+        }
+        inner_sum = 0.5 * (v2b-v2a) * inner_sum;
+        outer_sum += Gauss76Wt[i] * inner_sum * sin_theta;
     }
 
-    double answer = 0.5*(v1b-v1a)*sumi;
+    double answer = 0.5*(v1b-v1a)*outer_sum;
 
     // Normalize by Pi (Eqn. 16). 
@@ -77,8 +58,9 @@
     // The factor 2 appears because the theta integral has been defined between 
     // 0 and pi/2, instead of 0 to pi.
-    answer *= (2.0/M_PI); //Form factor P(q)
+    answer /= M_PI_2; //Form factor P(q)
 
     // Multiply by contrast^2 and volume^2
-    answer *= (sld-solvent_sld)*(sld-solvent_sld)*volume*volume;
+    const double volume = length_a * length_b * length_c;
+    answer *= square((sld-solvent_sld)*volume);
 
     // Convert from [1e-12 A-1] to [cm-1] 
@@ -86,16 +68,3 @@
 
     return answer;
-    
 }
-
-double Iqxy(double qx, double qy,
-    double sld,
-    double solvent_sld,
-    double length_a,
-    double b2a_ratio,
-    double c2a_ratio)
-{
-    double q = sqrt(qx*qx + qy*qy);
-    double intensity = Iq(q, sld, solvent_sld, length_a, b2a_ratio, c2a_ratio); 
-    return intensity;    
-}
Index: sasmodels/models/rectangular_prism.py
===================================================================
--- sasmodels/models/rectangular_prism.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/rectangular_prism.py	(revision ab2aea81f7fb392e5f24f1c76dd989914d8f7022)
@@ -3,12 +3,12 @@
 r"""
 
-This model provides the form factor, *P(q)*, for a rectangular prism.
+This model provides the form factor, $P(q)$, for a rectangular prism.
 
 Note that this model is almost totally equivalent to the existing
 :ref:`parallelepiped` model.
 The only difference is that the way the relevant
-parameters are defined here (*a*, *b/a*, *c/a* instead of *a*, *b*, *c*)
+parameters are defined here ($a$, $b/a$, $c/a$ instead of $a$, $b$, $c$)
 which allows use of polydispersity with this model while keeping the shape of
-the prism (e.g. setting *b/a* = 1 and *c/a* = 1 and applying polydispersity
+the prism (e.g. setting $b/a = 1$ and $c/a = 1$ and applying polydispersity
 to *a* will generate a distribution of cubes of different sizes).
 Note also that, contrary to :ref:`parallelepiped`, it does not compute
@@ -24,5 +24,5 @@
 Note also that the angle definitions used in the code and the present
 documentation correspond to those used in (Nayuk, 2012) (see Fig. 1 of
-that reference), with |theta| corresponding to |alpha| in that paper,
+that reference), with $\theta$ corresponding to $\alpha$ in that paper,
 and not to the usual convention used for example in the
 :ref:`parallelepiped` model. As the present model does not compute
@@ -30,17 +30,20 @@
 
 In this model the scattering from a massive parallelepiped with an
-orientation with respect to the scattering vector given by |theta|
-and |phi|
+orientation with respect to the scattering vector given by $\theta$
+and $\phi$
 
 .. math::
-  A_P\,(q) =  \frac{\sin \bigl( q \frac{C}{2} \cos\theta \bigr)}{\left( q \frac{C}{2}
-  \cos\theta \right)} \, \times \, \frac{\sin \bigl( q \frac{A}{2} \sin\theta \sin\phi
-  \bigr)}{\left( q \frac{A}{2} \sin\theta \sin\phi \right)} \, \times \, \frac{\sin \bigl(
-  q \frac{B}{2} \sin\theta \cos\phi \bigr)}{\left( q \frac{B}{2} \sin\theta \cos\phi \right)}
 
-where *A*, *B* and *C* are the sides of the parallelepiped and must fulfill
-:math:`A \le B \le C`, |theta| is the angle between the *z* axis and the
-longest axis of the parallelepiped *C*, and |phi| is the angle between the
-scattering vector (lying in the *xy* plane) and the *y* axis.
+  A_P\,(q) =
+      \frac{\sin \left( \tfrac{1}{2}qC \cos\theta \right) }{\tfrac{1}{2} qC \cos\theta}
+      \,\times\,
+      \frac{\sin \left( \tfrac{1}{2}qA \cos\theta \right) }{\tfrac{1}{2} qA \cos\theta}
+      \,\times\ ,
+      \frac{\sin \left( \tfrac{1}{2}qB \cos\theta \right) }{\tfrac{1}{2} qB \cos\theta}
+
+where $A$, $B$ and $C$ are the sides of the parallelepiped and must fulfill
+$A \le B \le C$, $\theta$ is the angle between the $z$ axis and the
+longest axis of the parallelepiped $C$, and $\phi$ is the angle between the
+scattering vector (lying in the $xy$ plane) and the $y$ axis.
 
 The normalized form factor in 1D is obtained averaging over all possible
@@ -48,5 +51,5 @@
 
 .. math::
-  P(q) =  \frac{2}{\pi} \times \, \int_0^{\frac{\pi}{2}} \,
+  P(q) =  \frac{2}{\pi} \int_0^{\frac{\pi}{2}} \,
   \int_0^{\frac{\pi}{2}} A_P^2(q) \, \sin\theta \, d\theta \, d\phi
 
@@ -54,9 +57,9 @@
 
 .. math::
-  I(q) = \mbox{scale} \times V \times (\rho_{\mbox{p}} -
-  \rho_{\mbox{solvent}})^2 \times P(q)
+  I(q) = \text{scale} \times V \times (\rho_\text{p} -
+  \rho_\text{solvent})^2 \times P(q)
 
-where *V* is the volume of the rectangular prism, :math:`\rho_{\mbox{p}}`
-is the scattering length of the parallelepiped, :math:`\rho_{\mbox{solvent}}`
+where $V$ is the volume of the rectangular prism, $\rho_\text{p}$
+is the scattering length of the parallelepiped, $\rho_\text{solvent}$
 is the scattering length of the solvent, and (if the data are in absolute
 units) *scale* represents the volume fraction (which is unitless).
@@ -125,5 +128,5 @@
 # parameters for demo
 demo = dict(scale=1, background=0,
-            sld=6.3e-6, sld_solvent=1.0e-6,
+            sld=6.3, sld_solvent=1.0,
             length_a=35, b2a_ratio=1, c2a_ratio=1,
             length_a_pd=0.1, length_a_pd_n=10,
Index: sasmodels/models/sc_paracrystal.c
===================================================================
--- sasmodels/models/sc_paracrystal.c	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/sc_paracrystal.c	(revision 4962519cc1910422a467e4f643dba223623e0e09)
@@ -49,17 +49,15 @@
 	double da = d_factor*dnn;
 	double temp1 = qq*qq*da*da;
-	double temp2 = pow( 1.0-exp(-1.0*temp1) ,3);
+	double temp2 = cube(-expm1(-temp1));
 	double temp3 = qq*dnn;
 	double temp4 = 2.0*exp(-0.5*temp1);
 	double temp5 = exp(-1.0*temp1);
 
-	double integrand = temp2*sc_eval(yy,xx,temp3,temp4,temp5);
-	integrand *= 2.0/M_PI;
+	double integrand = temp2*sc_eval(yy,xx,temp3,temp4,temp5)/M_PI_2;
 
 	return(integrand);
 }
 
-static
-double sc_crystal_kernel(double q,
+double Iq(double q,
           double dnn,
           double d_factor,
@@ -69,5 +67,5 @@
 {
 	const double va = 0.0;
-	const double vb = M_PI/2.0; //orientation average, outer integral
+	const double vb = M_PI_2; //orientation average, outer integral
 
     double summ=0.0;
@@ -103,6 +101,5 @@
 }
 
-static
-double sc_crystal_kernel_2d(double q, double q_x, double q_y,
+double Iqxy(double qx, double qy,
           double dnn,
           double d_factor,
@@ -114,115 +111,18 @@
           double psi)
 {
-    //convert angle degree to radian
-    theta = theta * M_PI_180;
-    phi = phi * M_PI_180;
-    psi = psi * M_PI_180;
+    double q, cos_a1, cos_a2, cos_a3;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, cos_a3, cos_a2, cos_a1);
 
-    const double qda_2 = pow(q*d_factor*dnn,2.0);
+    const double qd = q*dnn;
+    const double arg = 0.5*square(qd*d_factor);
+    const double tanh_qd = tanh(arg);
+    const double cosh_qd = cosh(arg);
+    const double Zq = tanh_qd/(1. - cos(qd*cos_a1)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*cos_a2)/cosh_qd)
+                    * tanh_qd/(1. - cos(qd*cos_a3)/cosh_qd);
 
-    double snt, cnt;
-    SINCOS(theta, snt, cnt);
-
-    double snp, cnp;
-    SINCOS(phi, snp, cnp);
-
-    double sns, cns;
-    SINCOS(psi, sns, cns);
-
-    /// Angles here are respect to detector coordinate instead of against
-    //  q coordinate(PRB 36, 3, 1754)
-    // a3 axis orientation
-
-    const double a3_x = cnt * cnp;
-    const double a3_y = snt;
-
-    // Compute the angle btw vector q and the a3 axis
-    double cos_val_a3 = a3_x*q_x + a3_y*q_y;
-
-    // a1 axis orientation
-    const double a1_x = -cnp*sns * snt+snp*cns;
-    const double a1_y = sns*cnt;
-
-    double cos_val_a1 = a1_x*q_x + a1_y*q_y;
-
-    // a2 axis orientation
-    const double a2_x = -snt*cns*cnp-sns*snp;
-    const double a2_y = cnt*cns;
-
-    // a2 axis
-    const double cos_val_a2 =  a2_x*q_x + a2_y*q_y;
-
-    // The following test should always pass
-    if (fabs(cos_val_a3)>1.0) {
-        //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-        cos_val_a3 = 1.0;
-    }
-    if (fabs(cos_val_a1)>1.0) {
-        //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-        cos_val_a1 = 1.0;
-    }
-    if (fabs(cos_val_a2)>1.0) {
-        //printf("parallel_ana_2D: Unexpected error: cos(alpha)>1\n");
-        cos_val_a3 = 1.0;
-    }
-
-    const double a3_dot_q = dnn*q*cos_val_a3;
-    const double a1_dot_q = dnn*q*cos_val_a1;
-    const double a2_dot_q = dnn*q*cos_val_a2;
-
-    // Call Zq=Z1*Z2*Z3
-    double Zq = (1.0-exp(-qda_2))/(1.0-2.0*exp(-0.5*qda_2)*cos(a1_dot_q)+exp(-qda_2));
-    Zq *= (1.0-exp(-qda_2))/(1.0-2.0*exp(-0.5*qda_2)*cos(a2_dot_q)+exp(-qda_2));
-    Zq *= (1.0-exp(-qda_2))/(1.0-2.0*exp(-0.5*qda_2)*cos(a3_dot_q)+exp(-qda_2));
-
-    // Use SphereForm directly from libigor
-    double answer = sphere_form(q, radius, sphere_sld, solvent_sld)*Zq;
-
-    //consider scales
-    const double latticeScale = sphere_volume(radius/dnn);
-    answer *= latticeScale;
-
-    return answer;
+    const double Fq = sphere_form(q, radius, sphere_sld, solvent_sld)*Zq;
+    //the occupied volume of the lattice
+    const double lattice_scale = sphere_volume(radius/dnn);
+    return lattice_scale * Fq;
 }
-
-double Iq(double q,
-          double dnn,
-          double d_factor,
-          double radius,
-          double sphere_sld,
-          double solvent_sld)
-{
-    return sc_crystal_kernel(q,
-              dnn,
-              d_factor,
-              radius,
-              sphere_sld,
-              solvent_sld);
-}
-
-// Iqxy is never called since no orientation or magnetic parameters.
-double Iqxy(double qx, double qy,
-            double dnn,
-            double d_factor,
-            double radius,
-            double sphere_sld,
-            double solvent_sld,
-            double theta,
-            double phi,
-            double psi)
-{
-    double q = sqrt(qx*qx + qy*qy);
-
-
-    return sc_crystal_kernel_2d(q, qx/q, qy/q,
-                  dnn,
-                  d_factor,
-                  radius,
-                  sphere_sld,
-                  solvent_sld,
-                  theta,
-                  phi,
-                  psi);
-
-}
-
Index: sasmodels/models/sc_paracrystal.py
===================================================================
--- sasmodels/models/sc_paracrystal.py	(revision 0bef47b278e819df7ea6b572375dd7bc9607227e)
+++ sasmodels/models/sc_paracrystal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -133,5 +133,5 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/sphere_form.c", "lib/gauss150.c", "sc_paracrystal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sphere_form.c", "lib/gauss150.c", "sc_paracrystal.c"]
 
 demo = dict(scale=1, background=0,
Index: sasmodels/models/sphere.py
===================================================================
--- sasmodels/models/sphere.py	(revision 7e6bea817616b3ff05f1c96c2a11a94853dc6276)
+++ sasmodels/models/sphere.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -66,5 +66,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "lib/sphere_form.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sphere_form.c"]
 
 # No volume normalization despite having a volume parameter
Index: sasmodels/models/spherical_sld.c
===================================================================
--- sasmodels/models/spherical_sld.c	(revision 54bcd4abeaba4faf03e24319c050286fc10969da)
+++ sasmodels/models/spherical_sld.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -34,5 +34,5 @@
     const double qr = q * r;
     const double qrsq = qr * qr;
-    const double bes = sph_j1c(qr);
+    const double bes = sas_3j1x_x(qr);
     double sinqr, cosqr;
     SINCOS(qr, sinqr, cosqr);
@@ -60,7 +60,7 @@
 
         // uniform shell; r=0 => r^3=0 => f=0, so works for core as well.
-        f -= M_4PI_3 * cube(r) * sld_l * sph_j1c(q*r);
+        f -= M_4PI_3 * cube(r) * sld_l * sas_3j1x_x(q*r);
         r += thickness[shell];
-        f += M_4PI_3 * cube(r) * sld_l * sph_j1c(q*r);
+        f += M_4PI_3 * cube(r) * sld_l * sas_3j1x_x(q*r);
 
         // iterate over sub_shells in the interface
@@ -92,5 +92,5 @@
     }
     // add in solvent effect
-    f -= M_4PI_3 * cube(r) * sld_solvent * sph_j1c(q*r);
+    f -= M_4PI_3 * cube(r) * sld_solvent * sas_3j1x_x(q*r);
 
     const double f2 = f * f * 1.0e-4;
Index: sasmodels/models/spherical_sld.py
===================================================================
--- sasmodels/models/spherical_sld.py	(revision 2d65d51719530a31ea7870f34610b834f36a1319)
+++ sasmodels/models/spherical_sld.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -214,5 +214,5 @@
              ]
 # pylint: enable=bad-whitespace, line-too-long
-source = ["lib/polevl.c", "lib/sas_erf.c", "lib/sph_j1c.c", "spherical_sld.c"]
+source = ["lib/polevl.c", "lib/sas_erf.c", "lib/sas_3j1x_x.c", "spherical_sld.c"]
 single = False  # TODO: fix low q behaviour
 
Index: sasmodels/models/stacked_disks.c
===================================================================
--- sasmodels/models/stacked_disks.c	(revision 0d6e86566d51a1fba22933bdbac8c40d36f9bd76)
+++ sasmodels/models/stacked_disks.c	(revision 6c3e2666e0741d50f118672e164dfa561dcc7765)
@@ -14,6 +14,18 @@
           double solvent_sld);
 
+double Iqxy(double qx, double qy,
+          double thick_core,
+          double thick_layer,
+          double radius,
+          double n_stacking,
+          double sigma_dnn,
+          double core_sld,
+          double layer_sld,
+          double solvent_sld,
+          double theta,
+          double phi);
+
 static
-double _kernel(double qq,
+double _kernel(double q,
                double radius,
                double core_sld,
@@ -22,5 +34,6 @@
                double halfheight,
                double thick_layer,
-               double zi,
+               double sin_alpha,
+               double cos_alpha,
                double sigma_dnn,
                double d,
@@ -28,45 +41,48 @@
 
 {
-	// qq is the q-value for the calculation (1/A)
-	// radius is the core radius of the cylinder (A)
-	// *_sld are the respective SLD's
-	// halfheight is the *Half* CORE-LENGTH of the cylinder = L (A)
-	// zi is the dummy variable for the integration (x in Feigin's notation)
-
-	const double besarg1 = qq*radius*sin(zi);
-	const double besarg2 = qq*radius*sin(zi);
-
-	const double sinarg1 = qq*halfheight*cos(zi);
-	const double sinarg2 = qq*(halfheight+thick_layer)*cos(zi);
-
-	const double be1 = sas_J1c(besarg1);
-	const double be2 = sas_J1c(besarg2);
-	const double si1 = sin(sinarg1)/sinarg1;
-	const double si2 = sin(sinarg2)/sinarg2;
-
-	const double dr1 = (core_sld-solvent_sld);
-	const double dr2 = (layer_sld-solvent_sld);
-	const double area = M_PI*radius*radius;
-	const double totald=2.0*(thick_layer+halfheight);
-
-	const double t1 = area*(2.0*halfheight)*dr1*(si1)*(be1);
-	const double t2 = area*dr2*(totald*si2-2.0*halfheight*si1)*(be2);
-
-
-	double retval =((t1+t2)*(t1+t2))*sin(zi);
-
-	// loop for the structure facture S(q)
-	double sqq=0.0;
-	for(int kk=1;kk<n_stacking;kk+=1) {
-		double dexpt=qq*cos(zi)*qq*cos(zi)*d*d*sigma_dnn*sigma_dnn*kk/2.0;
-		sqq=sqq+(n_stacking-kk)*cos(qq*cos(zi)*d*kk)*exp(-1.*dexpt);
-	}
-
-	// end of loop for S(q)
-	sqq=1.0+2.0*sqq/n_stacking;
-
-	retval *= sqq;
-
-	return(retval);
+    // q is the q-value for the calculation (1/A)
+    // radius is the core radius of the cylinder (A)
+    // *_sld are the respective SLD's
+    // halfheight is the *Half* CORE-LENGTH of the cylinder = L (A)
+    // zi is the dummy variable for the integration (x in Feigin's notation)
+
+    const double besarg1 = q*radius*sin_alpha;
+    //const double besarg2 = q*radius*sin_alpha;
+
+    const double sinarg1 = q*halfheight*cos_alpha;
+    const double sinarg2 = q*(halfheight+thick_layer)*cos_alpha;
+
+    const double be1 = sas_2J1x_x(besarg1);
+    //const double be2 = sas_2J1x_x(besarg2);
+    const double be2 = be1;
+    const double si1 = sas_sinx_x(sinarg1);
+    const double si2 = sas_sinx_x(sinarg2);
+
+    const double dr1 = core_sld - solvent_sld;
+    const double dr2 = layer_sld - solvent_sld;
+    const double area = M_PI*radius*radius;
+    const double totald = 2.0*(thick_layer + halfheight);
+
+    const double t1 = area * (2.0*halfheight) * dr1 * si1 * be1;
+    const double t2 = area * dr2 * (totald*si2 - 2.0*halfheight*si1) * be2;
+
+    double pq = square(t1 + t2);
+
+    // loop for the structure factor S(q)
+    double qd_cos_alpha = q*d*cos_alpha;
+    //d*cos_alpha is the projection of d onto q (in other words the component
+    //of d that is parallel to q.
+    double debye_arg = -0.5*square(qd_cos_alpha*sigma_dnn);
+    double sq=0.0;
+    for (int kk=1; kk<n_stacking; kk++) {
+        sq += (n_stacking-kk) * cos(qd_cos_alpha*kk) * exp(debye_arg*kk);
+    }
+    // end of loop for S(q)
+    sq = 1.0 + 2.0*sq/n_stacking;
+
+    return pq * sq * n_stacking;
+    // volume normalization should be per disk not per stack but form_volume
+    // is per stack so correct here for now.  Could change form_volume but
+    // if one ever wants to use P*S we need the ER based on the total volume
 }
 
@@ -83,94 +99,35 @@
                             double solvent_sld)
 {
-/*	StackedDiscsX  :  calculates the form factor of a stacked "tactoid" of core shell disks
+/*    StackedDiscsX  :  calculates the form factor of a stacked "tactoid" of core shell disks
 like clay platelets that are not exfoliated
 */
-	double summ = 0.0;	//initialize integral
-
-	double d=2.0*thick_layer+thick_core;
-	double halfheight = thick_core/2.0;
-
-	for(int i=0;i<N_POINTS_76;i++) {
-		double zi = (Gauss76Z[i] + 1.0)*M_PI/4.0;
-		double yyy = Gauss76Wt[i] *
-                    _kernel(q,
-		                   radius,
-		                   core_sld,
-		                   layer_sld,
-		                   solvent_sld,
-		                   halfheight,
-		                   thick_layer,
-		                   zi,
-		                   sigma_dnn,
-		                   d,
-		                   n_stacking);
-		summ += yyy;
-	}
-
-	double answer = M_PI/4.0*summ;
-
-	//Convert to [cm-1]
-	answer *= 1.0e-4;
-
-	return answer;
-}
-
-static double stacked_disks_kernel_2d(double q, double q_x, double q_y,
-                            double thick_core,
-                            double thick_layer,
-                            double radius,
-                            double n_stacking,
-                            double sigma_dnn,
-                            double core_sld,
-                            double layer_sld,
-                            double solvent_sld,
-                            double theta,
-                            double phi)
-{
-
-    double ct, st, cp, sp;
-
-    //convert angle degree to radian
-    theta = theta * M_PI/180.0;
-    phi = phi * M_PI/180.0;
-
-    SINCOS(theta, st, ct);
-    SINCOS(phi, sp, cp);
-
-    // silence compiler warnings about unused variable
-    (void) sp;
-
-    // parallelepiped orientation
-    const double cyl_x = st * cp;
-    const double cyl_y = st * sp;
-
-    // Compute the angle btw vector q and the
-    // axis of the parallelepiped
-    const double cos_val = cyl_x*q_x + cyl_y*q_y;
-
-    // Note: cos(alpha) = 0 and 1 will get an
-    // undefined value from Stackdisc_kern
-    double alpha = acos( cos_val );
-
-    // Call the IGOR library function to get the kernel
-    double d = 2 * thick_layer + thick_core;
-    double halfheight = thick_core/2.0;
-    double answer = _kernel(q,
-                     radius,
-                     core_sld,
-                     layer_sld,
-                     solvent_sld,
-                     halfheight,
-                     thick_layer,
-                     alpha,
-                     sigma_dnn,
-                     d,
-                     n_stacking);
-
-    answer /= sin(alpha);
-    //convert to [cm-1]
-    answer *= 1.0e-4;
-
-    return answer;
+    double summ = 0.0;    //initialize integral
+
+    double d = 2.0*thick_layer+thick_core;
+    double halfheight = 0.5*thick_core;
+
+    for(int i=0; i<N_POINTS_76; i++) {
+        double zi = (Gauss76Z[i] + 1.0)*M_PI_4;
+        double sin_alpha, cos_alpha; // slots to hold sincos function output
+        SINCOS(zi, sin_alpha, cos_alpha);
+        double yyy = _kernel(q,
+                           radius,
+                           core_sld,
+                           layer_sld,
+                           solvent_sld,
+                           halfheight,
+                           thick_layer,
+                           sin_alpha,
+                           cos_alpha,
+                           sigma_dnn,
+                           d,
+                           n_stacking);
+        summ += Gauss76Wt[i] * yyy * sin_alpha;
+    }
+
+    double answer = M_PI_4*summ;
+
+    //Convert to [cm-1]
+    return 1.0e-4*answer;
 }
 
@@ -179,6 +136,6 @@
                    double radius,
                    double n_stacking){
-    double d = 2 * thick_layer + thick_core;
-    return acos(-1.0) * radius * radius * d * n_stacking;
+    double d = 2.0 * thick_layer + thick_core;
+    return M_PI * radius * radius * d * n_stacking;
 }
 
@@ -203,2 +160,41 @@
                     solvent_sld);
 }
+
+
+double
+Iqxy(double qx, double qy,
+     double thick_core,
+     double thick_layer,
+     double radius,
+     double n_stacking,
+     double sigma_dnn,
+     double core_sld,
+     double layer_sld,
+     double solvent_sld,
+     double theta,
+     double phi)
+{
+    double q, sin_alpha, cos_alpha;
+    ORIENT_SYMMETRIC(qx, qy, theta, phi, q, sin_alpha, cos_alpha);
+
+    double d = 2.0 * thick_layer + thick_core;
+    double halfheight = 0.5*thick_core;
+    double answer = _kernel(q,
+                     radius,
+                     core_sld,
+                     layer_sld,
+                     solvent_sld,
+                     halfheight,
+                     thick_layer,
+                     sin_alpha,
+                     cos_alpha,
+                     sigma_dnn,
+                     d,
+                     n_stacking);
+
+    //convert to [cm-1]
+    answer *= 1.0e-4;
+
+    return answer;
+}
+
Index: sasmodels/models/stacked_disks.py
===================================================================
--- sasmodels/models/stacked_disks.py	(revision ef5a3145c555427a3626384f3a1ca07763f4d48f)
+++ sasmodels/models/stacked_disks.py	(revision b7e8b94b477e6d9a7e4e2666bb218d3568ee66d2)
@@ -1,24 +1,20 @@
 r"""
-This model provides the form factor, $P(q)$, for stacked discs (tactoids)
-with a core/layer structure where the form factor is normalized by the volume
-of the cylinder. Assuming the next neighbor distance (d-spacing) in a stack
-of parallel discs obeys a Gaussian distribution, a structure factor $S(q)$
-proposed by Kratky and Porod in 1949 is used in this function.
-
-Note that the resolution smearing calculation uses 76 Gauss quadrature points
-to properly smear the model since the function is HIGHLY oscillatory,
-especially around the q-values that correspond to the repeat distance of
-the layers.
-
-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}
-
 Definition
 ----------
 
+This model provides the form factor, $P(q)$, for stacked discs (tactoids)
+with a core/layer structure which is constructed itself as $P(q) S(Q)$
+multiplying a $P(q)$ for individual core/layer disks by a structure factor
+$S(q)$ proposed by Kratky and Porod in 1949\ [#CIT1949]_ assuming the next
+neighbor distance (d-spacing) in the stack of parallel discs obeys a Gaussian
+distribution. As such the normalization of this "composite" form factor is
+relative to the individual disk volume, not the volume of the stack of disks.
+This model is appropriate for example for non non exfoliated clay particles such
+as Laponite.
+
 .. figure:: img/stacked_disks_geometry.png
 
+   Geometry of a single core/layer disk
+
 The scattered intensity $I(q)$ is calculated as
 
@@ -26,6 +22,7 @@
 
     I(q) = N\int_{0}^{\pi /2}\left[ \Delta \rho_t
-    \left( V_t f_t(q) - V_c f_c(q)\right) + \Delta \rho_c V_c f_c(q)
-    \right]^2 S(q)\sin{\alpha}\ d\alpha + \text{background}
+    \left( V_t f_t(q,\alpha) - V_c f_c(q,\alpha)\right) + \Delta
+    \rho_c V_c f_c(q,\alpha)\right]^2 S(q,\alpha)\sin{\alpha}\ d\alpha
+    + \text{background}
 
 where the contrast
@@ -35,22 +32,17 @@
     \Delta \rho_i = \rho_i - \rho_\text{solvent}
 
-and $N$ is the number of discs per unit volume,
-$\alpha$ is the angle between the axis of the disc and $q$,
-and $V_t$ and $V_c$ are the total volume and the core volume of
-a single disc, respectively.
-
-.. math::
-
-    \left\langle f_{t}^2(q)\right\rangle_{\alpha} =
-    \int_{0}^{\pi/2}\left[
+and $N$ is the number of individual (single) discs per unit volume, $\alpha$ is
+the angle between the axis of the disc and $q$, and $V_t$ and $V_c$ are the
+total volume and the core volume of a single disc, respectively, and
+
+.. math::
+
+    f_t(q,\alpha) =
     \left(\frac{\sin(q(d+h)\cos{\alpha})}{q(d+h)\cos{\alpha}}\right)
     \left(\frac{2J_1(qR\sin{\alpha})}{qR\sin{\alpha}} \right)
-    \right]^2 \sin{\alpha}\ d\alpha
-
-    \left\langle f_{c}^2(q)\right\rangle_{\alpha} =
-    \int_{0}^{\pi/2}\left[
+
+    f_c(q,\alpha) =
     \left(\frac{\sin(qh)\cos{\alpha})}{qh\cos{\alpha}}\right)
     \left(\frac{2J_1(qR\sin{\alpha})}{qR\sin{\alpha}}\right)
-    \right]^2 \sin{\alpha}\ d\alpha
 
 where $d$ = thickness of the layer (*thick_layer*),
@@ -59,13 +51,16 @@
 .. math::
 
-    S(q) = 1 + \frac{1}{2}\sum_{k=1}^n(n-k)\cos{(kDq\cos{\alpha})}
-    \exp\left[ -k(q\cos{\alpha})^2\sigma_d/2\right]
+    S(q,\alpha) = 1 + \frac{1}{2}\sum_{k=1}^n(n-k)\cos{(kDq\cos{\alpha})}
+    \exp\left[ -k(q)^2(D\cos{\alpha}~\sigma_d)^2/2\right]
 
 where $n$ is the total number of the disc stacked (*n_stacking*),
 $D = 2(d+h)$ is the next neighbor center-to-center distance (d-spacing),
 and $\sigma_d$ = the Gaussian standard deviation of the d-spacing (*sigma_d*).
+Note that $D\cos(\alpha)$ is the component of $D$ parallel to $q$ and the last
+term in the equation above is effectively a Debye-Waller factor term. 
 
 .. note::
-    Each assembly in the stack is layer/core/layer, so the spacing of the
+
+    1. Each assembly in the stack is layer/core/layer, so the spacing of the
     cores is core plus two layers. The 2nd virial coefficient of the cylinder
     is calculated based on the *radius* and *length*
@@ -74,4 +69,9 @@
     is applied.
 
+    2. the resolution smearing calculation uses 76 Gaussian quadrature points
+    to properly smear the model since the function is HIGHLY oscillatory,
+    especially around the q-values that correspond to the repeat distance of
+    the layers.
+
 To provide easy access to the orientation of the stacked disks, we define
 the axis of the cylinder using two angles $\theta$ and $\varphi$.
@@ -79,27 +79,26 @@
 .. figure:: img/cylinder_angle_definition.jpg
 
-    Examples of the angles against
-    the detector plane.
-
-
-Our model uses the form factor calculations implemented in a c-library provided
-by the NIST Center for Neutron Research (Kline, 2006)
+    Examples of the angles against the detector plane.
+
+
+Our model is derived from the form factor calculations implemented in a
+c-library provided by the NIST Center for Neutron Research\ [#CIT_Kline]_
 
 References
 ----------
 
-A Guinier and G Fournet, *Small-Angle Scattering of X-Rays*,
-John Wiley and Sons, New York, 1955
-
-O Kratky and G Porod, *J. Colloid Science*, 4, (1949) 35
-
-J S Higgins and H C Benoit, *Polymers and Neutron Scattering*,
-Clarendon, Oxford, 1994
-
-**Author:** NIST IGOR/DANSE **on:** pre 2010
-
-**Last Modified by:** Piotr Rozyczko **on:** February 18, 2016
-
-**Last Reviewed by:** Richard Heenan **on:** March 22, 2016
+.. [#CIT1949] O Kratky and G Porod, *J. Colloid Science*, 4, (1949) 35
+.. [#CIT_Kline] S R Kline, *J Appl. Cryst.*, 39 (2006) 895
+.. [#] J S Higgins and H C Benoit, *Polymers and Neutron Scattering*,
+   Clarendon, Oxford, 1994
+.. [#] A Guinier and G Fournet, *Small-Angle Scattering of X-Rays*,
+   John Wiley and Sons, New York, 1955
+
+Authorship and Verification
+----------------------------
+
+* **Author:** NIST IGOR/DANSE **Date:** pre 2010
+* **Last Modified by:** Paul Butler and Paul Kienzle **on:** November 26, 2016
+* **Last Reviewed by:** Paul Butler and Paul Kienzle **on:** November 26, 2016
 """
 
@@ -107,5 +106,5 @@
 
 name = "stacked_disks"
-title = ""
+title = "Form factor for a stacked set of non exfoliated core/shell disks"
 description = """\
     One layer of disk consists of a core, a top layer, and a bottom layer.
@@ -149,94 +148,99 @@
             sld_layer=0.0,
             sld_solvent=5.0,
-            theta=0,
+            theta=90,
             phi=0)
-#After redefinition to spherical coordinates find new reasonable test values
-#tests = [
-#    # Accuracy tests based on content in test/utest_extra_models.py.
-#    # Added 2 tests with n_stacked = 5 using SasView 3.1.2 - PDB
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 1.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#      'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, 0.001, 5075.12],
-
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 5.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#      'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, 0.001, 5065.12793824],
-
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 5.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#      'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, 0.164, 0.0127673597265],
-
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 1.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#      'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, [0.001, 90.0], [5075.12, 0.001]],
-
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 1.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#      'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, ([0.4, 0.5]), [0.00105074, 0.00121761]],
-
-#    [{'thick_core': 10.0,
-#      'thick_layer': 15.0,
-#      'radius': 3000.0,
-#      'n_stacking': 1.0,
-#      'sigma_d': 0.0,
-#      'sld_core': 4.0,
-#      'sld_layer': -0.4,
-#     'solvent_sd': 5.0,
-#      'theta': 0.0,
-#      'phi': 0.0,
-#      'scale': 0.01,
-#      'background': 0.001,
-#     }, ([1.3, 1.57]), [0.0010039, 0.0010038]],
-#    ]
-# 21Mar2016   RKH notes that unit tests all have n_stacking=1, ought to test other values
-
+# After redefinition of spherical coordinates -
+# tests had in old coords theta=0, phi=0; new coords theta=90, phi=0
+# but should not matter here as so far all the tests are 1D not 2D
+tests = [
+# Accuracy tests based on content in test/utest_extra_models.py.
+# Added 2 tests with n_stacked = 5 using SasView 3.1.2 - PDB; for which alas q=0.001 values seem closer to n_stacked=1 not 5, changed assuming my 4.1 code OK, RKH
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 1.0,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+      'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+     }, 0.001, 5075.12],
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 5,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+      'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+#     }, 0.001, 5065.12793824],    n_stacking=1 not 5 ? slight change in value here 11jan2017, check other cpu types
+#     }, 0.001, 5075.11570493],
+     }, 0.001, 25325.635693],
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 5,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+      'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+#     }, 0.164, 0.0127673597265],    n_stacking=1 not 5 ?  slight change in value here 11jan2017, check other cpu types
+#     }, 0.164, 0.01480812968],
+     }, 0.164, 0.0598367986509],
+
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 1.0,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+      'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+# second test here was at q=90, changed it to q=5, note I(q) is then just value of flat background
+     }, [0.001, 5.0], [5075.12, 0.001]],
+
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 1.0,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+      'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+     }, ([0.4, 0.5]), [0.00105074, 0.00121761]],
+
+    [{'thick_core': 10.0,
+      'thick_layer': 15.0,
+      'radius': 3000.0,
+      'n_stacking': 1.0,
+      'sigma_d': 0.0,
+      'sld_core': 4.0,
+      'sld_layer': -0.4,
+     'solvent_sd': 5.0,
+      'theta': 90.0,
+      'phi': 0.0,
+      'scale': 0.01,
+      'background': 0.001,
+     }, ([1.3, 1.57]), [0.0010039, 0.0010038]],
+    ]
+# 11Jan2017   RKH checking unit test agai, note they are all 1D, no 2D
+
Index: sasmodels/models/star_polymer.c
===================================================================
--- sasmodels/models/star_polymer.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/star_polymer.c	(revision 3a48772f136eb2795903c63707474e7527b4a325)
@@ -6,11 +6,11 @@
 {
 
-    double u_2 = radius2 * pow(q,2);
+    double u_2 = radius2 * q * q;
     double v = u_2 * arms / (3.0 * arms - 2.0);
 
-    double term1 = v - 1.0 + exp(-v);
-    double term2 = ((arms - 1.0)/2.0)* pow((1.0 - exp(-v)),2.0);
+    double term1 = v + expm1(-v);
+    double term2 = ((arms - 1.0)/2.0) * square(expm1(-v));
 
-    return (2.0 * (term1 + term2)) / (arms * pow(v,2.0));
+    return (2.0 * (term1 + term2)) / (arms * v * v);
 
 }
Index: sasmodels/models/surface_fractal.c
===================================================================
--- sasmodels/models/surface_fractal.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/surface_fractal.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -1,2 +1,5 @@
+// Don't need invalid test since fractal_dim_surf is not polydisperse
+// #define INVALID(v) (v.fractal_dim_surf <= 1.0 || v.fractal_dim_surf >= 3.0)
+
 double form_volume(double radius);
 
@@ -11,27 +14,28 @@
     double cutoff_length)
 {
-    double pq, sq, mmo, result;
+    // calculate P(q)
+    const double pq = square(sas_3j1x_x(q*radius));
 
-    //Replaced the original formula with Taylor expansion near zero.
-    //pq = pow((3.0*(sin(q*radius) - q*radius*cos(q*radius))/pow((q*radius),3)),2);
+    // calculate S(q)
+    // Note: lim q->0 S(q) = -gamma(mmo) cutoff_length^mmo (mmo cutoff_length)
+    // however, the surface fractal formula is invalid outside the range
+    const double mmo = 5.0 - fractal_dim_surf;
+    const double sq = sas_gamma(mmo) * pow(cutoff_length, mmo)
+           * pow(1.0 + square(q*cutoff_length), -0.5*mmo)
+           * sin(-mmo * atan(q*cutoff_length)) / q;
 
-    pq = sph_j1c(q*radius);
-    pq = pq*pq;
+    // Empirically determined that the results are valid within this range.
+    // Above 1/r, the form starts to oscillate;  below
+    //const double result = (q > 5./(3-fractal_dim_surf)/cutoff_length) && q < 1./radius
+    //                      ? pq * sq : 0.);
 
-    //calculate S(q)
-    mmo = 5.0 - fractal_dim_surf;
-    sq  = sas_gamma(mmo)*sin(-(mmo)*atan(q*cutoff_length));
-    sq *= pow(cutoff_length, mmo);
-    sq /= pow((1.0 + (q*cutoff_length)*(q*cutoff_length)),(mmo/2.0));
-    sq /= q;
+    double result = pq * sq;
 
-    //combine and return
-    result = pq * sq;
-
-    return result;
+    // exclude negative results
+    return result > 0. ? result : 0.;
 }
-double form_volume(double radius){
-
-    return 1.333333333333333*M_PI*radius*radius*radius;
+double form_volume(double radius)
+{
+    return M_4PI_3*cube(radius);
 }
 
Index: sasmodels/models/surface_fractal.py
===================================================================
--- sasmodels/models/surface_fractal.py	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/surface_fractal.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -10,37 +10,22 @@
 .. math::
 
-    I(q) = scale \times P(q)S(q) + background
-
-.. math::
-
-    P(q) = F(qR)^2
-
-.. math::
-
-    F(x) = \frac{3\left[sin(x)-xcos(x)\right]}{x^3}
-
-.. math::
-
-    S(q) = \frac{\Gamma(5-D_S)\zeta^{5-D_S}}{\left[1+(q\zeta)^2
-    \right]^{(5-D_S)/2}}
-    \frac{sin\left[(D_S - 5) tan^{-1}(q\zeta) \right]}{q}
-
-.. math::
-
-    scale = scale\_factor \times NV^2(\rho_{particle} - \rho_{solvent})^2
-
-.. math::
-
-    V = \frac{4}{3}\pi R^3
+    I(q) &= \text{scale} \times P(q)S(q) + \text{background} \\
+    P(q) &= F(qR)^2 \\
+    F(x) &= \frac{3\left[\sin(x)-x\cos(x)\right]}{x^3} \\
+    S(q) &= \Gamma(5-D_S)\xi^{\,5-D_S}\left[1+(q\xi)^2 \right]^{-(5-D_S)/2}
+            \sin\left[-(5-D_S) \tan^{-1}(q\xi) \right] q^{-1} \\
+    \text{scale} &= \text{scale_factor}\, N V^2(\rho_\text{particle} - \rho_\text{solvent})^2 \\
+    V &= \frac{4}{3}\pi R^3
 
 where $R$ is the radius of the building block, $D_S$ is the **surface** fractal
-dimension,| \zeta\|  is the cut-off length, $\rho_{solvent}$ is the scattering
-length density of the solvent,
-and $\rho_{particle}$ is the scattering length density of particles.
+dimension, $\xi$ is the cut-off length, $\rho_\text{solvent}$ is the scattering
+length density of the solvent and $\rho_\text{particle}$ is the scattering
+length density of particles.
 
 .. note::
-    The surface fractal dimension $D_s$ is only valid if $1<surface\_dim<3$.
-    It is also only valid over a limited $q$ range (see the reference for
-    details)
+
+    The surface fractal dimension is only valid if $1<D_S<3$. The result is
+    only valid over a limited $q$ range, $\tfrac{5}{3-D_S}\xi^{\,-1} < q < R^{-1}$.
+    See the reference for details.
 
 
@@ -87,7 +72,7 @@
 # pylint: enable=bad-whitespace, line-too-long
 
-source = ["lib/sph_j1c.c", "lib/sas_gamma.c", "surface_fractal.c"]
+source = ["lib/sas_3j1x_x.c", "lib/sas_gamma.c", "surface_fractal.c"]
 
-demo = dict(scale=1, background=0,
+demo = dict(scale=1, background=1e-5,
             radius=10, fractal_dim_surf=2.0, cutoff_length=500)
 
Index: sasmodels/models/teubner_strey.py
===================================================================
--- sasmodels/models/teubner_strey.py	(revision b3f2a24980756e0d0b1e7b07be74b8df6c8674c5)
+++ sasmodels/models/teubner_strey.py	(revision 8393c74ed553108e237a0c93b1e6c65ff328e00d)
@@ -69,7 +69,8 @@
 B Jakobs, T Sottmann, R Strey, and I Grillo, *J. Chem. Phys.*, 115 (2001), 580
 """
+from __future__ import division
 
 import numpy as np
-from numpy import inf,power,pi
+from numpy import inf, pi
 
 name = "teubner_strey"
@@ -89,16 +90,12 @@
     ]
 
-def Iq(q, volfraction, sld, sld_solvent,d,xi):
+def Iq(q, volfraction_a, sld_a, sld_b, d, xi):
     """SAS form"""
-    drho2 = (sld-sld_solvent)*(sld-sld_solvent)
+    drho = sld_a - sld_b
     k = 2.0*pi*xi/d
-    a2 = power(1.0+power(k,2.0),2.0)
-    c1 = -2.0*xi*xi*power(k,2.0)+2*xi*xi
-    c2 = power(xi,4.0)
-    prefactor = 8.0*pi*volfraction*(1.0-volfraction)*drho2*c2/xi
-    #k2 = (2.0*pi/d)*(2.0*pi/d)
-    #xi2 = 1/(xi*xi)
-    #q2 = q*q
-    #result = prefactor/((xi2+k2)*(xi2+k2)+2.0*(xi2-k2)*q2+q2*q2)
+    a2 = (1.0 + k**2)**2
+    c1 = 2.0*xi**2 * (1.0 - k**2)
+    c2 = xi**4
+    prefactor = 8.0*pi * volfraction_a*(1.0 - volfraction_a) * drho**2 * c2/xi
     return 1.0e-4*prefactor / np.polyval([c2, c1, a2], q**2)
 
Index: sasmodels/models/triaxial_ellipsoid.c
===================================================================
--- sasmodels/models/triaxial_ellipsoid.c	(revision a8072067ae8a224e45d23ba71f663aadbe1ba4a9)
+++ sasmodels/models/triaxial_ellipsoid.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -10,5 +10,5 @@
 double form_volume(double radius_equat_minor, double radius_equat_major, double radius_polar)
 {
-    return 1.333333333333333*M_PI*radius_equat_minor*radius_equat_major*radius_polar;
+    return M_4PI_3*radius_equat_minor*radius_equat_major*radius_polar;
 }
 
@@ -37,5 +37,5 @@
             const double ysq = square(Gauss76Z[j]*zm + zb);
             const double t = q*sqrt(acosx2 + bsinx2*(1.0-ysq) + c2*ysq);
-            const double fq = sph_j1c(t);
+            const double fq = sas_3j1x_x(t);
             inner += Gauss76Wt[j] * fq * fq ;
         }
@@ -58,21 +58,11 @@
     double psi)
 {
-    double stheta, ctheta;
-    double sphi, cphi;
-    double spsi, cpsi;
+    double q, calpha, cmu, cnu;
+    ORIENT_ASYMMETRIC(qx, qy, theta, phi, psi, q, calpha, cmu, cnu);
 
-    const double q = sqrt(qx*qx + qy*qy);
-    const double qxhat = qx/q;
-    const double qyhat = qy/q;
-    SINCOS(theta*M_PI_180, stheta, ctheta);
-    SINCOS(phi*M_PI_180, sphi, cphi);
-    SINCOS(psi*M_PI_180, spsi, cpsi);
-    const double calpha = ctheta*cphi*qxhat + stheta*qyhat;
-    const double cnu = (-cphi*spsi*stheta + sphi*cpsi)*qxhat + spsi*ctheta*qyhat;
-    const double cmu = (-stheta*cpsi*cphi - spsi*sphi)*qxhat + ctheta*cpsi*qyhat;
     const double t = q*sqrt(radius_equat_minor*radius_equat_minor*cnu*cnu
                           + radius_equat_major*radius_equat_major*cmu*cmu
                           + radius_polar*radius_polar*calpha*calpha);
-    const double fq = sph_j1c(t);
+    const double fq = sas_3j1x_x(t);
     const double s = (sld - sld_solvent) * form_volume(radius_equat_minor, radius_equat_major, radius_polar);
 
Index: sasmodels/models/triaxial_ellipsoid.py
===================================================================
--- sasmodels/models/triaxial_ellipsoid.py	(revision 416f5c7f5af38d5e5d926a5b6b140a94e7e60502)
+++ sasmodels/models/triaxial_ellipsoid.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -104,5 +104,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "lib/gauss76.c", "triaxial_ellipsoid.c"]
+source = ["lib/sas_3j1x_x.c", "lib/gauss76.c", "triaxial_ellipsoid.c"]
 
 def ER(radius_equat_minor, radius_equat_major, radius_polar):
Index: sasmodels/models/unified_power_Rg.py
===================================================================
--- sasmodels/models/unified_power_Rg.py	(revision b3f2a24980756e0d0b1e7b07be74b8df6c8674c5)
+++ sasmodels/models/unified_power_Rg.py	(revision 66ca2a6cf404d09b601f9022e85222304a98dc8e)
@@ -3,8 +3,8 @@
 ----------
 
-The Beaucage model employs the empirical multiple level unified
-Exponential/Power-law fit method developed by G. Beaucage. Four functions
-are included so that 1, 2, 3, or 4 levels can be used. In addition a 0 level
-has been added which simply calculates
+This model employs the empirical multiple level unified Exponential/Power-law 
+fit method developed by Beaucage. Four functions are included so that 1, 2, 3, 
+or 4 levels can be used. In addition a 0 level has been added which simply 
+calculates
 
 .. math::
@@ -15,4 +15,12 @@
 many different types of particles, including fractal clusters, random coils
 (Debye equation), ellipsoidal particles, etc.
+
+The model works best for mass fractal systems characterized by Porod exponents 
+between 5/3 and 3. It should not be used for surface fractal systems. Hammouda 
+(2010) has pointed out a deficiency in the way this model handles the 
+transitioning between the Guinier and Porod regimes and which can create 
+artefacts that appear as kinks in the fitted model function.
+
+Also see the Guinier_Porod model.
 
 The empirical fit function is:
@@ -30,5 +38,7 @@
 .. math::
 
-    q_i^* = \frac{q}{\operatorname{erf}^3(q R_{gi}/\sqrt{6}}
+    q_i^* = q \left[\operatorname{erf}
+            \left(\frac{q R_{gi}}{\sqrt{6}}\right)
+        \right]^{-3}
 
 
@@ -56,4 +66,6 @@
 
 G Beaucage, *J. Appl. Cryst.*, 29 (1996) 134-146
+
+B Hammouda, *Analysis of the Beaucage model, J. Appl. Cryst.*, (2010), 43, 1474-1478
 
 """
Index: sasmodels/models/vesicle.c
===================================================================
--- sasmodels/models/vesicle.c	(revision 2c74c11fc4544381ecf9949bd1618c67ff624c6d)
+++ sasmodels/models/vesicle.c	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -8,8 +8,5 @@
 {
     //note that for the vesicle model, the volume is ONLY the shell volume
-    double volume;
-    volume =4.*M_PI*(radius+thickness)*(radius+thickness)*(radius+thickness)/3;
-    volume -=4.*M_PI*radius*radius*radius/3.;
-    return volume;
+    return M_4PI_3*(cube(radius+thickness) - cube(radius));
 }
 
@@ -32,15 +29,15 @@
     // core first, then add in shell
     contrast = sld_solvent-sld;
-    vol = 4.0*M_PI/3.0*radius*radius*radius;
-    f = vol*sph_j1c(q*radius)*contrast;
+    vol = M_4PI_3*cube(radius);
+    f = vol * sas_3j1x_x(q*radius) * contrast;
  
     //now the shell. No volume normalization as this is done by the caller
     contrast = sld-sld_solvent;
-    vol = 4.0*M_PI/3.0*(radius+thickness)*(radius+thickness)*(radius+thickness);
-    f += vol*sph_j1c(q*(radius+thickness))*contrast;
+    vol = M_4PI_3*cube(radius+thickness);
+    f += vol * sas_3j1x_x(q*(radius+thickness)) * contrast;
 
     //rescale to [cm-1]. 
-    f2 = volfraction*f*f*1.0e-4;
+    f2 = volfraction * f*f*1.0e-4;
     
-    return(f2);
+    return f2;
 }
Index: sasmodels/models/vesicle.py
===================================================================
--- sasmodels/models/vesicle.py	(revision e77872ef7445f58a9eb6ad1eb0015b7b63d53fac)
+++ sasmodels/models/vesicle.py	(revision 925ad6edca2969db8fb763eb02beee2c5a5f2872)
@@ -94,5 +94,5 @@
              ]
 
-source = ["lib/sph_j1c.c", "vesicle.c"]
+source = ["lib/sas_3j1x_x.c", "vesicle.c"]
 
 def ER(radius, thickness):
@@ -116,6 +116,6 @@
     '''
 
-    whole = 4. * pi * (radius + thickness) ** 3. / 3.
-    core = 4. * pi * radius ** 3. / 3.
+    whole = 4./3. * pi * (radius + thickness)**3
+    core = 4./3. * pi * radius**3
     return whole, whole - core
 
Index: sasmodels/resolution2d.py
===================================================================
--- sasmodels/resolution2d.py	(revision 40a87fa5096610e24c3ead10299f609e99f093f2)
+++ sasmodels/resolution2d.py	(revision 7e94989f214482b706d6c85060f63becff5d85ae)
@@ -64,9 +64,9 @@
         # just need q_calc and weights
         self.data = data
-        self.index = index
-
-        self.qx_data = data.qx_data[index]
-        self.qy_data = data.qy_data[index]
-        self.q_data = data.q_data[index]
+        self.index = index if index is not None else slice(None)
+
+        self.qx_data = data.qx_data[self.index]
+        self.qy_data = data.qy_data[self.index]
+        self.q_data = data.q_data[self.index]
 
         dqx = getattr(data, 'dqx_data', None)
@@ -74,6 +74,6 @@
         if dqx is not None and dqy is not None:
             # Here dqx and dqy mean dq_parr and dq_perp
-            self.dqx_data = dqx[index]
-            self.dqy_data = dqy[index]
+            self.dqx_data = dqx[self.index]
+            self.dqy_data = dqy[self.index]
             ## Remove singular points if exists
             self.dqx_data[self.dqx_data < SIGMA_ZERO] = SIGMA_ZERO
@@ -126,5 +126,5 @@
         # The angle (phi) of the original q point
         q_phi = np.arctan(q_phi).repeat(nbins)\
-            .reshape(nq, nbins).transpose().flatten()
+            .reshape([nq, nbins]).transpose().flatten()
         ## Find Gaussian weight for each dq bins: The weight depends only
         #  on r-direction (The integration may not need)
Index: sasmodels/rst2html.py
===================================================================
--- sasmodels/rst2html.py	(revision b217c71eb4946b935994265074fdaf992f34b66f)
+++ sasmodels/rst2html.py	(revision 0890871751ed39056683fe16b7c50393b3d469f3)
@@ -155,4 +155,13 @@
     assert replace_dollar(u"a (again $in parens$) a") == u"a (again :math:`in parens`) a"
 
+def view_rst_app(filename):
+    import wx  # type: ignore
+    app = wx.App()
+    view_rst(filename)
+    app.MainLoop()
+
+
 if __name__ == "__main__":
-    test_dollar()
+    import sys
+    view_rst_app(sys.argv[1])
+
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision a80e64c6fabe2895c22d039441c659df03f77dfa)
+++ sasmodels/sasview_model.py	(revision 64614ad601c59ff9c1ee778c4fe0ac411d0696f3)
@@ -50,5 +50,5 @@
     """
     import sys
-    import sas
+    import sas   # needed in order to set sas.models
     import sas.sascalc.fit
     sys.modules['sas.models'] = sas.sascalc.fit
@@ -57,6 +57,8 @@
     import sas.models
     from sasmodels.conversion_table import CONVERSION_TABLE
-    for new_name, conversion in CONVERSION_TABLE.items():
-        old_name = conversion[0]
+    for new_name, conversion in CONVERSION_TABLE.get((3,1,2), {}).items():
+        # CoreShellEllipsoidModel => core_shell_ellipsoid:1
+        new_name = new_name.split(':')[0]
+        old_name = conversion[0] if len(conversion) < 3 else conversion[2]
         module_attrs = {old_name: find_model(new_name)}
         ConstructedModule = type(old_name, (), module_attrs)
@@ -228,5 +230,5 @@
     magnetic_params = []
     fixed = []
-    for p in model_info.parameters.user_parameters():
+    for p in model_info.parameters.user_parameters({}, is2d=True):
         if p.type == 'orientation':
             orientation_params.append(p.name)
@@ -352,5 +354,5 @@
         self.dispersion = collections.OrderedDict()
         self.details = {}
-        for p in self._model_info.parameters.user_parameters():
+        for p in self._model_info.parameters.user_parameters({}, is2d=True):
             if p.name in hidden:
                 continue
@@ -580,4 +582,16 @@
             raise TypeError("evalDistribution expects q or [qx, qy], not %r"
                             % type(qdist))
+
+    def get_composition_models(self):
+        """
+            Returns usable models that compose this model
+        """
+        s_model = None
+        p_model = None
+        if hasattr(self._model_info, "composition") \
+           and self._model_info.composition is not None:
+            p_model = _make_model_from_info(self._model_info.composition[1][0])()
+            s_model = _make_model_from_info(self._model_info.composition[1][1])()
+        return p_model, s_model
 
     def calculate_Iq(self, qx, qy=None):
