Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision 2a7e20e9c4ba72da5c6088e743a64f3345ee554d)
+++ sasmodels/compare.py	(revision 3221de0ed9b3480163246ceb0503432126bd5134)
@@ -40,4 +40,5 @@
 from . import core
 from . import kerneldll
+from . import kernelcl
 from .data import plot_theory, empty_data1D, empty_data2D, load_data
 from .direct_model import DirectModel, get_mesh
@@ -623,4 +624,51 @@
 
 
+def time_calculation(calculator, pars, evals=1):
+    # type: (Calculator, ParameterSet, int) -> Tuple[np.ndarray, float]
+    """
+    Compute the average calculation time over N evaluations.
+
+    An additional call is generated without polydispersity in order to
+    initialize the calculation engine, and make the average more stable.
+    """
+    # initialize the code so time is more accurate
+    if evals > 1:
+        calculator(**suppress_pd(pars))
+    toc = tic()
+    # make sure there is at least one eval
+    value = calculator(**pars)
+    for _ in range(evals-1):
+        value = calculator(**pars)
+    average_time = toc()*1000. / evals
+    #print("I(q)",value)
+    return value, average_time
+
+def make_data(opts):
+    # type: (Dict[str, Any]) -> Tuple[Data, np.ndarray]
+    """
+    Generate an empty dataset, used with the model to set Q points
+    and resolution.
+
+    *opts* contains the options, with 'qmax', 'nq', 'res',
+    'accuracy', 'is2d' and 'view' parsed from the command line.
+    """
+    qmin, qmax, nq, res = opts['qmin'], opts['qmax'], opts['nq'], opts['res']
+    if opts['is2d']:
+        q = np.linspace(-qmax, qmax, nq)  # type: np.ndarray
+        data = empty_data2D(q, resolution=res)
+        data.accuracy = opts['accuracy']
+        set_beam_stop(data, qmin)
+        index = ~data.mask
+    else:
+        if opts['view'] == 'log' and not opts['zero']:
+            q = np.logspace(math.log10(qmin), math.log10(qmax), nq)
+        else:
+            q = np.linspace(qmin, qmax, nq)
+        if opts['zero']:
+            q = np.hstack((0, q))
+        data = empty_data1D(q, resolution=res)
+        index = slice(None, None)
+    return data, index
+
 DTYPE_MAP = {
     'half': '16',
@@ -643,10 +691,4 @@
     Return a model calculator using the OpenCL calculation engine.
     """
-    if not core.HAVE_OPENCL:
-        raise RuntimeError("OpenCL not available")
-    model = core.build_model(model_info, dtype=dtype, platform="ocl")
-    calculator = DirectModel(data, model, cutoff=cutoff)
-    calculator.engine = "OCL%s"%DTYPE_MAP[str(model.dtype)]
-    return calculator
 
 def eval_ctypes(model_info, data, dtype='double', cutoff=0.):
@@ -660,51 +702,4 @@
     return calculator
 
-def time_calculation(calculator, pars, evals=1):
-    # type: (Calculator, ParameterSet, int) -> Tuple[np.ndarray, float]
-    """
-    Compute the average calculation time over N evaluations.
-
-    An additional call is generated without polydispersity in order to
-    initialize the calculation engine, and make the average more stable.
-    """
-    # initialize the code so time is more accurate
-    if evals > 1:
-        calculator(**suppress_pd(pars))
-    toc = tic()
-    # make sure there is at least one eval
-    value = calculator(**pars)
-    for _ in range(evals-1):
-        value = calculator(**pars)
-    average_time = toc()*1000. / evals
-    #print("I(q)",value)
-    return value, average_time
-
-def make_data(opts):
-    # type: (Dict[str, Any]) -> Tuple[Data, np.ndarray]
-    """
-    Generate an empty dataset, used with the model to set Q points
-    and resolution.
-
-    *opts* contains the options, with 'qmax', 'nq', 'res',
-    'accuracy', 'is2d' and 'view' parsed from the command line.
-    """
-    qmin, qmax, nq, res = opts['qmin'], opts['qmax'], opts['nq'], opts['res']
-    if opts['is2d']:
-        q = np.linspace(-qmax, qmax, nq)  # type: np.ndarray
-        data = empty_data2D(q, resolution=res)
-        data.accuracy = opts['accuracy']
-        set_beam_stop(data, qmin)
-        index = ~data.mask
-    else:
-        if opts['view'] == 'log' and not opts['zero']:
-            q = np.logspace(math.log10(qmin), math.log10(qmax), nq)
-        else:
-            q = np.linspace(qmin, qmax, nq)
-        if opts['zero']:
-            q = np.hstack((0, q))
-        data = empty_data1D(q, resolution=res)
-        index = slice(None, None)
-    return data, index
-
 def make_engine(model_info, data, dtype, cutoff, ngauss=0):
     # type: (ModelInfo, Data, str, float) -> Calculator
@@ -718,8 +713,14 @@
         set_integration_size(model_info, ngauss)
 
-    if dtype is None or not dtype.endswith('!'):
-        return eval_opencl(model_info, data, dtype=dtype, cutoff=cutoff)
-    else:
-        return eval_ctypes(model_info, data, dtype=dtype[:-1], cutoff=cutoff)
+    if dtype != "default" and not dtype.endswith('!') and not kernelcl.use_opencl():
+        raise RuntimeError("OpenCL not available " + kernelcl.OPENCL_ERROR)
+
+    model = core.build_model(model_info, dtype=dtype, platform="ocl")
+    calculator = DirectModel(data, model, cutoff=cutoff)
+    engine_type = calculator._model.__class__.__name__.replace('Model','').upper()
+    bits = calculator._model.dtype.itemsize*8
+    precision = "fast" if getattr(calculator._model, 'fast', False) else str(bits)
+    calculator.engine = "%s[%s]" % (engine_type, precision)
+    return calculator
 
 def _show_invalid(data, theory):
Index: sasmodels/core.py
===================================================================
--- sasmodels/core.py	(revision 6dba2f0cd73248eb4964683c037a8dae144ffa5a)
+++ sasmodels/core.py	(revision 3221de0ed9b3480163246ceb0503432126bd5134)
@@ -25,13 +25,4 @@
 from . import custom
 
-# Other modules look for HAVE_OPENCL in core, not in kernelcl.
-HAVE_OPENCL = kernelcl.HAVE_OPENCL
-
-CUSTOM_MODEL_PATH = os.environ.get('SAS_MODELPATH', "")
-if CUSTOM_MODEL_PATH == "":
-    CUSTOM_MODEL_PATH = joinpath(os.path.expanduser("~"), ".sasmodels", "custom_models")
-    if not os.path.isdir(CUSTOM_MODEL_PATH):
-        os.makedirs(CUSTOM_MODEL_PATH)
-
 # pylint: disable=unused-import
 try:
@@ -42,4 +33,10 @@
     pass
 # pylint: enable=unused-import
+
+CUSTOM_MODEL_PATH = os.environ.get('SAS_MODELPATH', "")
+if CUSTOM_MODEL_PATH == "":
+    CUSTOM_MODEL_PATH = joinpath(os.path.expanduser("~"), ".sasmodels", "custom_models")
+    if not os.path.isdir(CUSTOM_MODEL_PATH):
+        os.makedirs(CUSTOM_MODEL_PATH)
 
 # TODO: refactor composite model support
@@ -271,5 +268,5 @@
     if platform is None:
         platform = "ocl"
-    if platform == "ocl" and not HAVE_OPENCL or not model_info.opencl:
+    if not kernelcl.use_opencl() or not model_info.opencl:
         platform = "dll"
 
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision e59f60a01e8c567fab81be6c56d98ce48144818c)
+++ sasmodels/kernelcl.py	(revision 3221de0ed9b3480163246ceb0503432126bd5134)
@@ -58,18 +58,18 @@
 import numpy as np  # type: ignore
 
+
+# Attempt to setup opencl. This may fail if the opencl package is not
+# installed or if it is installed but there are no devices available.
 try:
-    if os.environ.get("SAS_OPENCL", "").lower() == "none":
-        HAVE_OPENCL = False
-    else:
-        import pyopencl as cl  # type: ignore
-        from pyopencl import mem_flags as mf
-        from pyopencl.characterize import get_fast_inaccurate_build_options
-        # Ask OpenCL for the default context so that we know that one exists
-        cl.create_some_context(interactive=False)
-        HAVE_OPENCL = True
+    import pyopencl as cl  # type: ignore
+    from pyopencl import mem_flags as mf
+    from pyopencl.characterize import get_fast_inaccurate_build_options
+    # Ask OpenCL for the default context so that we know that one exists
+    cl.create_some_context(interactive=False)
+    HAVE_OPENCL = True
+    OPENCL_ERROR = ""
 except Exception as exc:
-    warnings.warn("OpenCL startup failed with ***"
-                  + str(exc) + "***; using C compiler instead")
     HAVE_OPENCL = False
+    OPENCL_ERROR = str(exc)
 
 from . import generate
@@ -130,6 +130,15 @@
 """
 
+def use_opencl():
+    return HAVE_OPENCL and os.environ.get("SAS_OPENCL", "").lower() != "none"
 
 ENV = None
+def reset_environment():
+    """
+    Call to create a new OpenCL context, such as after a change to SAS_OPENCL.
+    """
+    global ENV
+    ENV = GpuEnvironment() if use_opencl() else None
+
 def environment():
     # type: () -> "GpuEnvironment"
@@ -139,7 +148,9 @@
     This provides an OpenCL context and one queue per device.
     """
-    global ENV
-    if ENV is None:
-        ENV = GpuEnvironment()
+    if not HAVE_OPENCL:
+        warnings.warn("OpenCL startup failed with ***"
+                      + OPENCL_ERROR + "***; using C compiler instead")
+    elif ENV is None:
+        reset_environment()
     return ENV
 
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision f354e46a9018705400e4ab0a07ff2e36ce103031)
+++ sasmodels/model_test.py	(revision 3221de0ed9b3480163246ceb0503432126bd5134)
@@ -62,4 +62,5 @@
 from .exception import annotate_exception
 from .modelinfo import expand_pars
+from .kernelcl import use_opencl
 
 # pylint: disable=unused-import
@@ -134,5 +135,5 @@
 
             # test using dll if desired
-            if 'dll' in loaders or not core.HAVE_OPENCL:
+            if 'dll' in loaders or not use_opencl():
                 test_name = "%s-dll"%model_name
                 test_method_name = "test_%s_dll" % model_info.id
@@ -145,5 +146,5 @@
 
             # test using opencl if desired and available
-            if 'opencl' in loaders and core.HAVE_OPENCL:
+            if 'opencl' in loaders and use_opencl():
                 test_name = "%s-opencl"%model_name
                 test_method_name = "test_%s_opencl" % model_info.id
@@ -367,5 +368,5 @@
 
     # Build a test suite containing just the model
-    loaders = ['opencl'] if core.HAVE_OPENCL else ['dll']
+    loaders = ['opencl'] if use_opencl() else ['dll']
     models = [model]
     try:
@@ -424,5 +425,5 @@
         verbosity = 1
     if models and models[0] == 'opencl':
-        if not core.HAVE_OPENCL:
+        if not use_opencl():
             print("opencl is not available")
             return 1
@@ -434,8 +435,8 @@
         models = models[1:]
     elif models and models[0] == 'opencl_and_dll':
-        loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
+        loaders = ['opencl', 'dll'] if use_opencl() else ['dll']
         models = models[1:]
     else:
-        loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
+        loaders = ['opencl', 'dll'] if use_opencl() else ['dll']
     if not models:
         print("""\
@@ -466,5 +467,5 @@
     Run "nosetests sasmodels" on the command line to invoke it.
     """
-    loaders = ['opencl', 'dll'] if core.HAVE_OPENCL else ['dll']
+    loaders = ['opencl', 'dll'] if use_opencl() else ['dll']
     tests = make_suite(loaders, ['all'])
     def build_test(test):
@@ -476,5 +477,13 @@
         # variable test will be shared amongst all the tests, and we will be
         # repeatedly testing vesicle.
-        return lambda name: test.run_all(), test.test_name
+
+        # Note: in sasview sas.sasgui.perspectives.fitting.gpu_options
+        # requires that the test.description field be set.
+        wrap = lambda: test.run_all()
+        wrap.description = test.test_name
+        return wrap
+        # The following would work with nosetests and pytest:
+        #     return lambda name: test.run_all(), test.test_name
+
     for test in tests:
         yield build_test(test)
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision a69d8cd7ece8a1df933b123d2e032197783d80b4)
+++ sasmodels/sasview_model.py	(revision 3221de0ed9b3480163246ceb0503432126bd5134)
@@ -665,5 +665,4 @@
 
     def _calculate_Iq(self, qx, qy=None):
-        #core.HAVE_OPENCL = False
         if self._model is None:
             self._model = core.build_model(self._model_info)
