Changes in sasmodels/generate.py [2d81cfe:108e70e] in sasmodels


Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/generate.py

    r2d81cfe r108e70e  
    77    particular dimensions averaged over all orientations. 
    88 
    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. 
     9    *Iqac(qab, qc, p1, p2, ...)* returns the scattering at qab, qc 
     10    for a rotationally symmetric form with particular dimensions. 
     11    qab, qc are determined from shape orientation and scattering angles. 
     12    This call is used if the shape has orientation parameters theta and phi. 
     13 
     14    *Iqabc(qa, qb, qc, p1, p2, ...)* returns the scattering at qa, qb, qc 
     15    for a form with particular dimensions.  qa, qb, qc are determined from 
     16    shape orientation and scattering angles. This call is used if the shape 
     17    has orientation parameters theta, phi and psi. 
     18 
     19    *Iqxy(qx, qy, p1, p2, ...)* returns the scattering at qx, qy.  Use this 
     20    to create an arbitrary 2D theory function, needed for q-dependent 
     21    background functions and for models with non-uniform magnetism. 
    1522 
    1623    *form_volume(p1, p2, ...)* returns the volume of the form with particular 
     
    3138scale and background parameters for each model. 
    3239 
    33 *Iq*, *Iqxy*, *Imagnetic* and *form_volume* should be stylized C-99 
    34 functions written for OpenCL.  All functions need prototype declarations 
    35 even if the are defined before they are used.  OpenCL does not support 
    36 *#include* preprocessor directives, so instead the list of includes needs 
    37 to be given as part of the metadata in the kernel module definition. 
    38 The included files should be listed using a path relative to the kernel 
    39 module, or if using "lib/file.c" if it is one of the standard includes 
    40 provided with the sasmodels source.  The includes need to be listed in 
    41 order so that functions are defined before they are used. 
     40C code should be stylized C-99 functions written for OpenCL. All functions 
     41need prototype declarations even if the are defined before they are used. 
     42Although OpenCL supports *#include* preprocessor directives, the list of 
     43includes should be given as part of the metadata in the kernel module 
     44definition. The included files should be listed using a path relative to the 
     45kernel module, or if using "lib/file.c" if it is one of the standard includes 
     46provided with the sasmodels source. The includes need to be listed in order 
     47so that functions are defined before they are used. 
    4248 
    4349Floating point values should be declared as *double*.  For single precision 
     
    107113    present, the volume ratio is 1. 
    108114 
    109     *form_volume*, *Iq*, *Iqxy*, *Imagnetic* are strings containing the 
    110     C source code for the body of the volume, Iq, and Iqxy functions 
     115    *form_volume*, *Iq*, *Iqac*, *Iqabc* are strings containing 
     116    the C source code for the body of the volume, Iq, and Iqac functions 
    111117    respectively.  These can also be defined in the last source file. 
    112118 
    113     *Iq* and *Iqxy* also be instead be python functions defining the 
     119    *Iq*, *Iqac*, *Iqabc* also be instead be python functions defining the 
    114120    kernel.  If they are marked as *Iq.vectorized = True* then the 
    115121    kernel is passed the entire *q* vector at once, otherwise it is 
     
    168174from zlib import crc32 
    169175from inspect import currentframe, getframeinfo 
     176import logging 
    170177 
    171178import numpy as np  # type: ignore 
     
    181188    pass 
    182189# pylint: enable=unused-import 
     190 
     191logger = logging.getLogger(__name__) 
    183192 
    184193# jitter projection to use in the kernel code.  See explore/jitter.py 
     
    270279""" 
    271280 
     281 
     282def set_integration_size(info, n): 
     283    # type: (ModelInfo, int) -> None 
     284    """ 
     285    Update the model definition, replacing the gaussian integration with 
     286    a gaussian integration of a different size. 
     287 
     288    Note: this really ought to be a method in modelinfo, but that leads to 
     289    import loops. 
     290    """ 
     291    if (info.source and any(lib.startswith('lib/gauss') for lib in info.source)): 
     292        import os.path 
     293        from .gengauss import gengauss 
     294        path = os.path.join(MODEL_PATH, "lib", "gauss%d.c"%n) 
     295        if not os.path.exists(path): 
     296            gengauss(n, path) 
     297        info.source = ["lib/gauss%d.c"%n if lib.startswith('lib/gauss') 
     298                        else lib for lib in info.source] 
    272299 
    273300def format_units(units): 
     
    608635 
    609636""" 
    610 def _gen_fn(name, pars, body, filename, line): 
    611     # type: (str, List[Parameter], str, str, int) -> str 
     637def _gen_fn(model_info, name, pars): 
     638    # type: (ModelInfo, str, List[Parameter]) -> str 
    612639    """ 
    613640    Generate a function given pars and body. 
     
    621648    """ 
    622649    par_decl = ', '.join(p.as_function_argument() for p in pars) if pars else 'void' 
     650    body = getattr(model_info, name) 
     651    filename = model_info.filename 
     652    # Note: if symbol is defined strangely in the module then default it to 1 
     653    lineno = model_info.lineno.get(name, 1) 
    623654    return _FN_TEMPLATE % { 
    624655        'name': name, 'pars': par_decl, 'body': body, 
    625         'filename': filename.replace('\\', '\\\\'), 'line': line, 
     656        'filename': filename.replace('\\', '\\\\'), 'line': lineno, 
    626657    } 
    627658 
     
    638669 
    639670# type in IQXY pattern could be single, float, double, long double, ... 
    640 _IQXY_PATTERN = re.compile("^((inline|static) )? *([a-z ]+ )? *Iqxy *([(]|$)", 
     671_IQXY_PATTERN = re.compile(r"(^|\s)double\s+I(?P<mode>q(ab?c|xy))\s*[(]", 
    641672                           flags=re.MULTILINE) 
    642 def _have_Iqxy(sources): 
     673def find_xy_mode(source): 
    643674    # type: (List[str]) -> bool 
    644675    """ 
    645     Return true if any file defines Iqxy. 
     676    Return the xy mode as qa, qac, qabc or qxy. 
    646677 
    647678    Note this is not a C parser, and so can be easily confused by 
    648679    non-standard syntax.  Also, it will incorrectly identify the following 
    649     as having Iqxy:: 
     680    as having 2D models:: 
    650681 
    651682        /* 
    652         double Iqxy(qx, qy, ...) { ... fill this in later ... } 
     683        double Iqac(qab, qc, ...) { ... fill this in later ... } 
    653684        */ 
    654685 
    655     If you want to comment out an Iqxy function, use // on the front of the 
    656     line instead. 
    657     """ 
    658     for _path, code in sources: 
    659         if _IQXY_PATTERN.search(code): 
    660             return True 
    661     return False 
    662  
    663  
    664 def _add_source(source, code, path): 
     686    If you want to comment out the function, use // on the front of the 
     687    line:: 
     688 
     689        /* 
     690        // double Iqac(qab, qc, ...) { ... fill this in later ... } 
     691        */ 
     692 
     693    """ 
     694    for code in source: 
     695        m = _IQXY_PATTERN.search(code) 
     696        if m is not None: 
     697            return m.group('mode') 
     698    return 'qa' 
     699 
     700 
     701def _add_source(source, code, path, lineno=1): 
    665702    """ 
    666703    Add a file to the list of source code chunks, tagged with path and line. 
    667704    """ 
    668705    path = path.replace('\\', '\\\\') 
    669     source.append('#line 1 "%s"' % path) 
     706    source.append('#line %d "%s"' % (lineno, path)) 
    670707    source.append(code) 
    671708 
     
    698735    user_code = [(f, open(f).read()) for f in model_sources(model_info)] 
    699736 
    700     # What kind of 2D model do we need? 
    701     xy_mode = ('qa' if not _have_Iqxy(user_code) and not isinstance(model_info.Iqxy, str) 
    702                else 'qac' if not partable.is_asymmetric 
    703                else 'qabc') 
    704  
    705737    # Build initial sources 
    706738    source = [] 
     
    709741        _add_source(source, code, path) 
    710742 
     743    if model_info.c_code: 
     744        _add_source(source, model_info.c_code, model_info.filename, 
     745                    lineno=model_info.lineno.get('c_code', 1)) 
     746 
    711747    # Make parameters for q, qx, qy so that we can use them in declarations 
    712     q, qx, qy = [Parameter(name=v) for v in ('q', 'qx', 'qy')] 
     748    q, qx, qy, qab, qa, qb, qc \ 
     749        = [Parameter(name=v) for v in 'q qx qy qab qa qb qc'.split()] 
    713750    # Generate form_volume function, etc. from body only 
    714751    if isinstance(model_info.form_volume, str): 
    715752        pars = partable.form_volume_parameters 
    716         source.append(_gen_fn('form_volume', pars, model_info.form_volume, 
    717                               model_info.filename, model_info._form_volume_line)) 
     753        source.append(_gen_fn(model_info, 'form_volume', pars)) 
    718754    if isinstance(model_info.Iq, str): 
    719755        pars = [q] + partable.iq_parameters 
    720         source.append(_gen_fn('Iq', pars, model_info.Iq, 
    721                               model_info.filename, model_info._Iq_line)) 
     756        source.append(_gen_fn(model_info, 'Iq', pars)) 
    722757    if isinstance(model_info.Iqxy, str): 
    723         pars = [qx, qy] + partable.iqxy_parameters 
    724         source.append(_gen_fn('Iqxy', pars, model_info.Iqxy, 
    725                               model_info.filename, model_info._Iqxy_line)) 
     758        pars = [qx, qy] + partable.iq_parameters + partable.orientation_parameters 
     759        source.append(_gen_fn(model_info, 'Iqxy', pars)) 
     760    if isinstance(model_info.Iqac, str): 
     761        pars = [qab, qc] + partable.iq_parameters 
     762        source.append(_gen_fn(model_info, 'Iqac', pars)) 
     763    if isinstance(model_info.Iqabc, str): 
     764        pars = [qa, qb, qc] + partable.iq_parameters 
     765        source.append(_gen_fn(model_info, 'Iqabc', pars)) 
     766 
     767    # What kind of 2D model do we need?  Is it consistent with the parameters? 
     768    xy_mode = find_xy_mode(source) 
     769    if xy_mode == 'qabc' and not partable.is_asymmetric: 
     770        raise ValueError("asymmetric oriented models need to define Iqabc") 
     771    elif xy_mode == 'qac' and partable.is_asymmetric: 
     772        raise ValueError("symmetric oriented models need to define Iqac") 
     773    elif not partable.orientation_parameters and xy_mode in ('qac', 'qabc'): 
     774        raise ValueError("Unexpected function I%s for unoriented shape"%xy_mode) 
     775    elif partable.orientation_parameters and xy_mode not in ('qac', 'qabc'): 
     776        if xy_mode == 'qxy': 
     777            logger.warn("oriented shapes should define Iqac or Iqabc") 
     778        else: 
     779            raise ValueError("Expected function Iqac or Iqabc for oriented shape") 
    726780 
    727781    # Define the parameter table 
     
    749803    if xy_mode == 'qabc': 
    750804        pars = ",".join(["_qa", "_qb", "_qc"] + model_refs) 
    751         call_iqxy = "#define CALL_IQ_ABC(_qa,_qb,_qc,_v) Iqxy(%s)" % pars 
     805        call_iqxy = "#define CALL_IQ_ABC(_qa,_qb,_qc,_v) Iqabc(%s)" % pars 
    752806        clear_iqxy = "#undef CALL_IQ_ABC" 
    753807    elif xy_mode == 'qac': 
    754808        pars = ",".join(["_qa", "_qc"] + model_refs) 
    755         call_iqxy = "#define CALL_IQ_AC(_qa,_qc,_v) Iqxy(%s)" % pars 
     809        call_iqxy = "#define CALL_IQ_AC(_qa,_qc,_v) Iqac(%s)" % pars 
    756810        clear_iqxy = "#undef CALL_IQ_AC" 
    757     else:  # xy_mode == 'qa' 
     811    elif xy_mode == 'qa': 
    758812        pars = ",".join(["_qa"] + model_refs) 
    759813        call_iqxy = "#define CALL_IQ_A(_qa,_v) Iq(%s)" % pars 
    760814        clear_iqxy = "#undef CALL_IQ_A" 
     815    elif xy_mode == 'qxy': 
     816        orientation_refs = _call_pars("_v.", partable.orientation_parameters) 
     817        pars = ",".join(["_qx", "_qy"] + model_refs + orientation_refs) 
     818        call_iqxy = "#define CALL_IQ_XY(_qx,_qy,_v) Iqxy(%s)" % pars 
     819        clear_iqxy = "#undef CALL_IQ_XY" 
     820        if partable.orientation_parameters: 
     821            call_iqxy += "\n#define HAVE_THETA" 
     822            clear_iqxy += "\n#undef HAVE_THETA" 
     823        if partable.is_asymmetric: 
     824            call_iqxy += "\n#define HAVE_PSI" 
     825            clear_iqxy += "\n#undef HAVE_PSI" 
     826 
    761827 
    762828    magpars = [k-2 for k, p in enumerate(partable.call_parameters) 
Note: See TracChangeset for help on using the changeset viewer.