source: sasmodels/sasmodels/sasview_model.py @ 041bc75

core_shell_microgelscostrafo411magnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 041bc75 was e4bf271, checked in by mathieu, 8 years ago

Make sure plug-ins work with multiplication model. Re #751

  • Property mode set to 100644
File size: 25.6 KB
Line 
1"""
2Sasview model constructor.
3
4Given a module defining an OpenCL kernel such as sasmodels.models.cylinder,
5create a sasview model class to run that kernel as follows::
6
7    from sasmodels.sasview_model import load_custom_model
8    CylinderModel = load_custom_model('sasmodels/models/cylinder.py')
9"""
10from __future__ import print_function
11
12import math
13from copy import deepcopy
14import collections
15import traceback
16import logging
17from os.path import basename, splitext
18
19import numpy as np  # type: ignore
20
21from . import core
22from . import custom
23from . import generate
24from . import weights
25from . import modelinfo
26from .details import make_kernel_args, dispersion_mesh
27
28try:
29    from typing import Dict, Mapping, Any, Sequence, Tuple, NamedTuple, List, Optional, Union, Callable
30    from .modelinfo import ModelInfo, Parameter
31    from .kernel import KernelModel
32    MultiplicityInfoType = NamedTuple(
33        'MuliplicityInfo',
34        [("number", int), ("control", str), ("choices", List[str]),
35         ("x_axis_label", str)])
36    SasviewModelType = Callable[[int], "SasviewModel"]
37except ImportError:
38    pass
39
40SUPPORT_OLD_STYLE_PLUGINS = True
41
42def _register_old_models():
43    # type: () -> None
44    """
45    Place the new models into sasview under the old names.
46
47    Monkey patch sas.sascalc.fit as sas.models so that sas.models.pluginmodel
48    is available to the plugin modules.
49    """
50    import sys
51    import sas
52    import sas.sascalc.fit
53    sys.modules['sas.models'] = sas.sascalc.fit
54    sas.models = sas.sascalc.fit
55
56    import sas.models
57    from sasmodels.conversion_table import CONVERSION_TABLE
58    for new_name, conversion in CONVERSION_TABLE.items():
59        old_name = conversion[0]
60        module_attrs = {old_name: find_model(new_name)}
61        ConstructedModule = type(old_name, (), module_attrs)
62        old_path = 'sas.models.' + old_name
63        setattr(sas.models, old_path, ConstructedModule)
64        sys.modules[old_path] = ConstructedModule
65
66
67# TODO: separate x_axis_label from multiplicity info
68MultiplicityInfo = collections.namedtuple(
69    'MultiplicityInfo',
70    ["number", "control", "choices", "x_axis_label"],
71)
72
73MODELS = {}
74def find_model(modelname):
75    # type: (str) -> SasviewModelType
76    """
77    Find a model by name.  If the model name ends in py, try loading it from
78    custom models, otherwise look for it in the list of builtin models.
79    """
80    # TODO: used by sum/product model to load an existing model
81    # TODO: doesn't handle custom models properly
82    if modelname.endswith('.py'):
83        return load_custom_model(modelname)
84    elif modelname in MODELS:
85        return MODELS[modelname]
86    else:
87        raise ValueError("unknown model %r"%modelname)
88
89
90# TODO: figure out how to say that the return type is a subclass
91def load_standard_models():
92    # type: () -> List[SasviewModelType]
93    """
94    Load and return the list of predefined models.
95
96    If there is an error loading a model, then a traceback is logged and the
97    model is not returned.
98    """
99    models = []
100    for name in core.list_models():
101        try:
102            MODELS[name] = _make_standard_model(name)
103            models.append(MODELS[name])
104        except Exception:
105            logging.error(traceback.format_exc())
106    if SUPPORT_OLD_STYLE_PLUGINS:
107        _register_old_models()
108
109    return models
110
111
112def load_custom_model(path):
113    # type: (str) -> SasviewModelType
114    """
115    Load a custom model given the model path.
116    """
117    kernel_module = custom.load_custom_kernel_module(path)
118    try:
119        model = kernel_module.Model
120        # Old style models do not set the name in the class attributes, so
121        # set it here; this name will be overridden when the object is created
122        # with an instance variable that has the same value.
123        if model.name == "":
124            model.name = splitext(basename(path))[0]
125        if not hasattr(model, 'filename'):
126            model.filename = kernel_module.__file__
127        if not hasattr(model, 'id'):
128            model.id = splitext(basename(model.filename))[0]
129    except AttributeError:
130        model_info = modelinfo.make_model_info(kernel_module)
131        model = _make_model_from_info(model_info)
132
133    # If a model name already exists and we are loading a different model,
134    # use the model file name as the model name.
135    if model.name in MODELS and not model.filename == MODELS[model.name].filename:
136        _previous_name = model.name
137        model.name = model.id
138       
139        # If the new model name is still in the model list (for instance,
140        # if we put a cylinder.py in our plug-in directory), then append
141        # an identifier.
142        if model.name in MODELS and not model.filename == MODELS[model.name].filename:
143            model.name = model.id + '_user'
144        logging.info("Model %s already exists: using %s [%s]", _previous_name, model.name, model.filename)
145
146    MODELS[model.name] = model
147    return model
148
149
150def _make_standard_model(name):
151    # type: (str) -> SasviewModelType
152    """
153    Load the sasview model defined by *name*.
154
155    *name* can be a standard model name or a path to a custom model.
156
157    Returns a class that can be used directly as a sasview model.
158    """
159    kernel_module = generate.load_kernel_module(name)
160    model_info = modelinfo.make_model_info(kernel_module)
161    return _make_model_from_info(model_info)
162
163
164def _make_model_from_info(model_info):
165    # type: (ModelInfo) -> SasviewModelType
166    """
167    Convert *model_info* into a SasView model wrapper.
168    """
169    def __init__(self, multiplicity=None):
170        SasviewModel.__init__(self, multiplicity=multiplicity)
171    attrs = _generate_model_attributes(model_info)
172    attrs['__init__'] = __init__
173    attrs['filename'] = model_info.filename
174    ConstructedModel = type(model_info.name, (SasviewModel,), attrs) # type: SasviewModelType
175    return ConstructedModel
176
177def _generate_model_attributes(model_info):
178    # type: (ModelInfo) -> Dict[str, Any]
179    """
180    Generate the class attributes for the model.
181
182    This should include all the information necessary to query the model
183    details so that you do not need to instantiate a model to query it.
184
185    All the attributes should be immutable to avoid accidents.
186    """
187
188    # TODO: allow model to override axis labels input/output name/unit
189
190    # Process multiplicity
191    non_fittable = []  # type: List[str]
192    xlabel = model_info.profile_axes[0] if model_info.profile is not None else ""
193    variants = MultiplicityInfo(0, "", [], xlabel)
194    for p in model_info.parameters.kernel_parameters:
195        if p.name == model_info.control:
196            non_fittable.append(p.name)
197            variants = MultiplicityInfo(
198                len(p.choices) if p.choices else int(p.limits[1]),
199                p.name, p.choices, xlabel
200            )
201            break
202
203    # Only a single drop-down list parameter available
204    fun_list = []
205    for p in model_info.parameters.kernel_parameters:
206        if p.choices:
207            fun_list = p.choices
208            if p.length > 1:
209                non_fittable.extend(p.id+str(k) for k in range(1, p.length+1))
210            break
211
212    # Organize parameter sets
213    orientation_params = []
214    magnetic_params = []
215    fixed = []
216    for p in model_info.parameters.user_parameters():
217        if p.type == 'orientation':
218            orientation_params.append(p.name)
219            orientation_params.append(p.name+".width")
220            fixed.append(p.name+".width")
221        elif p.type == 'magnetic':
222            orientation_params.append(p.name)
223            magnetic_params.append(p.name)
224            fixed.append(p.name+".width")
225
226
227    # Build class dictionary
228    attrs = {}  # type: Dict[str, Any]
229    attrs['_model_info'] = model_info
230    attrs['name'] = model_info.name
231    attrs['id'] = model_info.id
232    attrs['description'] = model_info.description
233    attrs['category'] = model_info.category
234    attrs['is_structure_factor'] = model_info.structure_factor
235    attrs['is_form_factor'] = model_info.ER is not None
236    attrs['is_multiplicity_model'] = variants[0] > 1
237    attrs['multiplicity_info'] = variants
238    attrs['orientation_params'] = tuple(orientation_params)
239    attrs['magnetic_params'] = tuple(magnetic_params)
240    attrs['fixed'] = tuple(fixed)
241    attrs['non_fittable'] = tuple(non_fittable)
242    attrs['fun_list'] = tuple(fun_list)
243
244    return attrs
245
246class SasviewModel(object):
247    """
248    Sasview wrapper for opencl/ctypes model.
249    """
250    # Model parameters for the specific model are set in the class constructor
251    # via the _generate_model_attributes function, which subclasses
252    # SasviewModel.  They are included here for typing and documentation
253    # purposes.
254    _model = None       # type: KernelModel
255    _model_info = None  # type: ModelInfo
256    #: load/save name for the model
257    id = None           # type: str
258    #: display name for the model
259    name = None         # type: str
260    #: short model description
261    description = None  # type: str
262    #: default model category
263    category = None     # type: str
264
265    #: names of the orientation parameters in the order they appear
266    orientation_params = None # type: Sequence[str]
267    #: names of the magnetic parameters in the order they appear
268    magnetic_params = None    # type: Sequence[str]
269    #: names of the fittable parameters
270    fixed = None              # type: Sequence[str]
271    # TODO: the attribute fixed is ill-named
272
273    # Axis labels
274    input_name = "Q"
275    input_unit = "A^{-1}"
276    output_name = "Intensity"
277    output_unit = "cm^{-1}"
278
279    #: default cutoff for polydispersity
280    cutoff = 1e-5
281
282    # Note: Use non-mutable values for class attributes to avoid errors
283    #: parameters that are not fitted
284    non_fittable = ()        # type: Sequence[str]
285
286    #: True if model should appear as a structure factor
287    is_structure_factor = False
288    #: True if model should appear as a form factor
289    is_form_factor = False
290    #: True if model has multiplicity
291    is_multiplicity_model = False
292    #: Mulitplicity information
293    multiplicity_info = None # type: MultiplicityInfoType
294
295    # Per-instance variables
296    #: parameter {name: value} mapping
297    params = None      # type: Dict[str, float]
298    #: values for dispersion width, npts, nsigmas and type
299    dispersion = None  # type: Dict[str, Any]
300    #: units and limits for each parameter
301    details = None     # type: Dict[str, Sequence[Any]]
302    #                  # actual type is Dict[str, List[str, float, float]]
303    #: multiplicity value, or None if no multiplicity on the model
304    multiplicity = None     # type: Optional[int]
305    #: memory for polydispersity array if using ArrayDispersion (used by sasview).
306    _persistency_dict = None # type: Dict[str, Tuple[np.ndarray, np.ndarray]]
307
308    def __init__(self, multiplicity=None):
309        # type: (Optional[int]) -> None
310
311        # TODO: _persistency_dict to persistency_dict throughout sasview
312        # TODO: refactor multiplicity to encompass variants
313        # TODO: dispersion should be a class
314        # TODO: refactor multiplicity info
315        # TODO: separate profile view from multiplicity
316        # The button label, x and y axis labels and scale need to be under
317        # the control of the model, not the fit page.  Maximum flexibility,
318        # the fit page would supply the canvas and the profile could plot
319        # how it wants, but this assumes matplotlib.  Next level is that
320        # we provide some sort of data description including title, labels
321        # and lines to plot.
322
323        # Get the list of hidden parameters given the mulitplicity
324        # Don't include multiplicity in the list of parameters
325        self.multiplicity = multiplicity
326        if multiplicity is not None:
327            hidden = self._model_info.get_hidden_parameters(multiplicity)
328            hidden |= set([self.multiplicity_info.control])
329        else:
330            hidden = set()
331        if self._model_info.structure_factor:
332            hidden.add('scale')
333            hidden.add('background')
334            self._model_info.parameters.defaults['background'] = 0.
335
336        self._persistency_dict = {}
337        self.params = collections.OrderedDict()
338        self.dispersion = collections.OrderedDict()
339        self.details = {}
340        for p in self._model_info.parameters.user_parameters():
341            if p.name in hidden:
342                continue
343            self.params[p.name] = p.default
344            self.details[p.id] = [p.units, p.limits[0], p.limits[1]]
345            if p.polydisperse:
346                self.details[p.id+".width"] = [
347                    "", 0.0, 1.0 if p.relative_pd else np.inf
348                ]
349                self.dispersion[p.name] = {
350                    'width': 0,
351                    'npts': 35,
352                    'nsigmas': 3,
353                    'type': 'gaussian',
354                }
355
356    def __get_state__(self):
357        # type: () -> Dict[str, Any]
358        state = self.__dict__.copy()
359        state.pop('_model')
360        # May need to reload model info on set state since it has pointers
361        # to python implementations of Iq, etc.
362        #state.pop('_model_info')
363        return state
364
365    def __set_state__(self, state):
366        # type: (Dict[str, Any]) -> None
367        self.__dict__ = state
368        self._model = None
369
370    def __str__(self):
371        # type: () -> str
372        """
373        :return: string representation
374        """
375        return self.name
376
377    def is_fittable(self, par_name):
378        # type: (str) -> bool
379        """
380        Check if a given parameter is fittable or not
381
382        :param par_name: the parameter name to check
383        """
384        return par_name in self.fixed
385        #For the future
386        #return self.params[str(par_name)].is_fittable()
387
388
389    def getProfile(self):
390        # type: () -> (np.ndarray, np.ndarray)
391        """
392        Get SLD profile
393
394        : return: (z, beta) where z is a list of depth of the transition points
395                beta is a list of the corresponding SLD values
396        """
397        args = {} # type: Dict[str, Any]
398        for p in self._model_info.parameters.kernel_parameters:
399            if p.id == self.multiplicity_info.control:
400                value = float(self.multiplicity)
401            elif p.length == 1:
402                value = self.params.get(p.id, np.NaN)
403            else:
404                value = np.array([self.params.get(p.id+str(k), np.NaN)
405                                  for k in range(1, p.length+1)])
406            args[p.id] = value
407
408        x, y = self._model_info.profile(**args)
409        return x, 1e-6*y
410
411    def setParam(self, name, value):
412        # type: (str, float) -> None
413        """
414        Set the value of a model parameter
415
416        :param name: name of the parameter
417        :param value: value of the parameter
418
419        """
420        # Look for dispersion parameters
421        toks = name.split('.')
422        if len(toks) == 2:
423            for item in self.dispersion.keys():
424                if item == toks[0]:
425                    for par in self.dispersion[item]:
426                        if par == toks[1]:
427                            self.dispersion[item][par] = value
428                            return
429        else:
430            # Look for standard parameter
431            for item in self.params.keys():
432                if item == name:
433                    self.params[item] = value
434                    return
435
436        raise ValueError("Model does not contain parameter %s" % name)
437
438    def getParam(self, name):
439        # type: (str) -> float
440        """
441        Set the value of a model parameter
442
443        :param name: name of the parameter
444
445        """
446        # Look for dispersion parameters
447        toks = name.split('.')
448        if len(toks) == 2:
449            for item in self.dispersion.keys():
450                if item == toks[0]:
451                    for par in self.dispersion[item]:
452                        if par == toks[1]:
453                            return self.dispersion[item][par]
454        else:
455            # Look for standard parameter
456            for item in self.params.keys():
457                if item == name:
458                    return self.params[item]
459
460        raise ValueError("Model does not contain parameter %s" % name)
461
462    def getParamList(self):
463        # type: () -> Sequence[str]
464        """
465        Return a list of all available parameters for the model
466        """
467        param_list = list(self.params.keys())
468        # WARNING: Extending the list with the dispersion parameters
469        param_list.extend(self.getDispParamList())
470        return param_list
471
472    def getDispParamList(self):
473        # type: () -> Sequence[str]
474        """
475        Return a list of polydispersity parameters for the model
476        """
477        # TODO: fix test so that parameter order doesn't matter
478        ret = ['%s.%s' % (p_name, ext)
479               for p_name in self.dispersion.keys()
480               for ext in ('npts', 'nsigmas', 'width')]
481        #print(ret)
482        return ret
483
484    def clone(self):
485        # type: () -> "SasviewModel"
486        """ Return a identical copy of self """
487        return deepcopy(self)
488
489    def run(self, x=0.0):
490        # type: (Union[float, (float, float), List[float]]) -> float
491        """
492        Evaluate the model
493
494        :param x: input q, or [q,phi]
495
496        :return: scattering function P(q)
497
498        **DEPRECATED**: use calculate_Iq instead
499        """
500        if isinstance(x, (list, tuple)):
501            # pylint: disable=unpacking-non-sequence
502            q, phi = x
503            return self.calculate_Iq([q*math.cos(phi)], [q*math.sin(phi)])[0]
504        else:
505            return self.calculate_Iq([x])[0]
506
507
508    def runXY(self, x=0.0):
509        # type: (Union[float, (float, float), List[float]]) -> float
510        """
511        Evaluate the model in cartesian coordinates
512
513        :param x: input q, or [qx, qy]
514
515        :return: scattering function P(q)
516
517        **DEPRECATED**: use calculate_Iq instead
518        """
519        if isinstance(x, (list, tuple)):
520            return self.calculate_Iq([x[0]], [x[1]])[0]
521        else:
522            return self.calculate_Iq([x])[0]
523
524    def evalDistribution(self, qdist):
525        # type: (Union[np.ndarray, Tuple[np.ndarray, np.ndarray], List[np.ndarray]]) -> np.ndarray
526        r"""
527        Evaluate a distribution of q-values.
528
529        :param qdist: array of q or a list of arrays [qx,qy]
530
531        * For 1D, a numpy array is expected as input
532
533        ::
534
535            evalDistribution(q)
536
537          where *q* is a numpy array.
538
539        * For 2D, a list of *[qx,qy]* is expected with 1D arrays as input
540
541        ::
542
543              qx = [ qx[0], qx[1], qx[2], ....]
544              qy = [ qy[0], qy[1], qy[2], ....]
545
546        If the model is 1D only, then
547
548        .. math::
549
550            q = \sqrt{q_x^2+q_y^2}
551
552        """
553        if isinstance(qdist, (list, tuple)):
554            # Check whether we have a list of ndarrays [qx,qy]
555            qx, qy = qdist
556            if not self._model_info.parameters.has_2d:
557                return self.calculate_Iq(np.sqrt(qx ** 2 + qy ** 2))
558            else:
559                return self.calculate_Iq(qx, qy)
560
561        elif isinstance(qdist, np.ndarray):
562            # We have a simple 1D distribution of q-values
563            return self.calculate_Iq(qdist)
564
565        else:
566            raise TypeError("evalDistribution expects q or [qx, qy], not %r"
567                            % type(qdist))
568
569    def calculate_Iq(self, qx, qy=None):
570        # type: (Sequence[float], Optional[Sequence[float]]) -> np.ndarray
571        """
572        Calculate Iq for one set of q with the current parameters.
573
574        If the model is 1D, use *q*.  If 2D, use *qx*, *qy*.
575
576        This should NOT be used for fitting since it copies the *q* vectors
577        to the card for each evaluation.
578        """
579        #core.HAVE_OPENCL = False
580        if self._model is None:
581            self._model = core.build_model(self._model_info)
582        if qy is not None:
583            q_vectors = [np.asarray(qx), np.asarray(qy)]
584        else:
585            q_vectors = [np.asarray(qx)]
586        calculator = self._model.make_kernel(q_vectors)
587        parameters = self._model_info.parameters
588        pairs = [self._get_weights(p) for p in parameters.call_parameters]
589        #weights.plot_weights(self._model_info, pairs)
590        call_details, values, is_magnetic = make_kernel_args(calculator, pairs)
591        #call_details.show()
592        #print("pairs", pairs)
593        #print("params", self.params)
594        #print("values", values)
595        #print("is_mag", is_magnetic)
596        result = calculator(call_details, values, cutoff=self.cutoff,
597                            magnetic=is_magnetic)
598        calculator.release()
599        self._model.release()
600        return result
601
602    def calculate_ER(self):
603        # type: () -> float
604        """
605        Calculate the effective radius for P(q)*S(q)
606
607        :return: the value of the effective radius
608        """
609        if self._model_info.ER is None:
610            return 1.0
611        else:
612            value, weight = self._dispersion_mesh()
613            fv = self._model_info.ER(*value)
614            #print(values[0].shape, weights.shape, fv.shape)
615            return np.sum(weight * fv) / np.sum(weight)
616
617    def calculate_VR(self):
618        # type: () -> float
619        """
620        Calculate the volf ratio for P(q)*S(q)
621
622        :return: the value of the volf ratio
623        """
624        if self._model_info.VR is None:
625            return 1.0
626        else:
627            value, weight = self._dispersion_mesh()
628            whole, part = self._model_info.VR(*value)
629            return np.sum(weight * part) / np.sum(weight * whole)
630
631    def set_dispersion(self, parameter, dispersion):
632        # type: (str, weights.Dispersion) -> Dict[str, Any]
633        """
634        Set the dispersion object for a model parameter
635
636        :param parameter: name of the parameter [string]
637        :param dispersion: dispersion object of type Dispersion
638        """
639        if parameter in self.params:
640            # TODO: Store the disperser object directly in the model.
641            # The current method of relying on the sasview GUI to
642            # remember them is kind of funky.
643            # Note: can't seem to get disperser parameters from sasview
644            # (1) Could create a sasview model that has not yet been
645            # converted, assign the disperser to one of its polydisperse
646            # parameters, then retrieve the disperser parameters from the
647            # sasview model.
648            # (2) Could write a disperser parameter retriever in sasview.
649            # (3) Could modify sasview to use sasmodels.weights dispersers.
650            # For now, rely on the fact that the sasview only ever uses
651            # new dispersers in the set_dispersion call and create a new
652            # one instead of trying to assign parameters.
653            self.dispersion[parameter] = dispersion.get_pars()
654        else:
655            raise ValueError("%r is not a dispersity or orientation parameter")
656
657    def _dispersion_mesh(self):
658        # type: () -> List[Tuple[np.ndarray, np.ndarray]]
659        """
660        Create a mesh grid of dispersion parameters and weights.
661
662        Returns [p1,p2,...],w where pj is a vector of values for parameter j
663        and w is a vector containing the products for weights for each
664        parameter set in the vector.
665        """
666        pars = [self._get_weights(p)
667                for p in self._model_info.parameters.call_parameters
668                if p.type == 'volume']
669        return dispersion_mesh(self._model_info, pars)
670
671    def _get_weights(self, par):
672        # type: (Parameter) -> Tuple[np.ndarray, np.ndarray]
673        """
674        Return dispersion weights for parameter
675        """
676        if par.name not in self.params:
677            if par.name == self.multiplicity_info.control:
678                return [self.multiplicity], [1.0]
679            else:
680                # For hidden parameters use the default value.
681                value = self._model_info.parameters.defaults.get(par.name, np.NaN)
682                return [value], [1.0]
683        elif par.polydisperse:
684            dis = self.dispersion[par.name]
685            if dis['type'] == 'array':
686                value, weight = dis['values'], dis['weights']
687            else:
688                value, weight = weights.get_weights(
689                    dis['type'], dis['npts'], dis['width'], dis['nsigmas'],
690                    self.params[par.name], par.limits, par.relative_pd)
691            return value, weight / np.sum(weight)
692        else:
693            return [self.params[par.name]], [1.0]
694
695def test_model():
696    # type: () -> float
697    """
698    Test that a sasview model (cylinder) can be run.
699    """
700    Cylinder = _make_standard_model('cylinder')
701    cylinder = Cylinder()
702    return cylinder.evalDistribution([0.1, 0.1])
703
704def test_structure_factor():
705    # type: () -> float
706    """
707    Test that a sasview model (cylinder) can be run.
708    """
709    Model = _make_standard_model('hardsphere')
710    model = Model()
711    value = model.evalDistribution([0.1, 0.1])
712    if np.isnan(value):
713        raise ValueError("hardsphere returns null")
714
715def test_rpa():
716    # type: () -> float
717    """
718    Test that a sasview model (cylinder) can be run.
719    """
720    RPA = _make_standard_model('rpa')
721    rpa = RPA(3)
722    return rpa.evalDistribution([0.1, 0.1])
723
724
725def test_model_list():
726    # type: () -> None
727    """
728    Make sure that all models build as sasview models.
729    """
730    from .exception import annotate_exception
731    for name in core.list_models():
732        try:
733            _make_standard_model(name)
734        except:
735            annotate_exception("when loading "+name)
736            raise
737
738def test_old_name():
739    # type: () -> None
740    """
741    Load and run cylinder model from sas.models.CylinderModel
742    """
743    if not SUPPORT_OLD_STYLE_PLUGINS:
744        return
745    try:
746        # if sasview is not on the path then don't try to test it
747        import sas
748    except ImportError:
749        return
750    load_standard_models()
751    from sas.models.CylinderModel import CylinderModel
752    CylinderModel().evalDistribution([0.1, 0.1])
753
754if __name__ == "__main__":
755    print("cylinder(0.1,0.1)=%g"%test_model())
Note: See TracBrowser for help on using the repository browser.