source: sasmodels/sasmodels/gen.py @ ff7119b

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

docu update

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