Index: doc/guide/plugin.rst
===================================================================
--- doc/guide/plugin.rst	(revision 31fc4ad0933a6d3f10f38cf74b060ca21352e976)
+++ doc/guide/plugin.rst	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -1033,10 +1033,10 @@
           "radius": 120., "radius_pd": 0.2, "radius_pd_n":45},
          0.2, 0.228843],
-        [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45}, "ER", 120.],
-        [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45}, "VR", 1.],
+        [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45},
+         0.1, None, None, 120., None, 1.],  # q, F, F^2, R_eff, V, form:shell
     ]
 
 
-**tests=[[{parameters}, q, result], ...]** is a list of lists.
+**tests=[[{parameters}, q, Iq], ...]** is a list of lists.
 Each list is one test and contains, in order:
 
@@ -1050,7 +1050,10 @@
 - input and output values can themselves be lists if you have several
   $q$ values to test for the same model parameters.
-- for testing *ER* and *VR*, give the inputs as "ER" and "VR" respectively;
-  the output for *VR* should be the sphere/shell ratio, not the individual
-  sphere and shell values.
+- for testing effective radius, volume and form:shell volume ratio, use the
+  extended form of the tests results, with *None, None, R_eff, V, V_r*
+  instead of *Iq*.  This calls the kernel *Fq* function instead of *Iq*.
+- for testing F and F^2 (used for beta approximation) do the same as the
+  effective radius test, but include values for the first two elements,
+  $<F(q)>$ and $<F^2(q)>$.
 
 .. _Test_Your_New_Model:
Index: sasmodels/direct_model.py
===================================================================
--- sasmodels/direct_model.py	(revision 7b9e4ddca7ad3551d8fe3696d2afe75b0018599e)
+++ sasmodels/direct_model.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -64,41 +64,15 @@
     return calculator(call_details, values, cutoff, is_magnetic)
 
-def call_ER(model_info, pars):
-    # type: (ModelInfo, ParameterSet) -> float
-    """
-    Call the model ER function using *values*.
-
-    *model_info* is either *model.info* if you have a loaded model,
-    or *kernel.info* if you have a model kernel prepared for evaluation.
-    """
-    if model_info.ER is None:
-        return 1.0
-    elif not model_info.parameters.form_volume_parameters:
-        # handle the case where ER is provided but model is not polydisperse
-        return model_info.ER()
-    else:
-        value, weight = _vol_pars(model_info, pars)
-        individual_radii = model_info.ER(*value)
-        return np.sum(weight*individual_radii) / np.sum(weight)
-
-
-def call_VR(model_info, pars):
-    # type: (ModelInfo, ParameterSet) -> float
-    """
-    Call the model VR function using *pars*.
-
-    *model_info* is either *model.info* if you have a loaded model,
-    or *kernel.info* if you have a model kernel prepared for evaluation.
-    """
-    if model_info.VR is None:
-        return 1.0
-    elif not model_info.parameters.form_volume_parameters:
-        # handle the case where ER is provided but model is not polydisperse
-        return model_info.VR()
-    else:
-        value, weight = _vol_pars(model_info, pars)
-        whole, part = model_info.VR(*value)
-        return np.sum(weight*part)/np.sum(weight*whole)
-
+def call_Fq(calculator, pars, cutoff=0., mono=False):
+    # type: (Kernel, ParameterSet, float, bool) -> np.ndarray
+    """
+    Like :func:`call_kernel`, but returning F, F^2, R_eff, V, V_form/V_shell.
+    """
+    R_eff_type = int(pars.pop('radius_effective_type', 1.0))
+    mesh = get_mesh(calculator.info, pars, dim=calculator.dim, mono=mono)
+    #print("pars", list(zip(*mesh))[0])
+    call_details, values, is_magnetic = make_kernel_args(calculator, mesh)
+    #print("values:", values)
+    return calculator.Fq(call_details, values, cutoff, is_magnetic, R_eff_type)
 
 def call_profile(model_info, **pars):
@@ -419,20 +393,17 @@
     model_name = sys.argv[1]
     call = sys.argv[2].upper()
-    if call != "ER_VR":
-        try:
-            values = [float(v) for v in call.split(',')]
-        except ValueError:
-            values = []
-        if len(values) == 1:
-            q, = values
-            data = empty_data1D([q])
-        elif len(values) == 2:
-            qx, qy = values
-            data = empty_data2D([qx], [qy])
-        else:
-            print("use q or qx,qy or ER or VR")
-            sys.exit(1)
+    try:
+        values = [float(v) for v in call.split(',')]
+    except ValueError:
+        values = []
+    if len(values) == 1:
+        q, = values
+        data = empty_data1D([q])
+    elif len(values) == 2:
+        qx, qy = values
+        data = empty_data2D([qx], [qy])
     else:
-        data = empty_data1D([0.001])  # Data not used in ER/VR
+        print("use q or qx,qy")
+        sys.exit(1)
 
     model_info = load_model_info(model_name)
@@ -442,11 +413,6 @@
                 for pair in sys.argv[3:]
                 for k, v in [pair.split('=')])
-    if call == "ER_VR":
-        ER = call_ER(model_info, pars)
-        VR = call_VR(model_info, pars)
-        print(ER, VR)
-    else:
-        Iq = calculator(**pars)
-        print(Iq[0])
+    Iq = calculator(**pars)
+    print(Iq[0])
 
 if __name__ == "__main__":
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 74e9b5fa9e460b3726df690a1411aef195f15b4e)
+++ sasmodels/model_test.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -59,5 +59,5 @@
 from . import core
 from .core import list_models, load_model_info, build_model
-from .direct_model import call_kernel, call_ER, call_VR
+from .direct_model import call_kernel, call_Fq
 from .exception import annotate_exception
 from .modelinfo import expand_pars
@@ -215,6 +215,5 @@
                 ({}, [(0.1, 0.1)]*2, [None]*2),
                 # test that ER/VR will run if they exist
-                ({}, 'ER', None),
-                ({}, 'VR', None),
+                ({}, 0.1, None, None, None, None, None),
                 ]
             tests = smoke_tests
@@ -288,5 +287,5 @@
             # type: (KernelModel, TestCondition) -> None
             """Run a single test case."""
-            user_pars, x, y = test
+            user_pars, x, y = test[:3]
             pars = expand_pars(self.info.parameters, user_pars)
             invalid = invalid_pars(self.info.parameters, pars)
@@ -301,37 +300,67 @@
             self.assertEqual(len(y), len(x))
 
-            if x[0] == 'ER':
-                actual = np.array([call_ER(model.info, pars)])
-            elif x[0] == 'VR':
-                actual = np.array([call_VR(model.info, pars)])
-            elif isinstance(x[0], tuple):
+            if isinstance(x[0], tuple):
                 qx, qy = zip(*x)
                 q_vectors = [np.array(qx), np.array(qy)]
-                kernel = model.make_kernel(q_vectors)
-                actual = call_kernel(kernel, pars)
             else:
                 q_vectors = [np.array(x)]
-                kernel = model.make_kernel(q_vectors)
+
+            kernel = model.make_kernel(q_vectors)
+            if len(test) == 3:
                 actual = call_kernel(kernel, pars)
-
-            self.assertTrue(len(actual) > 0)
-            self.assertEqual(len(y), len(actual))
-
-            for xi, yi, actual_yi in zip(x, y, actual):
+                self._check_vectors(x, y, actual, 'I')
+                return actual
+            else:
+                y1 = y
+                y2 = test[3] if not isinstance(test[3], list) else [test[3]]
+                F1, F2, R_eff, volume, volume_ratio = call_Fq(kernel, pars)
+                if F1 is not None:  # F1 is none for models with Iq instead of Fq
+                    self._check_vectors(x, y1, F1, 'F')
+                self._check_vectors(x, y2, F2, 'F^2')
+                self._check_scalar(test[4], R_eff, 'R_eff')
+                self._check_scalar(test[5], volume, 'volume')
+                self._check_scalar(test[6], volume_ratio, 'form:shell ratio')
+                return F2
+
+        def _check_scalar(self, target, actual, name):
+            if target is None:
+                # smoke test --- make sure it runs and produces a value
+                self.assertTrue(not np.isnan(actual),
+                                'invalid %s: %s' % (name, actual))
+            elif np.isnan(target):
+                # make sure nans match
+                self.assertTrue(np.isnan(actual),
+                                '%s: expected:%s; actual:%s'
+                                % (name, target, actual))
+            else:
+                # is_near does not work for infinite values, so also test
+                # for exact values.
+                self.assertTrue(target == actual or is_near(target, actual, 5),
+                                '%s: expected:%s; actual:%s'
+                                % (name, target, actual))
+
+        def _check_vectors(self, x, target, actual, name='I'):
+            self.assertTrue(len(actual) > 0,
+                            '%s(...) expected return'%name)
+            if target is None:
+                return
+            self.assertEqual(len(target), len(actual),
+                             '%s(...) returned wrong length'%name)
+            for xi, yi, actual_yi in zip(x, target, actual):
                 if yi is None:
                     # smoke test --- make sure it runs and produces a value
                     self.assertTrue(not np.isnan(actual_yi),
-                                    'invalid f(%s): %s' % (xi, actual_yi))
+                                    'invalid %s(%s): %s' % (name, xi, actual_yi))
                 elif np.isnan(yi):
+                    # make sure nans match
                     self.assertTrue(np.isnan(actual_yi),
-                                    'f(%s): expected:%s; actual:%s'
-                                    % (xi, yi, actual_yi))
+                                    '%s(%s): expected:%s; actual:%s'
+                                    % (name, xi, yi, actual_yi))
                 else:
                     # is_near does not work for infinite values, so also test
-                    # for exact values.  Note that this will not
+                    # for exact values.
                     self.assertTrue(yi == actual_yi or is_near(yi, actual_yi, 5),
-                                    'f(%s); expected:%s; actual:%s'
-                                    % (xi, yi, actual_yi))
-            return actual
+                                    '%s(%s); expected:%s; actual:%s'
+                                    % (name, xi, yi, actual_yi))
 
     return ModelTestCase
@@ -345,4 +374,7 @@
     invalid = []
     for par in sorted(pars.keys()):
+        # special handling of R_eff mode, which is not a usual parameter
+        if par == 'radius_effective_type':
+            continue
         parts = par.split('_pd')
         if len(parts) > 1 and parts[1] not in ("", "_n", "nsigma", "type"):
Index: sasmodels/models/_spherepy.py
===================================================================
--- sasmodels/models/_spherepy.py	(revision ca4444fb3f1d373b0e94842e6733943d12ce79e9)
+++ sasmodels/models/_spherepy.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -71,4 +71,7 @@
     return 1.333333333333333 * pi * radius ** 3
 
+def effective_radius(mode, radius):
+    return radius
+
 def Iq(q, sld, sld_solvent, radius):
     #print "q",q
@@ -105,9 +108,4 @@
 sesans.vectorized = True  # sesans accepts an array of z values
 
-def ER(radius):
-    return radius
-
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0,
             sld=6, sld_solvent=1,
Index: sasmodels/models/core_shell_bicelle_elliptical.py
===================================================================
--- sasmodels/models/core_shell_bicelle_elliptical.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/core_shell_bicelle_elliptical.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -184,8 +184,8 @@
 
 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}, '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,
Index: sasmodels/models/core_shell_bicelle_elliptical_belt_rough.py
===================================================================
--- sasmodels/models/core_shell_bicelle_elliptical_belt_rough.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/core_shell_bicelle_elliptical_belt_rough.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -186,6 +186,6 @@
 
 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}, '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,
Index: sasmodels/models/core_shell_sphere.py
===================================================================
--- sasmodels/models/core_shell_sphere.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/core_shell_sphere.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -96,5 +96,5 @@
 
 tests = [
-#    [{'radius': 20.0, 'thickness': 10.0}, 'ER', 30.0],
+    [{'radius': 20.0, 'thickness': 10.0}, 0.1, None, None, 30.0, 4.*pi/3*30**3, 1.0],
 
     # The SasView test result was 0.00169, with a background of 0.001
Index: sasmodels/models/cylinder.py
===================================================================
--- sasmodels/models/cylinder.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/cylinder.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -167,4 +167,5 @@
             phi_pd=10, phi_pd_n=5)
 
+# pylint: disable=bad-whitespace, line-too-long
 qx, qy = 0.2 * np.cos(2.5), 0.2 * np.sin(2.5)
 # After redefinition of angles, find new tests values.  Was 10 10 in old coords
@@ -180,3 +181,18 @@
 ]
 del qx, qy  # not necessary to delete, but cleaner
+
+# Default radius and length
+radius, length = parameters[2][2], parameters[3][2]
+tests.extend([
+    ({'radius_effective_type': 0}, 0.1, None, None, 0., pi*radius**2*length, 1.0),
+    ({'radius_effective_type': 1}, 0.1, None, None, (0.75*radius**2*length)**(1./3.), None, None),
+    ({'radius_effective_type': 2}, 0.1, None, None, radius, None, None),
+    ({'radius_effective_type': 3}, 0.1, None, None, length/2., None, None),
+    ({'radius_effective_type': 4}, 0.1, None, None, min(radius, length/2.), None, None),
+    ({'radius_effective_type': 5}, 0.1, None, None, max(radius, length/2.), None, None),
+    ({'radius_effective_type': 6}, 0.1, None, None, np.sqrt(4*radius**2 + length**2)/2., None, None),
+])
+del radius, length
+# pylint: enable=bad-whitespace, line-too-long
+
 # ADDED by:  RKH  ON: 18Mar2016 renamed sld's etc
Index: sasmodels/models/dab.py
===================================================================
--- sasmodels/models/dab.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/dab.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -74,7 +74,3 @@
     return pars
 
-# ER defaults to 1.0
-
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0, cor_length=50)
Index: sasmodels/models/gaussian_peak.py
===================================================================
--- sasmodels/models/gaussian_peak.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/gaussian_peak.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -50,6 +50,4 @@
     """
 
-# VR defaults to 1.0
-
 def random():
     peak_pos = 10**np.random.uniform(-3, -1)
Index: sasmodels/models/hardsphere.py
===================================================================
--- sasmodels/models/hardsphere.py	(revision 71b751d6a3a4ef4164f3b322841ddf3875c025c5)
+++ sasmodels/models/hardsphere.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -162,7 +162,4 @@
     return pars
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(radius_effective=200, volfraction=0.2,
             radius_effective_pd=0.1, radius_effective_pd_n=40)
Index: sasmodels/models/hayter_msa.py
===================================================================
--- sasmodels/models/hayter_msa.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/hayter_msa.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -89,6 +89,4 @@
     return 1.0;
     """
-# ER defaults to 0.0
-# VR defaults to 1.0
 
 def random():
Index: sasmodels/models/hollow_cylinder.py
===================================================================
--- sasmodels/models/hollow_cylinder.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/hollow_cylinder.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -69,4 +69,5 @@
 * **Last Reviewed by:** Paul Butler **Date:** September 06, 2018
 """
+from __future__ import division
 
 import numpy as np
@@ -132,8 +133,15 @@
 qx = q*cos(pi/6.0)
 qy = q*sin(pi/6.0)
+radius = parameters[0][2]
+thickness = parameters[1][2]
+length = parameters[2][2]
 # Parameters for unit tests
 tests = [
     [{}, 0.00005, 1764.926],
-#    [{}, 'VR', 0.55555556],
+    [{}, 0.1, None, None,
+     (3./4*(radius+thickness)**2*length)**(1./3),  # R_eff from volume
+     pi*((radius+thickness)**2-radius**2)*length,  # shell volume
+     (radius+thickness)**2/((radius+thickness)**2 - radius**2), # form:shell ratio
+    ],
     [{}, 0.001, 1756.76],
     [{}, (qx, qy), 2.36885476192],
Index: sasmodels/models/lamellar.py
===================================================================
--- sasmodels/models/lamellar.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/lamellar.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -95,7 +95,4 @@
     return pars
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0,
             sld=6, sld_solvent=1,
Index: sasmodels/models/lamellar_hg.py
===================================================================
--- sasmodels/models/lamellar_hg.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/lamellar_hg.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -109,7 +109,4 @@
     return pars
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0,
             length_tail=15, length_head=10,
Index: sasmodels/models/lamellar_hg_stack_caille.py
===================================================================
--- sasmodels/models/lamellar_hg_stack_caille.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/lamellar_hg_stack_caille.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -122,7 +122,4 @@
     """
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 def random():
     total_thickness = 10**np.random.uniform(2, 4.7)
Index: sasmodels/models/lamellar_stack_caille.py
===================================================================
--- sasmodels/models/lamellar_stack_caille.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/lamellar_stack_caille.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -120,7 +120,4 @@
     """
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0,
             thickness=67., Nlayers=3.75, d_spacing=200.,
Index: sasmodels/models/lamellar_stack_paracrystal.py
===================================================================
--- sasmodels/models/lamellar_stack_paracrystal.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/lamellar_stack_paracrystal.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -147,7 +147,4 @@
     return pars
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(scale=1, background=0,
             thickness=33, Nlayers=20, d_spacing=250, sigma_d=0.2,
Index: sasmodels/models/sphere.py
===================================================================
--- sasmodels/models/sphere.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/sphere.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -83,5 +83,5 @@
       "radius": 120., "radius_pd": 0.2, "radius_pd_n":45},
      0.2, 0.228843],
-#    [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45}, "ER", 120.],
-#    [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45}, "VR", 1.],
+    [{"radius": 120., "radius_pd": 0.2, "radius_pd_n":45},
+     0.1, None, None, 120., None, 1.0],
 ]
Index: sasmodels/models/squarewell.py
===================================================================
--- sasmodels/models/squarewell.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/squarewell.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -130,7 +130,4 @@
 """
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 def random():
     pars = dict(
Index: sasmodels/models/stickyhardsphere.py
===================================================================
--- sasmodels/models/stickyhardsphere.py	(revision 2d81cfe3be42579d563407fb9275b7b516fee234)
+++ sasmodels/models/stickyhardsphere.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -182,7 +182,4 @@
 """
 
-# ER defaults to 0.0
-# VR defaults to 1.0
-
 demo = dict(radius_effective=200, volfraction=0.2, perturb=0.05,
             stickiness=0.2, radius_effective_pd=0.1, radius_effective_pd_n=40)
Index: sasmodels/models/vesicle.py
===================================================================
--- sasmodels/models/vesicle.py	(revision ee60aa768b29c58e20b19e94578ec3a94a0889d7)
+++ sasmodels/models/vesicle.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
@@ -128,5 +128,4 @@
          [{}, 0.100600200401, 1.77063682331],
          [{}, 0.5, 0.00355351388906],
-#         [{}, 'ER', 130.],
-#         [{}, 'VR', 0.54483386436],
+         [{}, 0.1, None, None, 130., None, 1./0.54483386436],  # R_eff, form:shell
         ]
