source: sasmodels/sasmodels/core.py @ 17bbadd

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

refactor so all model defintion queries use model_info; better documentation of model_info structure; initial implementation of product model (broken)

  • Property mode set to 100644
File size: 7.7 KB
Line 
1"""
2Core model handling routines.
3"""
4
5from os.path import basename, dirname, join as joinpath
6from glob import glob
7
8import numpy as np
9
10from . import models
11from . import weights
12from . import generate
13
14from . import kernelpy
15from . import kerneldll
16try:
17    from . import kernelcl
18    HAVE_OPENCL = True
19except:
20    HAVE_OPENCL = False
21
22__all__ = [
23    "list_models", "load_model_info", "precompile_dll",
24    "build_model", "make_kernel", "call_kernel", "call_ER_VR",
25]
26
27def list_models():
28    """
29    Return the list of available models on the model path.
30    """
31    root = dirname(__file__)
32    files = sorted(glob(joinpath(root, 'models', "[a-zA-Z]*.py")))
33    available_models = [basename(f)[:-3] for f in files]
34    return available_models
35
36
37def load_model_info(model_name):
38    """
39    Load a model definition given the model name.
40
41    This returns a handle to the module defining the model.  This can be
42    used with functions in generate to build the docs or extract model info.
43    """
44    __import__('sasmodels.models.'+model_name)
45    kernel_module = getattr(models, model_name, None)
46    return generate.make_model_info(kernel_module)
47
48
49def precompile_dll(model_name, dtype="double"):
50    """
51    Precompile the dll for a model.
52
53    Returns the path to the compiled model, or None if the model is a pure
54    python model.
55
56    This can be used when build the windows distribution of sasmodels
57    (which may be missing the OpenCL driver and the dll compiler), or
58    otherwise sharing models with windows users who do not have a compiler.
59
60    See :func:`sasmodels.kerneldll.make_dll` for details on controlling the
61    dll path and the allowed floating point precision.
62    """
63    model_info = load_model_info(model_name)
64    source = generate.make_source(model_info)
65    return kerneldll.make_dll(source, model_info, dtype=dtype) if source else None
66
67
68def isstr(s):
69    """
70    Return True if *s* is a string-like object.
71    """
72    try: s + ''
73    except: return False
74    return True
75
76def build_model(model_info, dtype=None, platform="ocl"):
77    """
78    Prepare the model for the default execution platform.
79
80    This will return an OpenCL model, a DLL model or a python model depending
81    on the model and the computing platform.
82
83    *model_info* is the model definition structure returned from
84    :func:`load_model_info`.
85
86    *dtype* indicates whether the model should use single or double precision
87    for the calculation. Any valid numpy single or double precision identifier
88    is valid, such as 'single', 'f', 'f32', or np.float32 for single, or
89    'double', 'd', 'f64'  and np.float64 for double.  If *None*, then use
90    'single' unless the model defines single=False.
91
92    *platform* should be "dll" to force the dll to be used for C models,
93    otherwise it uses the default "ocl".
94    """
95    source = generate.make_source(model_info)
96    if dtype is None:
97        dtype = 'single' if model_info['single'] else 'double'
98    if callable(model_info.get('Iq', None)):
99        return kernelpy.PyModel(model_info)
100
101    ## for debugging:
102    ##  1. uncomment open().write so that the source will be saved next time
103    ##  2. run "python -m sasmodels.direct_model $MODELNAME" to save the source
104    ##  3. recomment the open.write() and uncomment open().read()
105    ##  4. rerun "python -m sasmodels.direct_model $MODELNAME"
106    ##  5. uncomment open().read() so that source will be regenerated from model
107    # open(model_info['name']+'.c','w').write(source)
108    # source = open(model_info['name']+'.cl','r').read()
109
110    if (platform == "dll"
111            or not HAVE_OPENCL
112            or not kernelcl.environment().has_type(dtype)):
113        return kerneldll.load_dll(source, model_info, dtype)
114    else:
115        return kernelcl.GpuModel(source, model_info, dtype)
116
117def make_kernel(model, q_vectors):
118    """
119    Return a computation kernel from the model definition and the q input.
120    """
121    return model(q_vectors)
122
123def get_weights(model_info, pars, name):
124    """
125    Generate the distribution for parameter *name* given the parameter values
126    in *pars*.
127
128    Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma"
129    from the *pars* dictionary for parameter value and parameter dispersion.
130    """
131    relative = name in model_info['partype']['pd-rel']
132    limits = model_info['limits'][name]
133    disperser = pars.get(name+'_pd_type', 'gaussian')
134    value = pars.get(name, model_info['defaults'][name])
135    npts = pars.get(name+'_pd_n', 0)
136    width = pars.get(name+'_pd', 0.0)
137    nsigma = pars.get(name+'_pd_nsigma', 3.0)
138    value, weight = weights.get_weights(
139        disperser, npts, width, nsigma, value, limits, relative)
140    return value, weight / np.sum(weight)
141
142def dispersion_mesh(pars):
143    """
144    Create a mesh grid of dispersion parameters and weights.
145
146    Returns [p1,p2,...],w where pj is a vector of values for parameter j
147    and w is a vector containing the products for weights for each
148    parameter set in the vector.
149    """
150    value, weight = zip(*pars)
151    if len(value) > 1:
152        value = [v.flatten() for v in np.meshgrid(*value)]
153        weight = np.vstack([v.flatten() for v in np.meshgrid(*weight)])
154        weight = np.prod(weight, axis=0)
155    return value, weight
156
157def call_kernel(kernel, pars, cutoff=0):
158    """
159    Call *kernel* returned from :func:`make_kernel` with parameters *pars*.
160
161    *cutoff* is the limiting value for the product of dispersion weights used
162    to perform the multidimensional dispersion calculation more quickly at a
163    slight cost to accuracy. The default value of *cutoff=0* integrates over
164    the entire dispersion cube.  Using *cutoff=1e-5* can be 50% faster, but
165    with an error of about 1%, which is usually less than the measurement
166    uncertainty.
167    """
168    fixed_pars = [pars.get(name, kernel.info['defaults'][name])
169                  for name in kernel.fixed_pars]
170    pd_pars = [get_weights(kernel.info, pars, name) for name in kernel.pd_pars]
171    return kernel(fixed_pars, pd_pars, cutoff=cutoff)
172
173def call_ER_VR(model_info, vol_pars):
174    """
175    Return effect radius and volume ratio for the model.
176
177    *info* is either *kernel.info* for *kernel=make_kernel(model,q)*
178    or *model.info*.
179
180    *pars* are the parameters as expected by :func:`call_kernel`.
181    """
182    ER = model_info.get('ER', None)
183    VR = model_info.get('VR', None)
184    value, weight = dispersion_mesh(vol_pars)
185
186    individual_radii = ER(*value) if ER else 1.0
187    whole, part = VR(*value) if VR else (1.0, 1.0)
188
189    effect_radius = np.sum(weight*individual_radii) / np.sum(weight)
190    volume_ratio = np.sum(weight*part)/np.sum(weight*whole)
191    return effect_radius, volume_ratio
192
193
194def call_ER(info, pars):
195    """
196    Call the model ER function using *pars*.
197    *info* is either *model.info* if you have a loaded model, or *kernel.info*
198    if you have a model kernel prepared for evaluation.
199    """
200    ER = info.get('ER', None)
201    if ER is None:
202        return 1.0
203    else:
204        vol_pars = [get_weights(info, pars, name)
205                    for name in info['partype']['volume']]
206        value, weight = dispersion_mesh(vol_pars)
207        individual_radii = ER(*value)
208        #print(values[0].shape, weights.shape, fv.shape)
209        return np.sum(weight*individual_radii) / np.sum(weight)
210
211def call_VR(info, pars):
212    """
213    Call the model VR function using *pars*.
214    *info* is either *model.info* if you have a loaded model, or *kernel.info*
215    if you have a model kernel prepared for evaluation.
216    """
217    VR = info.get('VR', None)
218    if VR is None:
219        return 1.0
220    else:
221        vol_pars = [get_weights(info, pars, name)
222                    for name in info['partype']['volume']]
223        value, weight = dispersion_mesh(vol_pars)
224        whole, part = VR(*value)
225        return np.sum(weight*part)/np.sum(weight*whole)
226
227# TODO: remove call_ER, call_VR
228
Note: See TracBrowser for help on using the repository browser.