source: sasmodels/sasmodels/gen.py @ 19dcb933

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

build docs for models

  • 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 *real*.  Depending on how the
40function is called, a macro will replace *real* with *float* or *double*.
41Unfortunately, MacOSX is picky about floating point constants, which
42should be defined with value + 'f' if they are of type *float* or just
43a bare value if they are of type *double*.  The solution is a macro
44*REAL(value)* which adds the 'f' if compiling for single precision
45floating point.  [Note: we could write a clever regular expression
46which automatically detects real-valued constants.  If we wanted to
47make our code more C-like, we could define variables with double but
48replace them with float before compiling for single precision.]
49
50OpenCL has a *sincos* function which can improve performance when both
51the *sin* and *cos* values are needed for a particular argument.  Since
52this function does not exist in C99, all use of *sincos* should be
53replaced by the macro *SINCOS(value,sn,cn)* where *sn* and *cn* are
54previously declared *real* values.  *value* may be an expression.  When
55compiled for systems without OpenCL, *SINCOS* will be replaced by
56*sin* and *cos* calls.
57
58If the input parameters are invalid, the scattering calculator should
59return a negative number. Particularly with polydispersity, there are
60some sets of shape parameters which lead to nonsensical forms, such
61as a capped cylinder where the cap radius is smaller than the
62cylinder radius.  The polydispersity calculation will ignore these points,
63effectively chopping the parameter weight distributions at the boundary
64of the infeasible region.  The resulting scattering will be set to
65background.  This will work correctly even when polydispersity is off.
66
67*ER* and *VR* are python functions which operate on parameter vectors.
68The constructor code will generate the necessary vectors for computing
69them with the desired polydispersity.
70
71The available kernel parameters are defined as a list, with each parameter
72defined as a sublist with the following elements:
73
74    *name* is the name that will be used in the call to the kernel
75    function and the name that will be displayed to the user.  Names
76    should be lower case, with words separated by underscore.  If
77    acronyms are used, the whole acronym should be upper case.
78
79    *units* should be one of *degrees* for angles, *Ang* for lengths,
80    *1e-6/Ang^2* for SLDs.
81
82    *default value* will be the initial value for  the model when it
83    is selected, or when an initial value is not otherwise specified.
84
85    [*lb*, *ub*] are the hard limits on the parameter value, used to limit
86    the polydispersity density function.  In the fit, the parameter limits
87    given to the fit are the limits  on the central value of the parameter.
88    If there is polydispersity, it will evaluate parameter values outside
89    the fit limits, but not outside the hard limits specified in the model.
90    If there are no limits, use +/-inf imported from numpy.
91
92    *type* indicates how the parameter will be used.  "volume" parameters
93    will be used in all functions.  "orientation" parameters will be used
94    in *Iqxy* and *Imagnetic*.  "magnetic* parameters will be used in
95    *Imagnetic* only.  If *type* is the empty string, the parameter will
96    be used in all of *Iq*, *Iqxy* and *Imagnetic*.
97
98    *description* is a short description of the parameter.  This will
99    be displayed in the parameter table and used as a tool tip for the
100    parameter value in the user interface.
101
102The kernel module must set variables defining the kernel meta data:
103
104    *name* is the model name
105
106    *title* is a short description of the model, suitable for a tool tip,
107    or a one line model summary in a table of models.
108
109    *description* is an extended description of the model to be displayed
110    while the model parameters are being edited.
111
112    *parameters* is the list of parameters.  Parameters in the kernel
113    functions must appear in the same order as they appear in the
114    parameters list.  Two additional parameters, *scale* and *background*
115    are added to the beginning of the parameter list.  They will show up
116    in the documentation as model parameters, but they are never sent to
117    the kernel functions.
118
119    *source* is the list of C-99 source files that must be joined to
120    create the OpenCL kernel functions.  The files defining the functions
121    need to be listed before the files which use the functions.
122
123    *ER* is a python function defining the effective radius.  If it is
124    not present, the effective radius is 0.
125
126    *VR* is a python function defining the volume ratio.  If it is not
127    present, the volume ratio is 1.
128
129    *form_volume*, *Iq*, *Iqxy*, *Imagnetic* are strings containing the
130    C source code for the body of the volume, Iq, and Iqxy functions
131    respectively.  These can also be defined in the last source file.
132
133An *info* dictionary is constructed from the kernel meta data and
134returned to the caller.  It includes the additional fields
135
136
137The model evaluator, function call sequence consists of q inputs and the return vector,
138followed by the loop value/weight vector, followed by the values for
139the non-polydisperse parameters, followed by the lengths of the
140polydispersity loops.  To construct the call for 1D models, the
141categories *fixed-1d* and *pd-1d* list the names of the parameters
142of the non-polydisperse and the polydisperse parameters respectively.
143Similarly, *fixed-2d* and *pd-2d* provide parameter names for 2D models.
144The *pd-rel* category is a set of those parameters which give
145polydispersitiy as a portion of the value (so a 10% length dispersity
146would use a polydispersity value of 0.1) rather than absolute
147dispersity such as an angle plus or minus 15 degrees.
148
149The *volume* category lists the volume parameters in order for calls
150to volume within the kernel (used for volume normalization) and for
151calls to ER and VR for effective radius and volume ratio respectively.
152
153The *orientation* and *magnetic* categories list the orientation and
154magnetic parameters.  These are used by the sasview interface.  The
155blank category is for parameters such as scale which don't have any
156other marking.
157
158The doc string at the start of the kernel module will be used to
159construct the model documentation web pages.  Embedded figures should
160appear in the subdirectory "img" beside the model definition, and tagged
161with the kernel module name to avoid collision with other models.  Some
162file systems are case-sensitive, so only use lower case characters for
163file names and extensions.
164
165
166The function :func:`make` loads the metadata from the module and returns
167the kernel source.  The function :func:`doc` extracts the doc string
168and adds the parameter table to the top.  The function :func:`sources`
169returns a list of files required by the model.
170"""
171
172# TODO: identify model files which have changed since loading and reload them.
173
174__all__ = ["make, doc", "sources"]
175
176import sys
177import os
178import os.path
179
180import numpy as np
181
182F64 = np.dtype('float64')
183F32 = np.dtype('float32')
184
185# Scale and background, which are parameters common to every form factor
186COMMON_PARAMETERS = [
187    [ "scale", "", 1, [0, np.inf], "", "Source intensity" ],
188    [ "background", "1/cm", 0, [0, np.inf], "", "Source background" ],
189    ]
190
191
192# Conversion from units defined in the parameter table for each model
193# to units displayed in the sphinx documentation.
194RST_UNITS = {
195    "Ang": "|Ang|",
196    "1/Ang^2": "|Ang^-2|",
197    "1e-6/Ang^2": "|1e-6Ang^-2|",
198    "degrees": "degree",
199    "1/cm": "|cm^-1|",
200    "": "None",
201    }
202
203# Headers for the parameters tables in th sphinx documentation
204PARTABLE_HEADERS = [
205    "Parameter",
206    "Description",
207    "Units",
208    "Default value",
209    ]
210
211# Minimum width for a default value (this is shorter than the column header
212# width, so will be ignored).
213PARTABLE_VALUE_WIDTH = 10
214
215# Header included before every kernel.
216# This makes sure that the appropriate math constants are defined, and does
217# whatever is required to make the kernel compile as pure C rather than
218# as an OpenCL kernel.
219KERNEL_HEADER = """\
220// GENERATED CODE --- DO NOT EDIT ---
221// Code is produced by sasmodels.gen from sasmodels/models/MODEL.c
222
223#ifdef __OPENCL_VERSION__
224# define USE_OPENCL
225#endif
226
227// If opencl is not available, then we are compiling a C function
228// Note: if using a C++ compiler, then define kernel as extern "C"
229#ifndef USE_OPENCL
230#  include <math.h>
231#  define REAL(x) (x)
232#  ifndef real
233#      define real double
234#  endif
235#  define global
236#  define local
237#  define constant const
238#  define kernel
239#  define SINCOS(angle,svar,cvar) do {svar=sin(angle);cvar=cos(angle);} while (0)
240#  define powr(a,b) pow(a,b)
241#else
242#  ifdef USE_SINCOS
243#    define SINCOS(angle,svar,cvar) svar=sincos(angle,&cvar)
244#  else
245#    define SINCOS(angle,svar,cvar) do {svar=sin(angle);cvar=cos(angle);} while (0)
246#  endif
247#endif
248
249// Standard mathematical constants:
250//   M_E, M_LOG2E, M_LOG10E, M_LN2, M_LN10, M_PI, M_PI_2=pi/2, M_PI_4=pi/4,
251//   M_1_PI=1/pi, M_2_PI=2/pi, M_2_SQRTPI=2/sqrt(pi), SQRT2, SQRT1_2=sqrt(1/2)
252// OpenCL defines M_constant_F for float constants, and nothing if double
253// is not enabled on the card, which is why these constants may be missing
254#ifndef M_PI
255#  define M_PI REAL(3.141592653589793)
256#endif
257#ifndef M_PI_2
258#  define M_PI_2 REAL(1.570796326794897)
259#endif
260#ifndef M_PI_4
261#  define M_PI_4 REAL(0.7853981633974483)
262#endif
263
264// Non-standard pi/180, used for converting between degrees and radians
265#ifndef M_PI_180
266#  define M_PI_180 REAL(0.017453292519943295)
267#endif
268"""
269
270
271# The I(q) kernel and the I(qx, qy) kernel have one and two q parameters
272# respectively, so the template builder will need to do extra work to
273# declare, initialize and pass the q parameters.
274KERNEL_1D = {
275    'fn': "Iq",
276    'q_par_decl': "global const real *q,",
277    'qinit': "const real qi = q[i];",
278    'qcall': "qi",
279    'qwork': ["q"],
280    }
281
282KERNEL_2D = {
283    'fn': "Iqxy",
284    'q_par_decl': "global const real *qx,\n    global const real *qy,",
285    'qinit': "const real qxi = qx[i];\n    const real qyi = qy[i];",
286    'qcall': "qxi, qyi",
287    'qwork': ["qx", "qy"],
288    }
289
290# Generic kernel template for the polydispersity loop.
291# This defines the opencl kernel that is available to the host.  The same
292# structure is used for Iq and Iqxy kernels, so extra flexibility is needed
293# for q parameters.  The polydispersity loop is built elsewhere and
294# substituted into this template.
295KERNEL_TEMPLATE = """\
296kernel void %(name)s(
297    %(q_par_decl)s
298    global real *result,
299#ifdef USE_OPENCL
300    global real *loops_g,
301#else
302    const int Nq,
303#endif
304    local real *loops,
305    const real cutoff,
306    %(par_decl)s
307    )
308{
309#ifdef USE_OPENCL
310  // copy loops info to local memory
311  event_t e = async_work_group_copy(loops, loops_g, (%(pd_length)s)*2, 0);
312  wait_group_events(1, &e);
313
314  int i = get_global_id(0);
315  int Nq = get_global_size(0);
316#endif
317
318#ifdef USE_OPENCL
319  if (i < Nq)
320#else
321  #pragma omp parallel for
322  for (int i=0; i < Nq; i++)
323#endif
324  {
325    %(qinit)s
326    real ret=REAL(0.0), norm=REAL(0.0);
327    real vol=REAL(0.0), norm_vol=REAL(0.0);
328%(loops)s
329    if (vol*norm_vol != REAL(0.0)) {
330      ret *= norm_vol/vol;
331    }
332    result[i] = scale*ret/norm+background;
333  }
334}
335"""
336
337# Polydispersity loop level.
338# This pulls the parameter value and weight from the looping vector in order
339# in preperation for a nested loop.
340LOOP_OPEN="""\
341for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) {
342  const real %(name)s = loops[2*(%(name)s_i%(offset)s)];
343  const real %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\
344"""
345
346# Polydispersity loop body.
347# This computes the weight, and if it is sufficient, calls the scattering
348# function and adds it to the total.  If there is a volume normalization,
349# it will also be added here.
350LOOP_BODY="""\
351const real weight = %(weight_product)s;
352if (weight > cutoff) {
353  const real I = %(fn)s(%(qcall)s, %(pcall)s);
354  if (I>=REAL(0.0)) { // scattering cannot be negative
355    ret += weight*I%(sasview_spherical)s;
356    norm += weight;
357    %(volume_norm)s
358  }
359  //else { printf("exclude qx,qy,I:%%g,%%g,%%g\\n",%(qcall)s,I); }
360}
361//else { printf("exclude weight:%%g\\n",weight); }\
362"""
363
364# Use this when integrating over orientation
365SPHERICAL_CORRECTION="""\
366// Correction factor for spherical integration p(theta) I(q) sin(theta) dtheta
367real spherical_correction = (Ntheta>1 ? fabs(sin(M_PI_180*theta)) : REAL(1.0));\
368"""
369# Use this to reproduce sasview behaviour
370SASVIEW_SPHERICAL_CORRECTION="""\
371// Correction factor for spherical integration p(theta) I(q) sin(theta) dtheta
372real spherical_correction = (Ntheta>1 ? fabs(cos(M_PI_180*theta))*M_PI_2 : REAL(1.0));\
373"""
374
375# Volume normalization.
376# If there are "volume" polydispersity parameters, then these will be used
377# to call the form_volume function from the user supplied kernel, and accumulate
378# a normalized weight.
379VOLUME_NORM="""const real vol_weight = %(weight)s;
380    vol += vol_weight*form_volume(%(pars)s);
381    norm_vol += vol_weight;\
382"""
383
384# functions defined as strings in the .py module
385WORK_FUNCTION="""\
386real %(name)s(%(pars)s);
387real %(name)s(%(pars)s)
388{
389%(body)s
390}\
391"""
392
393# Documentation header for the module, giving the model name, its short
394# description and its parameter table.  The remainder of the doc comes
395# from the module docstring.
396DOC_HEADER=""".. _%(name)s:
397
398%(label)s
399=======================================================
400
401%(title)s
402
403%(parameters)s
404
405The returned value is scaled to units of |cm^-1|.
406
407%(docs)s
408"""
409
410def indent(s, depth):
411    """
412    Indent a string of text with *depth* additional spaces on each line.
413    """
414    spaces = " "*depth
415    sep = "\n"+spaces
416    return spaces + sep.join(s.split("\n"))
417
418
419def kernel_name(info, is_2D):
420    """
421    Name of the exported kernel symbol.
422    """
423    return info['name'] + "_" + ("Iqxy" if is_2D else "Iq")
424
425
426def make_kernel(info, is_2D):
427    """
428    Build a kernel call from metadata supplied by the user.
429
430    *info* is the json object defined in the kernel file.
431
432    *form* is either "Iq" or "Iqxy".
433
434    This does not create a complete OpenCL kernel source, only the top
435    level kernel call with polydispersity and a call to the appropriate
436    Iq or Iqxy function.
437    """
438
439    # If we are building the Iqxy kernel, we need to propagate qx,qy
440    # parameters, otherwise we can
441    dim = "2d" if is_2D else "1d"
442    fixed_pars = info['partype']['fixed-'+dim]
443    pd_pars = info['partype']['pd-'+dim]
444    vol_pars = info['partype']['volume']
445    q_pars = KERNEL_2D if is_2D else KERNEL_1D
446    fn = q_pars['fn']
447
448    # Build polydispersity loops
449    depth = 4
450    offset = ""
451    loop_head = []
452    loop_end = []
453    for name in pd_pars:
454        subst = { 'name': name, 'offset': offset }
455        loop_head.append(indent(LOOP_OPEN%subst, depth))
456        loop_end.insert(0, (" "*depth) + "}")
457        offset += '+N'+name
458        depth += 2
459
460    # The volume parameters in the inner loop are used to call the volume()
461    # function in the kernel, with the parameters defined in vol_pars and the
462    # weight product defined in weight.  If there are no volume parameters,
463    # then there will be no volume normalization.
464    if vol_pars:
465        subst = {
466            'weight': "*".join(p+"_w" for p in vol_pars),
467            'pars': ", ".join(vol_pars),
468            }
469        volume_norm = VOLUME_NORM%subst
470    else:
471        volume_norm = ""
472
473    # Define the inner loop function call
474    # The parameters to the f(q,p1,p2...) call should occur in the same
475    # order as given in the parameter info structure.  This may be different
476    # from the parameter order in the call to the kernel since the kernel
477    # call places all fixed parameters before all polydisperse parameters.
478    fq_pars = [p[0] for p in info['parameters'][len(COMMON_PARAMETERS):]
479               if p[0] in set(fixed_pars+pd_pars)]
480    if False and "theta" in pd_pars:
481        spherical_correction = [indent(SPHERICAL_CORRECTION, depth)]
482        weights = [p+"_w" for p in pd_pars]+['spherical_correction']
483        sasview_spherical = ""
484    elif "theta" in pd_pars:
485        spherical_correction = [indent(SASVIEW_SPHERICAL_CORRECTION,depth)]
486        weights = [p+"_w" for p in pd_pars]
487        sasview_spherical = "*spherical_correction"
488    else:
489        spherical_correction = []
490        weights = [p+"_w" for p in pd_pars]
491        sasview_spherical = ""
492    subst = {
493        'weight_product': "*".join(weights),
494        'volume_norm': volume_norm,
495        'fn': fn,
496        'qcall': q_pars['qcall'],
497        'pcall': ", ".join(fq_pars), # skip scale and background
498        'sasview_spherical': sasview_spherical,
499        }
500    loop_body = [indent(LOOP_BODY%subst, depth)]
501    loops = "\n".join(loop_head+spherical_correction+loop_body+loop_end)
502
503    # declarations for non-pd followed by pd pars
504    # e.g.,
505    #     const real sld,
506    #     const int Nradius
507    fixed_par_decl = ",\n    ".join("const real %s"%p for p in fixed_pars)
508    pd_par_decl = ",\n    ".join("const int N%s"%p for p in pd_pars)
509    if fixed_par_decl and pd_par_decl:
510        par_decl = ",\n    ".join((fixed_par_decl, pd_par_decl))
511    elif fixed_par_decl:
512        par_decl = fixed_par_decl
513    else:
514        par_decl = pd_par_decl
515
516    # Finally, put the pieces together in the kernel.
517    subst = {
518        # kernel name is, e.g., cylinder_Iq
519        'name': kernel_name(info, is_2D),
520        # to declare, e.g., global real q[],
521        'q_par_decl': q_pars['q_par_decl'],
522        # to declare, e.g., real sld, int Nradius, int Nlength
523        'par_decl': par_decl,
524        # to copy global to local pd pars we need, e.g., Nradius+Nlength
525        'pd_length': "+".join('N'+p for p in pd_pars),
526        # the q initializers, e.g., real qi = q[i];
527        'qinit': q_pars['qinit'],
528        # the actual polydispersity loop
529        'loops': loops,
530        }
531    kernel = KERNEL_TEMPLATE%subst
532
533    # If the working function is defined in the kernel metadata as a
534    # string, translate the string to an actual function definition
535    # and put it before the kernel.
536    if info[fn]:
537        subst = {
538            'name': fn,
539            'pars': ", ".join("real "+p for p in q_pars['qwork']+fq_pars),
540            'body': info[fn],
541            }
542        kernel = "\n".join((WORK_FUNCTION%subst, kernel))
543    return kernel
544
545def make_partable(pars):
546    """
547    Generate the parameter table to include in the sphinx documentation.
548    """
549    pars = COMMON_PARAMETERS + pars
550    column_widths = [
551        max(len(p[0]) for p in pars),
552        max(len(p[-1]) for p in pars),
553        max(len(RST_UNITS[p[1]]) for p in pars),
554        PARTABLE_VALUE_WIDTH,
555        ]
556    column_widths = [max(w, len(h))
557                     for w,h in zip(column_widths, PARTABLE_HEADERS)]
558
559    sep = " ".join("="*w for w in column_widths)
560    lines = [
561        sep,
562        " ".join("%-*s"%(w,h) for w,h in zip(column_widths, PARTABLE_HEADERS)),
563        sep,
564        ]
565    for p in pars:
566        lines.append(" ".join([
567            "%-*s"%(column_widths[0],p[0]),
568            "%-*s"%(column_widths[1],p[-1]),
569            "%-*s"%(column_widths[2],RST_UNITS[p[1]]),
570            "%*g"%(column_widths[3],p[2]),
571            ]))
572    lines.append(sep)
573    return "\n".join(lines)
574
575def _search(search_path, filename):
576    """
577    Find *filename* in *search_path*.
578
579    Raises ValueError if file does not exist.
580    """
581    for path in search_path:
582        target = os.path.join(path, filename)
583        if os.path.exists(target):
584            return target
585    raise ValueError("%r not found in %s"%(filename, search_path))
586
587def sources(info):
588    """
589    Return a list of the sources file paths for the module.
590    """
591    from os.path import abspath, dirname, join as joinpath
592    search_path = [ dirname(info['filename']),
593                    abspath(joinpath(dirname(__file__),'models')) ]
594    return [_search(search_path, f) for f in info['source']]
595
596def make_model(info):
597    """
598    Generate the code for the kernel defined by info, using source files
599    found in the given search path.
600    """
601    source = [open(f).read() for f in sources(info)]
602    # If the form volume is defined as a string, then wrap it in a
603    # function definition and place it after the external sources but
604    # before the kernel functions.  If the kernel functions are strings,
605    # they will be translated in the make_kernel call.
606    if info['form_volume']:
607        subst = {
608            'name': "form_volume",
609            'pars': ", ".join("real "+p for p in info['partype']['volume']),
610            'body': info['form_volume'],
611            }
612        source.append(WORK_FUNCTION%subst)
613    kernel_Iq = make_kernel(info, is_2D=False)
614    kernel_Iqxy = make_kernel(info, is_2D=True)
615    kernel = "\n\n".join([KERNEL_HEADER]+source+[kernel_Iq, kernel_Iqxy])
616    return kernel
617
618def categorize_parameters(pars):
619    """
620    Build parameter categories out of the the parameter definitions.
621
622    Returns a dictionary of categories.
623    """
624    partype = {
625        'volume': [], 'orientation': [], 'magnetic': [], '': [],
626        'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [],
627        'pd-rel': set(),
628    }
629
630    for p in pars:
631        name,ptype = p[0],p[4]
632        if ptype == 'volume':
633            partype['pd-1d'].append(name)
634            partype['pd-2d'].append(name)
635            partype['pd-rel'].add(name)
636        elif ptype == 'magnetic':
637            partype['fixed-2d'].append(name)
638        elif ptype == 'orientation':
639            partype['pd-2d'].append(name)
640        elif ptype == '':
641            partype['fixed-1d'].append(name)
642            partype['fixed-2d'].append(name)
643        else:
644            raise ValueError("unknown parameter type %r"%ptype)
645        partype[ptype].append(name)
646
647    return partype
648
649def make(kernel_module):
650    """
651    Build an OpenCL/ctypes function from the definition in *kernel_module*.
652
653    The module can be loaded with a normal python import statement if you
654    know which module you need, or with __import__('sasmodels.model.'+name)
655    if the name is in a string.
656    """
657    # TODO: allow Iq and Iqxy to be defined in python
658    from os.path import abspath
659    #print kernelfile
660    info = dict(
661        filename = abspath(kernel_module.__file__),
662        name = kernel_module.name,
663        title = kernel_module.title,
664        description = kernel_module.description,
665        parameters = COMMON_PARAMETERS + kernel_module.parameters,
666        source = getattr(kernel_module, 'source', []),
667        )
668    # Fill in attributes which default to None
669    info.update((k,getattr(kernel_module, k, None))
670                for k in ('ER', 'VR', 'form_volume', 'Iq', 'Iqxy'))
671    # Fill in the derived attributes
672    info['limits'] = dict((p[0],p[3]) for p in info['parameters'])
673    info['partype'] = categorize_parameters(info['parameters'])
674
675    source = make_model(info)
676
677    return source, info
678
679def doc(kernel_module):
680    """
681    Return the documentation for the model.
682    """
683    subst = dict(name=kernel_module.name.replace('_','-'),
684                 label=" ".join(kernel_module.name.split('_')).capitalize(),
685                 title=kernel_module.title,
686                 parameters=make_partable(kernel_module.parameters),
687                 docs=kernel_module.__doc__)
688    return DOC_HEADER%subst
689
690
691
692def demo_time():
693    import datetime
694    tic = datetime.datetime.now()
695    toc = lambda: (datetime.datetime.now()-tic).total_seconds()
696    path = os.path.dirname("__file__")
697    doc, c = make_model(os.path.join(path, "models", "cylinder.c"))
698    print "time:",toc()
699
700def demo():
701    from os.path import join as joinpath, dirname
702    c, info, doc = make_model(joinpath(dirname(__file__), "models", "cylinder.c"))
703    #print doc
704    #print c
705
706if __name__ == "__main__":
707    demo()
Note: See TracBrowser for help on using the repository browser.