source: sasmodels/sasmodels/modelinfo.py @ a5b8477

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since a5b8477 was a5b8477, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

update docs to work with the new ModelInfo/ParameterTable? classes

  • Property mode set to 100644
File size: 37.1 KB
Line 
1"""
2Model Info and Parameter Tables
3===============================
4
5Defines :class:`ModelInfo` and :class:`ParameterTable` and the routines for
6manipulating them.  In particular, :func:`make_model_info` converts a kernel
7module into the model info block as seen by the rest of the sasmodels library.
8"""
9from __future__ import print_function
10
11from copy import copy
12from os.path import abspath, basename, splitext
13
14import numpy as np  # type: ignore
15
16from .details import mono_details
17
18# Optional typing
19try:
20    from typing import Tuple, List, Union, Dict, Optional, Any, Callable, Sequence, Set
21except ImportError:
22    pass
23else:
24    from .details import CallDetails
25    Limits = Tuple[float, float]
26    #LimitsOrChoice = Union[Limits, Tuple[Sequence[str]]]
27    ParameterDef = Tuple[str, str, float, Limits, str, str]
28    ParameterSetUser = Dict[str, Union[float, List[float]]]
29    ParameterSet = Dict[str, float]
30    TestInput = Union[str, float, List[float], Tuple[float, float], List[Tuple[float, float]]]
31    TestValue = Union[float, List[float]]
32    TestCondition = Tuple[ParameterSetUser, TestInput, TestValue]
33
34MAX_PD = 4 #: Maximum number of simultaneously polydisperse parameters
35
36# assumptions about common parameters exist throughout the code, such as:
37# (1) kernel functions Iq, Iqxy, form_volume, ... don't see them
38# (2) kernel drivers assume scale is par[0] and background is par[1]
39# (3) mixture models drop the background on components and replace the scale
40#     with a scale that varies from [-inf, inf]
41# (4) product models drop the background and reassign scale
42# and maybe other places.
43# Note that scale and background cannot be coordinated parameters whose value
44# depends on the some polydisperse parameter with the current implementation
45COMMON_PARAMETERS = [
46    ("scale", "", 1, (0.0, np.inf), "", "Source intensity"),
47    ("background", "1/cm", 1e-3, (0.0, np.inf), "", "Source background"),
48]
49assert (len(COMMON_PARAMETERS) == 2
50        and COMMON_PARAMETERS[0][0]=="scale"
51        and COMMON_PARAMETERS[1][0]=="background"), "don't change common parameters"
52
53
54def make_parameter_table(pars):
55    # type: (List[ParameterDef]) -> ParameterTable
56    """
57    Construct a parameter table from a list of parameter definitions.
58
59    This is used by the module processor to convert the parameter block into
60    the parameter table seen in the :class:`ModelInfo` for the module.
61    """
62    processed = []
63    for p in pars:
64        if not isinstance(p, (list, tuple)) or len(p) != 6:
65            raise ValueError("Parameter should be [name, units, default, limits, type, desc], but got %r"
66                             %str(p))
67        processed.append(parse_parameter(*p))
68    partable = ParameterTable(processed)
69    return partable
70
71def parse_parameter(name, units='', default=np.NaN,
72                    user_limits=None, ptype='', description=''):
73    # type: (str, str, float, Sequence[Any], str, str) -> Parameter
74    """
75    Parse an individual parameter from the parameter definition block.
76
77    This does type and value checking on the definition, leading
78    to early failure in the model loading process and easier debugging.
79    """
80    # Parameter is a user facing class.  Do robust type checking.
81    if not isstr(name):
82        raise ValueError("expected string for parameter name %r"%name)
83    if not isstr(units):
84        raise ValueError("expected units to be a string for %s"%name)
85
86    # Process limits as [float, float] or [[str, str, ...]]
87    choices = []  # type: List[str]
88    if user_limits is None:
89        limits = (-np.inf, np.inf)
90    elif not isinstance(user_limits, (tuple, list)):
91        raise ValueError("invalid limits for %s"%name)
92    else:
93        # if limits is [[str,...]], then this is a choice list field,
94        # and limits are 1 to length of string list
95        if isinstance(user_limits[0], (tuple, list)):
96            choices = user_limits[0]
97            limits = (0., len(choices)-1.)
98            if not all(isstr(k) for k in choices):
99                raise ValueError("choices must be strings for %s"%name)
100        else:
101            try:
102                low, high = user_limits
103                limits = (float(low), float(high))
104            except Exception:
105                print("user_limits",user_limits)
106                raise ValueError("invalid limits for %s"%name)
107            else:
108                if low >= high:
109                    raise ValueError("require lower limit < upper limit")
110
111    # Process default value as float, making sure it is in range
112    if not isinstance(default, (int, float)):
113        raise ValueError("expected default %r to be a number for %s"
114                         % (default, name))
115    if default < limits[0] or default > limits[1]:
116        raise ValueError("default value %r not in range for %s"
117                         % (default, name))
118
119    # Check for valid parameter type
120    if ptype not in ("volume", "orientation", "sld", "magnetic", ""):
121        raise ValueError("unexpected type %r for %s" % (ptype, name))
122
123    # Check for valid parameter description
124    if not isstr(description):
125        raise ValueError("expected description to be a string")
126
127    # Parameter id for name[n] does not include [n]
128    if "[" in name:
129        if not name.endswith(']'):
130            raise ValueError("Expected name[len] for vector parameter %s"%name)
131        pid, ref = name[:-1].split('[', 1)
132        ref = ref.strip()
133    else:
134        pid, ref = name, None
135
136    # automatically identify sld types
137    if ptype== '' and (pid.startswith('sld') or pid.endswith('sld')):
138        ptype = 'sld'
139
140    # Check if using a vector definition, name[k], as the parameter name
141    if ref:
142        if ref == '':
143            raise ValueError("Need to specify vector length for %s"%name)
144        try:
145            length = int(ref)
146            control = None
147        except ValueError:
148            length = None
149            control = ref
150    else:
151        length = 1
152        control = None
153
154    # Build the parameter
155    parameter = Parameter(name=name, units=units, default=default,
156                          limits=limits, ptype=ptype, description=description)
157
158    # TODO: need better control over whether a parameter is polydisperse
159    parameter.polydisperse = ptype in ('orientation', 'volume')
160    parameter.relative_pd = ptype == 'volume'
161    parameter.choices = choices
162    parameter.length = length
163    parameter.length_control = control
164
165    return parameter
166
167
168def expand_pars(partable, pars):
169    # type: (ParameterTable, ParameterSetUser) ->  ParameterSet
170    """
171    Create demo parameter set from key-value pairs.
172
173    *pars* are the key-value pairs to use for the parameters.  Any
174    parameters not specified in *pars* are set from the *partable* defaults.
175
176    If *pars* references vector fields, such as thickness[n], then support
177    different ways of assigning the demo values, including assigning a
178    specific value (e.g., thickness3=50.0), assigning a new value to all
179    (e.g., thickness=50.0) or assigning values using list notation.
180    """
181    if pars is None:
182        result = partable.defaults
183    else:
184        lookup = dict((p.id, p) for p in partable.kernel_parameters)
185        result = partable.defaults.copy()
186        scalars = dict((name, value) for name, value in pars.items()
187                       if name not in lookup or lookup[name].length == 1)
188        vectors = dict((name,value) for name,value in pars.items()
189                       if name in lookup and lookup[name].length > 1)
190        if vectors:
191            for name, value in vectors.items():
192                if np.isscalar(value):
193                    # support for the form
194                    #    dict(thickness=0, thickness2=50)
195                    for k in range(1, lookup[name].length+1):
196                        key = name+str(k)
197                        if key not in scalars:
198                            scalars[key] = vectors
199                else:
200                    # supoprt for the form
201                    #    dict(thickness=[20,10,3])
202                    for (k,v) in enumerate(value):
203                        scalars[name+str(k)] = v
204        result.update(scalars)
205
206    return result
207
208def prefix_parameter(par, prefix):
209    # type: (Parameter, str) -> Parameter
210    """
211    Return a copy of the parameter with its name prefixed.
212    """
213    new_par = copy(par)
214    new_par.name = prefix + par.name
215    new_par.id = prefix + par.id
216
217def suffix_parameter(par, suffix):
218    # type: (Parameter, str) -> Parameter
219    """
220    Return a copy of the parameter with its name prefixed.
221    """
222    new_par = copy(par)
223    # If name has the form x[n], replace with x_suffix[n]
224    new_par.name = par.id + suffix + par.name[len(par.id):]
225    new_par.id = par.id + suffix
226
227class Parameter(object):
228    """
229    The available kernel parameters are defined as a list, with each parameter
230    defined as a sublist with the following elements:
231
232    *name* is the name that will be used in the call to the kernel
233    function and the name that will be displayed to the user.  Names
234    should be lower case, with words separated by underscore.  If
235    acronyms are used, the whole acronym should be upper case.
236
237    *units* should be one of *degrees* for angles, *Ang* for lengths,
238    *1e-6/Ang^2* for SLDs.
239
240    *default value* will be the initial value for  the model when it
241    is selected, or when an initial value is not otherwise specified.
242
243    *limits = [lb, ub]* are the hard limits on the parameter value, used to
244    limit the polydispersity density function.  In the fit, the parameter limits
245    given to the fit are the limits  on the central value of the parameter.
246    If there is polydispersity, it will evaluate parameter values outside
247    the fit limits, but not outside the hard limits specified in the model.
248    If there are no limits, use +/-inf imported from numpy.
249
250    *type* indicates how the parameter will be used.  "volume" parameters
251    will be used in all functions.  "orientation" parameters will be used
252    in *Iqxy* and *Imagnetic*.  "magnetic* parameters will be used in
253    *Imagnetic* only.  If *type* is the empty string, the parameter will
254    be used in all of *Iq*, *Iqxy* and *Imagnetic*.  "sld" parameters
255    can automatically be promoted to magnetic parameters, each of which
256    will have a magnitude and a direction, which may be different from
257    other sld parameters. The volume parameters are used for calls
258    to form_volume within the kernel (required for volume normalization)
259    and for calls to ER and VR for effective radius and volume ratio
260    respectively.
261
262    *description* is a short description of the parameter.  This will
263    be displayed in the parameter table and used as a tool tip for the
264    parameter value in the user interface.
265
266    Additional values can be set after the parameter is created:
267
268    * *length* is the length of the field if it is a vector field
269
270    * *length_control* is the parameter which sets the vector length
271
272    * *is_control* is True if the parameter is a control parameter for a vector
273
274    * *polydisperse* is true if the parameter accepts a polydispersity
275
276    * *relative_pd* is true if that polydispersity is a portion of the
277    value (so a 10% length dipsersity would use a polydispersity value of 0.1)
278    rather than absolute dispersisity (such as an angle plus or minus
279    15 degrees).
280
281    These values are set by :func:`make_parameter_table` and
282    :func:`parse_parameter` therein.
283    """
284    def __init__(self, name, units='', default=None, limits=(-np.inf, np.inf),
285                 ptype='', description=''):
286        # type: (str, str, float, Limits, str, str) -> None
287        self.id = name.split('[')[0].strip() # type: str
288        self.name = name                     # type: str
289        self.units = units                   # type: str
290        self.default = default               # type: float
291        self.limits = limits                 # type: Limits
292        self.type = ptype                    # type: str
293        self.description = description       # type: str
294
295        # Length and length_control will be filled in once the complete
296        # parameter table is available.
297        self.length = 1                      # type: int
298        self.length_control = None           # type: Optional[str]
299        self.is_control = False              # type: bool
300
301        # TODO: need better control over whether a parameter is polydisperse
302        self.polydisperse = False            # type: bool
303        self.relative_pd = False             # type: bool
304
305        # choices are also set externally.
306        self.choices = []                    # type: List[str]
307
308    def as_definition(self):
309        # type: () -> str
310        """
311        Declare space for the variable in a parameter structure.
312
313        For example, the parameter thickness with length 3 will
314        return "double thickness[3];", with no spaces before and
315        no new line character afterward.
316        """
317        if self.length == 1:
318            return "double %s;"%self.id
319        else:
320            return "double %s[%d];"%(self.id, self.length)
321
322    def as_function_argument(self):
323        # type: () -> str
324        """
325        Declare the variable as a function argument.
326
327        For example, the parameter thickness with length 3 will
328        return "double *thickness", with no spaces before and
329        no comma afterward.
330        """
331        if self.length == 1:
332            return "double %s"%self.id
333        else:
334            return "double *%s"%self.id
335
336    def as_call_reference(self, prefix=""):
337        # type: (str) -> str
338        # Note: if the parameter is a struct type, then we will need to use
339        # &prefix+id.  For scalars and vectors we can just use prefix+id.
340        return prefix + self.id
341
342    def __str__(self):
343        # type: () -> str
344        return "<%s>"%self.name
345
346    def __repr__(self):
347        # type: () -> str
348        return "P<%s>"%self.name
349
350
351class ParameterTable(object):
352    """
353    ParameterTable manages the list of available parameters.
354
355    There are a couple of complications which mean that the list of parameters
356    for the kernel differs from the list of parameters that the user sees.
357
358    (1) Common parameters.  Scale and background are implicit to every model,
359    but are not passed to the kernel.
360
361    (2) Vector parameters.  Vector parameters are passed to the kernel as a
362    pointer to an array, e.g., thick[], but they are seen by the user as n
363    separate parameters thick1, thick2, ...
364
365    Therefore, the parameter table is organized by how it is expected to be
366    used. The following information is needed to set up the kernel functions:
367
368    * *kernel_parameters* is the list of parameters in the kernel parameter
369    table, with vector parameter p declared as p[].
370
371    * *iq_parameters* is the list of parameters to the Iq(q, ...) function,
372    with vector parameter p sent as p[].
373
374    * *iqxy_parameters* is the list of parameters to the Iqxy(qx, qy, ...)
375    function, with vector parameter p sent as p[].
376
377    * *form_volume_parameters* is the list of parameters to the form_volume(...)
378    function, with vector parameter p sent as p[].
379
380    Problem details, which sets up the polydispersity loops, requires the
381    following:
382
383    * *theta_offset* is the offset of the theta parameter in the kernel parameter
384    table, with vector parameters counted as n individual parameters
385    p1, p2, ..., or offset is -1 if there is no theta parameter.
386
387    * *max_pd* is the maximum number of polydisperse parameters, with vector
388    parameters counted as n individual parameters p1, p2, ...  Note that
389    this number is limited to sasmodels.modelinfo.MAX_PD.
390
391    * *npars* is the total number of parameters to the kernel, with vector
392    parameters counted as n individual parameters p1, p2, ...
393
394    * *call_parameters* is the complete list of parameters to the kernel,
395    including scale and background, with vector parameters recorded as
396    individual parameters p1, p2, ...
397
398    * *active_1d* is the set of names that may be polydisperse for 1d data
399
400    * *active_2d* is the set of names that may be polydisperse for 2d data
401
402    User parameters are the set of parameters visible to the user, including
403    the scale and background parameters that the kernel does not see.  User
404    parameters don't use vector notation, and instead use p1, p2, ...
405
406    * *control_parameters* is the
407
408    """
409    # scale and background are implicit parameters
410    COMMON = [Parameter(*p) for p in COMMON_PARAMETERS]
411
412    def __init__(self, parameters):
413        # type: (List[Parameter]) -> None
414        self.kernel_parameters = parameters
415        self._set_vector_lengths()
416        self.call_parameters = self._get_call_parameters()
417        self.defaults = self._get_defaults()
418        #self._name_table= dict((p.id, p) for p in parameters)
419
420        # Set the kernel parameters.  Assumes background and scale are the
421        # first two parameters in the parameter list, but these are not sent
422        # to the underlying kernel functions.
423        self.iq_parameters = [p for p in self.kernel_parameters
424                              if p.type not in ('orientation', 'magnetic')]
425        self.iqxy_parameters = [p for p in self.kernel_parameters
426                                if p.type != 'magnetic']
427        self.form_volume_parameters = [p for p in self.kernel_parameters
428                                       if p.type == 'volume']
429
430        # Theta offset
431        offset = 0
432        for p in self.kernel_parameters:
433            if p.name == 'theta':
434                self.theta_offset = offset
435                break
436            offset += p.length
437        else:
438            self.theta_offset = -1
439
440        # number of polydisperse parameters
441        num_pd = sum(p.length for p in self.kernel_parameters if p.polydisperse)
442        # Don't use more polydisperse parameters than are available in the model
443        # Note: we can do polydispersity on arbitrary parameters, so it is not
444        # clear that this is a good idea; it does however make the poly_details
445        # code easier to write, so we will leave it in for now.
446        self.max_pd = min(num_pd, MAX_PD)
447
448        self.npars = sum(p.length for p in self.kernel_parameters)
449
450        # true if has 2D parameters
451        self.has_2d = any(p.type in ('orientation', 'magnetic')
452                          for p in self.kernel_parameters)
453
454        self.pd_1d = set(p.name for p in self.call_parameters
455                         if p.polydisperse and p.type not in ('orientation', 'magnetic'))
456        self.pd_2d = set(p.name for p in self.call_parameters
457                         if p.polydisperse and p.type != 'magnetic')
458
459
460    def _set_vector_lengths(self):
461        # type: () -> None
462        """
463        Walk the list of kernel parameters, setting the length field of the
464        vector parameters from the upper limit of the reference parameter.
465
466        This needs to be done once the entire parameter table is available
467        since the reference may still be undefined when the parameter is
468        initially created.
469
470        Note: This modifies the underlying parameter object.
471        """
472        # Sort out the length of the vector parameters such as thickness[n]
473        for p in self.kernel_parameters:
474            if p.length_control:
475                for ref in self.kernel_parameters:
476                    if ref.id == p.length_control:
477                        break
478                else:
479                    raise ValueError("no reference variable %r for %s"
480                                     % (p.length_control, p.name))
481                ref.is_control = True
482                low, high = ref.limits
483                if int(low) != low or int(high) != high or low < 0 or high > 20:
484                    raise ValueError("expected limits on %s to be within [0, 20]"
485                                     % ref.name)
486                p.length = int(high)
487
488    def _get_defaults(self):
489        # type: () -> ParameterSet
490        """
491        Get a list of parameter defaults from the parameters.
492
493        Expands vector parameters into parameter id+number.
494        """
495        # Construct default values, including vector defaults
496        defaults = {}
497        for p in self.call_parameters:
498            if p.length == 1:
499                defaults[p.id] = p.default
500            else:
501                for k in range(1, p.length+1):
502                    defaults["%s%d"%(p.id, k)] = p.default
503        return defaults
504
505    def _get_call_parameters(self):
506        # type: () -> List[Parameter]
507        full_list = self.COMMON[:]
508        for p in self.kernel_parameters:
509            if p.length == 1:
510                full_list.append(p)
511            else:
512                for k in range(1, p.length+1):
513                    pk = Parameter(p.id+str(k), p.units, p.default,
514                                   p.limits, p.type, p.description)
515                    pk.polydisperse = p.polydisperse
516                    pk.relative_pd = p.relative_pd
517                    full_list.append(pk)
518        return full_list
519
520    def user_parameters(self, pars={}, is2d=True):
521        # type: (Dict[str, float], bool) -> List[Parameter]
522        """
523        Return the list of parameters for the given data type.
524
525        Vector parameters are expanded as in place.  If multiple parameters
526        share the same vector length, then the parameters will be interleaved
527        in the result.  The control parameters come first.  For example,
528        if the parameter table is ordered as::
529
530            sld_core
531            sld_shell[num_shells]
532            sld_solvent
533            thickness[num_shells]
534            num_shells
535
536        and *pars[num_shells]=2* then the returned list will be::
537
538            num_shells
539            scale
540            background
541            sld_core
542            sld_shell1
543            thickness1
544            sld_shell2
545            thickness2
546            sld_solvent
547
548        Note that shell/thickness pairs are grouped together in the result
549        even though they were not grouped in the incoming table.  The control
550        parameter is always returned first since the GUI will want to set it
551        early, and rerender the table when it is changed.
552        """
553        # control parameters go first
554        control = [p for p in self.kernel_parameters if p.is_control]
555
556        # Gather entries such as name[n] into groups of the same n
557        dependent = {} # type: Dict[str, List[Parameter]]
558        dependent.update((p.id, []) for p in control)
559        for p in self.kernel_parameters:
560            if p.length_control is not None:
561                dependent[p.length_control].append(p)
562
563        # Gather entries such as name[4] into groups of the same length
564        fixed = {}  # type: Dict[int, List[Parameter]]
565        for p in self.kernel_parameters:
566            if p.length > 1 and p.length_control is None:
567                fixed.setdefault(p.length, []).append(p)
568
569        # Using the call_parameters table, we already have expanded forms
570        # for each of the vector parameters; put them in a lookup table
571        expanded_pars = dict((p.name, p) for p in self.call_parameters)
572
573        # Gather the user parameters in order
574        result = control + self.COMMON
575        for p in self.kernel_parameters:
576            if not is2d and p.type in ('orientation', 'magnetic'):
577                pass
578            elif p.is_control:
579                pass # already added
580            elif p.length_control is not None:
581                table = dependent.get(p.length_control, [])
582                if table:
583                    # look up length from incoming parameters
584                    table_length = int(pars.get(p.length_control, p.length))
585                    del dependent[p.length_control] # first entry seen
586                    for k in range(1, table_length+1):
587                        for entry in table:
588                            result.append(expanded_pars[entry.id+str(k)])
589                else:
590                    pass # already processed all entries
591            elif p.length > 1:
592                table = fixed.get(p.length, [])
593                if table:
594                    table_length = p.length
595                    del fixed[p.length]
596                    for k in range(1, table_length+1):
597                        for entry in table:
598                            result.append(expanded_pars[entry.id+str(k)])
599                else:
600                    pass # already processed all entries
601            else:
602                result.append(p)
603
604        return result
605
606def isstr(x):
607    # type: (Any) -> bool
608    """
609    Return True if the object is a string.
610    """
611    # TODO: 2-3 compatible tests for str, including unicode strings
612    return isinstance(x, str)
613
614def make_model_info(kernel_module):
615    # type: (module) -> ModelInfo
616    """
617    Extract the model definition from the loaded kernel module.
618
619    Fill in default values for parts of the module that are not provided.
620
621    Note: vectorized Iq and Iqxy functions will be created for python
622    models when the model is first called, not when the model is loaded.
623    """
624    info = ModelInfo()
625    #print("make parameter table", kernel_module.parameters)
626    parameters = make_parameter_table(getattr(kernel_module, 'parameters', []))
627    demo = expand_pars(parameters, getattr(kernel_module, 'demo', None))
628    filename = abspath(kernel_module.__file__)
629    kernel_id = splitext(basename(filename))[0]
630    name = getattr(kernel_module, 'name', None)
631    if name is None:
632        name = " ".join(w.capitalize() for w in kernel_id.split('_'))
633
634    info.id = kernel_id  # string used to load the kernel
635    info.filename = abspath(kernel_module.__file__)
636    info.name = name
637    info.title = getattr(kernel_module, 'title', name+" model")
638    info.description = getattr(kernel_module, 'description', 'no description')
639    info.parameters = parameters
640    info.demo = demo
641    info.composition = None
642    info.docs = kernel_module.__doc__
643    info.category = getattr(kernel_module, 'category', None)
644    info.single = getattr(kernel_module, 'single', True)
645    info.structure_factor = getattr(kernel_module, 'structure_factor', False)
646    info.profile_axes = getattr(kernel_module, 'profile_axes', ['x', 'y'])
647    info.source = getattr(kernel_module, 'source', [])
648    # TODO: check the structure of the tests
649    info.tests = getattr(kernel_module, 'tests', [])
650    info.ER = getattr(kernel_module, 'ER', None) # type: ignore
651    info.VR = getattr(kernel_module, 'VR', None) # type: ignore
652    info.form_volume = getattr(kernel_module, 'form_volume', None) # type: ignore
653    info.Iq = getattr(kernel_module, 'Iq', None) # type: ignore
654    info.Iqxy = getattr(kernel_module, 'Iqxy', None) # type: ignore
655    info.profile = getattr(kernel_module, 'profile', None) # type: ignore
656    info.sesans = getattr(kernel_module, 'sesans', None) # type: ignore
657    info.control = getattr(kernel_module, 'control', None)
658    info.hidden = getattr(kernel_module, 'hidden', None) # type: ignore
659
660    # Precalculate the monodisperse parameter details
661    info.mono_details = mono_details(info)
662    return info
663
664class ModelInfo(object):
665    """
666    Interpret the model definition file, categorizing the parameters.
667
668    The module can be loaded with a normal python import statement if you
669    know which module you need, or with __import__('sasmodels.model.'+name)
670    if the name is in a string.
671
672    The structure should be mostly static, other than the delayed definition
673    of *Iq* and *Iqxy* if they need to be defined.
674    """
675    #: Full path to the file defining the kernel, if any.
676    filename = None         # type: Optiona[str]
677    #: Id of the kernel used to load it from the filesystem.
678    id = None               # type: str
679    #: Display name of the model, which defaults to the model id but with
680    #: capitalization of the parts so for example core_shell defaults to
681    #: "Core Shell".
682    name = None             # type: str
683    #: Short description of the model.
684    title = None            # type: str
685    #: Long description of the model.
686    description = None      # type: str
687    #: Model parameter table. Parameters are defined using a list of parameter
688    #: definitions, each of which is contains parameter name, units,
689    #: default value, limits, type and description.  See :class:`Parameter`
690    #: for details on the individual parameters.  The parameters are gathered
691    #: into a :class:`ParameterTable`, which provides various views into the
692    #: parameter list.
693    parameters = None       # type: ParameterTable
694    #: Demo parameters as a *parameter:value* map used as the default values
695    #: for :mod:`compare`.  Any parameters not set in *demo* will use the
696    #: defaults from the parameter table.  That means no polydispersity, and
697    #: in the case of multiplicity models, a minimal model with no interesting
698    #: scattering.
699    demo = None             # type: Dict[str, float]
700    #: Composition is None if this is an independent model, or it is a
701    #: tuple with comoposition type ('product' or 'misture') and a list of
702    #: :class:`ModelInfo` blocks for the composed objects.  This allows us
703    #: to rebuild a complete mixture or product model from the info block.
704    #: *composition* is not given in the model definition file, but instead
705    #: arises when the model is constructed using names such as
706    #: *sphere*hardsphere* or *cylinder+sphere*.
707    composition = None      # type: Optional[Tuple[str, List[ModelInfo]]]
708    #: Name of the control parameter for a variant model such as :ref:`rpa`.
709    #: The *control* parameter should appear in the parameter table, with
710    #: limits defined as *[CASES]*, for case names such as
711    #: *CASES = ["diblock copolymer", "triblock copolymer", ...]*.
712    #: This should give *limits=[[case1, case2, ...]]*, but the
713    #: model loader translates this to *limits=[0, len(CASES)-1]*, and adds
714    #: *choices=CASES* to the :class:`Parameter` definition. Note that
715    #: models can use a list of cases as a parameter without it being a
716    #: control parameter.  Either way, the parameter is sent to the model
717    #: evaluator as *float(choice_num)*, where choices are numbered from 0.
718    #: See also :attr:`hidden`.
719    control = None          # type: str
720    #: Different variants require different parameters.  In order to show
721    #: just the parameters needed for the variant selected by :attr:`control`,
722    #: you should provide a function *hidden(control) -> set(['a', 'b', ...])*
723    #: indicating which parameters need to be hidden.  For multiplicity
724    #: models, you need to use the complete name of the parameter, including
725    #: its number.  So for example, if variant "a" uses only *sld1* and *sld2*,
726    #: then *sld3*, *sld4* and *sld5* of multiplicity parameter *sld[5]*
727    #: should be in the hidden set.
728    hidden = None           # type: Optional[Callable[[int], Set[str]]]
729    #: Doc string from the top of the model file.  This should be formatted
730    #: using ReStructuredText format, with latex markup in ".. math"
731    #: environments, or in dollar signs.  This will be automatically
732    #: extracted to a .rst file by :func:`generate.make_docs`, then
733    #: converted to HTML or PDF by Sphinx.
734    docs = None             # type: str
735    #: Location of the model description in the documentation.  This takes the
736    #: form of "section" or "section:subsection".  So for example,
737    #: :ref:`porod` uses *category="shape-independent"* so it is in the
738    #: :ref:`Shape-independent` section whereas
739    #: :ref:`capped_cylinder` uses: *category="shape:cylinder"*, which puts
740    #: it in the :ref:`shape-cylinder` section.
741    category = None         # type: Optional[str]
742    #: True if the model can be computed accurately with single precision.
743    #: This is True by default, but models such as :ref:`bcc_paracrystal` set
744    #: it to False because they require double precision calculations.
745    single = None           # type: bool
746    #: True if the model is a structure factor used to model the interaction
747    #: between form factor models.  This will default to False if it is not
748    #: provided in the file.
749    structure_factor = None # type: bool
750    #: List of C source files used to define the model.  The source files
751    #: should define the *Iq* function, and possibly *Iqxy*, though a default
752    #: *Iqxy = Iq(sqrt(qx**2+qy**2)* will be created if no *Iqxy* is provided.
753    #: Files containing the most basic functions must appear first in the list,
754    #: followed by the files that use those functions.  Form factors are
755    #: indicated by providing a :attr:`ER` function.
756    source = None           # type: List[str]
757    #: The set of tests that must pass.  The format of the tests is described
758    #: in :mod:`model_test`.
759    tests = None            # type: List[TestCondition]
760    #: Returns the effective radius of the model given its volume parameters.
761    #: The presence of *ER* indicates that the model is a form factor model
762    #: that may be used together with a structure factor to form an implicit
763    #: multiplication model.
764    #:
765    #: The parameters to the *ER* function must be marked with type *volume*.
766    #: in the parameter table.  They will appear in the same order as they
767    #: do in the table.  The values passed to *ER* will be vectors, with one
768    #: value for each polydispersity condition.  For example, if the model
769    #: is polydisperse over both length and radius, then both length and
770    #: radius will have the same number of values in the vector, with one
771    #: value for each *length X radius*.  If only *radius* is polydisperse,
772    #: then the value for *length* will be repeated once for each value of
773    #: *radius*.  The *ER* function should return one effective radius for
774    #: each parameter set.  Multiplicity parameters will be received as
775    #: arrays, with one row per polydispersity condition.
776    ER = None               # type: Optional[Callable[[np.ndarray], np.ndarray]]
777    #: Returns the occupied volume and the total volume for each parameter set.
778    #: See :attr:`ER` for details on the parameters.
779    VR = None               # type: Optional[Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]]
780    #: Returns the form volume for python-based models.  Form volume is needed
781    #: for volume normalization in the polydispersity integral.  If no
782    #: parameters are *volume* parameters, then form volume is not needed.
783    #: For C-based models, (with :attr:`sources` defined, or with :attr:`Iq`
784    #: defined using a string containing C code), form_volume must also be
785    #: C code, either defined as a string, or in the sources.
786    form_volume = None      # type: Union[None, str, Callable[[np.ndarray], float]]
787    #: Returns *I(q, a, b, ...)* for parameters *a*, *b*, etc. defined
788    #: by the parameter table.  *Iq* can be defined as a python function, or
789    #: as a C function.  If it is defined in C, then set *Iq* to the body of
790    #: the C function, including the return statement.  This function takes
791    #: values for *q* and each of the parameters as separate *double* values
792    #: (which may be converted to float or long double by sasmodels).  All
793    #: source code files listed in :attr:`sources` will be loaded before the
794    #: *Iq* function is defined.  If *Iq* is not present, then sources should
795    #: define *static double Iq(double q, double a, double b, ...)* which
796    #: will return *I(q, a, b, ...)*.  Multiplicity parameters are sent as
797    #: pointers to doubles.  Constants in floating point expressions should
798    #: include the decimal point. See :mod:`generate` for more details.
799    Iq = None               # type: Union[None, str, Callable[[np.ndarray], np.ndarray]]
800    #: Returns *I(qx, qy, a, b, ...)*.  The interface follows :attr:`Iq`.
801    Iqxy = None             # type: Union[None, str, Callable[[np.ndarray], np.ndarray]]
802    #: Returns a model profile curve *x, y*.  If *profile* is defined, this
803    #: curve will appear in response to the *Show* button in SasView.  Use
804    #: :attr:`profile_axes` to set the axis labels.  Note that *y* values
805    #: will be scaled by 1e6 before plotting.
806    profile = None          # type: Optional[Callable[[np.ndarray], None]]
807    #: Axis labels for the :attr:`profile` plot.  The default is *['x', 'y']*.
808    #: Only the *x* component is used for now.
809    profile_axes = None     # type: Tuple[str, str]
810    #: Returns *sesans(z, a, b, ...)* for models which can directly compute
811    #: the SESANS correlation function.  Note: not currently implemented.
812    sesans = None           # type: Optional[Callable[[np.ndarray], np.ndarray]]
813    #: :class:details.CallDetails data for mono-disperse function evaluation.
814    #: This field is created automatically by the model loader, and should
815    #: not be defined as part of the model definition file.
816    mono_details = None     # type: CallDetails
817
818    def __init__(self):
819        # type: () -> None
820        pass
821
822    def get_hidden_parameters(self, control):
823        """
824        Returns the set of hidden parameters for the model.  *control* is the
825        value of the control parameter.  Note that multiplicity models have
826        an implicit control parameter, which is the parameter that controls
827        the multiplicity.
828        """
829        if self.hidden is not None:
830            hidden = self.hidden(control)
831        else:
832            controls = [p for p in self.parameters.kernel_parameters]
833            if len(controls) != 1:
834                raise ValueError("more than one control parameter")
835            hidden = set(p.id+str(k)
836                         for p in self.parameters.kernel_parameters
837                         for k in range(control+1, p.length+1)
838                         if p.length > 1)
839        return hidden
Note: See TracBrowser for help on using the repository browser.