source: sasmodels/sasmodels/generate.py @ eb69cce

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

make model docs more consistent; build pdf docs

  • Property mode set to 100644
File size: 24.9 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:`sources`
194returns a list of files required by the model.
195"""
196
197# TODO: identify model files which have changed since loading and reload them.
198
199__all__ = ["make", "doc", "sources", "use_single", "use_long_double"]
200
201import sys
202from os.path import abspath, dirname, join as joinpath, exists, basename, \
203    splitext
204import re
205
206import numpy as np
207C_KERNEL_TEMPLATE_PATH = joinpath(dirname(__file__), 'kernel_template.c')
208
209F32 = np.dtype('float32')
210F64 = np.dtype('float64')
211try:  # CRUFT: older numpy does not support float128
212    F128 = np.dtype('float128')
213except TypeError:
214    F128 = None
215
216
217# Scale and background, which are parameters common to every form factor
218COMMON_PARAMETERS = [
219    ["scale", "", 1, [0, np.inf], "", "Source intensity"],
220    ["background", "1/cm", 0, [0, np.inf], "", "Source background"],
221    ]
222
223
224# Conversion from units defined in the parameter table for each model
225# to units displayed in the sphinx documentation.
226RST_UNITS = {
227    "Ang": "|Ang|",
228    "1/Ang": "|Ang^-1|",
229    "1/Ang^2": "|Ang^-2|",
230    "1e-6/Ang^2": "|1e-6Ang^-2|",
231    "degrees": "degree",
232    "1/cm": "|cm^-1|",
233    "": "None",
234    }
235
236# Headers for the parameters tables in th sphinx documentation
237PARTABLE_HEADERS = [
238    "Parameter",
239    "Description",
240    "Units",
241    "Default value",
242    ]
243
244# Minimum width for a default value (this is shorter than the column header
245# width, so will be ignored).
246PARTABLE_VALUE_WIDTH = 10
247
248# Documentation header for the module, giving the model name, its short
249# description and its parameter table.  The remainder of the doc comes
250# from the module docstring.
251DOC_HEADER = """.. _%(id)s:
252
253%(name)s
254=======================================================
255
256%(title)s
257
258%(parameters)s
259
260%(returns)s
261
262%(docs)s
263"""
264
265def format_units(par):
266    return RST_UNITS.get(par, par)
267
268def make_partable(pars):
269    """
270    Generate the parameter table to include in the sphinx documentation.
271    """
272    column_widths = [
273        max(len(p[0]) for p in pars),
274        max(len(p[-1]) for p in pars),
275        max(len(format_units(p[1])) for p in pars),
276        PARTABLE_VALUE_WIDTH,
277        ]
278    column_widths = [max(w, len(h))
279                     for w, h in zip(column_widths, PARTABLE_HEADERS)]
280
281    sep = " ".join("="*w for w in column_widths)
282    lines = [
283        sep,
284        " ".join("%-*s" % (w, h) for w, h in zip(column_widths, PARTABLE_HEADERS)),
285        sep,
286        ]
287    for p in pars:
288        lines.append(" ".join([
289            "%-*s" % (column_widths[0], p[0]),
290            "%-*s" % (column_widths[1], p[-1]),
291            "%-*s" % (column_widths[2], format_units(p[1])),
292            "%*g" % (column_widths[3], p[2]),
293            ]))
294    lines.append(sep)
295    return "\n".join(lines)
296
297def _search(search_path, filename):
298    """
299    Find *filename* in *search_path*.
300
301    Raises ValueError if file does not exist.
302    """
303    for path in search_path:
304        target = joinpath(path, filename)
305        if exists(target):
306            return target
307    raise ValueError("%r not found in %s" % (filename, search_path))
308
309def sources(info):
310    """
311    Return a list of the sources file paths for the module.
312    """
313    search_path = [dirname(info['filename']),
314                   abspath(joinpath(dirname(__file__), 'models'))]
315    return [_search(search_path, f) for f in info['source']]
316
317def use_single(source):
318    """
319    Convert code from double precision to single precision.
320    """
321    # Convert double keyword to float.  Accept an 'n' parameter for vector
322    # values, where n is 2, 4, 8 or 16. Assume complex numbers are represented
323    # as cdouble which is typedef'd to double2.
324    source = re.sub(r'(^|[^a-zA-Z0-9_]c?)double(([248]|16)?($|[^a-zA-Z0-9_]))',
325                    r'\1float\2', source)
326    # Convert floating point constants to single by adding 'f' to the end.
327    # OS/X driver complains if you don't do this.
328    source = re.sub(r'[^a-zA-Z_](\d*[.]\d+|\d+[.]\d*)([eE][+-]?\d+)?',
329                    r'\g<0>f', source)
330    return source
331
332def use_long_double(source):
333    """
334    Convert code from double precision to long double precision.
335    """
336    # Convert double keyword to float.  Accept an 'n' parameter for vector
337    # values, where n is 2, 4, 8 or 16. Assume complex numbers are represented
338    # as cdouble which is typedef'd to double2.
339    source = re.sub(r'(^|[^a-zA-Z0-9_]c?)double(([248]|16)?($|[^a-zA-Z0-9_]))',
340                    r'\1long double\2', source)
341    # Convert floating point constants to single by adding 'f' to the end.
342    # OS/X driver complains if you don't do this.
343    source = re.sub(r'[^a-zA-Z_](\d*[.]\d+|\d+[.]\d*)([eE][+-]?\d+)?',
344                    r'\g<0>L', source)
345    return source
346
347
348def kernel_name(info, is_2D):
349    """
350    Name of the exported kernel symbol.
351    """
352    return info['name'] + "_" + ("Iqxy" if is_2D else "Iq")
353
354
355def categorize_parameters(pars):
356    """
357    Build parameter categories out of the the parameter definitions.
358
359    Returns a dictionary of categories.
360    """
361    partype = {
362        'volume': [], 'orientation': [], 'magnetic': [], '': [],
363        'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [],
364        'pd-rel': set(),
365    }
366
367    for p in pars:
368        name, ptype = p[0], p[4]
369        if ptype == 'volume':
370            partype['pd-1d'].append(name)
371            partype['pd-2d'].append(name)
372            partype['pd-rel'].add(name)
373        elif ptype == 'magnetic':
374            partype['fixed-2d'].append(name)
375        elif ptype == 'orientation':
376            partype['pd-2d'].append(name)
377        elif ptype == '':
378            partype['fixed-1d'].append(name)
379            partype['fixed-2d'].append(name)
380        else:
381            raise ValueError("unknown parameter type %r" % ptype)
382        partype[ptype].append(name)
383
384    return partype
385
386def indent(s, depth):
387    """
388    Indent a string of text with *depth* additional spaces on each line.
389    """
390    spaces = " "*depth
391    sep = "\n" + spaces
392    return spaces + sep.join(s.split("\n"))
393
394
395def build_polydispersity_loops(pd_pars):
396    """
397    Build polydispersity loops
398
399    Returns loop opening and loop closing
400    """
401    LOOP_OPEN = """\
402for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) {
403  const double %(name)s = loops[2*(%(name)s_i%(offset)s)];
404  const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\
405"""
406    depth = 4
407    offset = ""
408    loop_head = []
409    loop_end = []
410    for name in pd_pars:
411        subst = {'name': name, 'offset': offset}
412        loop_head.append(indent(LOOP_OPEN % subst, depth))
413        loop_end.insert(0, (" "*depth) + "}")
414        offset += '+N' + name
415        depth += 2
416    return "\n".join(loop_head), "\n".join(loop_end)
417
418C_KERNEL_TEMPLATE = None
419def make_model(info):
420    """
421    Generate the code for the kernel defined by info, using source files
422    found in the given search path.
423    """
424    # TODO: need something other than volume to indicate dispersion parameters
425    # No volume normalization despite having a volume parameter.
426    # Thickness is labelled a volume in order to trigger polydispersity.
427    # May want a separate dispersion flag, or perhaps a separate category for
428    # disperse, but not volume.  Volume parameters also use relative values
429    # for the distribution rather than the absolute values used by angular
430    # dispersion.  Need to be careful that necessary parameters are available
431    # for computing volume even if we allow non-disperse volume parameters.
432
433    # Load template
434    global C_KERNEL_TEMPLATE
435    if C_KERNEL_TEMPLATE is None:
436        with open(C_KERNEL_TEMPLATE_PATH) as fid:
437            C_KERNEL_TEMPLATE = fid.read()
438
439    # Load additional sources
440    source = [open(f).read() for f in sources(info)]
441
442    # Prepare defines
443    defines = []
444    partype = info['partype']
445    pd_1d = partype['pd-1d']
446    pd_2d = partype['pd-2d']
447    fixed_1d = partype['fixed-1d']
448    fixed_2d = partype['fixed-1d']
449
450    iq_parameters = [p[0]
451                     for p in info['parameters'][2:] # skip scale, background
452                     if p[0] in set(fixed_1d + pd_1d)]
453    iqxy_parameters = [p[0]
454                       for p in info['parameters'][2:] # skip scale, background
455                       if p[0] in set(fixed_2d + pd_2d)]
456    volume_parameters = [p[0]
457                         for p in info['parameters']
458                         if p[4] == 'volume']
459
460    # Fill in defintions for volume parameters
461    if volume_parameters:
462        defines.append(('VOLUME_PARAMETERS',
463                        ','.join(volume_parameters)))
464        defines.append(('VOLUME_WEIGHT_PRODUCT',
465                        '*'.join(p + '_w' for p in volume_parameters)))
466
467    # Generate form_volume function from body only
468    if info['form_volume'] is not None:
469        if volume_parameters:
470            vol_par_decl = ', '.join('double ' + p for p in volume_parameters)
471        else:
472            vol_par_decl = 'void'
473        defines.append(('VOLUME_PARAMETER_DECLARATIONS',
474                        vol_par_decl))
475        fn = """\
476double form_volume(VOLUME_PARAMETER_DECLARATIONS);
477double form_volume(VOLUME_PARAMETER_DECLARATIONS) {
478    %(body)s
479}
480""" % {'body':info['form_volume']}
481        source.append(fn)
482
483    # Fill in definitions for Iq parameters
484    defines.append(('IQ_KERNEL_NAME', info['name'] + '_Iq'))
485    defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters)))
486    if fixed_1d:
487        defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS',
488                        ', \\\n    '.join('const double %s' % p for p in fixed_1d)))
489    if pd_1d:
490        defines.append(('IQ_WEIGHT_PRODUCT',
491                        '*'.join(p + '_w' for p in pd_1d)))
492        defines.append(('IQ_DISPERSION_LENGTH_DECLARATIONS',
493                        ', \\\n    '.join('const int N%s' % p for p in pd_1d)))
494        defines.append(('IQ_DISPERSION_LENGTH_SUM',
495                        '+'.join('N' + p for p in pd_1d)))
496        open_loops, close_loops = build_polydispersity_loops(pd_1d)
497        defines.append(('IQ_OPEN_LOOPS',
498                        open_loops.replace('\n', ' \\\n')))
499        defines.append(('IQ_CLOSE_LOOPS',
500                        close_loops.replace('\n', ' \\\n')))
501    if info['Iq'] is not None:
502        defines.append(('IQ_PARAMETER_DECLARATIONS',
503                        ', '.join('double ' + p for p in iq_parameters)))
504        fn = """\
505double Iq(double q, IQ_PARAMETER_DECLARATIONS);
506double Iq(double q, IQ_PARAMETER_DECLARATIONS) {
507    %(body)s
508}
509""" % {'body':info['Iq']}
510        source.append(fn)
511
512    # Fill in definitions for Iqxy parameters
513    defines.append(('IQXY_KERNEL_NAME', info['name'] + '_Iqxy'))
514    defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters)))
515    if fixed_2d:
516        defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS',
517                        ', \\\n    '.join('const double %s' % p for p in fixed_2d)))
518    if pd_2d:
519        defines.append(('IQXY_WEIGHT_PRODUCT',
520                        '*'.join(p + '_w' for p in pd_2d)))
521        defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS',
522                        ', \\\n    '.join('const int N%s' % p for p in pd_2d)))
523        defines.append(('IQXY_DISPERSION_LENGTH_SUM',
524                        '+'.join('N' + p for p in pd_2d)))
525        open_loops, close_loops = build_polydispersity_loops(pd_2d)
526        defines.append(('IQXY_OPEN_LOOPS',
527                        open_loops.replace('\n', ' \\\n')))
528        defines.append(('IQXY_CLOSE_LOOPS',
529                        close_loops.replace('\n', ' \\\n')))
530    if info['Iqxy'] is not None:
531        defines.append(('IQXY_PARAMETER_DECLARATIONS',
532                        ', '.join('double ' + p for p in iqxy_parameters)))
533        fn = """\
534double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS);
535double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS) {
536    %(body)s
537}
538""" % {'body':info['Iqxy']}
539        source.append(fn)
540
541    # Need to know if we have a theta parameter for Iqxy; it is not there
542    # for the magnetic sphere model, for example, which has a magnetic
543    # orientation but no shape orientation.
544    if 'theta' in pd_2d:
545        defines.append(('IQXY_HAS_THETA', '1'))
546
547    #for d in defines: print d
548    DEFINES = '\n'.join('#define %s %s' % (k, v) for k, v in defines)
549    SOURCES = '\n\n'.join(source)
550    return C_KERNEL_TEMPLATE % {
551        'DEFINES':DEFINES,
552        'SOURCES':SOURCES,
553        }
554
555def make_info(kernel_module):
556    """
557    Interpret the model definition file, categorizing the parameters.
558    """
559    #print kernelfile
560    category = getattr(kernel_module, 'category', None)
561    parameters = COMMON_PARAMETERS + kernel_module.parameters
562    # Default the demo parameters to the starting values for the individual
563    # parameters if an explicit demo parameter set has not been specified.
564    demo_parameters = getattr(kernel_module, 'demo', None)
565    if demo_parameters is None:
566        demo_parameters = dict((p[0],p[2]) for p in parameters)
567    filename = abspath(kernel_module.__file__)
568    kernel_id = splitext(basename(filename))[0]
569    name = getattr(kernel_module, 'name', None)
570    if name is None:
571        name = " ".join(w.capitalize() for w in kernel_id.split('_'))
572    info = dict(
573        id = kernel_id,  # string used to load the kernel
574        filename=abspath(kernel_module.__file__),
575        name=name,
576        title=kernel_module.title,
577        description=kernel_module.description,
578        category=category,
579        parameters=parameters,
580        demo=demo_parameters,
581        source=getattr(kernel_module, 'source', []),
582        oldname=kernel_module.oldname,
583        oldpars=kernel_module.oldpars,
584        )
585    # Fill in attributes which default to None
586    info.update((k, getattr(kernel_module, k, None))
587                for k in ('ER', 'VR', 'form_volume', 'Iq', 'Iqxy'))
588    # Fill in the derived attributes
589    info['limits'] = dict((p[0], p[3]) for p in info['parameters'])
590    info['partype'] = categorize_parameters(info['parameters'])
591    info['defaults'] = dict((p[0], p[2]) for p in info['parameters'])
592    return info
593
594def make(kernel_module):
595    """
596    Build an OpenCL/ctypes function from the definition in *kernel_module*.
597
598    The module can be loaded with a normal python import statement if you
599    know which module you need, or with __import__('sasmodels.model.'+name)
600    if the name is in a string.
601    """
602    info = make_info(kernel_module)
603    # Assume if one part of the kernel is python then all parts are.
604    source = make_model(info) if not callable(info['Iq']) else None
605    return source, info
606
607def doc(kernel_module):
608    """
609    Return the documentation for the model.
610    """
611    Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale."
612    Sq_units = "The returned value is a dimensionless structure factor, $S(q)$."
613    info = make_info(kernel_module)
614    is_Sq = ("structure-factor" in info['category'])
615    subst = dict(id=info['id'].replace('_', '-'),
616                 name=info['name'],
617                 title=info['title'],
618                 parameters=make_partable(info['parameters']),
619                 returns=Sq_units if is_Sq else Iq_units,
620                 docs=kernel_module.__doc__)
621    return DOC_HEADER % subst
622
623
624
625def demo_time():
626    from .models import cylinder
627    import datetime
628    tic = datetime.datetime.now()
629    make(cylinder)
630    toc = (datetime.datetime.now() - tic).total_seconds()
631    print "time:", toc
632
633def main():
634    if len(sys.argv) <= 1:
635        print "usage: python -m sasmodels.generate modelname"
636    else:
637        name = sys.argv[1]
638        import sasmodels.models
639        __import__('sasmodels.models.' + name)
640        model = getattr(sasmodels.models, name)
641        source, _ = make(model)
642        print source
643
644if __name__ == "__main__":
645    main()
Note: See TracBrowser for help on using the repository browser.