Index: doc/guide/fitting_sq.rst
===================================================================
--- doc/guide/fitting_sq.rst	(revision e62c019dc943138d33b75fbe9df287770f52a282)
+++ doc/guide/fitting_sq.rst	(revision 694c6d05ce7883894bb6e42d0f49b330f5bbb2c6)
@@ -14,78 +14,220 @@
    This help document is under development
 
-.. figure:: p_and_s_buttons.png
-
-**Product models**, or $P@S$ models for short, multiply the structure factor
-$S(Q)$ by the form factor $P(Q)$, modulated by the **effective radius** of the
-form factor.
-
-Many of the parameters in $P@S$ models take on specific meanings so that they
-can be handled correctly inside SasView:
+**Product models**, or $P@S$ models for short, multiply the form factor
+$P(Q)$ by the structure factor $S(Q)$, modulated by the **effective radius**
+of the form factor. For the theory behind this, see :ref:`PStheory` later.
+
+**If writing your own** $P@S$ **models, DO NOT give your model parameters**
+**these names!**
+
+Parameters
+^^^^^^^^^^
+
+Many parameters are common amongst $P@S$ models, but take on specific meanings:
 
 * *scale*:
 
-  In simple $P(Q)$ models **scale** often represents the volume fraction of
-  material.
-
-  In $P@S$ models **scale** should be set to 1.0, as the $P@S$ model contains a
-  **volfraction** parameter.
+    Overall model scale factor.
+
+    To compute number density $n$ the volume fraction $V_f$ (parameterised as
+    **volfraction**) is needed.  In most $P(Q)$ models $V_f$ is not defined and
+    **scale** is used instead. Some $P(Q)$ models, such as the *vesicle*, do
+    define **volfraction** and so can leave **scale** at 1.0.
+
+    Structure factor models $S(Q)$ contain **volfraction**. In $P@S$ models
+    this is *also* used as the volume fraction for the form factor model
+    $P(Q)$, *replacing* any **volfraction** parameter in $P(Q)$. This means
+    that $P@S$ models can also leave **scale** at 1.0.
+
+    If the volume fraction required for $S(Q)$ is *not* the volume fraction
+    needed to compute the $n$ for $P(Q)$, then leave **volfraction** as the
+    $V_f$ for $S(Q)$ and use **scale** to define the $V_f$ for $P(Q)$ as
+    $V_f$ = **scale**  $\cdot$  **volfraction**.  This situation may occur in
+    a mixed phase system where the effective volume fraction needed to compute
+    the structure is much higher than the true volume fraction.
 
 * *volfraction*:
 
-  The volume fraction of material.
-
-  For hollow shapes, **volfraction** still represents the volume fraction of
-  material but the $S(Q)$ calculation needs the volume fraction *enclosed by*
-  *the shape.* SasView scales the user-specified volume fraction by the ratio
-  form:shell computed from the average form volume and average shell volume
-  returned from the $P(Q)$ calculation (the original volfraction is divided
-  by the shell volume to compute the number density, and then $P@S$ is scaled
-  by that to get the absolute scaling on the final $I(Q)$).
+    The volume fraction of material, $V_f$.
+
+    For hollow shapes, **volfraction** still represents the volume fraction of
+    material but the $S(Q)$ calculation needs the volume fraction *enclosed by*
+    *the shape.*  To remedy this the user-specified **volfraction** is scaled
+    by the ratio form:shell computed from the average form volume and average
+    shell volume returned from the $P(Q)$ calculation when calculating $S(Q)$.
+    The original **volfraction** is divided by the shell volume to compute the
+    number density $n$ used in the $P@S$ model to get the absolute scaling on
+    the final $I(Q)$.
 
 * *radius_effective*:
 
-  The radial distance determining the range of the $S(Q)$ interaction.
-
-  This may, or may not, be the same as any "size" parameters describing the
-  form of the shape. For example, in a system containing freely-rotating
-  cylinders, the volume of space each cylinder requires to tumble will be
-  much larger than the volume of the cylinder itself. Thus the effective
-  radius will be larger than either the radius or half-length of the
-  cylinder. It may be sensible to tie or constrain **radius_effective** to one
-  or other of these "size" parameters.
-
-  If just part of the $S(Q)$ calculation, the value of **radius_effective** may
-  be polydisperse. If it is calculated by $P(Q)$, then it will be the weighted
-  average of the effective radii computed for the polydisperse shape
-  parameters.
+    The radial distance determining the range of the $S(Q)$ interaction.
+
+    This may be estimated from the "size" parameters $\mathbf \xi$ describing
+    the form of the shape.  For example, in a system containing freely-rotating
+    cylinders, the volume of space each cylinder requires to tumble will be
+    much larger than the volume of the cylinder itself. Thus the *effective*
+    radius of a cylinder will be larger than either its actual radius or half-
+    length.
+
+    In use, it may be sensible to tie or constrain **radius_effective**
+    to one or other of the "size" parameters describing the form of the shape.
+
+    **radius_effective** may also be specified directly, independent of the
+    estimate from $P(Q)$.
+
+    If **radius_effective** is calculated by $P(Q)$, it will be the
+    weighted average of the effective radii computed for the polydisperse
+    shape parameters, and that average is used to compute $S(Q)$. When
+    specified directly, the value of **radius_effective** may be
+    polydisperse, and $S(Q)$ will be averaged over a range of effective
+    radii. Whether this makes any physical sense will depend on the system.
+
+.. note::
+
+   The following additional parameters are only available in SasView 5.0 and
+   later.
+
+ *radius_effective_mode*:
+
+    Defines how the effective radius (parameter **radius_effective**) should
+    be computed from the parameters of the shape.
+
+    When **radius_effective_mode = 0** then unconstrained **radius_effective**
+    parameter in the $S(Q)$ model is used. *This is the default in SasView*
+    *versions 4.x and earlier*. Otherwise, in SasView 5.0 and later,
+    **radius_effective_mode = k** represents an index in a list of alternative
+    **radius_effective** calculations which will appear in a drop-down box.
+
+    For example, the *ellipsoid* model defines the following
+    **radius_effective_modes**::
+
+        1 => average curvature
+        2 => equivalent volume sphere
+        3 => min radius
+        4 => max radius
+
+    Note: **radius_effective_mode** will only appear in the parameter table if
+    the model defines the list of modes, otherwise it will be set permanently
+    to 0 for the user-defined effective radius.
+    
+    **WARNING! If** $P(Q)$ **is multiplied by** $S(Q)$ **in the FitPage,**
+    **instead of being generated in the Sum|Multi dialog, the**
+    **radius_effective used is constrained (equivalent to**
+    **radius_effective_mode = 1)**.
 
 * *structure_factor_mode*:
 
-  If the $P@S$ model supports the $\beta(Q)$ *decoupling correction* [1] then
-  **structure_factor_mode** will appear in the parameter table after the $S(Q)$
-  parameters.
-
-  If **structure_factor_mode = 0** then the *local monodisperse approximation*
-  will be used, i.e.:
-
-    $I(Q)$ = $(scale$ / $volume)$ x $P(Q)$ x $S(Q)$ + $background$
-
-  If **structure_factor_mode = 1** then the $\beta(q)$ correction will be
-  used, i.e.:
-
-    $I(Q)$ = $(scale$ x $volfraction$ / $volume)$ x $( <F(Q)^2>$ + $<F(Q)>^2$ x $(S(Q)$ - $1) )$ + $background$
-
-    where $P(Q)$ = $<|F(Q)|^2>$.
-
-  This is equivalent to:
-
-    $I(Q)$ = $(scale$ / $volume)$ x $P(Q)$ x $( 1$ + $\beta(Q)$ x $(S(Q)$ - $1) )$ + $background$
-
-  The $\beta(Q)$ decoupling approximation has the effect of damping the
-  oscillations in the normal (local monodisperse) $S(Q)$. When $\beta(Q)$ = 1
-  the local monodisperse approximation is recovered.
-
-  More mode options may appear in future as more complicated operations are
-  added.
+    The type of structure factor calculation to use.
+
+    If the $P@S$ model supports the $\beta(Q)$ *decoupling correction* [1]
+    then **structure_factor_mode** will appear in the parameter table after
+    the $S(Q)$ parameters.
+
+    If **structure_factor_mode = 0** then the
+    *local monodisperse approximation* will be used, i.e.:
+
+    .. math::
+        I(Q) = \text{scale} \frac{V_f}{V} P(Q) S(Q) + \text{background}
+
+    where $P(Q) = \langle F(Q)^2 \rangle$. *This is the default in SasView*
+    *versions 4.x and earlier*.
+
+    If **structure_factor_mode = 1** then the $\beta(Q)$ correction will be
+    used, i.e.:
+
+    .. math::
+        I(Q) = \text{scale} \frac{V_f}{V} P(Q) [ 1 + \beta(Q) (S(Q) - 1) ]
+        + \text{background}
+
+    The $\beta(Q)$ decoupling approximation has the effect of damping the
+    oscillations in the normal (local monodisperse) $S(Q)$. When $\beta(Q) = 1$
+    the local monodisperse approximation is recovered. *This mode is only*
+    *available in SasView 5.0 and later*.
+
+    More mode options may appear in future as more complicated operations are
+    added.
+
+.. _PStheory:
+
+Theory
+^^^^^^
+
+Scattering at vector $\mathbf Q$ for an individual particle with
+shape parameters $\mathbf\xi$ and contrast $\rho_c(\mathbf r, \mathbf\xi)$
+is computed from the square of the amplitude, $F(\mathbf Q, \mathbf\xi)$, as
+
+.. math::
+    I(\mathbf Q) = F(\mathbf Q, \mathbf\xi) F^*(\mathbf Q, \mathbf\xi)
+        \big/ V(\mathbf\xi)
+
+with the particle volume $V(\mathbf \xi)$ and
+
+.. math::
+    F(\mathbf Q, \mathbf\xi) = \int_{\mathbb R^3} \rho_c(\mathbf r, \mathbf\xi)
+        e^{i \mathbf Q \cdot \mathbf r} \,\mathrm d \mathbf r = F
+
+The 1-D scattering pattern for monodisperse particles uses the orientation
+average in spherical coordinates,
+
+.. math::
+    I(Q) = n \langle F F^*\rangle = \frac{n}{4\pi}
+    \int_{\theta=0}^{\pi} \int_{\phi=0}^{2\pi}
+    F F^* \sin(\theta) \,\mathrm d\phi \mathrm d\theta
+
+where $F(\mathbf Q,\mathbf\xi)$ uses
+$\mathbf Q = [Q \sin\theta\cos\phi, Q \sin\theta\sin\phi, Q \cos\theta]^T$.
+A $u$-substitution may be used, with $\alpha = \cos \theta$,
+$\surd(1 - \alpha^2) = \sin \theta$, and
+$\mathrm d\alpha = -\sin\theta\,\mathrm d\theta$.
+Here,
+
+.. math:: n = V_f/V(\mathbf\xi)
+
+is the number density of scatterers estimated from the volume fraction $V_f$
+of particles in solution. In this formalism, each incoming
+wave interacts with exactly one particle before being scattered into the
+detector. All interference effects are within the particle itself.
+The detector accumulates counts in proportion to the relative probability
+at each pixel. The extension to heterogeneous systems is simply a matter of
+adding the scattering patterns in proportion to the number density of each
+particle. That is, given shape parameters $\mathbf\xi$ with probability
+$P_\mathbf{\xi}$,
+
+.. math::
+
+    I(Q) = \int_\Xi n(\mathbf\xi) \langle F F^* \rangle \,\mathrm d\xi
+         = V_f\frac{\int_\Xi P_\mathbf{\xi} \langle F F^* \rangle
+         \,\mathrm d\mathbf\xi}{\int_\Xi P_\mathbf\xi V(\mathbf\xi)\,\mathrm d\mathbf\xi}
+
+This approximation is valid in the dilute limit, where particles are
+sufficiently far apart that the interaction between them can be ignored.
+
+As concentration increases, a structure factor term $S(Q)$ can be included,
+giving the monodisperse approximation for the interaction between particles,
+with
+
+.. math:: I(Q) = n \langle F F^* \rangle S(Q)
+
+For particles without spherical symmetry, the decoupling approximation
+is more accurate, with
+
+.. math::
+
+    I(Q) = n [\langle F F^* \rangle
+        + \langle F \rangle \langle F \rangle^* (S(Q) - 1)]
+
+Or equivalently,
+
+.. math:: I(Q) = P(Q)[1 + \beta\,(S(Q) - 1)]
+
+with the form factor $P(Q) = n \langle F F^* \rangle$ and
+$\beta = \langle F \rangle \langle F \rangle^* \big/ \langle F F^* \rangle$.
+These approximations can be extended to heterogeneous systems using averages
+over size, $\langle \cdot \rangle_\mathbf\xi = \int_\Xi P_\mathbf\xi \langle\cdot\rangle\,\mathrm d\mathbf\xi \big/ \int_\Xi P_\mathbf\xi \,\mathrm d\mathbf\xi$ and setting
+$n = V_f\big/\langle V \rangle_\mathbf\xi$.
+
+Further improvements can be made using the local monodisperse
+approximation (LMA) or using partial structure factors [2].
 
 References
@@ -94,7 +236,10 @@
 .. [#] Kotlarchyk, M.; Chen, S.-H. *J. Chem. Phys.*, 1983, 79, 2461
 
+.. [#] Bressler I., Kohlbrecher J., Thunemann A.F. *J. Appl. Crystallogr.*
+   48 (2015) 1587-1598
+
 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
 
 *Document History*
 
-| 2019-03-30 Paul Kienzle & Steve King
+| 2019-03-31 Paul Kienzle, Steve King & Richard Heenan
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision a34b8119b864fa10b972c2a11f92a9c5e13207f1)
+++ sasmodels/kernelcl.py	(revision 9fac5f5b2fd6a440d3b4df58c692047b489d5ba3)
@@ -158,11 +158,11 @@
 ENV = None
 def reset_environment():
-    # type: () -> None
-    """
-    Call to create a new OpenCL context, such as after a change to SAS_OPENCL.
+    # type: () -> "GpuEnvironment"
+    """
+    Return a new OpenCL context, such as after a change to SAS_OPENCL.
     """
     global ENV
     ENV = GpuEnvironment() if use_opencl() else None
-
+    return ENV
 
 def environment():
Index: sasmodels/models/hardsphere.py
===================================================================
--- sasmodels/models/hardsphere.py	(revision c1e44e5b1121db2a5c034ed4dfd86dac0f666034)
+++ sasmodels/models/hardsphere.py	(revision 4d00de6876e77964fc2e6ebe210a2cdda4427b69)
@@ -16,5 +16,5 @@
    Earlier versions of SasView did not incorporate the so-called
    $\beta(q)$ ("beta") correction [1] for polydispersity and non-sphericity.
-   This is only available in SasView versions 4.2.2 and higher.
+   This is only available in SasView versions 5.0 and higher.
 
 radius_effective is the effective hard sphere radius.
Index: sasmodels/models/hayter_msa.py
===================================================================
--- sasmodels/models/hayter_msa.py	(revision c1e44e5b1121db2a5c034ed4dfd86dac0f666034)
+++ sasmodels/models/hayter_msa.py	(revision 4d00de6876e77964fc2e6ebe210a2cdda4427b69)
@@ -18,5 +18,5 @@
    Earlier versions of SasView did not incorporate the so-called
    $\beta(q)$ ("beta") correction [3] for polydispersity and non-sphericity.
-   This is only available in SasView versions 4.2.2 and higher.
+   This is only available in SasView versions 5.0 and higher.
 
 The salt concentration is used to compute the ionic strength of the solution
Index: sasmodels/models/squarewell.py
===================================================================
--- sasmodels/models/squarewell.py	(revision c1e44e5b1121db2a5c034ed4dfd86dac0f666034)
+++ sasmodels/models/squarewell.py	(revision 4d00de6876e77964fc2e6ebe210a2cdda4427b69)
@@ -20,5 +20,5 @@
    Earlier versions of SasView did not incorporate the so-called
    $\beta(q)$ ("beta") correction [2] for polydispersity and non-sphericity.
-   This is only available in SasView versions 4.2.2 and higher.
+   This is only available in SasView versions 5.0 and higher.
 
 The well width $(\lambda)$ is defined as multiples of the particle diameter
Index: sasmodels/models/stickyhardsphere.py
===================================================================
--- sasmodels/models/stickyhardsphere.py	(revision c1e44e5b1121db2a5c034ed4dfd86dac0f666034)
+++ sasmodels/models/stickyhardsphere.py	(revision 4d00de6876e77964fc2e6ebe210a2cdda4427b69)
@@ -56,5 +56,5 @@
    Earlier versions of SasView did not incorporate the so-called
    $\beta(q)$ ("beta") correction [3] for polydispersity and non-sphericity.
-   This is only available in SasView versions 4.2.2 and higher.
+   This is only available in SasView versions 5.0 and higher.
 
 In SasView the effective radius may be calculated from the parameters
Index: sasmodels/sesans.py
===================================================================
--- sasmodels/sesans.py	(revision b297ba9e837aada12de87cd46ea7a01bfe549da7)
+++ sasmodels/sesans.py	(revision da33725a02f35789084f7139c291d0a9403db3ab)
@@ -15,4 +15,5 @@
 from numpy import pi  # type: ignore
 from scipy.special import j0
+
 
 class SesansTransform(object):
@@ -38,11 +39,11 @@
 
     # transform arrays
-    _H = None  # type: np.ndarray
-    _H0 = None # type: np.ndarray
+    _H = None   # type: np.ndarray
+    _H0 = None  # type: np.ndarray
 
-    def __init__(self, z, SElength, lam, zaccept, Rmax):
+    def __init__(self, z, SElength, lam, zaccept, Rmax, log_spacing=1.0003):
         # type: (np.ndarray, float, float) -> None
-        #import logging; logging.info("creating SESANS transform")
         self.q = z
+        self.log_spacing = log_spacing
         self._set_hankel(SElength, lam, zaccept, Rmax)
 
@@ -59,21 +60,21 @@
     def _set_hankel(self, SElength, lam, zaccept, Rmax):
         # type: (np.ndarray, float, float) -> None
-        # Force float32 arrays, otherwise run into memory problems on some machines
-        SElength = np.asarray(SElength, dtype='float32')
-
-        #Rmax = #value in text box somewhere in FitPage?
+        SElength = np.asarray(SElength)
         q_max = 2*pi / (SElength[1] - SElength[0])
         q_min = 0.1 * 2*pi / (np.size(SElength) * SElength[-1])
-        q = np.arange(q_min, q_max, q_min, dtype='float32')
-        dq = q_min
+        q = np.exp(np.arange(np.log(q_min), np.log(q_max),
+                             np.log(self.log_spacing)))
 
-        H0 = np.float32(dq/(2*pi)) * q
+        dq = np.diff(q)
+        dq = np.insert(dq, 0, dq[0])
 
-        repq = np.tile(q, (SElength.size, 1)).T
-        repSE = np.tile(SElength, (q.size, 1))
-        H = np.float32(dq/(2*pi)) * j0(repSE*repq) * repq
+        H0 = dq/(2*pi) * q
 
-        replam = np.tile(lam, (q.size, 1))
-        reptheta = np.arcsin(repq*replam/2*np.pi)
+        H = np.outer(q, SElength)
+        j0(H, out=H)
+        H *= (dq * q / (2*pi)).reshape((-1, 1))
+
+        reptheta = np.outer(q, lam/(2*pi))
+        np.arcsin(reptheta, out=reptheta)
         mask = reptheta > zaccept
         H[mask] = 0
