Index: sasmodels/bumps_model.py
===================================================================
--- sasmodels/bumps_model.py	(revision 7ae2b7f85895f1eddd6ef18f37ff501467a34961)
+++ sasmodels/bumps_model.py	(revision 04dc6971e45748ea6403588af4f52ee10891af08)
@@ -11,6 +11,7 @@
 
 """
-
-import warnings
+from __future__ import print_function
+
+__all__ = [ "Model", "Experiment" ]
 
 import numpy as np  # type: ignore
@@ -19,48 +20,23 @@
 from .direct_model import DataMixin
 
-__all__ = [
-    "Model", "Experiment",
-    ]
-
-# CRUFT: old style bumps wrapper which doesn't separate data and model
-# pylint: disable=invalid-name
-def BumpsModel(data, model, cutoff=1e-5, **kw):
-    r"""
-    Bind a model to data, along with a polydispersity cutoff.
-
-    *data* is a :class:`data.Data1D`, :class:`data.Data2D` or
-    :class:`data.Sesans` object.  Use :func:`data.empty_data1D` or
-    :func:`data.empty_data2D` to define $q, \Delta q$ calculation
-    points for displaying the SANS curve when there is no measured data.
-
-    *model* is a runnable module as returned from :func:`core.load_model`.
-
-    *cutoff* is the polydispersity weight cutoff.
-
-    Any additional *key=value* pairs are model dependent parameters.
-
-    Returns an :class:`Experiment` object.
-
-    Note that the usual Bumps semantics is not fully supported, since
-    assigning *M.name = parameter* on the returned experiment object
-    does not set that parameter in the model.  Range setting will still
-    work as expected though.
-
-    .. deprecated:: 0.1
-        Use :class:`Experiment` instead.
-    """
-    warnings.warn("Use of BumpsModel is deprecated.  Use bumps_model.Experiment instead.")
-
-    # Create the model and experiment
-    model = Model(model, **kw)
-    experiment = Experiment(data=data, model=model, cutoff=cutoff)
-
-    # Copy the model parameters up to the experiment object.
-    for k, v in model.parameters().items():
-        setattr(experiment, k, v)
-    return experiment
+try:
+    from typing import Dict, Union, Tuple, Any
+    from .data import Data1D, Data2D
+    from .kernel import KernelModel
+    from .modelinfo import ModelInfo
+    Data = Union[Data1D, Data2D]
+except ImportError:
+    pass
+
+try:
+    # Optional import. This allows the doc builder and nosetests to run even
+    # when bumps is not on the path.
+    from bumps.names import Parameter # type: ignore
+except ImportError:
+    pass
 
 
 def create_parameters(model_info, **kwargs):
+    # type: (ModelInfo, **Union[float, str, Parameter]) -> Tuple[Dict[str, Parameter], Dict[str, str]]
     """
     Generate Bumps parameters from the model info.
@@ -71,5 +47,6 @@
     Any additional *key=value* pairs are initial values for the parameters
     to the models.  Uninitialized parameters will use the model default
-    value.
+    value.  The value can be a float, a bumps parameter, or in the case
+    of the distribution type parameter, a string.
 
     Returns a dictionary of *{name: Parameter}* containing the bumps
@@ -77,10 +54,6 @@
     *{name: str}* containing the polydispersity distribution types.
     """
-    # lazy import; this allows the doc builder and nosetests to run even
-    # when bumps is not on the path.
-    from bumps.names import Parameter  # type: ignore
-
-    pars = {}     # floating point parameters
-    pd_types = {} # distribution names
+    pars = {}     # type: Dict[str, Parameter]
+    pd_types = {} # type: Dict[str, str]
     for p in model_info.parameters.call_parameters:
         value = kwargs.pop(p.name, p.default)
@@ -96,5 +69,5 @@
                 pars[name] = Parameter.default(value, name=name, limits=limits)
             name = p.name + '_pd_type'
-            pd_types[name] = kwargs.pop(name, 'gaussian')
+            pd_types[name] = str(kwargs.pop(name, 'gaussian'))
 
     if kwargs:  # args not corresponding to parameters
@@ -115,5 +88,6 @@
     """
     def __init__(self, model, **kwargs):
-        self._sasmodel = model
+        # type: (KernelModel, **Dict[str, Union[float, Parameter]]) -> None
+        self.sasmodel = model
         pars, pd_types = create_parameters(model.info, **kwargs)
         for k, v in pars.items():
@@ -125,4 +99,5 @@
 
     def parameters(self):
+        # type: () -> Dict[str, Parameter]
         """
         Return a dictionary of parameters objects for the parameters,
@@ -132,4 +107,5 @@
 
     def state(self):
+        # type: () -> Dict[str, Union[Parameter, str]]
         """
         Return a dictionary of current values for all the parameters,
@@ -156,20 +132,23 @@
     The resulting model can be used directly in a Bumps FitProblem call.
     """
+    _cache = None # type: Dict[str, np.ndarray]
     def __init__(self, data, model, cutoff=1e-5):
-
+        # type: (Data, Model, float) -> None
         # remember inputs so we can inspect from outside
         self.model = model
         self.cutoff = cutoff
-        self._interpret_data(data, model._sasmodel)
-        self.update()
+        self._interpret_data(data, model.sasmodel)
+        self._cache = {}
 
     def update(self):
+        # type: () -> None
         """
         Call when model parameters have changed and theory needs to be
         recalculated.
         """
-        self._cache = {}
+        self._cache.clear()
 
     def numpoints(self):
+        # type: () -> float
         """
         Return the number of data points
@@ -178,4 +157,5 @@
 
     def parameters(self):
+        # type: () -> Dict[str, Parameter]
         """
         Return a dictionary of parameters
@@ -184,4 +164,5 @@
 
     def theory(self):
+        # type: () -> np.ndarray
         """
         Return the theory corresponding to the model parameters.
@@ -196,4 +177,5 @@
 
     def residuals(self):
+        # type: () -> np.ndarray
         """
         Return theory minus data normalized by uncertainty.
@@ -203,4 +185,5 @@
 
     def nllf(self):
+        # type: () -> float
         """
         Return the negative log likelihood of seeing data given the model
@@ -210,5 +193,5 @@
         delta = self.residuals()
         #if np.any(np.isnan(R)): print("NaN in residuals")
-        return 0.5 * np.sum(delta ** 2)
+        return 0.5 * np.sum(delta**2)
 
     #def __call__(self):
@@ -216,4 +199,5 @@
 
     def plot(self, view='log'):
+        # type: (str) -> None
         """
         Plot the data and residuals.
@@ -223,4 +207,5 @@
 
     def simulate_data(self, noise=None):
+        # type: (float) -> None
         """
         Generate simulated data.
@@ -230,4 +215,5 @@
 
     def save(self, basename):
+        # type: (str) -> None
         """
         Save the model parameters and data into a file.
@@ -240,4 +226,5 @@
 
     def __getstate__(self):
+        # type: () -> Dict[str, Any]
         # Can't pickle gpu functions, so instead make them lazy
         state = self.__dict__.copy()
@@ -246,4 +233,6 @@
 
     def __setstate__(self, state):
+        # type: (Dict[str, Any]) -> None
         # pylint: disable=attribute-defined-outside-init
         self.__dict__ = state
+
Index: sasmodels/kernel.py
===================================================================
--- sasmodels/kernel.py	(revision 7ae2b7f85895f1eddd6ef18f37ff501467a34961)
+++ sasmodels/kernel.py	(revision 04dc6971e45748ea6403588af4f52ee10891af08)
@@ -19,4 +19,5 @@
 
 class KernelModel(object):
+    info = None  # type: ModelInfo
     def make_kernel(self, q_vectors):
         # type: (List[np.ndarray]) -> "Kernel"
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision 04045f41855bd4054e1eb902a6934d1c403980a2)
+++ sasmodels/modelinfo.py	(revision 04dc6971e45748ea6403588af4f52ee10891af08)
@@ -24,5 +24,5 @@
     from .details import CallDetails
     Limits = Tuple[float, float]
-    LimitsOrChoice = Union[Limits, Tuple[Sequence[str]]]
+    #LimitsOrChoice = Union[Limits, Tuple[Sequence[str]]]
     ParameterDef = Tuple[str, str, float, Limits, str, str]
     ParameterSetUser = Dict[str, Union[float, List[float]]]
@@ -71,5 +71,5 @@
 def parse_parameter(name, units='', default=np.NaN,
                     user_limits=None, ptype='', description=''):
-    # type: (str, str, float, LimitsOrChoice, str, str) -> Parameter
+    # type: (str, str, float, Limits, str, str) -> Parameter
     """
     Parse an individual parameter from the parameter definition block.
@@ -732,5 +732,5 @@
     profile = None          # type: Optional[Callable[[np.ndarray], None]]
     sesans = None           # type: Optional[Callable[[np.ndarray], np.ndarray]]
-    hidden = None           # type: Optional[Callable[int], Set[str]]
+    hidden = None           # type: Optional[Callable[[int], Set[str]]]
     mono_details = None     # type: CallDetails
 
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision a18c5b3f745fd0c1a4256b8e11c624fee2f4e9a3)
+++ sasmodels/sasview_model.py	(revision 04dc6971e45748ea6403588af4f52ee10891af08)
@@ -31,5 +31,5 @@
 
 try:
-    from typing import Dict, Mapping, Any, Sequence, Tuple, NamedTuple, List, Optional
+    from typing import Dict, Mapping, Any, Sequence, Tuple, NamedTuple, List, Optional, Union
     from .modelinfo import ModelInfo, Parameter
     from .kernel import KernelModel
@@ -42,5 +42,5 @@
 
 # TODO: separate x_axis_label from multiplicity info
-# The x-axis label belongs with the profile generating function
+# The profile x-axis label belongs with the profile generating function
 MultiplicityInfo = collections.namedtuple(
     'MultiplicityInfo',
@@ -220,14 +220,12 @@
     dispersion = None  # type: Dict[str, Any]
     #: units and limits for each parameter
-    details = None     # type: Mapping[str, Tuple(str, float, float)]
-    #: multiplicity used, or None if no multiplicity controls
+    details = None     # type: Mapping[str, Tuple[str, float, float]]
+    #: multiplicity value, or None if no multiplicity on the model
     multiplicity = None     # type: Optional[int]
+    #: memory for polydispersity array if using ArrayDispersion (used by sasview).
+    _persistency_dict = None # type: Dict[str, Tuple[np.ndarray, np.ndarray]]
 
     def __init__(self, multiplicity=None):
-        # type: () -> None
-
-        ## _persistency_dict is used by sas.perspectives.fitting.basepage
-        ## to store dispersity reference.
-        self._persistency_dict = {}
+        # type: (Optional[int]) -> None
 
         # TODO: _persistency_dict to persistency_dict throughout sasview
@@ -252,4 +250,5 @@
             hidden = set()
 
+        self._persistency_dict = {}
         self.params = collections.OrderedDict()
         self.dispersion = {}
@@ -375,9 +374,9 @@
 
     def getParamList(self):
-        # type: () - > Sequence[str]
+        # type: () -> Sequence[str]
         """
         Return a list of all available parameters for the model
         """
-        param_list = self.params.keys()
+        param_list = list(self.params.keys())
         # WARNING: Extending the list with the dispersion parameters
         param_list.extend(self.getDispParamList())
@@ -385,5 +384,5 @@
 
     def getDispParamList(self):
-        # type: () - > Sequence[str]
+        # type: () -> Sequence[str]
         """
         Return a list of polydispersity parameters for the model
@@ -398,5 +397,5 @@
 
     def clone(self):
-        # type: () - > "SasviewModel"
+        # type: () -> "SasviewModel"
         """ Return a identical copy of self """
         return deepcopy(self)
@@ -439,5 +438,5 @@
 
     def evalDistribution(self, qdist):
-        # type: (Union[np.ndarray, Tuple[np.ndarray, np.ndarray], List[np.ndarray]) -> np.ndarray
+        # type: (Union[np.ndarray, Tuple[np.ndarray, np.ndarray], List[np.ndarray]]) -> np.ndarray
         r"""
         Evaluate a distribution of q-values.
