source: sasmodels/sasmodels/generate.py @ 0d0aee1

core_shell_microgelscostrafo411magnetic_modelrelease_v0.94release_v0.95ticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 0d0aee1 was 0d0aee1, checked in by piotr, 8 years ago

Added micelle_spherical_shell

  • Property mode set to 100644
File size: 27.5 KB
Line 
1"""
2SAS model constructor.
3
4Small angle scattering models are defined by a set of kernel functions:
5
6    *Iq(q, p1, p2, ...)* returns the scattering at q for a form with
7    particular dimensions averaged over all orientations.
8
9    *Iqxy(qx, qy, p1, p2, ...)* returns the scattering at qx, qy for a form
10    with particular dimensions for a single orientation.
11
12    *Imagnetic(qx, qy, result[], p1, p2, ...)* returns the scattering for the
13    polarized neutron spin states (up-up, up-down, down-up, down-down) for
14    a form with particular dimensions for a single orientation.
15
16    *form_volume(p1, p2, ...)* returns the volume of the form with particular
17    dimension.
18
19    *ER(p1, p2, ...)* returns the effective radius of the form with
20    particular dimensions.
21
22    *VR(p1, p2, ...)* returns the volume ratio for core-shell style forms.
23
24These functions are defined in a kernel module .py script and an associated
25set of .c files.  The model constructor will use them to create models with
26polydispersity across volume and orientation parameters, and provide
27scale and background parameters for each model.
28
29*Iq*, *Iqxy*, *Imagnetic* and *form_volume* should be stylized C-99
30functions written for OpenCL.  All functions need prototype declarations
31even if the are defined before they are used.  OpenCL does not support
32*#include* preprocessor directives, so instead the list of includes needs
33to be given as part of the metadata in the kernel module definition.
34The included files should be listed using a path relative to the kernel
35module, or if using "lib/file.c" if it is one of the standard includes
36provided with the sasmodels source.  The includes need to be listed in
37order so that functions are defined before they are used.
38
39Floating point values should be declared as *double*.  For single precision
40calculations, *double* will be replaced by *float*.  The single precision
41conversion will also tag floating point constants with "f" to make them
42single precision constants.  When using integral values in floating point
43expressions, they should be expressed as floating point values by including
44a decimal point.  This includes 0., 1. and 2.
45
46OpenCL has a *sincos* function which can improve performance when both
47the *sin* and *cos* values are needed for a particular argument.  Since
48this function does not exist in C99, all use of *sincos* should be
49replaced by the macro *SINCOS(value, sn, cn)* where *sn* and *cn* are
50previously declared *double* variables.  When compiled for systems without
51OpenCL, *SINCOS* will be replaced by *sin* and *cos* calls.   If *value* is
52an expression, it will appear twice in this case; whether or not it will be
53evaluated twice depends on the quality of the compiler.
54
55If the input parameters are invalid, the scattering calculator should
56return a negative number. Particularly with polydispersity, there are
57some sets of shape parameters which lead to nonsensical forms, such
58as a capped cylinder where the cap radius is smaller than the
59cylinder radius.  The polydispersity calculation will ignore these points,
60effectively chopping the parameter weight distributions at the boundary
61of the infeasible region.  The resulting scattering will be set to
62background.  This will work correctly even when polydispersity is off.
63
64*ER* and *VR* are python functions which operate on parameter vectors.
65The constructor code will generate the necessary vectors for computing
66them with the desired polydispersity.
67
68The available kernel parameters are defined as a list, with each parameter
69defined as a sublist with the following elements:
70
71    *name* is the name that will be used in the call to the kernel
72    function and the name that will be displayed to the user.  Names
73    should be lower case, with words separated by underscore.  If
74    acronyms are used, the whole acronym should be upper case.
75
76    *units* should be one of *degrees* for angles, *Ang* for lengths,
77    *1e-6/Ang^2* for SLDs.
78
79    *default value* will be the initial value for  the model when it
80    is selected, or when an initial value is not otherwise specified.
81
82    [*lb*, *ub*] are the hard limits on the parameter value, used to limit
83    the polydispersity density function.  In the fit, the parameter limits
84    given to the fit are the limits  on the central value of the parameter.
85    If there is polydispersity, it will evaluate parameter values outside
86    the fit limits, but not outside the hard limits specified in the model.
87    If there are no limits, use +/-inf imported from numpy.
88
89    *type* indicates how the parameter will be used.  "volume" parameters
90    will be used in all functions.  "orientation" parameters will be used
91    in *Iqxy* and *Imagnetic*.  "magnetic* parameters will be used in
92    *Imagnetic* only.  If *type* is the empty string, the parameter will
93    be used in all of *Iq*, *Iqxy* and *Imagnetic*.
94
95    *description* is a short description of the parameter.  This will
96    be displayed in the parameter table and used as a tool tip for the
97    parameter value in the user interface.
98
99The kernel module must set variables defining the kernel meta data:
100
101    *id* is an implicit variable formed from the filename.  It will be
102    a valid python identifier, and will be used as the reference into
103    the html documentation, with '_' replaced by '-'.
104
105    *name* is the model name as displayed to the user.  If it is missing,
106    it will be constructed from the id.
107
108    *title* is a short description of the model, suitable for a tool tip,
109    or a one line model summary in a table of models.
110
111    *description* is an extended description of the model to be displayed
112    while the model parameters are being edited.
113
114    *parameters* is the list of parameters.  Parameters in the kernel
115    functions must appear in the same order as they appear in the
116    parameters list.  Two additional parameters, *scale* and *background*
117    are added to the beginning of the parameter list.  They will show up
118    in the documentation as model parameters, but they are never sent to
119    the kernel functions.
120
121    *category* is the default category for the model.  Models in the
122    *structure-factor* category do not have *scale* and *background*
123    added.
124
125    *source* is the list of C-99 source files that must be joined to
126    create the OpenCL kernel functions.  The files defining the functions
127    need to be listed before the files which use the functions.
128
129    *ER* is a python function defining the effective radius.  If it is
130    not present, the effective radius is 0.
131
132    *VR* is a python function defining the volume ratio.  If it is not
133    present, the volume ratio is 1.
134
135    *form_volume*, *Iq*, *Iqxy*, *Imagnetic* are strings containing the
136    C source code for the body of the volume, Iq, and Iqxy functions
137    respectively.  These can also be defined in the last source file.
138
139    *Iq* and *Iqxy* also be instead be python functions defining the
140    kernel.  If they are marked as *Iq.vectorized = True* then the
141    kernel is passed the entire *q* vector at once, otherwise it is
142    passed values one *q* at a time.  The performance improvement of
143    this step is significant.
144
145    *demo* is a dictionary of parameter=value defining a set of
146    parameters to use by default when *compare* is called.  Any
147    parameter not set in *demo* gets the initial value from the
148    parameter list.  *demo* is mostly needed to set the default
149    polydispersity values for tests.
150
151    *oldname* is the name of the model in sasview before sasmodels
152    was split into its own package, and *oldpars* is a dictionary
153    of *parameter: old_parameter* pairs defining the new names for
154    the parameters.  This is used by *compare* to check the values
155    of the new model against the values of the old model before
156    you are ready to add the new model to sasmodels.
157
158
159An *info* dictionary is constructed from the kernel meta data and
160returned to the caller.
161
162The model evaluator, function call sequence consists of q inputs and the return vector,
163followed by the loop value/weight vector, followed by the values for
164the non-polydisperse parameters, followed by the lengths of the
165polydispersity loops.  To construct the call for 1D models, the
166categories *fixed-1d* and *pd-1d* list the names of the parameters
167of the non-polydisperse and the polydisperse parameters respectively.
168Similarly, *fixed-2d* and *pd-2d* provide parameter names for 2D models.
169The *pd-rel* category is a set of those parameters which give
170polydispersitiy as a portion of the value (so a 10% length dispersity
171would use a polydispersity value of 0.1) rather than absolute
172dispersity such as an angle plus or minus 15 degrees.
173
174The *volume* category lists the volume parameters in order for calls
175to volume within the kernel (used for volume normalization) and for
176calls to ER and VR for effective radius and volume ratio respectively.
177
178The *orientation* and *magnetic* categories list the orientation and
179magnetic parameters.  These are used by the sasview interface.  The
180blank category is for parameters such as scale which don't have any
181other marking.
182
183The doc string at the start of the kernel module will be used to
184construct the model documentation web pages.  Embedded figures should
185appear in the subdirectory "img" beside the model definition, and tagged
186with the kernel module name to avoid collision with other models.  Some
187file systems are case-sensitive, so only use lower case characters for
188file names and extensions.
189
190
191The function :func:`make` loads the metadata from the module and returns
192the kernel source.  The function :func:`doc` extracts the doc string
193and adds the parameter table to the top.  The function :func:`model_sources`
194returns a list of files required by the model.
195
196Code follows the C99 standard with the following extensions and conditions::
197
198    M_PI_180 = pi/180
199    M_4PI_3 = 4pi/3
200    square(x) = x*x
201    cube(x) = x*x*x
202    sinc(x) = sin(x)/x, with sin(0)/0 -> 1
203    all double precision constants must include the decimal point
204    all double declarations may be converted to half, float, or long double
205    FLOAT_SIZE is the number of bytes in the converted variables
206"""
207from __future__ import print_function
208
209# TODO: identify model files which have changed since loading and reload them.
210
211import sys
212from os.path import abspath, dirname, join as joinpath, exists, basename, \
213    splitext
214import re
215import string
216
217import numpy as np
218
219#__all__ = ["make", "doc", "model_sources", "convert_type"]
220
221C_KERNEL_TEMPLATE_PATH = joinpath(dirname(__file__), 'kernel_template.c')
222
223F16 = np.dtype('float16')
224F32 = np.dtype('float32')
225F64 = np.dtype('float64')
226try:  # CRUFT: older numpy does not support float128
227    F128 = np.dtype('float128')
228except TypeError:
229    F128 = None
230
231# Scale and background, which are parameters common to every form factor
232COMMON_PARAMETERS = [
233    ["scale", "", 1, [0, np.inf], "", "Source intensity"],
234    ["background", "1/cm", 0, [0, np.inf], "", "Source background"],
235    ]
236
237# Conversion from units defined in the parameter table for each model
238# to units displayed in the sphinx documentation.
239RST_UNITS = {
240    "Ang": "|Ang|",
241    "1/Ang": "|Ang^-1|",
242    "1/Ang^2": "|Ang^-2|",
243    "1e-6/Ang^2": "|1e-6Ang^-2|",
244    "1e15/cm^3": "|1e15cm^3|",
245    "degrees": "degree",
246    "1/cm": "|cm^-1|",
247    "1/cm^3": "|cm^-3|",
248    "Ang/cm": "|Ang*cm^-1|",
249    "Ang^3": "|Ang^3|",
250    "": "None",
251    }
252
253# Headers for the parameters tables in th sphinx documentation
254PARTABLE_HEADERS = [
255    "Parameter",
256    "Description",
257    "Units",
258    "Default value",
259    ]
260
261# Minimum width for a default value (this is shorter than the column header
262# width, so will be ignored).
263PARTABLE_VALUE_WIDTH = 10
264
265# Documentation header for the module, giving the model name, its short
266# description and its parameter table.  The remainder of the doc comes
267# from the module docstring.
268DOC_HEADER = """.. _%(id)s:
269
270%(name)s
271=======================================================
272
273%(title)s
274
275%(parameters)s
276
277%(returns)s
278
279%(docs)s
280"""
281
282def format_units(units):
283    """
284    Convert units into ReStructured Text format.
285    """
286    return "string" if isinstance(units, list) else RST_UNITS.get(units, units)
287
288def make_partable(pars):
289    """
290    Generate the parameter table to include in the sphinx documentation.
291    """
292    column_widths = [
293        max(len(p[0]) for p in pars),
294        max(len(p[-1]) for p in pars),
295        max(len(format_units(p[1])) for p in pars),
296        PARTABLE_VALUE_WIDTH,
297        ]
298    column_widths = [max(w, len(h))
299                     for w, h in zip(column_widths, PARTABLE_HEADERS)]
300
301    sep = " ".join("="*w for w in column_widths)
302    lines = [
303        sep,
304        " ".join("%-*s" % (w, h)
305                 for w, h in zip(column_widths, PARTABLE_HEADERS)),
306        sep,
307        ]
308    for p in pars:
309        lines.append(" ".join([
310            "%-*s" % (column_widths[0], p[0]),
311            "%-*s" % (column_widths[1], p[-1]),
312            "%-*s" % (column_widths[2], format_units(p[1])),
313            "%*g" % (column_widths[3], p[2]),
314            ]))
315    lines.append(sep)
316    return "\n".join(lines)
317
318def _search(search_path, filename):
319    """
320    Find *filename* in *search_path*.
321
322    Raises ValueError if file does not exist.
323    """
324    for path in search_path:
325        target = joinpath(path, filename)
326        if exists(target):
327            return target
328    raise ValueError("%r not found in %s" % (filename, search_path))
329
330def model_sources(info):
331    """
332    Return a list of the sources file paths for the module.
333    """
334    search_path = [dirname(info['filename']),
335                   abspath(joinpath(dirname(__file__), 'models'))]
336    return [_search(search_path, f) for f in info['source']]
337
338# Pragmas for enable OpenCL features.  Be sure to protect them so that they
339# still compile even if OpenCL is not present.
340_F16_PRAGMA = """\
341#if defined(__OPENCL_VERSION__) && !defined(cl_khr_fp16)
342#  pragma OPENCL EXTENSION cl_khr_fp16: enable
343#endif
344"""
345
346_F64_PRAGMA = """\
347#if defined(__OPENCL_VERSION__) && !defined(cl_khr_fp64)
348#  pragma OPENCL EXTENSION cl_khr_fp64: enable
349#endif
350"""
351
352def convert_type(source, dtype):
353    """
354    Convert code from double precision to the desired type.
355
356    Floating point constants are tagged with 'f' for single precision or 'L'
357    for long double precision.
358    """
359    if dtype == F16:
360        fbytes = 2
361        source = _F16_PRAGMA + _convert_type(source, "half", "f")
362    elif dtype == F32:
363        fbytes = 4
364        source = _convert_type(source, "float", "f")
365    elif dtype == F64:
366        fbytes = 8
367        source = _F64_PRAGMA + source  # Source is already double
368    elif dtype == F128:
369        fbytes = 16
370        source = _convert_type(source, "long double", "L")
371    else:
372        raise ValueError("Unexpected dtype in source conversion: %s"%dtype)
373    return ("#define FLOAT_SIZE %d\n"%fbytes)+source
374
375
376def _convert_type(source, type_name, constant_flag):
377    """
378    Replace 'double' with *type_name* in *source*, tagging floating point
379    constants with *constant_flag*.
380    """
381    # Convert double keyword to float/long double/half.
382    # Accept an 'n' # parameter for vector # values, where n is 2, 4, 8 or 16.
383    # Assume complex numbers are represented as cdouble which is typedef'd
384    # to double2.
385    source = re.sub(r'(^|[^a-zA-Z0-9_]c?)double(([248]|16)?($|[^a-zA-Z0-9_]))',
386                    r'\1%s\2'%type_name, source)
387    # Convert floating point constants to single by adding 'f' to the end,
388    # or long double with an 'L' suffix.  OS/X complains if you don't do this.
389    source = re.sub(r'[^a-zA-Z_](\d*[.]\d+|\d+[.]\d*)([eE][+-]?\d+)?',
390                    r'\g<0>%s'%constant_flag, source)
391    return source
392
393
394def kernel_name(info, is_2d):
395    """
396    Name of the exported kernel symbol.
397    """
398    return info['name'] + "_" + ("Iqxy" if is_2d else "Iq")
399
400
401def categorize_parameters(pars):
402    """
403    Build parameter categories out of the the parameter definitions.
404
405    Returns a dictionary of categories.
406    """
407    partype = {
408        'volume': [], 'orientation': [], 'magnetic': [], '': [],
409        'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [],
410        'pd-rel': set(),
411    }
412
413    for p in pars:
414        name, ptype = p[0], p[4]
415        if ptype == 'volume':
416            partype['pd-1d'].append(name)
417            partype['pd-2d'].append(name)
418            partype['pd-rel'].add(name)
419        elif ptype == 'magnetic':
420            partype['fixed-2d'].append(name)
421        elif ptype == 'orientation':
422            partype['pd-2d'].append(name)
423        elif ptype == '':
424            partype['fixed-1d'].append(name)
425            partype['fixed-2d'].append(name)
426        else:
427            raise ValueError("unknown parameter type %r" % ptype)
428        partype[ptype].append(name)
429
430    return partype
431
432def indent(s, depth):
433    """
434    Indent a string of text with *depth* additional spaces on each line.
435    """
436    spaces = " "*depth
437    sep = "\n" + spaces
438    return spaces + sep.join(s.split("\n"))
439
440
441LOOP_OPEN = """\
442for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) {
443  const double %(name)s = loops[2*(%(name)s_i%(offset)s)];
444  const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\
445"""
446def build_polydispersity_loops(pd_pars):
447    """
448    Build polydispersity loops
449
450    Returns loop opening and loop closing
451    """
452    depth = 4
453    offset = ""
454    loop_head = []
455    loop_end = []
456    for name in pd_pars:
457        subst = {'name': name, 'offset': offset}
458        loop_head.append(indent(LOOP_OPEN % subst, depth))
459        loop_end.insert(0, (" "*depth) + "}")
460        offset += '+N' + name
461        depth += 2
462    return "\n".join(loop_head), "\n".join(loop_end)
463
464C_KERNEL_TEMPLATE = None
465def make_model(info):
466    """
467    Generate the code for the kernel defined by info, using source files
468    found in the given search path.
469    """
470    # TODO: need something other than volume to indicate dispersion parameters
471    # No volume normalization despite having a volume parameter.
472    # Thickness is labelled a volume in order to trigger polydispersity.
473    # May want a separate dispersion flag, or perhaps a separate category for
474    # disperse, but not volume.  Volume parameters also use relative values
475    # for the distribution rather than the absolute values used by angular
476    # dispersion.  Need to be careful that necessary parameters are available
477    # for computing volume even if we allow non-disperse volume parameters.
478
479    # Load template
480    global C_KERNEL_TEMPLATE
481    if C_KERNEL_TEMPLATE is None:
482        with open(C_KERNEL_TEMPLATE_PATH) as fid:
483            C_KERNEL_TEMPLATE = fid.read()
484
485    # Load additional sources
486    source = [open(f).read() for f in model_sources(info)]
487
488    # Prepare defines
489    defines = []
490    partype = info['partype']
491    pd_1d = partype['pd-1d']
492    pd_2d = partype['pd-2d']
493    fixed_1d = partype['fixed-1d']
494    fixed_2d = partype['fixed-1d']
495
496    iq_parameters = [p[0]
497                     for p in info['parameters'][2:] # skip scale, background
498                     if p[0] in set(fixed_1d + pd_1d)]
499    iqxy_parameters = [p[0]
500                       for p in info['parameters'][2:] # skip scale, background
501                       if p[0] in set(fixed_2d + pd_2d)]
502    volume_parameters = [p[0]
503                         for p in info['parameters']
504                         if p[4] == 'volume']
505
506    # Fill in defintions for volume parameters
507    if volume_parameters:
508        defines.append(('VOLUME_PARAMETERS',
509                        ','.join(volume_parameters)))
510        defines.append(('VOLUME_WEIGHT_PRODUCT',
511                        '*'.join(p + '_w' for p in volume_parameters)))
512
513    # Generate form_volume function from body only
514    if info['form_volume'] is not None:
515        if volume_parameters:
516            vol_par_decl = ', '.join('double ' + p for p in volume_parameters)
517        else:
518            vol_par_decl = 'void'
519        defines.append(('VOLUME_PARAMETER_DECLARATIONS',
520                        vol_par_decl))
521        fn = """\
522double form_volume(VOLUME_PARAMETER_DECLARATIONS);
523double form_volume(VOLUME_PARAMETER_DECLARATIONS) {
524    %(body)s
525}
526""" % {'body':info['form_volume']}
527        source.append(fn)
528
529    # Fill in definitions for Iq parameters
530    defines.append(('IQ_KERNEL_NAME', info['name'] + '_Iq'))
531    defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters)))
532    if fixed_1d:
533        defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS',
534                        ', \\\n    '.join('const double %s' % p for p in fixed_1d)))
535    if pd_1d:
536        defines.append(('IQ_WEIGHT_PRODUCT',
537                        '*'.join(p + '_w' for p in pd_1d)))
538        defines.append(('IQ_DISPERSION_LENGTH_DECLARATIONS',
539                        ', \\\n    '.join('const int N%s' % p for p in pd_1d)))
540        defines.append(('IQ_DISPERSION_LENGTH_SUM',
541                        '+'.join('N' + p for p in pd_1d)))
542        open_loops, close_loops = build_polydispersity_loops(pd_1d)
543        defines.append(('IQ_OPEN_LOOPS',
544                        open_loops.replace('\n', ' \\\n')))
545        defines.append(('IQ_CLOSE_LOOPS',
546                        close_loops.replace('\n', ' \\\n')))
547    if info['Iq'] is not None:
548        defines.append(('IQ_PARAMETER_DECLARATIONS',
549                        ', '.join('double ' + p for p in iq_parameters)))
550        fn = """\
551double Iq(double q, IQ_PARAMETER_DECLARATIONS);
552double Iq(double q, IQ_PARAMETER_DECLARATIONS) {
553    %(body)s
554}
555""" % {'body':info['Iq']}
556        source.append(fn)
557
558    # Fill in definitions for Iqxy parameters
559    defines.append(('IQXY_KERNEL_NAME', info['name'] + '_Iqxy'))
560    defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters)))
561    if fixed_2d:
562        defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS',
563                        ', \\\n    '.join('const double %s' % p for p in fixed_2d)))
564    if pd_2d:
565        defines.append(('IQXY_WEIGHT_PRODUCT',
566                        '*'.join(p + '_w' for p in pd_2d)))
567        defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS',
568                        ', \\\n    '.join('const int N%s' % p for p in pd_2d)))
569        defines.append(('IQXY_DISPERSION_LENGTH_SUM',
570                        '+'.join('N' + p for p in pd_2d)))
571        open_loops, close_loops = build_polydispersity_loops(pd_2d)
572        defines.append(('IQXY_OPEN_LOOPS',
573                        open_loops.replace('\n', ' \\\n')))
574        defines.append(('IQXY_CLOSE_LOOPS',
575                        close_loops.replace('\n', ' \\\n')))
576    if info['Iqxy'] is not None:
577        defines.append(('IQXY_PARAMETER_DECLARATIONS',
578                        ', '.join('double ' + p for p in iqxy_parameters)))
579        fn = """\
580double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS);
581double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS) {
582    %(body)s
583}
584""" % {'body':info['Iqxy']}
585        source.append(fn)
586
587    # Need to know if we have a theta parameter for Iqxy; it is not there
588    # for the magnetic sphere model, for example, which has a magnetic
589    # orientation but no shape orientation.
590    if 'theta' in pd_2d:
591        defines.append(('IQXY_HAS_THETA', '1'))
592
593    #for d in defines: print(d)
594    defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines)
595    sources = '\n\n'.join(source)
596    return C_KERNEL_TEMPLATE % {
597        'DEFINES': defines,
598        'SOURCES': sources,
599        }
600
601def make_info(kernel_module):
602    """
603    Interpret the model definition file, categorizing the parameters.
604    """
605    #print(kernelfile)
606    category = getattr(kernel_module, 'category', None)
607    parameters = COMMON_PARAMETERS + kernel_module.parameters
608    # Default the demo parameters to the starting values for the individual
609    # parameters if an explicit demo parameter set has not been specified.
610    demo_parameters = getattr(kernel_module, 'demo', None)
611    if demo_parameters is None:
612        demo_parameters = dict((p[0], p[2]) for p in parameters)
613    filename = abspath(kernel_module.__file__)
614    kernel_id = splitext(basename(filename))[0]
615    name = getattr(kernel_module, 'name', None)
616    if name is None:
617        name = " ".join(w.capitalize() for w in kernel_id.split('_'))
618    info = dict(
619        id=kernel_id,  # string used to load the kernel
620        filename=abspath(kernel_module.__file__),
621        name=name,
622        title=kernel_module.title,
623        description=kernel_module.description,
624        category=category,
625        parameters=parameters,
626        demo=demo_parameters,
627        source=getattr(kernel_module, 'source', []),
628        oldname=kernel_module.oldname,
629        oldpars=kernel_module.oldpars,
630        )
631    # Fill in attributes which default to None
632    info.update((k, getattr(kernel_module, k, None))
633                for k in ('ER', 'VR', 'form_volume', 'Iq', 'Iqxy'))
634    # Fill in the derived attributes
635    info['limits'] = dict((p[0], p[3]) for p in info['parameters'])
636    info['partype'] = categorize_parameters(info['parameters'])
637    info['defaults'] = dict((p[0], p[2]) for p in info['parameters'])
638    return info
639
640def make(kernel_module):
641    """
642    Build an OpenCL/ctypes function from the definition in *kernel_module*.
643
644    The module can be loaded with a normal python import statement if you
645    know which module you need, or with __import__('sasmodels.model.'+name)
646    if the name is in a string.
647    """
648    info = make_info(kernel_module)
649    # Assume if one part of the kernel is python then all parts are.
650    source = make_model(info) if not callable(info['Iq']) else None
651    return source, info
652
653section_marker = re.compile(r'\A(?P<first>[%s])(?P=first)*\Z'
654                            %re.escape(string.punctuation))
655def _convert_section_titles_to_boldface(lines):
656    """
657    Do the actual work of identifying and converting section headings.
658    """
659    prior = None
660    for line in lines:
661        if prior is None:
662            prior = line
663        elif section_marker.match(line):
664            if len(line) >= len(prior):
665                yield "".join(("**", prior, "**"))
666                prior = None
667            else:
668                yield prior
669                prior = line
670        else:
671            yield prior
672            prior = line
673    if prior is not None:
674        yield prior
675
676def convert_section_titles_to_boldface(s):
677    """
678    Use explicit bold-face rather than section headings so that the table of
679    contents is not polluted with section names from the model documentation.
680
681    Sections are identified as the title line followed by a line of punctuation
682    at least as long as the title line.
683    """
684    return "\n".join(_convert_section_titles_to_boldface(s.split('\n')))
685
686def doc(kernel_module):
687    """
688    Return the documentation for the model.
689    """
690    Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale."
691    Sq_units = "The returned value is a dimensionless structure factor, $S(q)$."
692    info = make_info(kernel_module)
693    is_Sq = ("structure-factor" in info['category'])
694    #docs = kernel_module.__doc__
695    docs = convert_section_titles_to_boldface(kernel_module.__doc__)
696    subst = dict(id=info['id'].replace('_', '-'),
697                 name=info['name'],
698                 title=info['title'],
699                 parameters=make_partable(info['parameters']),
700                 returns=Sq_units if is_Sq else Iq_units,
701                 docs=docs)
702    return DOC_HEADER % subst
703
704
705
706def demo_time():
707    """
708    Show how long it takes to process a model.
709    """
710    from .models import cylinder
711    import datetime
712    tic = datetime.datetime.now()
713    make(cylinder)
714    toc = (datetime.datetime.now() - tic).total_seconds()
715    print("time: %g"%toc)
716
717def main():
718    """
719    Program which prints the source produced by the model.
720    """
721    if len(sys.argv) <= 1:
722        print("usage: python -m sasmodels.generate modelname")
723    else:
724        name = sys.argv[1]
725        import sasmodels.models
726        __import__('sasmodels.models.' + name)
727        model = getattr(sasmodels.models, name)
728        source, _ = make(model)
729        print(source)
730
731if __name__ == "__main__":
732    main()
Note: See TracBrowser for help on using the repository browser.