source: sasmodels/sasmodels/modelinfo.py @ 04dc697

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

more type hinting

  • Property mode set to 100644
File size: 31.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, Limits, 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                raise ValueError("invalid limits for %s"%name)
106            else:
107                if low >= high:
108                    raise ValueError("require lower limit < upper limit")
109
110    # Process default value as float, making sure it is in range
111    if not isinstance(default, (int, float)):
112        raise ValueError("expected default %r to be a number for %s"
113                         % (default, name))
114    if default < limits[0] or default > limits[1]:
115        raise ValueError("default value %r not in range for %s"
116                         % (default, name))
117
118    # Check for valid parameter type
119    if ptype not in ("volume", "orientation", "sld", "magnetic", ""):
120        raise ValueError("unexpected type %r for %s" % (ptype, name))
121
122    # Check for valid parameter description
123    if not isstr(description):
124        raise ValueError("expected description to be a string")
125
126    # Parameter id for name[n] does not include [n]
127    if "[" in name:
128        if not name.endswith(']'):
129            raise ValueError("Expected name[len] for vector parameter %s"%name)
130        pid, ref = name[:-1].split('[', 1)
131        ref = ref.strip()
132    else:
133        pid, ref = name, None
134
135    # automatically identify sld types
136    if ptype== '' and (pid.startswith('sld') or pid.endswith('sld')):
137        ptype = 'sld'
138
139    # Check if using a vector definition, name[k], as the parameter name
140    if ref:
141        if ref == '':
142            raise ValueError("Need to specify vector length for %s"%name)
143        try:
144            length = int(ref)
145            control = None
146        except ValueError:
147            length = None
148            control = ref
149    else:
150        length = 1
151        control = None
152
153    # Build the parameter
154    parameter = Parameter(name=name, units=units, default=default,
155                          limits=limits, ptype=ptype, description=description)
156
157    # TODO: need better control over whether a parameter is polydisperse
158    parameter.polydisperse = ptype in ('orientation', 'volume')
159    parameter.relative_pd = ptype == 'volume'
160    parameter.choices = choices
161    parameter.length = length
162    parameter.length_control = control
163
164    return parameter
165
166
167def expand_pars(partable, pars):
168    # type: (ParameterTable, ParameterSetUser) ->  ParameterSet
169    """
170    Create demo parameter set from key-value pairs.
171
172    *pars* are the key-value pairs to use for the parameters.  Any
173    parameters not specified in *pars* are set from the *partable* defaults.
174
175    If *pars* references vector fields, such as thickness[n], then support
176    different ways of assigning the demo values, including assigning a
177    specific value (e.g., thickness3=50.0), assigning a new value to all
178    (e.g., thickness=50.0) or assigning values using list notation.
179    """
180    if pars is None:
181        result = partable.defaults
182    else:
183        lookup = dict((p.id, p) for p in partable.kernel_parameters)
184        result = partable.defaults.copy()
185        scalars = dict((name, value) for name, value in pars.items()
186                       if name not in lookup or lookup[name].length == 1)
187        vectors = dict((name,value) for name,value in pars.items()
188                       if name in lookup and lookup[name].length > 1)
189        if vectors:
190            for name, value in vectors.items():
191                if np.isscalar(value):
192                    # support for the form
193                    #    dict(thickness=0, thickness2=50)
194                    for k in range(1, lookup[name].length+1):
195                        key = name+str(k)
196                        if key not in scalars:
197                            scalars[key] = vectors
198                else:
199                    # supoprt for the form
200                    #    dict(thickness=[20,10,3])
201                    for (k,v) in enumerate(value):
202                        scalars[name+str(k)] = v
203        result.update(scalars)
204
205    return result
206
207def prefix_parameter(par, prefix):
208    # type: (Parameter, str) -> Parameter
209    """
210    Return a copy of the parameter with its name prefixed.
211    """
212    new_par = copy(par)
213    new_par.name = prefix + par.name
214    new_par.id = prefix + par.id
215
216def suffix_parameter(par, suffix):
217    # type: (Parameter, str) -> Parameter
218    """
219    Return a copy of the parameter with its name prefixed.
220    """
221    new_par = copy(par)
222    # If name has the form x[n], replace with x_suffix[n]
223    new_par.name = par.id + suffix + par.name[len(par.id):]
224    new_par.id = par.id + suffix
225
226class Parameter(object):
227    """
228    The available kernel parameters are defined as a list, with each parameter
229    defined as a sublist with the following elements:
230
231    *name* is the name that will be used in the call to the kernel
232    function and the name that will be displayed to the user.  Names
233    should be lower case, with words separated by underscore.  If
234    acronyms are used, the whole acronym should be upper case.
235
236    *units* should be one of *degrees* for angles, *Ang* for lengths,
237    *1e-6/Ang^2* for SLDs.
238
239    *default value* will be the initial value for  the model when it
240    is selected, or when an initial value is not otherwise specified.
241
242    *limits = [lb, ub]* are the hard limits on the parameter value, used to
243    limit the polydispersity density function.  In the fit, the parameter limits
244    given to the fit are the limits  on the central value of the parameter.
245    If there is polydispersity, it will evaluate parameter values outside
246    the fit limits, but not outside the hard limits specified in the model.
247    If there are no limits, use +/-inf imported from numpy.
248
249    *type* indicates how the parameter will be used.  "volume" parameters
250    will be used in all functions.  "orientation" parameters will be used
251    in *Iqxy* and *Imagnetic*.  "magnetic* parameters will be used in
252    *Imagnetic* only.  If *type* is the empty string, the parameter will
253    be used in all of *Iq*, *Iqxy* and *Imagnetic*.  "sld" parameters
254    can automatically be promoted to magnetic parameters, each of which
255    will have a magnitude and a direction, which may be different from
256    other sld parameters. The volume parameters are used for calls
257    to form_volume within the kernel (required for volume normalization)
258    and for calls to ER and VR for effective radius and volume ratio
259    respectively.
260
261    *description* is a short description of the parameter.  This will
262    be displayed in the parameter table and used as a tool tip for the
263    parameter value in the user interface.
264
265    Additional values can be set after the parameter is created:
266
267    * *length* is the length of the field if it is a vector field
268
269    * *length_control* is the parameter which sets the vector length
270
271    * *is_control* is True if the parameter is a control parameter for a vector
272
273    * *polydisperse* is true if the parameter accepts a polydispersity
274
275    * *relative_pd* is true if that polydispersity is a portion of the
276    value (so a 10% length dipsersity would use a polydispersity value of 0.1)
277    rather than absolute dispersisity (such as an angle plus or minus
278    15 degrees).
279
280    In the usual process these values are set by :func:`make_parameter_table`
281    and :func:`parse_parameter` therein.
282    """
283    def __init__(self, name, units='', default=None, limits=(-np.inf, np.inf),
284                 ptype='', description=''):
285        # type: (str, str, float, Limits, str, str) -> None
286        self.id = name.split('[')[0].strip() # type: str
287        self.name = name                     # type: str
288        self.units = units                   # type: str
289        self.default = default               # type: float
290        self.limits = limits                 # type: Limits
291        self.type = ptype                    # type: str
292        self.description = description       # type: str
293
294        # Length and length_control will be filled in once the complete
295        # parameter table is available.
296        self.length = 1                      # type: int
297        self.length_control = None           # type: Optional[str]
298        self.is_control = False              # type: bool
299
300        # TODO: need better control over whether a parameter is polydisperse
301        self.polydisperse = False            # type: bool
302        self.relative_pd = False             # type: bool
303
304        # choices are also set externally.
305        self.choices = []                    # type: List[str]
306
307    def as_definition(self):
308        # type: () -> str
309        """
310        Declare space for the variable in a parameter structure.
311
312        For example, the parameter thickness with length 3 will
313        return "double thickness[3];", with no spaces before and
314        no new line character afterward.
315        """
316        if self.length == 1:
317            return "double %s;"%self.id
318        else:
319            return "double %s[%d];"%(self.id, self.length)
320
321    def as_function_argument(self):
322        # type: () -> str
323        """
324        Declare the variable as a function argument.
325
326        For example, the parameter thickness with length 3 will
327        return "double *thickness", with no spaces before and
328        no comma afterward.
329        """
330        if self.length == 1:
331            return "double %s"%self.id
332        else:
333            return "double *%s"%self.id
334
335    def as_call_reference(self, prefix=""):
336        # type: (str) -> str
337        # Note: if the parameter is a struct type, then we will need to use
338        # &prefix+id.  For scalars and vectors we can just use prefix+id.
339        return prefix + self.id
340
341    def __str__(self):
342        # type: () -> str
343        return "<%s>"%self.name
344
345    def __repr__(self):
346        # type: () -> str
347        return "P<%s>"%self.name
348
349
350class ParameterTable(object):
351    """
352    ParameterTable manages the list of available parameters.
353
354    There are a couple of complications which mean that the list of parameters
355    for the kernel differs from the list of parameters that the user sees.
356
357    (1) Common parameters.  Scale and background are implicit to every model,
358    but are not passed to the kernel.
359
360    (2) Vector parameters.  Vector parameters are passed to the kernel as a
361    pointer to an array, e.g., thick[], but they are seen by the user as n
362    separate parameters thick1, thick2, ...
363
364    Therefore, the parameter table is organized by how it is expected to be
365    used. The following information is needed to set up the kernel functions:
366
367    * *kernel_parameters* is the list of parameters in the kernel parameter
368    table, with vector parameter p declared as p[].
369
370    * *iq_parameters* is the list of parameters to the Iq(q, ...) function,
371    with vector parameter p sent as p[].
372
373    * *iqxy_parameters* is the list of parameters to the Iqxy(qx, qy, ...)
374    function, with vector parameter p sent as p[].
375
376    * *form_volume_parameters* is the list of parameters to the form_volume(...)
377    function, with vector parameter p sent as p[].
378
379    Problem details, which sets up the polydispersity loops, requires the
380    following:
381
382    * *theta_offset* is the offset of the theta parameter in the kernel parameter
383    table, with vector parameters counted as n individual parameters
384    p1, p2, ..., or offset is -1 if there is no theta parameter.
385
386    * *max_pd* is the maximum number of polydisperse parameters, with vector
387    parameters counted as n individual parameters p1, p2, ...  Note that
388    this number is limited to sasmodels.modelinfo.MAX_PD.
389
390    * *npars* is the total number of parameters to the kernel, with vector
391    parameters counted as n individual parameters p1, p2, ...
392
393    * *call_parameters* is the complete list of parameters to the kernel,
394    including scale and background, with vector parameters recorded as
395    individual parameters p1, p2, ...
396
397    * *active_1d* is the set of names that may be polydisperse for 1d data
398
399    * *active_2d* is the set of names that may be polydisperse for 2d data
400
401    User parameters are the set of parameters visible to the user, including
402    the scale and background parameters that the kernel does not see.  User
403    parameters don't use vector notation, and instead use p1, p2, ...
404
405    * *control_parameters* is the
406
407    """
408    # scale and background are implicit parameters
409    COMMON = [Parameter(*p) for p in COMMON_PARAMETERS]
410
411    def __init__(self, parameters):
412        # type: (List[Parameter]) -> None
413        self.kernel_parameters = parameters
414        self._set_vector_lengths()
415        self.call_parameters = self._get_call_parameters()
416        self.defaults = self._get_defaults()
417        #self._name_table= dict((p.id, p) for p in parameters)
418
419        # Set the kernel parameters.  Assumes background and scale are the
420        # first two parameters in the parameter list, but these are not sent
421        # to the underlying kernel functions.
422        self.iq_parameters = [p for p in self.kernel_parameters
423                              if p.type not in ('orientation', 'magnetic')]
424        self.iqxy_parameters = [p for p in self.kernel_parameters
425                                if p.type != 'magnetic']
426        self.form_volume_parameters = [p for p in self.kernel_parameters
427                                       if p.type == 'volume']
428
429        # Theta offset
430        offset = 0
431        for p in self.kernel_parameters:
432            if p.name == 'theta':
433                self.theta_offset = offset
434                break
435            offset += p.length
436        else:
437            self.theta_offset = -1
438
439        # number of polydisperse parameters
440        num_pd = sum(p.length for p in self.kernel_parameters if p.polydisperse)
441        # Don't use more polydisperse parameters than are available in the model
442        # Note: we can do polydispersity on arbitrary parameters, so it is not
443        # clear that this is a good idea; it does however make the poly_details
444        # code easier to write, so we will leave it in for now.
445        self.max_pd = min(num_pd, MAX_PD)
446
447        self.npars = sum(p.length for p in self.kernel_parameters)
448
449        # true if has 2D parameters
450        self.has_2d = any(p.type in ('orientation', 'magnetic')
451                          for p in self.kernel_parameters)
452
453        self.pd_1d = set(p.name for p in self.call_parameters
454                         if p.polydisperse and p.type not in ('orientation', 'magnetic'))
455        self.pd_2d = set(p.name for p in self.call_parameters
456                         if p.polydisperse and p.type != 'magnetic')
457
458
459    def _set_vector_lengths(self):
460        # type: () -> None
461        """
462        Walk the list of kernel parameters, setting the length field of the
463        vector parameters from the upper limit of the reference parameter.
464
465        This needs to be done once the entire parameter table is available
466        since the reference may still be undefined when the parameter is
467        initially created.
468
469        Note: This modifies the underlying parameter object.
470        """
471        # Sort out the length of the vector parameters such as thickness[n]
472        for p in self.kernel_parameters:
473            if p.length_control:
474                for ref in self.kernel_parameters:
475                    if ref.id == p.length_control:
476                        break
477                else:
478                    raise ValueError("no reference variable %r for %s"
479                                     % (p.length_control, p.name))
480                ref.is_control = True
481                low, high = ref.limits
482                if int(low) != low or int(high) != high or low < 0 or high > 20:
483                    raise ValueError("expected limits on %s to be within [0, 20]"
484                                     % ref.name)
485                p.length = int(high)
486
487    def _get_defaults(self):
488        # type: () -> ParameterSet
489        """
490        Get a list of parameter defaults from the parameters.
491
492        Expands vector parameters into parameter id+number.
493        """
494        # Construct default values, including vector defaults
495        defaults = {}
496        for p in self.call_parameters:
497            if p.length == 1:
498                defaults[p.id] = p.default
499            else:
500                for k in range(1, p.length+1):
501                    defaults["%s%d"%(p.id, k)] = p.default
502        return defaults
503
504    def _get_call_parameters(self):
505        # type: () -> List[Parameter]
506        full_list = self.COMMON[:]
507        for p in self.kernel_parameters:
508            if p.length == 1:
509                full_list.append(p)
510            else:
511                for k in range(1, p.length+1):
512                    pk = Parameter(p.id+str(k), p.units, p.default,
513                                   p.limits, p.type, p.description)
514                    pk.polydisperse = p.polydisperse
515                    pk.relative_pd = p.relative_pd
516                    full_list.append(pk)
517        return full_list
518
519    def user_parameters(self, pars={}, is2d=True):
520        # type: (Dict[str, float], bool) -> List[Parameter]
521        """
522        Return the list of parameters for the given data type.
523
524        Vector parameters are expanded as in place.  If multiple parameters
525        share the same vector length, then the parameters will be interleaved
526        in the result.  The control parameters come first.  For example,
527        if the parameter table is ordered as::
528
529            sld_core
530            sld_shell[num_shells]
531            sld_solvent
532            thickness[num_shells]
533            num_shells
534
535        and *pars[num_shells]=2* then the returned list will be::
536
537            num_shells
538            scale
539            background
540            sld_core
541            sld_shell1
542            thickness1
543            sld_shell2
544            thickness2
545            sld_solvent
546
547        Note that shell/thickness pairs are grouped together in the result
548        even though they were not grouped in the incoming table.  The control
549        parameter is always returned first since the GUI will want to set it
550        early, and rerender the table when it is changed.
551        """
552        # control parameters go first
553        control = [p for p in self.kernel_parameters if p.is_control]
554
555        # Gather entries such as name[n] into groups of the same n
556        dependent = {} # type: Dict[str, List[Parameter]]
557        dependent.update((p.id, []) for p in control)
558        for p in self.kernel_parameters:
559            if p.length_control is not None:
560                dependent[p.length_control].append(p)
561
562        # Gather entries such as name[4] into groups of the same length
563        fixed = {}  # type: Dict[int, List[Parameter]]
564        for p in self.kernel_parameters:
565            if p.length > 1 and p.length_control is None:
566                fixed.setdefault(p.length, []).append(p)
567
568        # Using the call_parameters table, we already have expanded forms
569        # for each of the vector parameters; put them in a lookup table
570        expanded_pars = dict((p.name, p) for p in self.call_parameters)
571
572        # Gather the user parameters in order
573        result = control + self.COMMON
574        for p in self.kernel_parameters:
575            if not is2d and p.type in ('orientation', 'magnetic'):
576                pass
577            elif p.is_control:
578                pass # already added
579            elif p.length_control is not None:
580                table = dependent.get(p.length_control, [])
581                if table:
582                    # look up length from incoming parameters
583                    table_length = int(pars.get(p.length_control, p.length))
584                    del dependent[p.length_control] # first entry seen
585                    for k in range(1, table_length+1):
586                        for entry in table:
587                            result.append(expanded_pars[entry.id+str(k)])
588                else:
589                    pass # already processed all entries
590            elif p.length > 1:
591                table = fixed.get(p.length, [])
592                if table:
593                    table_length = p.length
594                    del fixed[p.length]
595                    for k in range(1, table_length+1):
596                        for entry in table:
597                            result.append(expanded_pars[entry.id+str(k)])
598                else:
599                    pass # already processed all entries
600            else:
601                result.append(p)
602
603        return result
604
605def isstr(x):
606    # type: (Any) -> bool
607    """
608    Return True if the object is a string.
609    """
610    # TODO: 2-3 compatible tests for str, including unicode strings
611    return isinstance(x, str)
612
613def make_model_info(kernel_module):
614    # type: (module) -> ModelInfo
615    """
616    Extract the model definition from the loaded kernel module.
617
618    Fill in default values for parts of the module that are not provided.
619
620    Note: vectorized Iq and Iqxy functions will be created for python
621    models when the model is first called, not when the model is loaded.
622    """
623    info = ModelInfo()
624    #print("make parameter table", kernel_module.parameters)
625    parameters = make_parameter_table(getattr(kernel_module, 'parameters', []))
626    demo = expand_pars(parameters, getattr(kernel_module, 'demo', None))
627    filename = abspath(kernel_module.__file__)
628    kernel_id = splitext(basename(filename))[0]
629    name = getattr(kernel_module, 'name', None)
630    if name is None:
631        name = " ".join(w.capitalize() for w in kernel_id.split('_'))
632
633    info.id = kernel_id  # string used to load the kernel
634    info.filename = abspath(kernel_module.__file__)
635    info.name = name
636    info.title = getattr(kernel_module, 'title', name+" model")
637    info.description = getattr(kernel_module, 'description', 'no description')
638    info.parameters = parameters
639    info.demo = demo
640    info.composition = None
641    info.docs = kernel_module.__doc__
642    info.category = getattr(kernel_module, 'category', None)
643    info.single = getattr(kernel_module, 'single', True)
644    info.structure_factor = getattr(kernel_module, 'structure_factor', False)
645    info.profile_axes = getattr(kernel_module, 'profile_axes', ['x', 'y'])
646    info.variant_info = getattr(kernel_module, 'variant_info', None)
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 *model_info* structure contains the following fields:
673
674    * *id* is the id of the kernel
675    * *name* is the display name of the kernel
676    * *filename* is the full path to the module defining the file (if any)
677    * *title* is a short description of the kernel
678    * *description* is a long description of the kernel (this doesn't seem
679      very useful since the Help button on the model page brings you directly
680      to the documentation page)
681    * *docs* is the docstring from the module.  Use :func:`make_doc` to
682    * *category* specifies the model location in the docs
683    * *parameters* is the model parameter table
684    * *single* is True if the model allows single precision
685    * *structure_factor* is True if the model is useable in a product
686    * *variant_info* contains the information required to select between
687      model variants (e.g., the list of cases) or is None if there are no
688      model variants
689    * *par_type* categorizes the model parameters. See
690      :func:`categorize_parameters` for details.
691    * *demo* contains the *{parameter: value}* map used in compare (and maybe
692      for the demo plot, if plots aren't set up to use the default values).
693      If *demo* is not given in the file, then the default values will be used.
694    * *tests* is a set of tests that must pass
695    * *source* is the list of library files to include in the C model build
696    * *Iq*, *Iqxy*, *form_volume*, *ER*, *VR* and *sesans* are python functions
697      implementing the kernel for the module, or None if they are not
698      defined in python
699    * *composition* is None if the model is independent, otherwise it is a
700      tuple with composition type ('product' or 'mixture') and a list of
701      *model_info* blocks for the composition objects.  This allows us to
702      build complete product and mixture models from just the info.
703    * *control* is the name of the control parameter if there is one.
704    * *hidden* returns the list of hidden parameters given the value of the
705      control parameter
706
707    The structure should be mostly static, other than the delayed definition
708    of *Iq* and *Iqxy* if they need to be defined.
709    """
710    id = None               # type: str
711    filename = None         # type: str
712    name = None             # type: str
713    title = None            # type: str
714    description = None      # type: str
715    parameters = None       # type: ParameterTable
716    demo = None             # type: Dict[str, float]
717    composition = None      # type: Optional[Tuple[str, List[ModelInfo]]]
718    control = None          # type: str
719    docs = None             # type: str
720    category = None         # type: Optional[str]
721    single = None           # type: bool
722    structure_factor = None # type: bool
723    profile_axes = None     # type: Tuple[str, str]
724    variant_info = None     # type: Optional[List[str]]
725    source = None           # type: List[str]
726    tests = None            # type: List[TestCondition]
727    ER = None               # type: Optional[Callable[[np.ndarray], np.ndarray]]
728    VR = None               # type: Optional[Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]]
729    form_volume = None      # type: Union[None, str, Callable[[np.ndarray], float]]
730    Iq = None               # type: Union[None, str, Callable[[np.ndarray], np.ndarray]]
731    Iqxy = None             # type: Union[None, str, Callable[[np.ndarray], np.ndarray]]
732    profile = None          # type: Optional[Callable[[np.ndarray], None]]
733    sesans = None           # type: Optional[Callable[[np.ndarray], np.ndarray]]
734    hidden = None           # type: Optional[Callable[[int], Set[str]]]
735    mono_details = None     # type: CallDetails
736
737    def __init__(self):
738        # type: () -> None
739        pass
740
741    def get_hidden_parameters(self, control):
742        if self.hidden is not None:
743            hidden = self.hidden(control)
744        else:
745            controls = [p for p in self.parameters.kernel_parameters]
746            if len(controls) != 1:
747                raise ValueError("more than one control parameter")
748            hidden = set(p.id+str(k)
749                         for p in self.parameters.kernel_parameters
750                         for k in range(control+1, p.length+1)
751                         if p.length > 1)
752        return hidden
Note: See TracBrowser for help on using the repository browser.