Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision e44432d7890080c5b9a440696903718a52c60eba)
+++ sasmodels/generate.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -24,8 +24,10 @@
     dimension, or 1.0 if no volume normalization is required.
 
-    *ER(p1, p2, ...)* returns the effective radius of the form with
-    particular dimensions.
-
-    *VR(p1, p2, ...)* returns the volume ratio for core-shell style forms.
+    *shell_volume(p1, p2, ...)* returns the volume of the shell for forms
+    which are hollow.
+
+    *effective_radius(mode, p1, p2, ...)* returns the effective radius of
+    the form with particular dimensions.  Mode determines the type of
+    effective radius returned, with mode=1 for equivalent volume.
 
     #define INVALID(v) (expr)  returns False if v.parameter is invalid
@@ -72,7 +74,4 @@
 background.  This will work correctly even when polydispersity is off.
 
-*ER* and *VR* are python functions which operate on parameter vectors.
-The constructor code will generate the necessary vectors for computing
-them with the desired polydispersity.
 The kernel module must set variables defining the kernel meta data:
 
@@ -106,10 +105,4 @@
     create the OpenCL kernel functions.  The files defining the functions
     need to be listed before the files which use the functions.
-
-    *ER* is a python function defining the effective radius.  If it is
-    not present, the effective radius is 0.
-
-    *VR* is a python function defining the volume ratio.  If it is not
-    present, the volume ratio is 1.
 
     *form_volume*, *Iq*, *Iqac*, *Iqabc* are strings containing
Index: sasmodels/mixture.py
===================================================================
--- sasmodels/mixture.py	(revision ecf895e58d2474f33943e2931dfdca4633a22dc8)
+++ sasmodels/mixture.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -143,5 +143,4 @@
     #model_info.tests = []
     #model_info.source = []
-    # Iq, Iqxy, form_volume, ER, VR and sesans
     # Remember the component info blocks so we can build the model
     model_info.composition = ('mixture', parts)
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 304c775207a9dbbadb23ad414848454d66b556d1)
+++ sasmodels/model_test.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -10,14 +10,15 @@
 
 Each model is tested using the default parameters at q=0.1, (qx, qy)=(0.1, 0.1),
-and the ER and VR are computed.  The return values at these points are not
-considered.  The test is only to verify that the models run to completion,
-and do not produce inf or NaN.
+and Fq is called to make sure R_eff, volume and volume ratio are computed.
+The return values at these points are not considered.  The test is only to
+verify that the models run to completion, and do not produce inf or NaN.
 
 Tests are defined with the *tests* attribute in the model.py file.  *tests*
 is a list of individual tests to run, where each test consists of the
 parameter values for the test, the q-values and the expected results.  For
-the effective radius test, the q-value should be 'ER'.  For the VR test,
-the q-value should be 'VR'.  For 1-D tests, either specify the q value or
-a list of q-values, and the corresponding I(q) value, or list of I(q) values.
+the effective radius test and volume ratio tests, use the extended output
+form, which checks each output of kernel.Fq. For 1-D tests, either specify
+the q value or a list of q-values, and the corresponding I(q) value, or
+list of I(q) values.
 
 That is::
@@ -32,6 +33,5 @@
                         [I(qx1, qy1), I(qx2, qy2), ...]],
 
-        [ {parameters}, 'ER', ER(pars) ],
-        [ {parameters}, 'VR', VR(pars) ],
+        [ {parameters}, q, F(q), F^2(q), R_eff, V, V_r ],
         ...
     ]
@@ -214,5 +214,5 @@
                 ({}, [0.001, 0.01, 0.1], [None]*3),
                 ({}, [(0.1, 0.1)]*2, [None]*2),
-                # test that ER/VR will run if they exist
+                # test that Fq will run
                 ({}, 0.1, None, None, None, None, None),
                 ]
@@ -249,7 +249,5 @@
         def _find_missing_tests(self):
             # type: () -> None
-            """make sure there are 1D, 2D, ER and VR tests as appropriate"""
-            model_has_VR = callable(self.info.VR)
-            model_has_ER = callable(self.info.ER)
+            """make sure there are 1D and 2D tests as appropriate"""
             model_has_1D = True
             model_has_2D = any(p.type == 'orientation'
@@ -259,6 +257,4 @@
             single = [test for test in self.info.tests
                       if not isinstance(test[2], list) and test[2] is not None]
-            tests_has_VR = any(test[1] == 'VR' for test in single)
-            tests_has_ER = any(test[1] == 'ER' for test in single)
             tests_has_1D_single = any(isinstance(test[1], float) for test in single)
             tests_has_2D_single = any(isinstance(test[1], tuple) for test in single)
@@ -273,8 +269,4 @@
 
             missing = []
-            if model_has_VR and not tests_has_VR:
-                missing.append("VR")
-            if model_has_ER and not tests_has_ER:
-                missing.append("ER")
             if model_has_1D and not (tests_has_1D_single or tests_has_1D_multiple):
                 missing.append("1D")
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision e44432d7890080c5b9a440696903718a52c60eba)
+++ sasmodels/modelinfo.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -264,7 +264,7 @@
     will have a magnitude and a direction, which may be different from
     other sld parameters. The volume parameters are used for calls
-    to form_volume within the kernel (required for volume normalization)
-    and for calls to ER and VR for effective radius and volume ratio
-    respectively.
+    to form_volume within the kernel (required for volume normalization),
+    to shell_volume (for hollow shapes), and to effective_radius (for
+    structure factor interactions) respectively.
 
     *description* is a short description of the parameter.  This will
@@ -799,6 +799,4 @@
     info.c_code = getattr(kernel_module, 'c_code', None)
     info.effective_radius = getattr(kernel_module, 'effective_radius', None)
-    info.ER = None  # CRUFT
-    info.VR = None  # CRUFT
     # TODO: check the structure of the tests
     info.tests = getattr(kernel_module, 'tests', [])
@@ -932,24 +930,6 @@
     #: use those functions.
     source = None           # type: List[str]
-    #: The set of tests that must pass.  The format of the tests is described
-    #: in :mod:`model_test`.
-    tests = None            # type: List[TestCondition]
-    #: Returns the effective radius of the model given its volume parameters.
-    #: The presence of *ER* indicates that the model is a form factor model
-    #: that may be used together with a structure factor to form an implicit
-    #: multiplication model.
-    #:
-    #: The parameters to the *ER* function must be marked with type *volume*.
-    #: in the parameter table.  They will appear in the same order as they
-    #: do in the table.  The values passed to *ER* will be vectors, with one
-    #: value for each polydispersity condition.  For example, if the model
-    #: is polydisperse over both length and radius, then both length and
-    #: radius will have the same number of values in the vector, with one
-    #: value for each *length X radius*.  If only *radius* is polydisperse,
-    #: then the value for *length* will be repeated once for each value of
-    #: *radius*.  The *ER* function should return one effective radius for
-    #: each parameter set.  Multiplicity parameters will be received as
-    #: arrays, with one row per polydispersity condition.
-    c_code = None
+    #: inline source code, added after all elements of source
+    c_code = None           # type: Optional[str]
     #: Returns the form volume for python-based models.  Form volume is needed
     #: for volume normalization in the polydispersity integral.  If no
@@ -1001,4 +981,7 @@
     #: Line numbers for symbols defining C code
     lineno = None           # type: Dict[str, int]
+    #: The set of tests that must pass.  The format of the tests is described
+    #: in :mod:`model_test`.
+    tests = None            # type: List[TestCondition]
 
     def __init__(self):
Index: sasmodels/product.py
===================================================================
--- sasmodels/product.py	(revision e44432d7890080c5b9a440696903718a52c60eba)
+++ sasmodels/product.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -61,6 +61,4 @@
     return pars
 
-# TODO: core_shell_sphere model has suppressed the volume ratio calculation
-# revert it after making VR and ER available at run time as constraints.
 def make_product_info(p_info, s_info):
     # type: (ModelInfo, ModelInfo) -> ModelInfo
@@ -126,5 +124,4 @@
     #model_info.tests = []
     #model_info.source = []
-    # Iq, Iqxy, form_volume, ER, VR and sesans
     # Remember the component info blocks so we can build the model
     model_info.composition = ('product', [p_info, s_info])
@@ -331,5 +328,5 @@
         #print("R_eff=%d:%g, volfrac=%g, volume ratio=%g"%(radius_type, effective_radius, volfrac, volume_ratio))
         if radius_type > 0:
-            # set the value to the model ER and set the weight to 1
+            # set the value to the model R_eff and set the weight to 1
             s_values[2] = s_values[2+s_npars+s_offset[0]] = effective_radius
             s_values[2+s_npars+s_offset[0]+nweights] = 1.0
@@ -366,38 +363,2 @@
         self.p_kernel.release()
         self.s_kernel.release()
-
-
-def calc_er_vr(model_info, call_details, values):
-    # type: (ModelInfo, ParameterSet) -> Tuple[float, float]
-
-    if model_info.ER is None and model_info.VR is None:
-        return 1.0, 1.0
-
-    nvalues = model_info.parameters.nvalues
-    value = values[nvalues:nvalues + call_details.num_weights]
-    weight = values[nvalues + call_details.num_weights: nvalues + 2*call_details.num_weights]
-    npars = model_info.parameters.npars
-    # Note: changed from pairs ([v], [w]) to triples (p, [v], [w]), but the
-    # dispersion mesh code doesn't actually care about the nominal parameter
-    # value, p, so set it to None.
-    pairs = [(None, value[offset:offset+length], weight[offset:offset+length])
-             for p, offset, length
-             in zip(model_info.parameters.call_parameters[2:2+npars],
-                    call_details.offset,
-                    call_details.length)
-             if p.type == 'volume']
-    value, weight = dispersion_mesh(model_info, pairs)
-
-    if model_info.ER is not None:
-        individual_radii = model_info.ER(*value)
-        radius_effective = np.sum(weight*individual_radii) / np.sum(weight)
-    else:
-        radius_effective = 1.0
-
-    if model_info.VR is not None:
-        whole, part = model_info.VR(*value)
-        volume_ratio = np.sum(weight*part)/np.sum(weight*whole)
-    else:
-        volume_ratio = 1.0
-
-    return radius_effective, volume_ratio
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision e44432d7890080c5b9a440696903718a52c60eba)
+++ sasmodels/sasview_model.py	(revision 39a06c989ba87dcedc6e5270d59fc4c9dd47ffe5)
@@ -695,5 +695,5 @@
             return self._calculate_Iq(qx, qy)
 
-    def _calculate_Iq(self, qx, qy=None):
+    def _calculate_Iq(self, qx, qy=None, Fq=False, effective_radius_type=1):
         if self._model is None:
             self._model = core.build_model(self._model_info)
@@ -715,4 +715,8 @@
         #print("values", values)
         #print("is_mag", is_magnetic)
+        if Fq:
+            result = calculator.Fq(call_details, values, cutoff=self.cutoff,
+                                   magnetic=is_magnetic,
+                                   effective_radius_type=effective_radius_type)
         result = calculator(call_details, values, cutoff=self.cutoff,
                             magnetic=is_magnetic)
@@ -726,5 +730,6 @@
         return result, lazy_results
 
-    def calculate_ER(self):
+
+    def calculate_ER(self, mode=1):
         # type: () -> float
         """
@@ -733,11 +738,6 @@
         :return: the value of the effective radius
         """
-        if self._model_info.ER is None:
-            return 1.0
-        else:
-            value, weight = self._dispersion_mesh()
-            fv = self._model_info.ER(*value)
-            #print(values[0].shape, weights.shape, fv.shape)
-            return np.sum(weight * fv) / np.sum(weight)
+        Fq = self._calculate_Iq([0.1], True, mode)
+        return Fq[2]
 
     def calculate_VR(self):
@@ -746,12 +746,8 @@
         Calculate the volf ratio for P(q)*S(q)
 
-        :return: the value of the volf ratio
-        """
-        if self._model_info.VR is None:
-            return 1.0
-        else:
-            value, weight = self._dispersion_mesh()
-            whole, part = self._model_info.VR(*value)
-            return np.sum(weight * part) / np.sum(weight * whole)
+        :return: the value of the form:shell volume ratio
+        """
+        Fq = self._calculate_Iq([0.1], True, mode)
+        return Fq[4]
 
     def set_dispersion(self, parameter, dispersion):
