Changes in / [0ce5710:62cf915] in sasmodels
- Files:
-
- 7 deleted
- 49 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/developer/index.rst
rb85be2d r56fc97a 3 3 *************************** 4 4 5 .. toctree::6 :numbered: 47 :maxdepth: 48 5 9 calculator.rst -
doc/genapi.py
ra5b8477 r3d5c6f8 59 59 #('alignment', 'GPU data alignment [unused]'), 60 60 ('bumps_model', 'Bumps interface'), 61 ('compare', 'Compare models on different compute engines'),62 61 ('convert', 'Sasview to sasmodel converter'), 63 62 ('core', 'Model access'), 64 ('data', 'Data layout and plotting routines'),65 ('details', 'Parameter packing for kernel calls'),66 63 ('direct_model', 'Simple interface'), 67 64 ('exception', 'Annotate exceptions'), 68 #('frozendict', 'Freeze a dictionary to make it immutable'),69 65 ('generate', 'Model parser'), 70 ('kernel', 'Evaluator type definitions'),71 66 ('kernelcl', 'OpenCL model evaluator'), 72 67 ('kerneldll', 'Ctypes model evaluator'), 73 68 ('kernelpy', 'Python model evaluator'), 74 ('list_pars', 'Identify all parameters in all models'),75 ('mixture', 'Mixture model evaluator'),76 69 ('model_test', 'Unit test support'), 77 ('modelinfo', 'Parameter and model definitions'),78 ('product', 'Product model evaluator'),79 70 ('resolution', '1-D resolution functions'), 80 71 ('resolution2d', '2-D resolution functions'), 81 72 ('sasview_model', 'Sasview interface'), 82 ('sesans', 'SESANS calculation routines'), 73 ('sesans', 'SESANS model evaluator'), 74 ('weights', 'Distribution functions'), 83 75 #('transition', 'Model stepper for automatic model selection'), 84 ('weights', 'Distribution functions'),85 76 ] 86 77 package='sasmodels' -
doc/genmodel.py
ra5b8477 r89f4163 1 from __future__ import print_function2 3 1 import sys, os, math, re 4 2 import numpy as np 5 3 import matplotlib.pyplot as plt 4 import pylab 6 5 sys.path.insert(0, os.path.abspath('..')) 7 6 from sasmodels import generate, core … … 9 8 from sasmodels.data import empty_data1D, empty_data2D 10 9 11 try: 12 from typing import Dict, Any 13 except ImportError: 14 pass 15 else: 16 from matplotlib.axes import Axes 17 from sasmodels.kernel import KernelModel 18 from sasmodels.modelinfo import ModelInfo 10 11 # Convert ../sasmodels/models/name.py to name 12 model_name = os.path.basename(sys.argv[1])[:-3] 13 model_info = core.load_model_info(model_name) 14 model = core.build_model(model_info) 15 16 # Load the doc string from the module definition file and store it in rst 17 docstr = generate.make_doc(model_info) 18 19 20 # Calculate 1D curve for default parameters 21 pars = dict((p.name, p.default) for p in model_info['parameters']) 22 23 # Plotting ranges and options 24 opts = { 25 'xscale' : 'log', 26 'yscale' : 'log' if not model_info['structure_factor'] else 'linear', 27 'zscale' : 'log' if not model_info['structure_factor'] else 'linear', 28 'q_min' : 0.001, 29 'q_max' : 1.0, 30 'nq' : 1000, 31 'nq2d' : 1000, 32 'vmin' : 1e-3, # floor for the 2D data results 33 'qx_max' : 0.5, 34 #'colormap' : 'gist_ncar', 35 'colormap' : 'nipy_spectral', 36 #'colormap' : 'jet', 37 } 38 19 39 20 40 def plot_1d(model, opts, ax): 21 # type: (KernelModel, Dict[str, Any], Axes) -> None22 """23 Create a 1-D image.24 """25 41 q_min, q_max, nq = opts['q_min'], opts['q_max'], opts['nq'] 26 42 q_min = math.log10(q_min) … … 31 47 Iq1D = calculator() 32 48 33 ax.plot(q, Iq1D, color='blue', lw=2, label=model .info.name)49 ax.plot(q, Iq1D, color='blue', lw=2, label=model_info['name']) 34 50 ax.set_xlabel(r'$Q \/(\AA^{-1})$') 35 51 ax.set_ylabel(r'$I(Q) \/(\mathrm{cm}^{-1})$') … … 39 55 40 56 def plot_2d(model, opts, ax): 41 # type: (KernelModel, Dict[str, Any], Axes) -> None42 """43 Create a 2-D image.44 """45 57 qx_max, nq2d = opts['qx_max'], opts['nq2d'] 46 q = np.linspace(-qx_max, qx_max, nq2d) # type: np.ndarray58 q = np.linspace(-qx_max, qx_max, nq2d) 47 59 data2d = empty_data2D(q, resolution=0.0) 48 60 calculator = DirectModel(data2d, model) … … 52 64 Iq2D = np.log(np.clip(Iq2D, opts['vmin'], np.inf)) 53 65 ax.imshow(Iq2D, interpolation='nearest', aspect=1, origin='lower', 54 66 extent=[-qx_max, qx_max, -qx_max, qx_max], cmap=opts['colormap']) 55 67 ax.set_xlabel(r'$Q_x \/(\AA^{-1})$') 56 68 ax.set_ylabel(r'$Q_y \/(\AA^{-1})$') 57 69 58 def figfile(model_info): 59 # type: (ModelInfo) -> str 60 return model_info.id + '_autogenfig.png' 70 # Generate image 71 fig_height = 3.0 # in 72 fig_left = 0.6 # in 73 fig_right = 0.5 # in 74 fig_top = 0.6*0.25 # in 75 fig_bottom = 0.6*0.75 76 if model_info['has_2d']: 77 plot_height = fig_height - (fig_top+fig_bottom) 78 plot_width = plot_height 79 fig_width = 2*(plot_width + fig_left + fig_right) 80 aspect = (fig_width, fig_height) 81 ratio = aspect[0]/aspect[1] 82 ax_left = fig_left/fig_width 83 ax_bottom = fig_bottom/fig_height 84 ax_height = plot_height/fig_height 85 ax_width = ax_height/ratio # square axes 86 fig = plt.figure(figsize=aspect) 87 ax2d = fig.add_axes([0.5+ax_left, ax_bottom, ax_width, ax_height]) 88 plot_2d(model, opts, ax2d) 89 ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) 90 plot_1d(model, opts, ax1d) 91 #ax.set_aspect('square') 92 else: 93 plot_height = fig_height - (fig_top+fig_bottom) 94 plot_width = (1+np.sqrt(5))/2*fig_height 95 fig_width = plot_width + fig_left + fig_right 96 ax_left = fig_left/fig_width 97 ax_bottom = fig_bottom/fig_height 98 ax_width = plot_width/fig_width 99 ax_height = plot_height/fig_height 100 aspect = (fig_width, fig_height) 101 fig = plt.figure(figsize=aspect) 102 ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) 103 plot_1d(model, opts, ax1d) 61 104 62 def make_figure(model_info, opts): 63 # type: (ModelInfo, Dict[str, Any]) -> None 64 """ 65 Generate the figure file to include in the docs. 66 """ 67 model = core.build_model(model_info) 105 # Save image in model/img 106 figname = model_name + '_autogenfig.png' 107 filename = os.path.join('model', 'img', figname) 108 plt.savefig(filename, bbox_inches='tight') 109 #print "figure saved in",filename 68 110 69 fig_height = 3.0 # in 70 fig_left = 0.6 # in 71 fig_right = 0.5 # in 72 fig_top = 0.6*0.25 # in 73 fig_bottom = 0.6*0.75 74 if model_info.parameters.has_2d: 75 plot_height = fig_height - (fig_top+fig_bottom) 76 plot_width = plot_height 77 fig_width = 2*(plot_width + fig_left + fig_right) 78 aspect = (fig_width, fig_height) 79 ratio = aspect[0]/aspect[1] 80 ax_left = fig_left/fig_width 81 ax_bottom = fig_bottom/fig_height 82 ax_height = plot_height/fig_height 83 ax_width = ax_height/ratio # square axes 84 fig = plt.figure(figsize=aspect) 85 ax2d = fig.add_axes([0.5+ax_left, ax_bottom, ax_width, ax_height]) 86 plot_2d(model, opts, ax2d) 87 ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) 88 plot_1d(model, opts, ax1d) 89 #ax.set_aspect('square') 90 else: 91 plot_height = fig_height - (fig_top+fig_bottom) 92 plot_width = (1+np.sqrt(5))/2*fig_height 93 fig_width = plot_width + fig_left + fig_right 94 ax_left = fig_left/fig_width 95 ax_bottom = fig_bottom/fig_height 96 ax_width = plot_width/fig_width 97 ax_height = plot_height/fig_height 98 aspect = (fig_width, fig_height) 99 fig = plt.figure(figsize=aspect) 100 ax1d = fig.add_axes([ax_left, ax_bottom, ax_width, ax_height]) 101 plot_1d(model, opts, ax1d) 111 # Auto caption for figure 112 captionstr = '\n' 113 captionstr += '.. figure:: img/' + model_info['id'] + '_autogenfig.png\n' 114 captionstr += '\n' 115 if model_info['has_2d']: 116 captionstr += ' 1D and 2D plots corresponding to the default parameters of the model.\n' 117 else: 118 captionstr += ' 1D plot corresponding to the default parameters of the model.\n' 119 captionstr += '\n' 102 120 103 # Save image in model/img 104 path = os.path.join('model', 'img', figfile(model_info)) 105 plt.savefig(path, bbox_inches='tight') 106 #print("figure saved in",path) 121 # Add figure reference and caption to documentation (at end, before References) 122 pattern = '\*\*REFERENCE' 123 m = re.search(pattern, docstr.upper()) 107 124 108 def gen_docs(model_info): 109 # type: (ModelInfo) -> None 110 """ 111 Generate the doc string with the figure inserted before the references. 112 """ 125 if m: 126 docstr1 = docstr[:m.start()] 127 docstr2 = docstr[m.start():] 128 docstr = docstr1 + captionstr + docstr2 129 else: 130 print '------------------------------------------------------------------' 131 print 'References NOT FOUND for model: ', model_info['id'] 132 print '------------------------------------------------------------------' 133 docstr = docstr + captionstr 113 134 114 # Load the doc string from the module definition file and store it in rst 115 docstr = generate.make_doc(model_info) 116 117 # Auto caption for figure 118 captionstr = '\n' 119 captionstr += '.. figure:: img/' + figfile(model_info) + '\n' 120 captionstr += '\n' 121 if model_info.parameters.has_2d: 122 captionstr += ' 1D and 2D plots corresponding to the default parameters of the model.\n' 123 else: 124 captionstr += ' 1D plot corresponding to the default parameters of the model.\n' 125 captionstr += '\n' 126 127 # Add figure reference and caption to documentation (at end, before References) 128 pattern = '\*\*REFERENCE' 129 match = re.search(pattern, docstr.upper()) 130 131 if match: 132 docstr1 = docstr[:match.start()] 133 docstr2 = docstr[match.start():] 134 docstr = docstr1 + captionstr + docstr2 135 else: 136 print('------------------------------------------------------------------') 137 print('References NOT FOUND for model: ', model_info.id) 138 print('------------------------------------------------------------------') 139 docstr += captionstr 140 141 open(sys.argv[2],'w').write(docstr) 142 143 def process_model(path): 144 # type: (str) -> None 145 """ 146 Generate doc file and image file for the given model definition file. 147 """ 148 149 # Load the model file 150 model_name = os.path.basename(path)[:-3] 151 model_info = core.load_model_info(model_name) 152 153 # Plotting ranges and options 154 opts = { 155 'xscale' : 'log', 156 'yscale' : 'log' if not model_info.structure_factor else 'linear', 157 'zscale' : 'log' if not model_info.structure_factor else 'linear', 158 'q_min' : 0.001, 159 'q_max' : 1.0, 160 'nq' : 1000, 161 'nq2d' : 1000, 162 'vmin' : 1e-3, # floor for the 2D data results 163 'qx_max' : 0.5, 164 #'colormap' : 'gist_ncar', 165 'colormap' : 'nipy_spectral', 166 #'colormap' : 'jet', 167 } 168 169 # Generate the RST file and the figure. Order doesn't matter. 170 gen_docs(model_info) 171 make_figure(model_info, opts) 172 173 if __name__ == "__main__": 174 process_model(sys.argv[1]) 135 open(sys.argv[2],'w').write(docstr) -
doc/gentoc.py
ra5b8477 r5041682 9 9 from sasmodels.core import load_model_info 10 10 11 try:12 from typing import Optional, BinaryIO, List, Dict13 except ImportError:14 pass15 else:16 from sasmodels.modelinfo import ModelInfo17 11 18 12 TEMPLATE="""\ … … 33 27 34 28 def _make_category(category_name, label, title, parent=None): 35 # type: (str, str, str, Optional[BinaryIO]) -> BinaryIO36 29 file = open(joinpath(MODEL_TOC_PATH, category_name+".rst"), "w") 37 30 file.write(TEMPLATE%{'label':label, 'title':title, 'bar':'*'*len(title)}) … … 41 34 42 35 def _add_subcategory(category_name, parent): 43 # type: (str, BinaryIO) -> None44 36 parent.write(" %s.rst\n"%category_name) 45 37 46 38 def _add_model(file, model_name): 47 # type: (IO[str], str) -> None48 39 file.write(" ../../model/%s.rst\n"%model_name) 49 40 50 41 def _maybe_make_category(category, models, cat_files, model_toc): 51 # type: (str, List[str], Dict[str, BinaryIO], BinaryIO) -> None52 42 if category not in cat_files: 53 43 print("Unexpected category %s containing"%category, models, file=sys.stderr) … … 56 46 57 47 def generate_toc(model_files): 58 # type: (List[str]) -> None59 48 if not model_files: 60 49 print("gentoc needs a list of model files", file=sys.stderr) 61 50 62 51 # find all categories 63 category = {} # type: Dict[str, List[str]]52 category = {} 64 53 for item in model_files: 65 54 # assume model is in sasmodels/models/name.py, and ignore the full path … … 67 56 if model_name.startswith('_'): continue 68 57 model_info = load_model_info(model_name) 69 if model_info .categoryis None:58 if model_info['category'] is None: 70 59 print("Missing category for", item, file=sys.stderr) 71 60 else: 72 category.setdefault(model_info .category,[]).append(model_name)61 category.setdefault(model_info['category'],[]).append(model_name) 73 62 74 63 # Check category names -
sasmodels/alignment.py
r7ae2b7f r3c56da87 18 18 to decide whether it is really required. 19 19 """ 20 import numpy as np # type: ignore20 import numpy as np 21 21 22 22 def align_empty(shape, dtype, alignment=128): -
sasmodels/bumps_model.py
r04dc697 rea75043 11 11 12 12 """ 13 from __future__ import print_function 14 15 __all__ = [ "Model", "Experiment" ] 16 17 import numpy as np # type: ignore 13 14 import warnings 15 16 import numpy as np 18 17 19 18 from .data import plot_theory 20 19 from .direct_model import DataMixin 21 20 22 try: 23 from typing import Dict, Union, Tuple, Any 24 from .data import Data1D, Data2D 25 from .kernel import KernelModel 26 from .modelinfo import ModelInfo 27 Data = Union[Data1D, Data2D] 28 except ImportError: 29 pass 30 31 try: 32 # Optional import. This allows the doc builder and nosetests to run even 33 # when bumps is not on the path. 34 from bumps.names import Parameter # type: ignore 35 except ImportError: 36 pass 21 __all__ = [ 22 "Model", "Experiment", 23 ] 24 25 # CRUFT: old style bumps wrapper which doesn't separate data and model 26 # pylint: disable=invalid-name 27 def BumpsModel(data, model, cutoff=1e-5, **kw): 28 r""" 29 Bind a model to data, along with a polydispersity cutoff. 30 31 *data* is a :class:`data.Data1D`, :class:`data.Data2D` or 32 :class:`data.Sesans` object. Use :func:`data.empty_data1D` or 33 :func:`data.empty_data2D` to define $q, \Delta q$ calculation 34 points for displaying the SANS curve when there is no measured data. 35 36 *model* is a runnable module as returned from :func:`core.load_model`. 37 38 *cutoff* is the polydispersity weight cutoff. 39 40 Any additional *key=value* pairs are model dependent parameters. 41 42 Returns an :class:`Experiment` object. 43 44 Note that the usual Bumps semantics is not fully supported, since 45 assigning *M.name = parameter* on the returned experiment object 46 does not set that parameter in the model. Range setting will still 47 work as expected though. 48 49 .. deprecated:: 0.1 50 Use :class:`Experiment` instead. 51 """ 52 warnings.warn("Use of BumpsModel is deprecated. Use bumps_model.Experiment instead.") 53 54 # Create the model and experiment 55 model = Model(model, **kw) 56 experiment = Experiment(data=data, model=model, cutoff=cutoff) 57 58 # Copy the model parameters up to the experiment object. 59 for k, v in model.parameters().items(): 60 setattr(experiment, k, v) 61 return experiment 37 62 38 63 39 64 def create_parameters(model_info, **kwargs): 40 # type: (ModelInfo, **Union[float, str, Parameter]) -> Tuple[Dict[str, Parameter], Dict[str, str]]41 65 """ 42 66 Generate Bumps parameters from the model info. … … 47 71 Any additional *key=value* pairs are initial values for the parameters 48 72 to the models. Uninitialized parameters will use the model default 49 value. The value can be a float, a bumps parameter, or in the case 50 of the distribution type parameter, a string. 73 value. 51 74 52 75 Returns a dictionary of *{name: Parameter}* containing the bumps … … 54 77 *{name: str}* containing the polydispersity distribution types. 55 78 """ 56 pars = {} # type: Dict[str, Parameter] 57 pd_types = {} # type: Dict[str, str] 58 for p in model_info.parameters.call_parameters: 79 # lazy import; this allows the doc builder and nosetests to run even 80 # when bumps is not on the path. 81 from bumps.names import Parameter 82 83 pars = {} 84 for p in model_info['parameters']: 59 85 value = kwargs.pop(p.name, p.default) 60 86 pars[p.name] = Parameter.default(value, name=p.name, limits=p.limits) 61 if p.polydisperse: 62 for part, default, limits in [ 63 ('_pd', 0., pars[p.name].limits), 64 ('_pd_n', 35., (0, 1000)), 65 ('_pd_nsigma', 3., (0, 10)), 66 ]: 67 name = p.name + part 68 value = kwargs.pop(name, default) 69 pars[name] = Parameter.default(value, name=name, limits=limits) 70 name = p.name + '_pd_type' 71 pd_types[name] = str(kwargs.pop(name, 'gaussian')) 87 for name in model_info['partype']['pd-2d']: 88 for xpart, xdefault, xlimits in [ 89 ('_pd', 0., pars[name].limits), 90 ('_pd_n', 35., (0, 1000)), 91 ('_pd_nsigma', 3., (0, 10)), 92 ]: 93 xname = name + xpart 94 xvalue = kwargs.pop(xname, xdefault) 95 pars[xname] = Parameter.default(xvalue, name=xname, limits=xlimits) 96 97 pd_types = {} 98 for name in model_info['partype']['pd-2d']: 99 xname = name + '_pd_type' 100 xvalue = kwargs.pop(xname, 'gaussian') 101 pd_types[xname] = xvalue 72 102 73 103 if kwargs: # args not corresponding to parameters … … 88 118 """ 89 119 def __init__(self, model, **kwargs): 90 # type: (KernelModel, **Dict[str, Union[float, Parameter]]) -> None 91 self.sasmodel = model 120 self._sasmodel = model 92 121 pars, pd_types = create_parameters(model.info, **kwargs) 93 122 for k, v in pars.items(): … … 99 128 100 129 def parameters(self): 101 # type: () -> Dict[str, Parameter]102 130 """ 103 131 Return a dictionary of parameters objects for the parameters, … … 107 135 108 136 def state(self): 109 # type: () -> Dict[str, Union[Parameter, str]]110 137 """ 111 138 Return a dictionary of current values for all the parameters, … … 132 159 The resulting model can be used directly in a Bumps FitProblem call. 133 160 """ 134 _cache = None # type: Dict[str, np.ndarray]135 161 def __init__(self, data, model, cutoff=1e-5): 136 # type: (Data, Model, float) -> None 162 137 163 # remember inputs so we can inspect from outside 138 164 self.model = model 139 165 self.cutoff = cutoff 140 self._interpret_data(data, model. sasmodel)141 self. _cache = {}166 self._interpret_data(data, model._sasmodel) 167 self.update() 142 168 143 169 def update(self): 144 # type: () -> None145 170 """ 146 171 Call when model parameters have changed and theory needs to be 147 172 recalculated. 148 173 """ 149 self._cache .clear()174 self._cache = {} 150 175 151 176 def numpoints(self): 152 # type: () -> float153 177 """ 154 178 Return the number of data points … … 157 181 158 182 def parameters(self): 159 # type: () -> Dict[str, Parameter]160 183 """ 161 184 Return a dictionary of parameters … … 164 187 165 188 def theory(self): 166 # type: () -> np.ndarray167 189 """ 168 190 Return the theory corresponding to the model parameters. … … 177 199 178 200 def residuals(self): 179 # type: () -> np.ndarray180 201 """ 181 202 Return theory minus data normalized by uncertainty. … … 185 206 186 207 def nllf(self): 187 # type: () -> float188 208 """ 189 209 Return the negative log likelihood of seeing data given the model … … 193 213 delta = self.residuals() 194 214 #if np.any(np.isnan(R)): print("NaN in residuals") 195 return 0.5 * np.sum(delta **2)215 return 0.5 * np.sum(delta ** 2) 196 216 197 217 #def __call__(self): … … 199 219 200 220 def plot(self, view='log'): 201 # type: (str) -> None202 221 """ 203 222 Plot the data and residuals. … … 207 226 208 227 def simulate_data(self, noise=None): 209 # type: (float) -> None210 228 """ 211 229 Generate simulated data. … … 215 233 216 234 def save(self, basename): 217 # type: (str) -> None218 235 """ 219 236 Save the model parameters and data into a file. … … 226 243 227 244 def __getstate__(self): 228 # type: () -> Dict[str, Any]229 245 # Can't pickle gpu functions, so instead make them lazy 230 246 state = self.__dict__.copy() … … 233 249 234 250 def __setstate__(self, state): 235 # type: (Dict[str, Any]) -> None236 251 # pylint: disable=attribute-defined-outside-init 237 252 self.__dict__ = state 238 -
sasmodels/compare.py
r7ae2b7f rf247314 34 34 import traceback 35 35 36 import numpy as np # type: ignore36 import numpy as np 37 37 38 38 from . import core 39 39 from . import kerneldll 40 from . import product 40 41 from .data import plot_theory, empty_data1D, empty_data2D 41 42 from .direct_model import DirectModel … … 209 210 Choose a parameter range based on parameter name and initial value. 210 211 """ 211 # process the polydispersity options212 212 if p.endswith('_pd_n'): 213 213 return [0, 100] … … 222 222 else: 223 223 return [-180, 180] 224 elif 'sld' in p: 225 return [-0.5, 10] 224 226 elif p.endswith('_pd'): 225 227 return [0, 1] 226 elif 'sld' in p:227 return [-0.5, 10]228 228 elif p == 'background': 229 229 return [0, 10] 230 230 elif p == 'scale': 231 231 return [0, 1e3] 232 elif p == 'case_num': 233 # RPA hack 234 return [0, 10] 232 235 elif v < 0: 236 # Kxy parameters in rpa model can be negative 233 237 return [2*v, -2*v] 234 238 else: … … 236 240 237 241 238 def _randomize_one( model_info,p, v):242 def _randomize_one(p, v): 239 243 """ 240 244 Randomize a single parameter. … … 242 246 if any(p.endswith(s) for s in ('_pd_n', '_pd_nsigma', '_pd_type')): 243 247 return v 244 245 # Find the parameter definition 246 for par in model_info.parameters.call_parameters: 247 if par.name == p: 248 break 249 else: 250 raise ValueError("unknown parameter %r"%p) 251 252 if len(par.limits) > 2: # choice list 253 return np.random.randint(len(par.limits)) 254 255 limits = par.limits 256 if not np.isfinite(limits).all(): 257 low, high = parameter_range(p, v) 258 limits = (max(limits[0], low), min(limits[1], high)) 259 260 return np.random.uniform(*limits) 261 262 263 def randomize_pars(model_info, pars, seed=None): 248 else: 249 return np.random.uniform(*parameter_range(p, v)) 250 251 252 def randomize_pars(pars, seed=None): 264 253 """ 265 254 Generate random values for all of the parameters. … … 272 261 with push_seed(seed): 273 262 # Note: the sort guarantees order `of calls to random number generator 274 pars = dict((p, _randomize_one( model_info,p, v))263 pars = dict((p, _randomize_one(p, v)) 275 264 for p, v in sorted(pars.items())) 276 265 return pars … … 284 273 cylinder radius in this case). 285 274 """ 286 name = model_info .id275 name = model_info['id'] 287 276 # if it is a product model, then just look at the form factor since 288 277 # none of the structure factors need any constraints. … … 305 294 # Make sure phi sums to 1.0 306 295 if pars['case_num'] < 2: 307 pars['Phi 1'] = 0.308 pars['Phi 2'] = 0.296 pars['Phia'] = 0. 297 pars['Phib'] = 0. 309 298 elif pars['case_num'] < 5: 310 pars['Phi 1'] = 0.311 total = sum(pars['Phi'+c] for c in ' 1234')312 for c in ' 1234':299 pars['Phia'] = 0. 300 total = sum(pars['Phi'+c] for c in 'abcd') 301 for c in 'abcd': 313 302 pars['Phi'+c] /= total 314 303 … … 317 306 Format the parameter list for printing. 318 307 """ 308 if is2d: 309 exclude = lambda n: False 310 else: 311 partype = model_info['partype'] 312 par1d = set(partype['fixed-1d']+partype['pd-1d']) 313 exclude = lambda n: n not in par1d 319 314 lines = [] 320 parameters = model_info.parameters321 for p in parameters.user_parameters(pars, is2d):315 for p in model_info['parameters']: 316 if exclude(p.name): continue 322 317 fields = dict( 323 value=pars.get(p. id, p.default),324 pd=pars.get(p. id+"_pd", 0.),325 n=int(pars.get(p. id+"_pd_n", 0)),326 nsigma=pars.get(p. id+"_pd_nsgima", 3.),327 type=pars.get(p. id+"_pd_type", 'gaussian'))318 value=pars.get(p.name, p.default), 319 pd=pars.get(p.name+"_pd", 0.), 320 n=int(pars.get(p.name+"_pd_n", 0)), 321 nsigma=pars.get(p.name+"_pd_nsgima", 3.), 322 type=pars.get(p.name+"_pd_type", 'gaussian')) 328 323 lines.append(_format_par(p.name, **fields)) 329 324 return "\n".join(lines) … … 368 363 369 364 # grab the sasview model, or create it if it is a product model 370 if model_info .composition:371 composition_type, parts = model_info .composition365 if model_info['composition']: 366 composition_type, parts = model_info['composition'] 372 367 if composition_type == 'product': 373 368 from sas.models.MultiplicationModel import MultiplicationModel … … 459 454 """ 460 455 # initialize the code so time is more accurate 461 if Nevals > 1: 462 value = calculator(**suppress_pd(pars)) 456 value = calculator(**suppress_pd(pars)) 463 457 toc = tic() 464 458 for _ in range(max(Nevals, 1)): # make sure there is at least one eval … … 667 661 """ 668 662 # Get the default values for the parameters 669 pars = {} 670 for p in model_info.parameters.call_parameters: 671 parts = [('', p.default)] 672 if p.polydisperse: 673 parts.append(('_pd', 0.0)) 674 parts.append(('_pd_n', 0)) 675 parts.append(('_pd_nsigma', 3.0)) 676 parts.append(('_pd_type', "gaussian")) 677 for ext, val in parts: 678 if p.length > 1: 679 dict(("%s%d%s"%(p.id,k,ext), val) for k in range(p.length)) 680 else: 681 pars[p.id+ext] = val 663 pars = dict((p.name, p.default) for p in model_info['parameters']) 664 665 # Fill in default values for the polydispersity parameters 666 for p in model_info['parameters']: 667 if p.type in ('volume', 'orientation'): 668 pars[p.name+'_pd'] = 0.0 669 pars[p.name+'_pd_n'] = 0 670 pars[p.name+'_pd_nsigma'] = 3.0 671 pars[p.name+'_pd_type'] = "gaussian" 682 672 683 673 # Plug in values given in demo 684 674 if use_demo: 685 pars.update(model_info .demo)675 pars.update(model_info['demo']) 686 676 return pars 687 677 … … 710 700 try: 711 701 model_info = core.load_model_info(name) 712 except ImportError asexc:702 except ImportError, exc: 713 703 print(str(exc)) 714 704 print("Could not find model; use one of:\n " + models) … … 784 774 785 775 if len(engines) == 0: 786 engines.extend(['single', ' double'])776 engines.extend(['single', 'sasview']) 787 777 elif len(engines) == 1: 788 if engines[0][0] != ' double':789 engines.append(' double')778 if engines[0][0] != 'sasview': 779 engines.append('sasview') 790 780 else: 791 781 engines.append('single') … … 817 807 #pars.update(set_pars) # set value before random to control range 818 808 if opts['seed'] > -1: 819 pars = randomize_pars( model_info,pars, seed=opts['seed'])809 pars = randomize_pars(pars, seed=opts['seed']) 820 810 print("Randomize using -random=%i"%opts['seed']) 821 811 if opts['mono']: … … 860 850 Explore the model using the Bumps GUI. 861 851 """ 862 import wx # type: ignore863 from bumps.names import FitProblem # type: ignore864 from bumps.gui.app_frame import AppFrame # type: ignore852 import wx 853 from bumps.names import FitProblem 854 from bumps.gui.app_frame import AppFrame 865 855 866 856 problem = FitProblem(Explore(opts)) … … 883 873 """ 884 874 def __init__(self, opts): 885 from bumps.cli import config_matplotlib # type: ignore875 from bumps.cli import config_matplotlib 886 876 from . import bumps_model 887 877 config_matplotlib() … … 889 879 model_info = opts['def'] 890 880 pars, pd_types = bumps_model.create_parameters(model_info, **opts['pars']) 891 # Initialize parameter ranges, fixing the 2D parameters for 1D data.892 881 if not opts['is2d']: 893 for p in model_info.parameters.user_parameters(is2d=False): 894 for ext in ['', '_pd', '_pd_n', '_pd_nsigma']: 895 k = p.name+ext 896 v = pars.get(k, None) 897 if v is not None: 898 v.range(*parameter_range(k, v.value)) 882 active = [base + ext 883 for base in model_info['partype']['pd-1d'] 884 for ext in ['', '_pd', '_pd_n', '_pd_nsigma']] 885 active.extend(model_info['partype']['fixed-1d']) 886 for k in active: 887 v = pars[k] 888 v.range(*parameter_range(k, v.value)) 899 889 else: 900 890 for k, v in pars.items(): -
sasmodels/compare_many.py
r7ae2b7f rce346b6 17 17 import traceback 18 18 19 import numpy as np # type: ignore19 import numpy as np 20 20 21 21 from . import core 22 from . import generate 22 23 from .compare import (MODELS, randomize_pars, suppress_pd, make_data, 23 24 make_engine, get_pars, columnize, … … 108 109 109 110 if is_2d: 110 if not model_info[' parameters'].has_2d:111 if not model_info['has_2d']: 111 112 print(',"1-D only"') 112 113 return … … 124 125 try: 125 126 result = fn(**pars) 126 except Exception: 127 except KeyboardInterrupt: 128 raise 129 except: 127 130 traceback.print_exc() 128 131 print("when comparing %s for %d"%(name, seed)) … … 243 246 base = sys.argv[5] 244 247 comp = sys.argv[6] if len(sys.argv) > 6 else "sasview" 245 except Exception:248 except: 246 249 traceback.print_exc() 247 250 print_usage() -
sasmodels/convert.json
r6e7ff6d rec45c4f 602 602 "RPAModel", 603 603 { 604 "N1": "Na", "N2": "Nb", "N3": "Nc", "N4": "Nd",605 "L1": "La", "L2": "Lb", "L3": "Lc", "L4": "Ld",606 "v1": "va", "v2": "vb", "v3": "vc", "v4": "vd",607 "b1": "ba", "b2": "bb", "b3": "bc", "b4": "bd",608 "Phi1": "Phia", "Phi2": "Phib", "Phi3": "Phic", "Phi4": "Phid",609 604 "case_num": "lcase_n" 610 605 } … … 625 620 } 626 621 ], 627 "_spherepy": [628 "SphereModel",629 {630 "sld": "sldSph",631 "radius": "radius",632 "sld_solvent": "sldSolv"633 }634 ],635 622 "spherical_sld": [ 636 623 "SphereSLDModel", 637 624 { 638 "n": "n_shells",639 625 "radius_core": "rad_core", 640 626 "sld_solvent": "sld_solv" -
sasmodels/convert.py
r7ae2b7f rf247314 62 62 # but only if we haven't already byteified it 63 63 if isinstance(data, dict) and not ignore_dicts: 64 return dict((_byteify(key, ignore_dicts=True), 65 _byteify(value, ignore_dicts=True)) 66 for key, value in data.items()) 64 return { 65 _byteify(key, ignore_dicts=True): _byteify(value, ignore_dicts=True) 66 for key, value in data.iteritems() 67 } 67 68 # if it's anything else, return it in its original form 68 69 return data … … 152 153 def revert_name(model_info): 153 154 _read_conversion_table() 154 oldname, oldpars = CONVERSION_TABLE.get(model_info .id, [None, {}])155 oldname, oldpars = CONVERSION_TABLE.get(model_info['id'], [None, {}]) 155 156 return oldname 156 157 157 158 def _get_old_pars(model_info): 158 159 _read_conversion_table() 159 oldname, oldpars = CONVERSION_TABLE.get(model_info .id, [None, {}])160 oldname, oldpars = CONVERSION_TABLE.get(model_info['id'], [None, {}]) 160 161 return oldpars 161 162 … … 164 165 Convert model from new style parameter names to old style. 165 166 """ 166 if model_info .compositionis not None:167 composition_type, parts = model_info .composition167 if model_info['composition'] is not None: 168 composition_type, parts = model_info['composition'] 168 169 if composition_type == 'product': 169 170 P, S = parts … … 179 180 180 181 # Note: update compare.constrain_pars to match 181 name = model_info .id182 if name in MODELS_WITHOUT_SCALE or model_info .structure_factor:182 name = model_info['id'] 183 if name in MODELS_WITHOUT_SCALE or model_info['structure_factor']: 183 184 if oldpars.pop('scale', 1.0) != 1.0: 184 185 warnings.warn("parameter scale not used in sasview %s"%name) 185 if name in MODELS_WITHOUT_BACKGROUND or model_info .structure_factor:186 if name in MODELS_WITHOUT_BACKGROUND or model_info['structure_factor']: 186 187 if oldpars.pop('background', 0.0) != 0.0: 187 188 warnings.warn("parameter background not used in sasview %s"%name) … … 205 206 elif name == 'rpa': 206 207 # convert scattering lengths from femtometers to centimeters 207 for p in "L 1", "L2", "L3", "L4":208 for p in "La", "Lb", "Lc", "Ld": 208 209 if p in oldpars: oldpars[p] *= 1e-13 209 210 elif name == 'core_shell_parallelepiped': … … 224 225 Restrict parameter values to those that will match sasview. 225 226 """ 226 name = model_info .id227 name = model_info['id'] 227 228 # Note: update convert.revert_model to match 228 if name in MODELS_WITHOUT_SCALE or model_info .structure_factor:229 if name in MODELS_WITHOUT_SCALE or model_info['structure_factor']: 229 230 pars['scale'] = 1 230 if name in MODELS_WITHOUT_BACKGROUND or model_info .structure_factor:231 if name in MODELS_WITHOUT_BACKGROUND or model_info['structure_factor']: 231 232 pars['background'] = 0 232 233 # sasview multiplies background by structure factor -
sasmodels/core.py
rfa5fd8d r4d76711 2 2 Core model handling routines. 3 3 """ 4 from __future__ import print_function5 6 4 __all__ = [ 7 "list_models", "load_model ", "load_model_info",8 "build_model", " precompile_dll",5 "list_models", "load_model_info", "precompile_dll", 6 "build_model", "make_kernel", "call_kernel", "call_ER_VR", 9 7 ] 10 8 11 from os.path import basename, dirname, join as joinpath 9 from os.path import basename, dirname, join as joinpath, splitext 12 10 from glob import glob 13 11 14 import numpy as np # type: ignore 15 12 import numpy as np 13 14 from . import models 15 from . import weights 16 16 from . import generate 17 from . import modelinfo 18 from . import product 17 # TODO: remove circular references between product and core 18 # product uses call_ER/call_VR, core uses make_product_info/ProductModel 19 #from . import product 19 20 from . import mixture 20 21 from . import kernelpy … … 23 24 from . import kernelcl 24 25 HAVE_OPENCL = True 25 except Exception:26 except: 26 27 HAVE_OPENCL = False 27 28 28 29 try: 29 from typing import List, Union, Optional, Any 30 DType = Union[None, str, np.dtype] 31 from .kernel import KernelModel 32 except ImportError: 33 pass 34 30 np.meshgrid([]) 31 meshgrid = np.meshgrid 32 except ValueError: 33 # CRUFT: np.meshgrid requires multiple vectors 34 def meshgrid(*args): 35 if len(args) > 1: 36 return np.meshgrid(*args) 37 else: 38 return [np.asarray(v) for v in args] 35 39 36 40 # TODO: refactor composite model support … … 47 51 48 52 def list_models(): 49 # type: () -> List[str]50 53 """ 51 54 Return the list of available models on the model path. … … 57 60 58 61 def isstr(s): 59 # type: (Any) -> bool60 62 """ 61 63 Return True if *s* is a string-like object. 62 64 """ 63 65 try: s + '' 64 except Exception: return False66 except: return False 65 67 return True 66 68 67 def load_model(model_name, dtype=None, platform='ocl'): 68 # type: (str, DType, str) -> KernelModel 69 def load_model(model_name, **kw): 69 70 """ 70 71 Load model info and build model. 71 72 *model_name* is the name of the model as used by :func:`load_model_info`. 73 Additional keyword arguments are passed directly to :func:`build_model`. 74 """ 75 return build_model(load_model_info(model_name), 76 dtype=dtype, platform=platform) 72 """ 73 return build_model(load_model_info(model_name), **kw) 77 74 78 75 79 76 def load_model_info(model_name): 80 # type: (str) -> modelinfo.ModelInfo81 77 """ 82 78 Load a model definition given the model name. … … 92 88 parts = model_name.split('*') 93 89 if len(parts) > 1: 90 from . import product 91 # Note: currently have circular reference 94 92 if len(parts) > 2: 95 93 raise ValueError("use P*S to apply structure factor S to model P") … … 98 96 99 97 kernel_module = generate.load_kernel_module(model_name) 100 return modelinfo.make_model_info(kernel_module)98 return generate.make_model_info(kernel_module) 101 99 102 100 103 101 def build_model(model_info, dtype=None, platform="ocl"): 104 # type: (modelinfo.ModelInfo, np.dtype, str) -> KernelModel105 102 """ 106 103 Prepare the model for the default execution platform. … … 121 118 otherwise it uses the default "ocl". 122 119 """ 123 composition = model_info. composition120 composition = model_info.get('composition', None) 124 121 if composition is not None: 125 122 composition_type, parts = composition … … 134 131 raise ValueError('unknown mixture type %s'%composition_type) 135 132 136 # If it is a python model, return it immediately137 if callable(model_info.Iq):138 return kernelpy.PyModel(model_info)139 140 133 ## for debugging: 141 134 ## 1. uncomment open().write so that the source will be saved next time … … 144 137 ## 4. rerun "python -m sasmodels.direct_model $MODELNAME" 145 138 ## 5. uncomment open().read() so that source will be regenerated from model 146 # open(model_info .name+'.c','w').write(source)147 # source = open(model_info .name+'.cl','r').read()139 # open(model_info['name']+'.c','w').write(source) 140 # source = open(model_info['name']+'.cl','r').read() 148 141 source = generate.make_source(model_info) 149 142 if dtype is None: 150 dtype = generate.F32 if model_info.single else generate.F64 143 dtype = 'single' if model_info['single'] else 'double' 144 if callable(model_info.get('Iq', None)): 145 return kernelpy.PyModel(model_info) 151 146 if (platform == "dll" 152 147 or not HAVE_OPENCL … … 157 152 158 153 def precompile_dll(model_name, dtype="double"): 159 # type: (str, DType) -> Optional[str]160 154 """ 161 155 Precompile the dll for a model. … … 174 168 source = generate.make_source(model_info) 175 169 return kerneldll.make_dll(source, model_info, dtype=dtype) if source else None 170 171 172 def get_weights(model_info, pars, name): 173 """ 174 Generate the distribution for parameter *name* given the parameter values 175 in *pars*. 176 177 Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma" 178 from the *pars* dictionary for parameter value and parameter dispersion. 179 """ 180 relative = name in model_info['partype']['pd-rel'] 181 limits = model_info['limits'][name] 182 disperser = pars.get(name+'_pd_type', 'gaussian') 183 value = pars.get(name, model_info['defaults'][name]) 184 npts = pars.get(name+'_pd_n', 0) 185 width = pars.get(name+'_pd', 0.0) 186 nsigma = pars.get(name+'_pd_nsigma', 3.0) 187 value, weight = weights.get_weights( 188 disperser, npts, width, nsigma, value, limits, relative) 189 return value, weight / np.sum(weight) 190 191 def dispersion_mesh(pars): 192 """ 193 Create a mesh grid of dispersion parameters and weights. 194 195 Returns [p1,p2,...],w where pj is a vector of values for parameter j 196 and w is a vector containing the products for weights for each 197 parameter set in the vector. 198 """ 199 value, weight = zip(*pars) 200 value = [v.flatten() for v in meshgrid(*value)] 201 weight = np.vstack([v.flatten() for v in meshgrid(*weight)]) 202 weight = np.prod(weight, axis=0) 203 return value, weight 204 205 def call_kernel(kernel, pars, cutoff=0, mono=False): 206 """ 207 Call *kernel* returned from *model.make_kernel* with parameters *pars*. 208 209 *cutoff* is the limiting value for the product of dispersion weights used 210 to perform the multidimensional dispersion calculation more quickly at a 211 slight cost to accuracy. The default value of *cutoff=0* integrates over 212 the entire dispersion cube. Using *cutoff=1e-5* can be 50% faster, but 213 with an error of about 1%, which is usually less than the measurement 214 uncertainty. 215 216 *mono* is True if polydispersity should be set to none on all parameters. 217 """ 218 fixed_pars = [pars.get(name, kernel.info['defaults'][name]) 219 for name in kernel.fixed_pars] 220 if mono: 221 pd_pars = [( np.array([pars[name]]), np.array([1.0]) ) 222 for name in kernel.pd_pars] 223 else: 224 pd_pars = [get_weights(kernel.info, pars, name) for name in kernel.pd_pars] 225 return kernel(fixed_pars, pd_pars, cutoff=cutoff) 226 227 def call_ER_VR(model_info, vol_pars): 228 """ 229 Return effect radius and volume ratio for the model. 230 231 *info* is either *kernel.info* for *kernel=make_kernel(model,q)* 232 or *model.info*. 233 234 *pars* are the parameters as expected by :func:`call_kernel`. 235 """ 236 ER = model_info.get('ER', None) 237 VR = model_info.get('VR', None) 238 value, weight = dispersion_mesh(vol_pars) 239 240 individual_radii = ER(*value) if ER else 1.0 241 whole, part = VR(*value) if VR else (1.0, 1.0) 242 243 effect_radius = np.sum(weight*individual_radii) / np.sum(weight) 244 volume_ratio = np.sum(weight*part)/np.sum(weight*whole) 245 return effect_radius, volume_ratio 246 247 248 def call_ER(model_info, values): 249 """ 250 Call the model ER function using *values*. *model_info* is either 251 *model.info* if you have a loaded model, or *kernel.info* if you 252 have a model kernel prepared for evaluation. 253 """ 254 ER = model_info.get('ER', None) 255 if ER is None: 256 return 1.0 257 else: 258 vol_pars = [get_weights(model_info, values, name) 259 for name in model_info['partype']['volume']] 260 value, weight = dispersion_mesh(vol_pars) 261 individual_radii = ER(*value) 262 #print(values[0].shape, weights.shape, fv.shape) 263 return np.sum(weight*individual_radii) / np.sum(weight) 264 265 def call_VR(model_info, values): 266 """ 267 Call the model VR function using *pars*. 268 *info* is either *model.info* if you have a loaded model, or *kernel.info* 269 if you have a model kernel prepared for evaluation. 270 """ 271 VR = model_info.get('VR', None) 272 if VR is None: 273 return 1.0 274 else: 275 vol_pars = [get_weights(model_info, values, name) 276 for name in model_info['partype']['volume']] 277 value, weight = dispersion_mesh(vol_pars) 278 whole, part = VR(*value) 279 return np.sum(weight*part)/np.sum(weight*whole) 280 281 # TODO: remove call_ER, call_VR 282 -
sasmodels/custom/__init__.py
r7ae2b7f rcd0a808 14 14 try: 15 15 # Python 3.5 and up 16 from importlib.util import spec_from_file_location, module_from_spec # type: ignore16 from importlib.util import spec_from_file_location, module_from_spec 17 17 def load_module_from_path(fullname, path): 18 18 spec = spec_from_file_location(fullname, path) -
sasmodels/data.py
ra5b8477 rd6f5da6 35 35 import traceback 36 36 37 import numpy as np # type: ignore 38 39 try: 40 from typing import Union, Dict, List, Optional 41 except ImportError: 42 pass 43 else: 44 Data = Union["Data1D", "Data2D", "SesansData"] 37 import numpy as np 45 38 46 39 def load_data(filename): 47 # type: (str) -> Data48 40 """ 49 41 Load data using a sasview loader. 50 42 """ 51 from sas.sascalc.dataloader.loader import Loader # type: ignore43 from sas.sascalc.dataloader.loader import Loader 52 44 loader = Loader() 53 45 data = loader.load(filename) … … 58 50 59 51 def set_beam_stop(data, radius, outer=None): 60 # type: (Data, float, Optional[float]) -> None61 52 """ 62 53 Add a beam stop of the given *radius*. If *outer*, make an annulus. 63 54 """ 64 from sas.dataloader.manipulations import Ringcut # type: ignore55 from sas.dataloader.manipulations import Ringcut 65 56 if hasattr(data, 'qx_data'): 66 57 data.mask = Ringcut(0, radius)(data) … … 74 65 75 66 def set_half(data, half): 76 # type: (Data, str) -> None77 67 """ 78 68 Select half of the data, either "right" or "left". 79 69 """ 80 from sas.dataloader.manipulations import Boxcut # type: ignore70 from sas.dataloader.manipulations import Boxcut 81 71 if half == 'right': 82 72 data.mask += \ … … 88 78 89 79 def set_top(data, cutoff): 90 # type: (Data, float) -> None91 80 """ 92 81 Chop the top off the data, above *cutoff*. 93 82 """ 94 from sas.dataloader.manipulations import Boxcut # type: ignore83 from sas.dataloader.manipulations import Boxcut 95 84 data.mask += \ 96 85 Boxcut(x_min=-np.inf, x_max=np.inf, y_min=-np.inf, y_max=cutoff)(data) … … 125 114 """ 126 115 def __init__(self, x=None, y=None, dx=None, dy=None): 127 # type: (Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]) -> None128 116 self.x, self.y, self.dx, self.dy = x, y, dx, dy 129 117 self.dxl = None … … 139 127 140 128 def xaxis(self, label, unit): 141 # type: (str, str) -> None142 129 """ 143 130 set the x axis label and unit … … 147 134 148 135 def yaxis(self, label, unit): 149 # type: (str, str) -> None150 136 """ 151 137 set the y axis label and unit … … 154 140 self._yunit = unit 155 141 156 class SesansData(Data1D): 157 def __init__(self, **kw): 158 Data1D.__init__(self, **kw) 159 self.lam = None # type: Optional[np.ndarray] 142 160 143 161 144 class Data2D(object): … … 192 175 """ 193 176 def __init__(self, x=None, y=None, z=None, dx=None, dy=None, dz=None): 194 # type: (Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]) -> None195 177 self.qx_data, self.dqx_data = x, dx 196 178 self.qy_data, self.dqy_data = y, dy … … 215 197 216 198 def xaxis(self, label, unit): 217 # type: (str, str) -> None218 199 """ 219 200 set the x axis label and unit … … 223 204 224 205 def yaxis(self, label, unit): 225 # type: (str, str) -> None226 206 """ 227 207 set the y axis label and unit … … 231 211 232 212 def zaxis(self, label, unit): 233 # type: (str, str) -> None234 213 """ 235 214 set the y axis label and unit … … 244 223 """ 245 224 def __init__(self, x=None, y=None, z=None): 246 # type: (float, float, Optional[float]) -> None247 225 self.x, self.y, self.z = x, y, z 248 226 … … 252 230 """ 253 231 def __init__(self, pixel_size=(None, None), distance=None): 254 # type: (Tuple[float, float], float) -> None255 232 self.pixel_size = Vector(*pixel_size) 256 233 self.distance = distance … … 261 238 """ 262 239 def __init__(self): 263 # type: () -> None264 240 self.wavelength = np.NaN 265 241 self.wavelength_unit = "A" … … 267 243 268 244 def empty_data1D(q, resolution=0.0): 269 # type: (np.ndarray, float) -> Data1D270 245 """ 271 246 Create empty 1D data using the given *q* as the x value. … … 284 259 285 260 def empty_data2D(qx, qy=None, resolution=0.0): 286 # type: (np.ndarray, Optional[np.ndarray], float) -> Data2D287 261 """ 288 262 Create empty 2D data using the given mesh. … … 298 272 Qx, Qy = np.meshgrid(qx, qy) 299 273 Qx, Qy = Qx.flatten(), Qy.flatten() 300 Iq = 100 * np.ones_like(Qx) # type: np.ndarray274 Iq = 100 * np.ones_like(Qx) 301 275 dIq = np.sqrt(Iq) 302 276 if resolution != 0: … … 326 300 327 301 def plot_data(data, view='log', limits=None): 328 # type: (Data, str, Optional[Tuple[float, float]]) -> None329 302 """ 330 303 Plot data loaded by the sasview loader. … … 350 323 def plot_theory(data, theory, resid=None, view='log', 351 324 use_data=True, limits=None, Iq_calc=None): 352 # type: (Data, Optional[np.ndarray], Optional[np.ndarray], str, bool, Optional[Tuple[float,float]], Optional[np.ndarray]) -> None353 325 """ 354 326 Plot theory calculation. … … 365 337 *limits* sets the intensity limits on the plot; if None then the limits 366 338 are inferred from the data. 367 368 *Iq_calc* is the raw theory values without resolution smearing369 339 """ 370 340 if hasattr(data, 'lam'): … … 378 348 379 349 def protect(fn): 380 # type: (Callable) -> Callable381 350 """ 382 351 Decorator to wrap calls in an exception trapper which prints the … … 389 358 try: 390 359 return fn(*args, **kw) 391 except Exception: 360 except KeyboardInterrupt: 361 raise 362 except: 392 363 traceback.print_exc() 393 364 … … 398 369 def _plot_result1D(data, theory, resid, view, use_data, 399 370 limits=None, Iq_calc=None): 400 # type: (Data1D, Optional[np.ndarray], Optional[np.ndarray], str, bool, Optional[Tuple[float, float]], Optional[np.ndarray]) -> None401 371 """ 402 372 Plot the data and residuals for 1D data. 403 373 """ 404 import matplotlib.pyplot as plt # type: ignore405 from numpy.ma import masked_array, masked # type: ignore374 import matplotlib.pyplot as plt 375 from numpy.ma import masked_array, masked 406 376 407 377 use_data = use_data and data.y is not None … … 476 446 @protect 477 447 def _plot_result_sesans(data, theory, resid, use_data, limits=None): 478 # type: (SesansData, Optional[np.ndarray], Optional[np.ndarray], bool, Optional[Tuple[float, float]]) -> None479 448 """ 480 449 Plot SESANS results. 481 450 """ 482 import matplotlib.pyplot as plt # type: ignore451 import matplotlib.pyplot as plt 483 452 use_data = use_data and data.y is not None 484 453 use_theory = theory is not None … … 487 456 488 457 if use_data or use_theory: 489 is_tof = (data.lam != data.lam[0]).any()458 is_tof = np.any(data.lam!=data.lam[0]) 490 459 if num_plots > 1: 491 460 plt.subplot(1, num_plots, 1) 492 461 if use_data: 493 462 if is_tof: 494 plt.errorbar(data.x, np.log(data.y)/(data.lam*data.lam), 495 yerr=data.dy/data.y/(data.lam*data.lam)) 463 plt.errorbar(data.x, np.log(data.y)/(data.lam*data.lam), yerr=data.dy/data.y/(data.lam*data.lam)) 496 464 else: 497 465 plt.errorbar(data.x, data.y, yerr=data.dy) … … 521 489 @protect 522 490 def _plot_result2D(data, theory, resid, view, use_data, limits=None): 523 # type: (Data2D, Optional[np.ndarray], Optional[np.ndarray], str, bool, Optional[Tuple[float,float]]) -> None524 491 """ 525 492 Plot the data and residuals for 2D data. 526 493 """ 527 import matplotlib.pyplot as plt # type: ignore494 import matplotlib.pyplot as plt 528 495 use_data = use_data and data.data is not None 529 496 use_theory = theory is not None … … 533 500 # Put theory and data on a common colormap scale 534 501 vmin, vmax = np.inf, -np.inf 535 target = None # type: Optional[np.ndarray]536 502 if use_data: 537 503 target = data.data[~data.mask] … … 582 548 @protect 583 549 def _plot_2d_signal(data, signal, vmin=None, vmax=None, view='log'): 584 # type: (Data2D, np.ndarray, Optional[float], Optional[float], str) -> Tuple[float, float]585 550 """ 586 551 Plot the target value for the data. This could be the data itself, … … 589 554 *scale* can be 'log' for log scale data, or 'linear'. 590 555 """ 591 import matplotlib.pyplot as plt # type: ignore592 from numpy.ma import masked_array # type: ignore556 import matplotlib.pyplot as plt 557 from numpy.ma import masked_array 593 558 594 559 image = np.zeros_like(data.qx_data) … … 624 589 625 590 def demo(): 626 # type: () -> None627 591 """ 628 592 Load and plot a SAS dataset. … … 631 595 set_beam_stop(data, 0.004) 632 596 plot_data(data) 633 import matplotlib.pyplot as plt # type: ignore 634 plt.show() 597 import matplotlib.pyplot as plt; plt.show() 635 598 636 599 -
sasmodels/direct_model.py
ra5b8477 r4d76711 23 23 from __future__ import print_function 24 24 25 import numpy as np # type: ignore 26 27 # TODO: fix sesans module 28 from . import sesans # type: ignore 29 from . import weights 25 import numpy as np 26 27 from .core import call_kernel, call_ER_VR 28 from . import sesans 30 29 from . import resolution 31 30 from . import resolution2d 32 from . import details33 34 try:35 from typing import Optional, Dict, Tuple36 except ImportError:37 pass38 else:39 from .data import Data40 from .kernel import Kernel, KernelModel41 from .modelinfo import Parameter, ParameterSet42 43 def call_kernel(kernel, pars, cutoff=0., mono=False):44 # type: (Kernel, ParameterSet, float, bool) -> np.ndarray45 """46 Call *kernel* returned from *model.make_kernel* with parameters *pars*.47 48 *cutoff* is the limiting value for the product of dispersion weights used49 to perform the multidimensional dispersion calculation more quickly at a50 slight cost to accuracy. The default value of *cutoff=0* integrates over51 the entire dispersion cube. Using *cutoff=1e-5* can be 50% faster, but52 with an error of about 1%, which is usually less than the measurement53 uncertainty.54 55 *mono* is True if polydispersity should be set to none on all parameters.56 """57 parameters = kernel.info.parameters58 if mono:59 active = lambda name: False60 elif kernel.dim == '1d':61 active = lambda name: name in parameters.pd_1d62 elif kernel.dim == '2d':63 active = lambda name: name in parameters.pd_2d64 else:65 active = lambda name: True66 67 vw_pairs = [(get_weights(p, pars) if active(p.name)68 else ([pars.get(p.name, p.default)], []))69 for p in parameters.call_parameters]70 71 call_details, weights, values = details.build_details(kernel, vw_pairs)72 return kernel(call_details, weights, values, cutoff)73 74 def get_weights(parameter, values):75 # type: (Parameter, Dict[str, float]) -> Tuple[np.ndarray, np.ndarray]76 """77 Generate the distribution for parameter *name* given the parameter values78 in *pars*.79 80 Uses "name", "name_pd", "name_pd_type", "name_pd_n", "name_pd_sigma"81 from the *pars* dictionary for parameter value and parameter dispersion.82 """83 value = float(values.get(parameter.name, parameter.default))84 relative = parameter.relative_pd85 limits = parameter.limits86 disperser = values.get(parameter.name+'_pd_type', 'gaussian')87 npts = values.get(parameter.name+'_pd_n', 0)88 width = values.get(parameter.name+'_pd', 0.0)89 nsigma = values.get(parameter.name+'_pd_nsigma', 3.0)90 if npts == 0 or width == 0:91 return [value], []92 value, weight = weights.get_weights(93 disperser, npts, width, nsigma, value, limits, relative)94 return value, weight / np.sum(weight)95 31 96 32 class DataMixin(object): … … 116 52 """ 117 53 def _interpret_data(self, data, model): 118 # type: (Data, KernelModel) -> None119 54 # pylint: disable=attribute-defined-outside-init 120 55 … … 132 67 self.data_type = 'Iq' 133 68 69 partype = model.info['partype'] 70 134 71 if self.data_type == 'sesans': 135 72 … … 145 82 q_mono = sesans.make_all_q(data) 146 83 elif self.data_type == 'Iqxy': 147 #if not model.info.parameters.has_2d:148 #raise ValueError("not 2D without orientation or magnetic parameters")84 if not partype['orientation'] and not partype['magnetic']: 85 raise ValueError("not 2D without orientation or magnetic parameters") 149 86 q = np.sqrt(data.qx_data**2 + data.qy_data**2) 150 87 qmin = getattr(data, 'qmin', 1e-16) … … 217 154 218 155 def _set_data(self, Iq, noise=None): 219 # type: (np.ndarray, Optional[float]) -> None220 156 # pylint: disable=attribute-defined-outside-init 221 157 if noise is not None: … … 235 171 236 172 def _calc_theory(self, pars, cutoff=0.0): 237 # type: (ParameterSet, float) -> np.ndarray238 173 if self._kernel is None: 239 self._kernel = self._model.make_kernel(self._kernel_inputs) 174 self._kernel = self._model.make_kernel(self._kernel_inputs) # pylint: disable=attribute-dedata_type 240 175 self._kernel_mono = (self._model.make_kernel(self._kernel_mono_inputs) 241 176 if self._kernel_mono_inputs else None) … … 271 206 """ 272 207 def __init__(self, data, model, cutoff=1e-5): 273 # type: (Data, KernelModel, float) -> None274 208 self.model = model 275 209 self.cutoff = cutoff … … 278 212 279 213 def __call__(self, **pars): 280 # type: (**float) -> np.ndarray281 214 return self._calc_theory(pars, cutoff=self.cutoff) 282 215 216 def ER_VR(self, **pars): 217 """ 218 Compute the equivalent radius and volume ratio for the model. 219 """ 220 return call_ER_VR(self.model.info, pars) 221 283 222 def simulate_data(self, noise=None, **pars): 284 # type: (Optional[float], **float) -> None285 223 """ 286 224 Generate simulated data for the model. … … 290 228 291 229 def main(): 292 # type: () -> None293 230 """ 294 231 Program to evaluate a particular model at a set of q values. … … 306 243 try: 307 244 values = [float(v) for v in call.split(',')] 308 except Exception:245 except: 309 246 values = [] 310 247 if len(values) == 1: -
sasmodels/generate.py
ra5b8477 r81ec7c8 21 21 22 22 *VR(p1, p2, ...)* returns the volume ratio for core-shell style forms. 23 24 #define INVALID(v) (expr) returns False if v.parameter is invalid25 for some parameter or other (e.g., v.bell_radius < v.radius). If26 necessary, the expression can call a function.27 23 28 24 These functions are defined in a kernel module .py script and an associated … … 69 65 The constructor code will generate the necessary vectors for computing 70 66 them with the desired polydispersity. 67 68 The available kernel parameters are defined as a list, with each parameter 69 defined 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 *limits = [lb, ub]* are the hard limits on the parameter value, used to 83 limit 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*. "sld" parameters 94 can automatically be promoted to magnetic parameters, each of which 95 will have a magnitude and a direction, which may be different from 96 other sld parameters. 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 71 102 The kernel module must set variables defining the kernel meta data: 72 103 … … 123 154 polydispersity values for tests. 124 155 125 A :class:`modelinfo.ModelInfo` structure is constructed from the kernel meta 126 data and returned to the caller. 156 An *model_info* dictionary is constructed from the kernel meta data and 157 returned to the caller. 158 159 The model evaluator, function call sequence consists of q inputs and the return vector, 160 followed by the loop value/weight vector, followed by the values for 161 the non-polydisperse parameters, followed by the lengths of the 162 polydispersity loops. To construct the call for 1D models, the 163 categories *fixed-1d* and *pd-1d* list the names of the parameters 164 of the non-polydisperse and the polydisperse parameters respectively. 165 Similarly, *fixed-2d* and *pd-2d* provide parameter names for 2D models. 166 The *pd-rel* category is a set of those parameters which give 167 polydispersitiy as a portion of the value (so a 10% length dispersity 168 would use a polydispersity value of 0.1) rather than absolute 169 dispersity such as an angle plus or minus 15 degrees. 170 171 The *volume* category lists the volume parameters in order for calls 172 to volume within the kernel (used for volume normalization) and for 173 calls to ER and VR for effective radius and volume ratio respectively. 174 175 The *orientation* and *magnetic* categories list the orientation and 176 magnetic parameters. These are used by the sasview interface. The 177 blank category is for parameters such as scale which don't have any 178 other marking. 127 179 128 180 The doc string at the start of the kernel module will be used to … … 132 184 file systems are case-sensitive, so only use lower case characters for 133 185 file names and extensions. 186 187 188 The function :func:`make` loads the metadata from the module and returns 189 the kernel source. The function :func:`make_doc` extracts the doc string 190 and adds the parameter table to the top. The function :func:`model_sources` 191 returns a list of files required by the model. 134 192 135 193 Code follows the C99 standard with the following extensions and conditions:: … … 143 201 all double declarations may be converted to half, float, or long double 144 202 FLOAT_SIZE is the number of bytes in the converted variables 145 146 :func:`load_kernel_module` loads the model definition file and147 :modelinfo:`make_model_info` parses it. :func:`make_source`148 converts C-based model definitions to C source code, including the149 polydispersity integral. :func:`model_sources` returns the list of150 source files the model depends on, and :func:`timestamp` returns151 the latest time stamp amongst the source files (so you can check if152 the model needs to be rebuilt).153 154 The function :func:`make_doc` extracts the doc string and adds the155 parameter table to the top. *make_figure* in *sasmodels/doc/genmodel*156 creates the default figure for the model. [These two sets of code157 should mignrate into docs.py so docs can be updated in one place].158 203 """ 159 204 from __future__ import print_function 160 205 206 #TODO: identify model files which have changed since loading and reload them. 161 207 #TODO: determine which functions are useful outside of generate 162 208 #__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 163 209 164 from os.path import abspath, dirname, join as joinpath, exists, getmtime 210 import sys 211 from os.path import abspath, dirname, join as joinpath, exists, basename, \ 212 splitext 165 213 import re 166 214 import string 167 215 import warnings 168 169 import numpy as np # type: ignore 170 171 from .modelinfo import Parameter 216 from collections import namedtuple 217 218 import numpy as np 219 172 220 from .custom import load_custom_kernel_module 173 221 174 try: 175 from typing import Tuple, Sequence, Iterator 176 from .modelinfo import ModelInfo 177 except ImportError: 178 pass 179 180 TEMPLATE_ROOT = dirname(__file__) 222 PARAMETER_FIELDS = ['name', 'units', 'default', 'limits', 'type', 'description'] 223 Parameter = namedtuple('Parameter', PARAMETER_FIELDS) 224 225 C_KERNEL_TEMPLATE_PATH = joinpath(dirname(__file__), 'kernel_template.c') 181 226 182 227 F16 = np.dtype('float16') … … 187 232 except TypeError: 188 233 F128 = None 234 235 # Scale and background, which are parameters common to every form factor 236 COMMON_PARAMETERS = [ 237 ["scale", "", 1, [0, np.inf], "", "Source intensity"], 238 ["background", "1/cm", 1e-3, [0, np.inf], "", "Source background"], 239 ] 189 240 190 241 # Conversion from units defined in the parameter table for each model … … 233 284 234 285 def format_units(units): 235 # type: (str) -> str236 286 """ 237 287 Convert units into ReStructured Text format. … … 240 290 241 291 def make_partable(pars): 242 # type: (List[Parameter]) -> str243 292 """ 244 293 Generate the parameter table to include in the sphinx documentation. … … 271 320 272 321 def _search(search_path, filename): 273 # type: (List[str], str) -> str274 322 """ 275 323 Find *filename* in *search_path*. … … 283 331 raise ValueError("%r not found in %s" % (filename, search_path)) 284 332 285 286 333 def model_sources(model_info): 287 # type: (ModelInfo) -> List[str]288 334 """ 289 335 Return a list of the sources file paths for the module. 290 336 """ 291 search_path = [dirname(model_info .filename),337 search_path = [dirname(model_info['filename']), 292 338 abspath(joinpath(dirname(__file__), 'models'))] 293 return [_search(search_path, f) for f in model_info.source] 294 295 def timestamp(model_info): 296 # type: (ModelInfo) -> int 297 """ 298 Return a timestamp for the model corresponding to the most recently 299 changed file or dependency. 300 """ 301 source_files = (model_sources(model_info) 302 + model_templates() 303 + [model_info.filename]) 304 newest = max(getmtime(f) for f in source_files) 305 return newest 339 return [_search(search_path, f) for f in model_info['source']] 340 341 # Pragmas for enable OpenCL features. Be sure to protect them so that they 342 # still compile even if OpenCL is not present. 343 _F16_PRAGMA = """\ 344 #if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp16) 345 # pragma OPENCL EXTENSION cl_khr_fp16: enable 346 #endif 347 """ 348 349 _F64_PRAGMA = """\ 350 #if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp64) 351 # pragma OPENCL EXTENSION cl_khr_fp64: enable 352 #endif 353 """ 306 354 307 355 def convert_type(source, dtype): 308 # type: (str, np.dtype) -> str309 356 """ 310 357 Convert code from double precision to the desired type. … … 315 362 if dtype == F16: 316 363 fbytes = 2 317 source = _ convert_type(source, "half", "f")364 source = _F16_PRAGMA + _convert_type(source, "half", "f") 318 365 elif dtype == F32: 319 366 fbytes = 4 … … 321 368 elif dtype == F64: 322 369 fbytes = 8 323 # no need to convert if itis already double370 source = _F64_PRAGMA + source # Source is already double 324 371 elif dtype == F128: 325 372 fbytes = 16 … … 331 378 332 379 def _convert_type(source, type_name, constant_flag): 333 # type: (str, str, str) -> str334 380 """ 335 381 Replace 'double' with *type_name* in *source*, tagging floating point … … 350 396 351 397 def kernel_name(model_info, is_2d): 352 # type: (ModelInfo, bool) -> str353 398 """ 354 399 Name of the exported kernel symbol. 355 400 """ 356 return model_info .name+ "_" + ("Iqxy" if is_2d else "Iq")401 return model_info['name'] + "_" + ("Iqxy" if is_2d else "Iq") 357 402 358 403 359 404 def indent(s, depth): 360 # type: (str, int) -> str361 405 """ 362 406 Indent a string of text with *depth* additional spaces on each line. … … 367 411 368 412 369 _template_cache = {} # type: Dict[str, Tuple[int, str, str]] 370 def load_template(filename): 371 # type: (str) -> str 372 path = joinpath(TEMPLATE_ROOT, filename) 373 mtime = getmtime(path) 374 if filename not in _template_cache or mtime > _template_cache[filename][0]: 375 with open(path) as fid: 376 _template_cache[filename] = (mtime, fid.read(), path) 377 return _template_cache[filename][1] 378 379 def model_templates(): 380 # type: () -> List[str] 381 # TODO: fails DRY; templates are listed in two places. 382 # should instead have model_info contain a list of paths 383 return [joinpath(TEMPLATE_ROOT, filename) 384 for filename in ('kernel_header.c', 'kernel_iq.c')] 385 386 387 _FN_TEMPLATE = """\ 388 double %(name)s(%(pars)s); 389 double %(name)s(%(pars)s) { 390 %(body)s 391 } 392 393 413 LOOP_OPEN = """\ 414 for (int %(name)s_i=0; %(name)s_i < N%(name)s; %(name)s_i++) { 415 const double %(name)s = loops[2*(%(name)s_i%(offset)s)]; 416 const double %(name)s_w = loops[2*(%(name)s_i%(offset)s)+1];\ 394 417 """ 395 396 def _gen_fn(name, pars, body): 397 # type: (str, List[Parameter], str) -> str 398 """ 399 Generate a function given pars and body. 400 401 Returns the following string:: 402 403 double fn(double a, double b, ...); 404 double fn(double a, double b, ...) { 405 .... 406 } 407 """ 408 par_decl = ', '.join(p.as_function_argument() for p in pars) if pars else 'void' 409 return _FN_TEMPLATE % {'name': name, 'body': body, 'pars': par_decl} 410 411 def _call_pars(prefix, pars): 412 # type: (str, List[Parameter]) -> List[str] 413 """ 414 Return a list of *prefix.parameter* from parameter items. 415 """ 416 return [p.as_call_reference(prefix) for p in pars] 417 418 _IQXY_PATTERN = re.compile("^((inline|static) )? *(double )? *Iqxy *([(]|$)", 419 flags=re.MULTILINE) 420 def _have_Iqxy(sources): 421 # type: (List[str]) -> bool 422 """ 423 Return true if any file defines Iqxy. 424 425 Note this is not a C parser, and so can be easily confused by 426 non-standard syntax. Also, it will incorrectly identify the following 427 as having Iqxy:: 428 429 /* 430 double Iqxy(qx, qy, ...) { ... fill this in later ... } 431 */ 432 433 If you want to comment out an Iqxy function, use // on the front of the 434 line instead. 435 """ 436 for code in sources: 437 if _IQXY_PATTERN.search(code): 438 return True 439 else: 440 return False 441 418 def build_polydispersity_loops(pd_pars): 419 """ 420 Build polydispersity loops 421 422 Returns loop opening and loop closing 423 """ 424 depth = 4 425 offset = "" 426 loop_head = [] 427 loop_end = [] 428 for name in pd_pars: 429 subst = {'name': name, 'offset': offset} 430 loop_head.append(indent(LOOP_OPEN % subst, depth)) 431 loop_end.insert(0, (" "*depth) + "}") 432 offset += '+N' + name 433 depth += 2 434 return "\n".join(loop_head), "\n".join(loop_end) 435 436 C_KERNEL_TEMPLATE = None 442 437 def make_source(model_info): 443 # type: (ModelInfo) -> str444 438 """ 445 439 Generate the OpenCL/ctypes kernel from the module info. 446 440 447 Uses source files found in the given search path. Returns None if this 448 is a pure python model, with no C source components. 449 """ 450 if callable(model_info.Iq): 451 raise ValueError("can't compile python model") 441 Uses source files found in the given search path. 442 """ 443 if callable(model_info['Iq']): 444 return None 452 445 453 446 # TODO: need something other than volume to indicate dispersion parameters … … 460 453 # for computing volume even if we allow non-disperse volume parameters. 461 454 462 partable = model_info.parameters 463 464 # Identify parameters for Iq, Iqxy, Iq_magnetic and form_volume. 465 # Note that scale and volume are not possible types. 466 467 # Load templates and user code 468 kernel_header = load_template('kernel_header.c') 469 kernel_code = load_template('kernel_iq.c') 470 user_code = [open(f).read() for f in model_sources(model_info)] 471 472 # Build initial sources 473 source = [kernel_header] + user_code 474 475 # Make parameters for q, qx, qy so that we can use them in declarations 476 q, qx, qy = [Parameter(name=v) for v in ('q', 'qx', 'qy')] 477 # Generate form_volume function, etc. from body only 478 if isinstance(model_info.form_volume, str): 479 pars = partable.form_volume_parameters 480 source.append(_gen_fn('form_volume', pars, model_info.form_volume)) 481 if isinstance(model_info.Iq, str): 482 pars = [q] + partable.iq_parameters 483 source.append(_gen_fn('Iq', pars, model_info.Iq)) 484 if isinstance(model_info.Iqxy, str): 485 pars = [qx, qy] + partable.iqxy_parameters 486 source.append(_gen_fn('Iqxy', pars, model_info.Iqxy)) 487 488 # Define the parameter table 489 source.append("#define PARAMETER_TABLE \\") 490 source.append("\\\n".join(p.as_definition() 491 for p in partable.kernel_parameters)) 492 493 # Define the function calls 494 if partable.form_volume_parameters: 495 refs = _call_pars("_v.", partable.form_volume_parameters) 496 call_volume = "#define CALL_VOLUME(_v) form_volume(%s)" % (",".join(refs)) 497 else: 498 # Model doesn't have volume. We could make the kernel run a little 499 # faster by not using/transferring the volume normalizations, but 500 # the ifdef's reduce readability more than is worthwhile. 501 call_volume = "#define CALL_VOLUME(v) 1.0" 502 source.append(call_volume) 503 504 refs = ["_q[_i]"] + _call_pars("_v.", partable.iq_parameters) 505 call_iq = "#define CALL_IQ(_q,_i,_v) Iq(%s)" % (",".join(refs)) 506 if _have_Iqxy(user_code): 507 # Call 2D model 508 refs = ["q[2*i]", "q[2*i+1]"] + _call_pars("_v.", partable.iqxy_parameters) 509 call_iqxy = "#define CALL_IQ(_q,_i,_v) Iqxy(%s)" % (",".join(refs)) 510 else: 511 # Call 1D model with sqrt(qx^2 + qy^2) 512 warnings.warn("Creating Iqxy = Iq(sqrt(qx^2 + qy^2))") 513 # still defined:: refs = ["q[i]"] + _call_pars("v", iq_parameters) 514 pars_sqrt = ["sqrt(_q[2*_i]*_q[2*_i]+_q[2*_i+1]*_q[2*_i+1])"] + refs[1:] 515 call_iqxy = "#define CALL_IQ(_q,_i,_v) Iq(%s)" % (",".join(pars_sqrt)) 516 517 # Fill in definitions for numbers of parameters 518 source.append("#define MAX_PD %s"%partable.max_pd) 519 source.append("#define NPARS %d"%partable.npars) 520 521 # TODO: allow mixed python/opencl kernels? 522 523 # define the Iq kernel 524 source.append("#define KERNEL_NAME %s_Iq"%model_info.name) 525 source.append(call_iq) 526 source.append(kernel_code) 527 source.append("#undef CALL_IQ") 528 source.append("#undef KERNEL_NAME") 529 530 # define the Iqxy kernel from the same source with different #defines 531 source.append("#define KERNEL_NAME %s_Iqxy"%model_info.name) 532 source.append(call_iqxy) 533 source.append(kernel_code) 534 source.append("#undef CALL_IQ") 535 source.append("#undef KERNEL_NAME") 536 537 return '\n'.join(source) 455 # Load template 456 global C_KERNEL_TEMPLATE 457 if C_KERNEL_TEMPLATE is None: 458 with open(C_KERNEL_TEMPLATE_PATH) as fid: 459 C_KERNEL_TEMPLATE = fid.read() 460 461 # Load additional sources 462 source = [open(f).read() for f in model_sources(model_info)] 463 464 # Prepare defines 465 defines = [] 466 partype = model_info['partype'] 467 pd_1d = partype['pd-1d'] 468 pd_2d = partype['pd-2d'] 469 fixed_1d = partype['fixed-1d'] 470 fixed_2d = partype['fixed-1d'] 471 472 iq_parameters = [p.name 473 for p in model_info['parameters'][2:] # skip scale, background 474 if p.name in set(fixed_1d + pd_1d)] 475 iqxy_parameters = [p.name 476 for p in model_info['parameters'][2:] # skip scale, background 477 if p.name in set(fixed_2d + pd_2d)] 478 volume_parameters = [p.name 479 for p in model_info['parameters'] 480 if p.type == 'volume'] 481 482 # Fill in defintions for volume parameters 483 if volume_parameters: 484 defines.append(('VOLUME_PARAMETERS', 485 ','.join(volume_parameters))) 486 defines.append(('VOLUME_WEIGHT_PRODUCT', 487 '*'.join(p + '_w' for p in volume_parameters))) 488 489 # Generate form_volume function from body only 490 if model_info['form_volume'] is not None: 491 if volume_parameters: 492 vol_par_decl = ', '.join('double ' + p for p in volume_parameters) 493 else: 494 vol_par_decl = 'void' 495 defines.append(('VOLUME_PARAMETER_DECLARATIONS', 496 vol_par_decl)) 497 fn = """\ 498 double form_volume(VOLUME_PARAMETER_DECLARATIONS); 499 double form_volume(VOLUME_PARAMETER_DECLARATIONS) { 500 %(body)s 501 } 502 """ % {'body':model_info['form_volume']} 503 source.append(fn) 504 505 # Fill in definitions for Iq parameters 506 defines.append(('IQ_KERNEL_NAME', model_info['name'] + '_Iq')) 507 defines.append(('IQ_PARAMETERS', ', '.join(iq_parameters))) 508 if fixed_1d: 509 defines.append(('IQ_FIXED_PARAMETER_DECLARATIONS', 510 ', \\\n '.join('const double %s' % p for p in fixed_1d))) 511 if pd_1d: 512 defines.append(('IQ_WEIGHT_PRODUCT', 513 '*'.join(p + '_w' for p in pd_1d))) 514 defines.append(('IQ_DISPERSION_LENGTH_DECLARATIONS', 515 ', \\\n '.join('const int N%s' % p for p in pd_1d))) 516 defines.append(('IQ_DISPERSION_LENGTH_SUM', 517 '+'.join('N' + p for p in pd_1d))) 518 open_loops, close_loops = build_polydispersity_loops(pd_1d) 519 defines.append(('IQ_OPEN_LOOPS', 520 open_loops.replace('\n', ' \\\n'))) 521 defines.append(('IQ_CLOSE_LOOPS', 522 close_loops.replace('\n', ' \\\n'))) 523 if model_info['Iq'] is not None: 524 defines.append(('IQ_PARAMETER_DECLARATIONS', 525 ', '.join('double ' + p for p in iq_parameters))) 526 fn = """\ 527 double Iq(double q, IQ_PARAMETER_DECLARATIONS); 528 double Iq(double q, IQ_PARAMETER_DECLARATIONS) { 529 %(body)s 530 } 531 """ % {'body':model_info['Iq']} 532 source.append(fn) 533 534 # Fill in definitions for Iqxy parameters 535 defines.append(('IQXY_KERNEL_NAME', model_info['name'] + '_Iqxy')) 536 defines.append(('IQXY_PARAMETERS', ', '.join(iqxy_parameters))) 537 if fixed_2d: 538 defines.append(('IQXY_FIXED_PARAMETER_DECLARATIONS', 539 ', \\\n '.join('const double %s' % p for p in fixed_2d))) 540 if pd_2d: 541 defines.append(('IQXY_WEIGHT_PRODUCT', 542 '*'.join(p + '_w' for p in pd_2d))) 543 defines.append(('IQXY_DISPERSION_LENGTH_DECLARATIONS', 544 ', \\\n '.join('const int N%s' % p for p in pd_2d))) 545 defines.append(('IQXY_DISPERSION_LENGTH_SUM', 546 '+'.join('N' + p for p in pd_2d))) 547 open_loops, close_loops = build_polydispersity_loops(pd_2d) 548 defines.append(('IQXY_OPEN_LOOPS', 549 open_loops.replace('\n', ' \\\n'))) 550 defines.append(('IQXY_CLOSE_LOOPS', 551 close_loops.replace('\n', ' \\\n'))) 552 if model_info['Iqxy'] is not None: 553 defines.append(('IQXY_PARAMETER_DECLARATIONS', 554 ', '.join('double ' + p for p in iqxy_parameters))) 555 fn = """\ 556 double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS); 557 double Iqxy(double qx, double qy, IQXY_PARAMETER_DECLARATIONS) { 558 %(body)s 559 } 560 """ % {'body':model_info['Iqxy']} 561 source.append(fn) 562 563 # Need to know if we have a theta parameter for Iqxy; it is not there 564 # for the magnetic sphere model, for example, which has a magnetic 565 # orientation but no shape orientation. 566 if 'theta' in pd_2d: 567 defines.append(('IQXY_HAS_THETA', '1')) 568 569 #for d in defines: print(d) 570 defines = '\n'.join('#define %s %s' % (k, v) for k, v in defines) 571 sources = '\n\n'.join(source) 572 return C_KERNEL_TEMPLATE % { 573 'DEFINES': defines, 574 'SOURCES': sources, 575 } 576 577 def categorize_parameters(pars): 578 """ 579 Build parameter categories out of the the parameter definitions. 580 581 Returns a dictionary of categories. 582 583 Note: these categories are subject to change, depending on the needs of 584 the UI and the needs of the kernel calling function. 585 586 The categories are as follows: 587 588 * *volume* list of volume parameter names 589 * *orientation* list of orientation parameters 590 * *magnetic* list of magnetic parameters 591 * *<empty string>* list of parameters that have no type info 592 593 Each parameter is in one and only one category. 594 595 The following derived categories are created: 596 597 * *fixed-1d* list of non-polydisperse parameters for 1D models 598 * *pd-1d* list of polydisperse parameters for 1D models 599 * *fixed-2d* list of non-polydisperse parameters for 2D models 600 * *pd-d2* list of polydisperse parameters for 2D models 601 """ 602 partype = { 603 'volume': [], 'orientation': [], 'magnetic': [], 'sld': [], '': [], 604 'fixed-1d': [], 'fixed-2d': [], 'pd-1d': [], 'pd-2d': [], 605 'pd-rel': set(), 606 } 607 608 for p in pars: 609 if p.type == 'volume': 610 partype['pd-1d'].append(p.name) 611 partype['pd-2d'].append(p.name) 612 partype['pd-rel'].add(p.name) 613 elif p.type == 'magnetic': 614 partype['fixed-2d'].append(p.name) 615 elif p.type == 'orientation': 616 partype['pd-2d'].append(p.name) 617 elif p.type in ('', 'sld'): 618 partype['fixed-1d'].append(p.name) 619 partype['fixed-2d'].append(p.name) 620 else: 621 raise ValueError("unknown parameter type %r" % p.type) 622 partype[p.type].append(p.name) 623 624 return partype 625 626 def process_parameters(model_info): 627 """ 628 Process parameter block, precalculating parameter details. 629 """ 630 # convert parameters into named tuples 631 for p in model_info['parameters']: 632 if p[4] == '' and (p[0].startswith('sld') or p[0].endswith('sld')): 633 p[4] = 'sld' 634 # TODO: make sure all models explicitly label their sld parameters 635 #raise ValueError("%s.%s needs to be explicitly set to type 'sld'" %(model_info['id'], p[0])) 636 637 pars = [Parameter(*p) for p in model_info['parameters']] 638 # Fill in the derived attributes 639 model_info['parameters'] = pars 640 partype = categorize_parameters(pars) 641 model_info['limits'] = dict((p.name, p.limits) for p in pars) 642 model_info['partype'] = partype 643 model_info['defaults'] = dict((p.name, p.default) for p in pars) 644 if model_info.get('demo', None) is None: 645 model_info['demo'] = model_info['defaults'] 646 model_info['has_2d'] = partype['orientation'] or partype['magnetic'] 647 538 648 539 649 def load_kernel_module(model_name): 540 # type: (str) -> module541 650 if model_name.endswith('.py'): 542 651 kernel_module = load_custom_kernel_module(model_name) … … 548 657 549 658 659 def make_model_info(kernel_module): 660 """ 661 Interpret the model definition file, categorizing the parameters. 662 663 The module can be loaded with a normal python import statement if you 664 know which module you need, or with __import__('sasmodels.model.'+name) 665 if the name is in a string. 666 667 The *model_info* structure contains the following fields: 668 669 * *id* is the id of the kernel 670 * *name* is the display name of the kernel 671 * *filename* is the full path to the module defining the file (if any) 672 * *title* is a short description of the kernel 673 * *description* is a long description of the kernel (this doesn't seem 674 very useful since the Help button on the model page brings you directly 675 to the documentation page) 676 * *docs* is the docstring from the module. Use :func:`make_doc` to 677 * *category* specifies the model location in the docs 678 * *parameters* is the model parameter table 679 * *single* is True if the model allows single precision 680 * *structure_factor* is True if the model is useable in a product 681 * *defaults* is the *{parameter: value}* table built from the parameter 682 description table. 683 * *limits* is the *{parameter: [min, max]}* table built from the 684 parameter description table. 685 * *partypes* categorizes the model parameters. See 686 :func:`categorize_parameters` for details. 687 * *demo* contains the *{parameter: value}* map used in compare (and maybe 688 for the demo plot, if plots aren't set up to use the default values). 689 If *demo* is not given in the file, then the default values will be used. 690 * *tests* is a set of tests that must pass 691 * *source* is the list of library files to include in the C model build 692 * *Iq*, *Iqxy*, *form_volume*, *ER*, *VR* and *sesans* are python functions 693 implementing the kernel for the module, or None if they are not 694 defined in python 695 * *composition* is None if the model is independent, otherwise it is a 696 tuple with composition type ('product' or 'mixture') and a list of 697 *model_info* blocks for the composition objects. This allows us to 698 build complete product and mixture models from just the info. 699 700 """ 701 parameters = COMMON_PARAMETERS + kernel_module.parameters 702 filename = abspath(kernel_module.__file__) 703 kernel_id = splitext(basename(filename))[0] 704 name = getattr(kernel_module, 'name', None) 705 if name is None: 706 name = " ".join(w.capitalize() for w in kernel_id.split('_')) 707 model_info = dict( 708 id=kernel_id, # string used to load the kernel 709 filename=abspath(kernel_module.__file__), 710 name=name, 711 title=kernel_module.title, 712 description=kernel_module.description, 713 parameters=parameters, 714 composition=None, 715 docs=kernel_module.__doc__, 716 category=getattr(kernel_module, 'category', None), 717 single=getattr(kernel_module, 'single', True), 718 structure_factor=getattr(kernel_module, 'structure_factor', False), 719 control=getattr(kernel_module, 'control', None), 720 demo=getattr(kernel_module, 'demo', None), 721 source=getattr(kernel_module, 'source', []), 722 tests=getattr(kernel_module, 'tests', []), 723 ) 724 process_parameters(model_info) 725 # Check for optional functions 726 functions = "ER VR form_volume Iq Iqxy shape sesans".split() 727 model_info.update((k, getattr(kernel_module, k, None)) for k in functions) 728 return model_info 550 729 551 730 section_marker = re.compile(r'\A(?P<first>[%s])(?P=first)*\Z' 552 731 %re.escape(string.punctuation)) 553 732 def _convert_section_titles_to_boldface(lines): 554 # type: (Sequence[str]) -> Iterator[str]555 733 """ 556 734 Do the actual work of identifying and converting section headings. … … 574 752 575 753 def convert_section_titles_to_boldface(s): 576 # type: (str) -> str577 754 """ 578 755 Use explicit bold-face rather than section headings so that the table of … … 585 762 586 763 def make_doc(model_info): 587 # type: (ModelInfo) -> str588 764 """ 589 765 Return the documentation for the model. … … 591 767 Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale." 592 768 Sq_units = "The returned value is a dimensionless structure factor, $S(q)$." 593 docs = convert_section_titles_to_boldface(model_info.docs) 594 pars = make_partable(model_info.parameters.COMMON 595 + model_info.parameters.kernel_parameters) 596 subst = dict(id=model_info.id.replace('_', '-'), 597 name=model_info.name, 598 title=model_info.title, 599 parameters=pars, 600 returns=Sq_units if model_info.structure_factor else Iq_units, 769 docs = convert_section_titles_to_boldface(model_info['docs']) 770 subst = dict(id=model_info['id'].replace('_', '-'), 771 name=model_info['name'], 772 title=model_info['title'], 773 parameters=make_partable(model_info['parameters']), 774 returns=Sq_units if model_info['structure_factor'] else Iq_units, 601 775 docs=docs) 602 776 return DOC_HEADER % subst … … 604 778 605 779 def demo_time(): 606 # type: () -> None607 780 """ 608 781 Show how long it takes to process a model. 609 782 """ 783 from .models import cylinder 610 784 import datetime 611 from .modelinfo import make_model_info612 from .models import cylinder613 614 785 tic = datetime.datetime.now() 615 786 make_source(make_model_info(cylinder)) … … 618 789 619 790 def main(): 620 # type: () -> None621 791 """ 622 792 Program which prints the source produced by the model. 623 793 """ 624 import sys625 from .modelinfo import make_model_info626 627 794 if len(sys.argv) <= 1: 628 795 print("usage: python -m sasmodels.generate modelname") -
sasmodels/kernelcl.py
ra5b8477 r4d76711 48 48 harmless, albeit annoying. 49 49 """ 50 from __future__ import print_function51 52 50 import os 53 51 import warnings 54 52 55 import numpy as np # type: ignore53 import numpy as np 56 54 57 55 try: 58 raise NotImplementedError("OpenCL not yet implemented for new kernel template") 59 import pyopencl as cl # type: ignore 56 import pyopencl as cl 60 57 # Ask OpenCL for the default context so that we know that one exists 61 58 cl.create_some_context(interactive=False) … … 64 61 raise RuntimeError("OpenCL not available") 65 62 66 from pyopencl import mem_flags as mf # type: ignore67 from pyopencl.characterize import get_fast_inaccurate_build_options # type: ignore63 from pyopencl import mem_flags as mf 64 from pyopencl.characterize import get_fast_inaccurate_build_options 68 65 69 66 from . import generate 70 from .kernel import KernelModel, Kernel71 72 try:73 from typing import Tuple, Callable, Any74 from .modelinfo import ModelInfo75 from .details import CallDetails76 except ImportError:77 pass78 67 79 68 # The max loops number is limited by the amount of local memory available … … 84 73 # of polydisperse parameters. 85 74 MAX_LOOPS = 2048 86 87 88 # Pragmas for enable OpenCL features. Be sure to protect them so that they89 # still compile even if OpenCL is not present.90 _F16_PRAGMA = """\91 #if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp16)92 # pragma OPENCL EXTENSION cl_khr_fp16: enable93 #endif94 """95 96 _F64_PRAGMA = """\97 #if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp64)98 # pragma OPENCL EXTENSION cl_khr_fp64: enable99 #endif100 """101 75 102 76 … … 168 142 raise RuntimeError("%s not supported for devices"%dtype) 169 143 170 source_list = [generate.convert_type(source, dtype)] 171 172 if dtype == generate.F16: 173 source_list.insert(0, _F16_PRAGMA) 174 elif dtype == generate.F64: 175 source_list.insert(0, _F64_PRAGMA) 176 144 source = generate.convert_type(source, dtype) 177 145 # Note: USE_SINCOS makes the intel cpu slower under opencl 178 146 if context.devices[0].type == cl.device_type.GPU: 179 source _list.insert(0, "#define USE_SINCOS\n")147 source = "#define USE_SINCOS\n" + source 180 148 options = (get_fast_inaccurate_build_options(context.devices[0]) 181 149 if fast else []) 182 source = "\n".join(source_list)183 150 program = cl.Program(context, source).build(options=options) 184 151 return program … … 253 220 key = "%s-%s-%s"%(name, dtype, fast) 254 221 if key not in self.compiled: 255 print("compiling",name)222 #print("compiling",name) 256 223 dtype = np.dtype(dtype) 257 program = compile_model(self.get_context(dtype), 258 str(source), dtype, fast) 224 program = compile_model(self.get_context(dtype), source, dtype, fast) 259 225 self.compiled[key] = program 260 226 return self.compiled[key] … … 319 285 320 286 321 class GpuModel( KernelModel):287 class GpuModel(object): 322 288 """ 323 289 GPU wrapper for a single model. … … 351 317 if self.program is None: 352 318 compiler = environment().compile_program 353 self.program = compiler(self.info .name, self.source,354 self. dtype, self.fast)319 self.program = compiler(self.info['name'], self.source, self.dtype, 320 self.fast) 355 321 is_2d = len(q_vectors) == 2 356 322 kernel_name = generate.kernel_name(self.info, is_2d) … … 363 329 """ 364 330 if self.program is not None: 365 environment().release_program(self.info .name)331 environment().release_program(self.info['name']) 366 332 self.program = None 367 333 … … 399 365 # at this point, so instead using 32, which is good on the set of 400 366 # architectures tested so far. 401 if self.is_2d: 402 # Note: 17 rather than 15 because results is 2 elements 403 # longer than input. 404 width = ((self.nq+17)//16)*16 405 self.q = np.empty((width, 2), dtype=dtype) 406 self.q[:self.nq, 0] = q_vectors[0] 407 self.q[:self.nq, 1] = q_vectors[1] 408 else: 409 # Note: 33 rather than 31 because results is 2 elements 410 # longer than input. 411 width = ((self.nq+33)//32)*32 412 self.q = np.empty(width, dtype=dtype) 413 self.q[:self.nq] = q_vectors[0] 414 self.global_size = [self.q.shape[0]] 367 self.q_vectors = [_stretch_input(q, self.dtype, 32) for q in q_vectors] 415 368 context = env.get_context(self.dtype) 369 self.global_size = [self.q_vectors[0].size] 416 370 #print("creating inputs of size", self.global_size) 417 self.q_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 418 hostbuf=self.q) 371 self.q_buffers = [ 372 cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=q) 373 for q in self.q_vectors 374 ] 419 375 420 376 def release(self): … … 422 378 Free the memory. 423 379 """ 424 if self.q is not None:425 self.q.release()426 self.q = None380 for b in self.q_buffers: 381 b.release() 382 self.q_buffers = [] 427 383 428 384 def __del__(self): 429 385 self.release() 430 386 431 class GpuKernel( Kernel):387 class GpuKernel(object): 432 388 """ 433 389 Callable SAS kernel. … … 449 405 Call :meth:`release` when done with the kernel instance. 450 406 """ 451 def __init__(self, kernel, model_info, q_vectors): 452 # type: (KernelModel, ModelInfo, List[np.ndarray]) -> None 453 max_pd = model_info.parameters.max_pd 454 npars = len(model_info.parameters.kernel_parameters)-2 455 q_input = GpuInput(q_vectors, kernel.dtype) 407 def __init__(self, kernel, model_info, q_vectors, dtype): 408 q_input = GpuInput(q_vectors, dtype) 456 409 self.kernel = kernel 457 410 self.info = model_info 458 self.dtype = kernel.dtype 459 self.dim = '2d' if q_input.is_2d else '1d' 460 self.pd_stop_index = 4*max_pd-1 461 # plus three for the normalization values 462 self.result = np.empty(q_input.nq+3, q_input.dtype) 411 self.res = np.empty(q_input.nq, q_input.dtype) 412 dim = '2d' if q_input.is_2d else '1d' 413 self.fixed_pars = model_info['partype']['fixed-' + dim] 414 self.pd_pars = model_info['partype']['pd-' + dim] 463 415 464 416 # Inputs and outputs for each kernel call 465 417 # Note: res may be shorter than res_b if global_size != nq 466 418 env = environment() 467 self.queue = env.get_queue(kernel.dtype) 468 469 # details is int32 data, padded to an 8 integer boundary 470 size = ((max_pd*5 + npars*3 + 2 + 7)//8)*8 471 self.result_b = cl.Buffer(self.queue.context, mf.READ_WRITE, 472 q_input.global_size[0] * kernel.dtype.itemsize) 473 self.q_input = q_input # allocated by GpuInput above 474 475 self._need_release = [ self.result_b, self.q_input ] 476 477 def __call__(self, call_details, weights, values, cutoff): 478 # type: (CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray 419 self.queue = env.get_queue(dtype) 420 self.loops_b = cl.Buffer(self.queue.context, mf.READ_WRITE, 421 2 * MAX_LOOPS * q_input.dtype.itemsize) 422 self.res_b = cl.Buffer(self.queue.context, mf.READ_WRITE, 423 q_input.global_size[0] * q_input.dtype.itemsize) 424 self.q_input = q_input 425 426 self._need_release = [self.loops_b, self.res_b, self.q_input] 427 428 def __call__(self, fixed_pars, pd_pars, cutoff): 479 429 real = (np.float32 if self.q_input.dtype == generate.F32 480 430 else np.float64 if self.q_input.dtype == generate.F64 481 431 else np.float16 if self.q_input.dtype == generate.F16 482 432 else np.float32) # will never get here, so use np.float32 483 assert call_details.dtype == np.int32 484 assert weights.dtype == real and values.dtype == real 485 486 context = self.queue.context 487 details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 488 hostbuf=call_details) 489 weights_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 490 hostbuf=weights) 491 values_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 492 hostbuf=values) 493 494 start, stop = 0, self.details[self.pd_stop_index] 495 args = [ 496 np.uint32(self.q_input.nq), np.uint32(start), np.uint32(stop), 497 self.details_b, self.weights_b, self.values_b, 498 self.q_input.q_b, self.result_b, real(cutoff), 499 ] 433 434 #print "pars", fixed_pars, pd_pars 435 res_bi = self.res_b 436 nq = np.uint32(self.q_input.nq) 437 if pd_pars: 438 cutoff = real(cutoff) 439 loops_N = [np.uint32(len(p[0])) for p in pd_pars] 440 loops = np.hstack(pd_pars) \ 441 if pd_pars else np.empty(0, dtype=self.q_input.dtype) 442 loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten() 443 #print("loops",Nloops, loops) 444 445 #import sys; print("opencl eval",pars) 446 #print("opencl eval",pars) 447 if len(loops) > 2 * MAX_LOOPS: 448 raise ValueError("too many polydispersity points") 449 450 loops_bi = self.loops_b 451 cl.enqueue_copy(self.queue, loops_bi, loops) 452 loops_l = cl.LocalMemory(len(loops.data)) 453 #ctx = environment().context 454 #loops_bi = cl.Buffer(ctx, mf.READ_ONLY|mf.COPY_HOST_PTR, hostbuf=loops) 455 dispersed = [loops_bi, loops_l, cutoff] + loops_N 456 else: 457 dispersed = [] 458 fixed = [real(p) for p in fixed_pars] 459 args = self.q_input.q_buffers + [res_bi, nq] + dispersed + fixed 500 460 self.kernel(self.queue, self.q_input.global_size, None, *args) 501 cl.enqueue_copy(self.queue, self.result, self.result_b) 502 [v.release() for v in (details_b, weights_b, values_b)] 503 504 return self.result[:self.nq] 461 cl.enqueue_copy(self.queue, self.res, res_bi) 462 463 return self.res 505 464 506 465 def release(self): -
sasmodels/kerneldll.py
ra5b8477 r4d76711 49 49 import os 50 50 import tempfile 51 import ctypes as ct # type: ignore 52 from ctypes import c_void_p, c_int32, c_longdouble, c_double, c_float # type: ignore 53 54 import numpy as np # type: ignore 51 import ctypes as ct 52 from ctypes import c_void_p, c_int, c_longdouble, c_double, c_float 53 import _ctypes 54 55 import numpy as np 55 56 56 57 from . import generate 57 from .kernel import KernelModel, Kernel 58 from .kernelpy import PyInput 58 from .kernelpy import PyInput, PyModel 59 59 from .exception import annotate_exception 60 from .generate import F16, F32, F6461 62 try:63 from typing import Tuple, Callable, Any64 from .modelinfo import ModelInfo65 from .details import CallDetails66 except ImportError:67 pass68 60 69 61 # Compiler platform details … … 89 81 COMPILE = "gcc -shared -fPIC -std=c99 -O2 -Wall %(source)s -o %(output)s -lm" 90 82 if "SAS_OPENMP" in os.environ: 91 COMPILE +=" -fopenmp"83 COMPILE = COMPILE + " -fopenmp" 92 84 else: 93 85 COMPILE = "cc -shared -fPIC -fopenmp -std=c99 -O2 -Wall %(source)s -o %(output)s -lm" … … 98 90 99 91 100 def dll_name(model_info, dtype): 101 # type: (ModelInfo, np.dtype) -> str 102 """ 103 Name of the dll containing the model. This is the base file name without 104 any path or extension, with a form such as 'sas_sphere32'. 105 """ 106 bits = 8*dtype.itemsize 107 return "sas_%s%d"%(model_info.id, bits) 108 109 110 def dll_path(model_info, dtype): 111 # type: (ModelInfo, np.dtype) -> str 112 """ 113 Complete path to the dll for the model. Note that the dll may not 114 exist yet if it hasn't been compiled. 115 """ 116 return os.path.join(DLL_PATH, dll_name(model_info, dtype)+".so") 117 118 119 def make_dll(source, model_info, dtype=F64): 120 # type: (str, ModelInfo, np.dtype) -> str 121 """ 122 Returns the path to the compiled model defined by *kernel_module*. 123 124 If the model has not been compiled, or if the source file(s) are newer 125 than the dll, then *make_dll* will compile the model before returning. 126 This routine does not load the resulting dll. 92 def dll_path(model_info, dtype="double"): 93 """ 94 Path to the compiled model defined by *model_info*. 95 """ 96 from os.path import join as joinpath, split as splitpath, splitext 97 basename = splitext(splitpath(model_info['filename'])[1])[0] 98 if np.dtype(dtype) == generate.F32: 99 basename += "32" 100 elif np.dtype(dtype) == generate.F64: 101 basename += "64" 102 else: 103 basename += "128" 104 return joinpath(DLL_PATH, basename+'.so') 105 106 107 def make_dll(source, model_info, dtype="double"): 108 """ 109 Load the compiled model defined by *kernel_module*. 110 111 Recompile if any files are newer than the model file. 127 112 128 113 *dtype* is a numpy floating point precision specifier indicating whether 129 the model should be single , double or long double precision. The default130 is double precision, *np.dtype('d')*.131 132 Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to False if single precision133 models are not allowed as DLLs.114 the model should be single or double precision. The default is double 115 precision. 116 117 The DLL is not loaded until the kernel is called so models can 118 be defined without using too many resources. 134 119 135 120 Set *sasmodels.kerneldll.DLL_PATH* to the compiled dll output path. 136 121 The default is the system temporary directory. 137 """ 138 if dtype == F16: 122 123 Set *sasmodels.ALLOW_SINGLE_PRECISION_DLLS* to True if single precision 124 models are allowed as DLLs. 125 """ 126 if callable(model_info.get('Iq', None)): 127 return PyModel(model_info) 128 129 dtype = np.dtype(dtype) 130 if dtype == generate.F16: 139 131 raise ValueError("16 bit floats not supported") 140 if dtype == F32 and not ALLOW_SINGLE_PRECISION_DLLS: 141 dtype = F64 # Force 64-bit dll 142 # Note: dtype may be F128 for long double precision 143 144 newest = generate.timestamp(model_info) 132 if dtype == generate.F32 and not ALLOW_SINGLE_PRECISION_DLLS: 133 dtype = generate.F64 # Force 64-bit dll 134 135 if dtype == generate.F32: # 32-bit dll 136 tempfile_prefix = 'sas_' + model_info['name'] + '32_' 137 elif dtype == generate.F64: 138 tempfile_prefix = 'sas_' + model_info['name'] + '64_' 139 else: 140 tempfile_prefix = 'sas_' + model_info['name'] + '128_' 141 142 source = generate.convert_type(source, dtype) 143 source_files = generate.model_sources(model_info) + [model_info['filename']] 145 144 dll = dll_path(model_info, dtype) 145 newest = max(os.path.getmtime(f) for f in source_files) 146 146 if not os.path.exists(dll) or os.path.getmtime(dll) < newest: 147 basename = dll_name(model_info, dtype) + "_" 148 fid, filename = tempfile.mkstemp(suffix=".c", prefix=basename) 149 source = generate.convert_type(source, dtype) 147 # Replace with a proper temp file 148 fid, filename = tempfile.mkstemp(suffix=".c", prefix=tempfile_prefix) 150 149 os.fdopen(fid, "w").write(source) 151 150 command = COMPILE%{"source":filename, "output":dll} … … 161 160 162 161 163 def load_dll(source, model_info, dtype=F64): 164 # type: (str, ModelInfo, np.dtype) -> "DllModel" 162 def load_dll(source, model_info, dtype="double"): 165 163 """ 166 164 Create and load a dll corresponding to the source, info pair returned … … 174 172 175 173 176 class DllModel(KernelModel): 174 IQ_ARGS = [c_void_p, c_void_p, c_int] 175 IQXY_ARGS = [c_void_p, c_void_p, c_void_p, c_int] 176 177 class DllModel(object): 177 178 """ 178 179 ctypes wrapper for a single model. … … 190 191 191 192 def __init__(self, dllpath, model_info, dtype=generate.F32): 192 # type: (str, ModelInfo, np.dtype) -> None193 193 self.info = model_info 194 194 self.dllpath = dllpath 195 self. _dll = None # type: ct.CDLL195 self.dll = None 196 196 self.dtype = np.dtype(dtype) 197 197 198 198 def _load_dll(self): 199 # type: () -> None 199 Nfixed1d = len(self.info['partype']['fixed-1d']) 200 Nfixed2d = len(self.info['partype']['fixed-2d']) 201 Npd1d = len(self.info['partype']['pd-1d']) 202 Npd2d = len(self.info['partype']['pd-2d']) 203 200 204 #print("dll", self.dllpath) 201 205 try: 202 self. _dll = ct.CDLL(self.dllpath)206 self.dll = ct.CDLL(self.dllpath) 203 207 except: 204 208 annotate_exception("while loading "+self.dllpath) … … 208 212 else c_double if self.dtype == generate.F64 209 213 else c_longdouble) 210 211 # int, int, int, int*, double*, double*, double*, double*, double*, double 212 argtypes = [c_int32]*3 + [c_void_p]*5 + [fp] 213 self._Iq = self._dll[generate.kernel_name(self.info, is_2d=False)] 214 self._Iqxy = self._dll[generate.kernel_name(self.info, is_2d=True)] 215 self._Iq.argtypes = argtypes 216 self._Iqxy.argtypes = argtypes 214 pd_args_1d = [c_void_p, fp] + [c_int]*Npd1d if Npd1d else [] 215 pd_args_2d = [c_void_p, fp] + [c_int]*Npd2d if Npd2d else [] 216 self.Iq = self.dll[generate.kernel_name(self.info, False)] 217 self.Iq.argtypes = IQ_ARGS + pd_args_1d + [fp]*Nfixed1d 218 219 self.Iqxy = self.dll[generate.kernel_name(self.info, True)] 220 self.Iqxy.argtypes = IQXY_ARGS + pd_args_2d + [fp]*Nfixed2d 221 222 self.release() 217 223 218 224 def __getstate__(self): 219 # type: () -> Tuple[ModelInfo, str]220 225 return self.info, self.dllpath 221 226 222 227 def __setstate__(self, state): 223 # type: (Tuple[ModelInfo, str]) -> None224 228 self.info, self.dllpath = state 225 self. _dll = None229 self.dll = None 226 230 227 231 def make_kernel(self, q_vectors): 228 # type: (List[np.ndarray]) -> DllKernel229 232 q_input = PyInput(q_vectors, self.dtype) 230 # Note: pickle not supported for DllKernel 231 if self._dll is None: 232 self._load_dll() 233 kernel = self._Iqxy if q_input.is_2d else self._Iq 233 if self.dll is None: self._load_dll() 234 kernel = self.Iqxy if q_input.is_2d else self.Iq 234 235 return DllKernel(kernel, self.info, q_input) 235 236 236 237 def release(self): 237 # type: () -> None238 238 """ 239 239 Release any resources associated with the model. … … 244 244 libHandle = dll._handle 245 245 #libHandle = ct.c_void_p(dll._handle) 246 del dll, self. _dll247 self. _dll = None246 del dll, self.dll 247 self.dll = None 248 248 #_ctypes.FreeLibrary(libHandle) 249 249 ct.windll.kernel32.FreeLibrary(libHandle) … … 252 252 253 253 254 class DllKernel( Kernel):254 class DllKernel(object): 255 255 """ 256 256 Callable SAS kernel. … … 272 272 """ 273 273 def __init__(self, kernel, model_info, q_input): 274 # type: (Callable[[], np.ndarray], ModelInfo, PyInput) -> None275 self.kernel = kernel276 274 self.info = model_info 277 275 self.q_input = q_input 278 self.dtype = q_input.dtype 279 self.dim = '2d' if q_input.is_2d else '1d' 280 self.result = np.empty(q_input.nq+1, q_input.dtype) 281 self.real = (np.float32 if self.q_input.dtype == generate.F32 282 else np.float64 if self.q_input.dtype == generate.F64 283 else np.float128) 284 285 def __call__(self, call_details, weights, values, cutoff): 286 # type: (CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray 287 288 #print("in kerneldll") 289 #print("weights", weights) 290 #print("values", values) 291 start, stop = 0, call_details.total_pd 292 args = [ 293 self.q_input.nq, # nq 294 start, # pd_start 295 stop, # pd_stop pd_stride[MAX_PD] 296 call_details.ctypes.data, # problem 297 weights.ctypes.data, # weights 298 values.ctypes.data, #pars 299 self.q_input.q.ctypes.data, #q 300 self.result.ctypes.data, # results 301 self.real(cutoff), # cutoff 302 ] 303 self.kernel(*args) # type: ignore 304 return self.result[:-1] 276 self.kernel = kernel 277 self.res = np.empty(q_input.nq, q_input.dtype) 278 dim = '2d' if q_input.is_2d else '1d' 279 self.fixed_pars = model_info['partype']['fixed-' + dim] 280 self.pd_pars = model_info['partype']['pd-' + dim] 281 282 # In dll kernel, but not in opencl kernel 283 self.p_res = self.res.ctypes.data 284 285 def __call__(self, fixed_pars, pd_pars, cutoff): 286 real = (np.float32 if self.q_input.dtype == generate.F32 287 else np.float64 if self.q_input.dtype == generate.F64 288 else np.float128) 289 290 nq = c_int(self.q_input.nq) 291 if pd_pars: 292 cutoff = real(cutoff) 293 loops_N = [np.uint32(len(p[0])) for p in pd_pars] 294 loops = np.hstack(pd_pars) 295 loops = np.ascontiguousarray(loops.T, self.q_input.dtype).flatten() 296 p_loops = loops.ctypes.data 297 dispersed = [p_loops, cutoff] + loops_N 298 else: 299 dispersed = [] 300 fixed = [real(p) for p in fixed_pars] 301 args = self.q_input.q_pointers + [self.p_res, nq] + dispersed + fixed 302 #print(pars) 303 self.kernel(*args) 304 305 return self.res 305 306 306 307 def release(self): 307 # type: () -> None308 308 """ 309 309 Release any resources associated with the kernel. 310 310 """ 311 self.q_input.release()311 pass -
sasmodels/kernelpy.py
r7ae2b7f r4d76711 7 7 :class:`kernelcl.GpuModel` and :class:`kerneldll.DllModel`. 8 8 """ 9 import numpy as np # type: ignore 10 from numpy import pi, cos #type: ignore 11 12 from . import details 9 import numpy as np 10 from numpy import pi, cos 11 13 12 from .generate import F64 14 from .kernel import KernelModel, Kernel 15 16 try: 17 from typing import Union, Callable 18 except: 19 pass 20 else: 21 DType = Union[None, str, np.dtype] 22 23 class PyModel(KernelModel): 13 14 class PyModel(object): 24 15 """ 25 16 Wrapper for pure python models. 26 17 """ 27 18 def __init__(self, model_info): 28 # Make sure Iq and Iqxy are available and vectorized29 _create_default_functions(model_info)30 19 self.info = model_info 31 20 32 21 def make_kernel(self, q_vectors): 33 22 q_input = PyInput(q_vectors, dtype=F64) 34 kernel = self.info .Iqxy if q_input.is_2d else self.info.Iq23 kernel = self.info['Iqxy'] if q_input.is_2d else self.info['Iq'] 35 24 return PyKernel(kernel, self.info, q_input) 36 25 … … 64 53 self.dtype = dtype 65 54 self.is_2d = (len(q_vectors) == 2) 66 if self.is_2d: 67 self.q = np.empty((self.nq, 2), dtype=dtype) 68 self.q[:, 0] = q_vectors[0] 69 self.q[:, 1] = q_vectors[1] 70 else: 71 self.q = np.empty(self.nq, dtype=dtype) 72 self.q[:self.nq] = q_vectors[0] 55 self.q_vectors = [np.ascontiguousarray(q, self.dtype) for q in q_vectors] 56 self.q_pointers = [q.ctypes.data for q in self.q_vectors] 73 57 74 58 def release(self): … … 76 60 Free resources associated with the model inputs. 77 61 """ 78 self.q = None79 80 class PyKernel( Kernel):62 self.q_vectors = [] 63 64 class PyKernel(object): 81 65 """ 82 66 Callable SAS kernel. … … 98 82 """ 99 83 def __init__(self, kernel, model_info, q_input): 100 self.dtype = np.dtype('d')101 84 self.info = model_info 102 85 self.q_input = q_input 103 86 self.res = np.empty(q_input.nq, q_input.dtype) 104 self.kernel = kernel 105 self.dim = '2d' if q_input.is_2d else '1d' 106 107 partable = model_info.parameters 108 kernel_parameters = (partable.iqxy_parameters if q_input.is_2d 109 else partable.iq_parameters) 110 volume_parameters = partable.form_volume_parameters 111 112 # Create an array to hold the parameter values. There will be a 113 # single array whose values are updated as the calculator goes 114 # through the loop. Arguments to the kernel and volume functions 115 # will use views into this vector, relying on the fact that a 116 # an array of no dimensions acts like a scalar. 117 parameter_vector = np.empty(len(partable.call_parameters)-2, 'd') 118 119 # Create views into the array to hold the arguments 120 offset = 0 121 kernel_args, volume_args = [], [] 122 for p in partable.kernel_parameters: 123 if p.length == 1: 124 # Scalar values are length 1 vectors with no dimensions. 125 v = parameter_vector[offset:offset+1].reshape(()) 87 dim = '2d' if q_input.is_2d else '1d' 88 # Loop over q unless user promises that the kernel is vectorized by 89 # taggining it with vectorized=True 90 if not getattr(kernel, 'vectorized', False): 91 if dim == '2d': 92 def vector_kernel(qx, qy, *args): 93 """ 94 Vectorized 2D kernel. 95 """ 96 return np.array([kernel(qxi, qyi, *args) 97 for qxi, qyi in zip(qx, qy)]) 126 98 else: 127 # Vector values are simple views. 128 v = parameter_vector[offset:offset+p.length] 129 offset += p.length 130 if p in kernel_parameters: 131 kernel_args.append(v) 132 if p in volume_parameters: 133 volume_args.append(v) 134 135 # Hold on to the parameter vector so we can use it to call kernel later. 136 # This may also be required to preserve the views into the vector. 137 self._parameter_vector = parameter_vector 138 139 # Generate a closure which calls the kernel with the views into the 140 # parameter array. 141 if q_input.is_2d: 142 form = model_info.Iqxy 143 qx, qy = q_input.q[:,0], q_input.q[:,1] 144 self._form = lambda: form(qx, qy, *kernel_args) 99 def vector_kernel(q, *args): 100 """ 101 Vectorized 1D kernel. 102 """ 103 return np.array([kernel(qi, *args) 104 for qi in q]) 105 self.kernel = vector_kernel 145 106 else: 146 form = model_info.Iq 147 q = q_input.q 148 self._form = lambda: form(q, *kernel_args) 149 150 # Generate a closure which calls the form_volume if it exists. 151 form_volume = model_info.form_volume 152 self._volume = ((lambda: form_volume(*volume_args)) if form_volume 153 else (lambda: 1.0)) 154 155 def __call__(self, call_details, weights, values, cutoff): 156 assert isinstance(call_details, details.CallDetails) 157 res = _loops(self._parameter_vector, self._form, self._volume, 158 self.q_input.nq, call_details, weights, values, cutoff) 107 self.kernel = kernel 108 fixed_pars = model_info['partype']['fixed-' + dim] 109 pd_pars = model_info['partype']['pd-' + dim] 110 vol_pars = model_info['partype']['volume'] 111 112 # First two fixed pars are scale and background 113 pars = [p.name for p in model_info['parameters'][2:]] 114 offset = len(self.q_input.q_vectors) 115 self.args = self.q_input.q_vectors + [None] * len(pars) 116 self.fixed_index = np.array([pars.index(p) + offset 117 for p in fixed_pars[2:]]) 118 self.pd_index = np.array([pars.index(p) + offset 119 for p in pd_pars]) 120 self.vol_index = np.array([pars.index(p) + offset 121 for p in vol_pars]) 122 try: self.theta_index = pars.index('theta') + offset 123 except ValueError: self.theta_index = -1 124 125 # Caller needs fixed_pars and pd_pars 126 self.fixed_pars = fixed_pars 127 self.pd_pars = pd_pars 128 129 def __call__(self, fixed, pd, cutoff=1e-5): 130 #print("fixed",fixed) 131 #print("pd", pd) 132 args = self.args[:] # grab a copy of the args 133 form, form_volume = self.kernel, self.info['form_volume'] 134 # First two fixed 135 scale, background = fixed[:2] 136 for index, value in zip(self.fixed_index, fixed[2:]): 137 args[index] = float(value) 138 res = _loops(form, form_volume, cutoff, scale, background, args, 139 pd, self.pd_index, self.vol_index, self.theta_index) 140 159 141 return res 160 142 … … 163 145 Free resources associated with the kernel. 164 146 """ 165 self.q_input.release()166 147 self.q_input = None 167 148 168 def _loops(parameters, form, form_volume, nq, call_details, 169 weights, values, cutoff): 170 # type: (np.ndarray, Callable[[], np.ndarray], Callable[[], float], int, details.CallDetails, np.ndarray, np.ndarray, float) -> None 149 def _loops(form, form_volume, cutoff, scale, background, 150 args, pd, pd_index, vol_index, theta_index): 151 """ 152 *form* is the name of the form function, which should be vectorized over 153 q, but otherwise have an interface like the opencl kernels, with the 154 q parameters first followed by the individual parameters in the order 155 given in model.parameters (see :mod:`sasmodels.generate`). 156 157 *form_volume* calculates the volume of the shape. *vol_index* gives 158 the list of volume parameters 159 160 *cutoff* ignores the corners of the dispersion hypercube 161 162 *scale*, *background* multiplies the resulting form and adds an offset 163 164 *args* is the prepopulated set of arguments to the form function, starting 165 with the q vectors, and including slots for all the parameters. The 166 values for the parameters will be substituted with values from the 167 dispersion functions. 168 169 *pd* is the list of dispersion parameters 170 171 *pd_index* are the indices of the dispersion parameters in *args* 172 173 *vol_index* are the indices of the volume parameters in *args* 174 175 *theta_index* is the index of the theta parameter for the sasview 176 spherical correction, or -1 if there is no angular dispersion 177 """ 178 171 179 ################################################################ 172 180 # # … … 178 186 # # 179 187 ################################################################ 180 parameters[:] = values[call_details.par_offset] 181 scale, background = values[0], values[1] 182 if call_details.num_active == 0: 183 norm = float(form_volume()) 184 if norm > 0.0: 185 return (scale/norm)*form() + background 188 189 weight = np.empty(len(pd), 'd') 190 if weight.size > 0: 191 # weight vector, to be populated by polydispersity loops 192 193 # identify which pd parameters are volume parameters 194 vol_weight_index = np.array([(index in vol_index) for index in pd_index]) 195 196 # Sort parameters in decreasing order of pd length 197 Npd = np.array([len(pdi[0]) for pdi in pd], 'i') 198 order = np.argsort(Npd)[::-1] 199 stride = np.cumprod(Npd[order]) 200 pd = [pd[index] for index in order] 201 pd_index = pd_index[order] 202 vol_weight_index = vol_weight_index[order] 203 204 fast_value = pd[0][0] 205 fast_weight = pd[0][1] 206 else: 207 stride = np.array([1]) 208 vol_weight_index = slice(None, None) 209 # keep lint happy 210 fast_value = [None] 211 fast_weight = [None] 212 213 ret = np.zeros_like(args[0]) 214 norm = np.zeros_like(ret) 215 vol = np.zeros_like(ret) 216 vol_norm = np.zeros_like(ret) 217 for k in range(stride[-1]): 218 # update polydispersity parameter values 219 fast_index = k % stride[0] 220 if fast_index == 0: # bottom loop complete ... check all other loops 221 if weight.size > 0: 222 for i, index, in enumerate(k % stride): 223 args[pd_index[i]] = pd[i][0][index] 224 weight[i] = pd[i][1][index] 186 225 else: 187 return np.ones(nq, 'd')*background 188 189 partial_weight = np.NaN 190 spherical_correction = 1.0 191 pd_stride = call_details.pd_stride[:call_details.num_active] 192 pd_length = call_details.pd_length[:call_details.num_active] 193 pd_offset = call_details.pd_offset[:call_details.num_active] 194 pd_index = np.empty_like(pd_offset) 195 offset = np.empty_like(call_details.par_offset) 196 theta = call_details.theta_par 197 fast_length = pd_length[0] 198 pd_index[0] = fast_length 199 total = np.zeros(nq, 'd') 200 norm = 0.0 201 for loop_index in range(call_details.total_pd): 202 # update polydispersity parameter values 203 if pd_index[0] == fast_length: 204 pd_index[:] = (loop_index/pd_stride)%pd_length 205 partial_weight = np.prod(weights[pd_offset+pd_index][1:]) 206 for k in range(call_details.num_coord): 207 par = call_details.par_coord[k] 208 coord = call_details.pd_coord[k] 209 this_offset = call_details.par_offset[par] 210 block_size = 1 211 for bit in range(len(pd_offset)): 212 if coord&1: 213 this_offset += block_size * pd_index[bit] 214 block_size *= pd_length[bit] 215 coord >>= 1 216 if coord == 0: break 217 offset[par] = this_offset 218 parameters[par] = values[this_offset] 219 if par == theta and not (call_details.par_coord[k]&1): 220 spherical_correction = max(abs(cos(pi/180 * parameters[theta])), 1e-6) 221 for k in range(call_details.num_coord): 222 if call_details.pd_coord[k]&1: 223 #par = call_details.par_coord[k] 224 parameters[par] = values[offset[par]] 225 #print "par",par,offset[par],parameters[par+2] 226 offset[par] += 1 227 if par == theta: 228 spherical_correction = max(abs(cos(pi/180 * parameters[theta])), 1e-6) 229 230 weight = partial_weight * weights[pd_offset[0] + pd_index[0]] 231 pd_index[0] += 1 232 if weight > cutoff: 233 # Call the scattering function 234 # Assume that NaNs are only generated if the parameters are bad; 235 # exclude all q for that NaN. Even better would be to have an 236 # INVALID expression like the C models, but that is too expensive. 237 I = form() 226 args[pd_index[0]] = fast_value[fast_index] 227 weight[0] = fast_weight[fast_index] 228 229 # Computes the weight, and if it is not sufficient then ignore this 230 # parameter set. 231 # Note: could precompute w1*...*wn so we only need to multiply by w0 232 w = np.prod(weight) 233 if w > cutoff: 234 # Note: can precompute spherical correction if theta_index is not 235 # the fast index. Correction factor for spherical integration 236 #spherical_correction = abs(cos(pi*args[phi_index])) if phi_index>=0 else 1.0 237 spherical_correction = (abs(cos(pi * args[theta_index])) * pi / 2 238 if theta_index >= 0 else 1.0) 239 #spherical_correction = 1.0 240 241 # Call the scattering function and adds it to the total. 242 I = form(*args) 238 243 if np.isnan(I).any(): continue 239 240 # update value and norm 241 weight *= spherical_correction 242 total += weight * I 243 norm += weight * form_volume() 244 245 if norm > 0.0: 246 return (scale/norm)*total + background 247 else: 248 return np.ones(nq, 'd')*background 249 250 251 def _create_default_functions(model_info): 252 """ 253 Autogenerate missing functions, such as Iqxy from Iq. 254 255 This only works for Iqxy when Iq is written in python. :func:`make_source` 256 performs a similar role for Iq written in C. This also vectorizes 257 any functions that are not already marked as vectorized. 258 """ 259 _create_vector_Iq(model_info) 260 _create_vector_Iqxy(model_info) # call create_vector_Iq() first 261 262 263 def _create_vector_Iq(model_info): 264 """ 265 Define Iq as a vector function if it exists. 266 """ 267 Iq = model_info.Iq 268 if callable(Iq) and not getattr(Iq, 'vectorized', False): 269 #print("vectorizing Iq") 270 def vector_Iq(q, *args): 271 """ 272 Vectorized 1D kernel. 273 """ 274 return np.array([Iq(qi, *args) for qi in q]) 275 vector_Iq.vectorized = True 276 model_info.Iq = vector_Iq 277 278 def _create_vector_Iqxy(model_info): 279 """ 280 Define Iqxy as a vector function if it exists, or default it from Iq(). 281 """ 282 Iq, Iqxy = model_info.Iq, model_info.Iqxy 283 if callable(Iqxy): 284 if not getattr(Iqxy, 'vectorized', False): 285 #print("vectorizing Iqxy") 286 def vector_Iqxy(qx, qy, *args): 287 """ 288 Vectorized 2D kernel. 289 """ 290 return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)]) 291 vector_Iqxy.vectorized = True 292 model_info.Iqxy = vector_Iqxy 293 elif callable(Iq): 294 #print("defaulting Iqxy") 295 # Iq is vectorized because create_vector_Iq was already called. 296 def default_Iqxy(qx, qy, *args): 297 """ 298 Default 2D kernel. 299 """ 300 return Iq(np.sqrt(qx**2 + qy**2), *args) 301 default_Iqxy.vectorized = True 302 model_info.Iqxy = default_Iqxy 303 244 ret += w * I * spherical_correction 245 norm += w 246 247 # Volume normalization. 248 # If there are "volume" polydispersity parameters, then these 249 # will be used to call the form_volume function from the user 250 # supplied kernel, and accumulate a normalized weight. 251 # Note: can precompute volume norm if fast index is not a volume 252 if form_volume: 253 vol_args = [args[index] for index in vol_index] 254 vol_weight = np.prod(weight[vol_weight_index]) 255 vol += vol_weight * form_volume(*vol_args) 256 vol_norm += vol_weight 257 258 positive = (vol * vol_norm != 0.0) 259 ret[positive] *= vol_norm[positive] / vol[positive] 260 result = scale * ret / norm + background 261 return result -
sasmodels/list_pars.py
r6d6508e ra84a0ca 25 25 for name in sorted(MODELS): 26 26 model_info = load_model_info(name) 27 for p in model_info .parameters.kernel_parameters:27 for p in model_info['parameters']: 28 28 partable.setdefault(p.name, []) 29 29 partable[p.name].append(name) -
sasmodels/mixture.py
r7ae2b7f r72a081d 12 12 """ 13 13 from copy import copy 14 import numpy as np # type: ignore14 import numpy as np 15 15 16 from .modelinfo import Parameter, ParameterTable, ModelInfo 17 from .kernel import KernelModel, Kernel 16 from .generate import process_parameters, COMMON_PARAMETERS, Parameter 18 17 19 try: 20 from typing import List 21 from .details import CallDetails 22 except ImportError: 23 pass 18 SCALE=0 19 BACKGROUND=1 20 EFFECT_RADIUS=2 21 VOLFRACTION=3 24 22 25 23 def make_mixture_info(parts): 26 # type: (List[ModelInfo]) -> ModelInfo27 24 """ 28 25 Create info block for product model. … … 30 27 flatten = [] 31 28 for part in parts: 32 if part .composition and part.composition[0] == 'mixture':33 flatten.extend(part .composition[1])29 if part['composition'] and part['composition'][0] == 'mixture': 30 flatten.extend(part['compostion'][1]) 34 31 else: 35 32 flatten.append(part) … … 37 34 38 35 # Build new parameter list 39 combined_pars =[]36 pars = COMMON_PARAMETERS + [] 40 37 for k, part in enumerate(parts): 41 38 # Parameter prefix per model, A_, B_, ... 42 # Note that prefix must also be applied to id and length_control43 # to support vector parameters44 39 prefix = chr(ord('A')+k) + '_' 45 combined_pars.append(Parameter(prefix+'scale')) 46 for p in part.parameters.kernel_parameters: 47 p = copy(p) 48 p.name = prefix + p.name 49 p.id = prefix + p.id 50 if p.length_control is not None: 51 p.length_control = prefix + p.length_control 52 combined_pars.append(p) 53 parameters = ParameterTable(combined_pars) 40 for p in part['parameters']: 41 # No background on the individual mixture elements 42 if p.name == 'background': 43 continue 44 # TODO: promote Parameter to a full class 45 # this code knows too much about the implementation! 46 p = list(p) 47 if p[0] == 'scale': # allow model subtraction 48 p[3] = [-np.inf, np.inf] # limits 49 p[0] = prefix+p[0] # relabel parameters with A_, ... 50 pars.append(p) 54 51 55 model_info = ModelInfo()56 model_info .id = '+'.join(part.id for part in parts)57 model_info .name = ' + '.join(part.name for part in parts)58 model_info .filename= None59 model_info .title = 'Mixture model with ' + model_info.name60 model_info .description = model_info.title61 model_info .docs = model_info.title62 model_info .category= "custom"63 model_info .parameters = parameters64 #model_info .single= any(part['single'] for part in parts)65 model_info .structure_factor= False66 model_info .variant_info= None67 #model_info .tests= []68 #model_info .source= []52 model_info = {} 53 model_info['id'] = '+'.join(part['id']) 54 model_info['name'] = ' + '.join(part['name']) 55 model_info['filename'] = None 56 model_info['title'] = 'Mixture model with ' + model_info['name'] 57 model_info['description'] = model_info['title'] 58 model_info['docs'] = model_info['title'] 59 model_info['category'] = "custom" 60 model_info['parameters'] = pars 61 #model_info['single'] = any(part['single'] for part in parts) 62 model_info['structure_factor'] = False 63 model_info['variant_info'] = None 64 #model_info['tests'] = [] 65 #model_info['source'] = [] 69 66 # Iq, Iqxy, form_volume, ER, VR and sesans 70 67 # Remember the component info blocks so we can build the model 71 model_info.composition = ('mixture', parts) 68 model_info['composition'] = ('mixture', parts) 69 process_parameters(model_info) 70 return model_info 72 71 73 72 74 class MixtureModel( KernelModel):73 class MixtureModel(object): 75 74 def __init__(self, model_info, parts): 76 # type: (ModelInfo, List[KernelModel]) -> None77 75 self.info = model_info 78 76 self.parts = parts 79 77 80 78 def __call__(self, q_vectors): 81 # type: (List[np.ndarray]) -> MixtureKernel82 79 # Note: may be sending the q_vectors to the n times even though they 83 80 # are only needed once. It would mess up modularity quite a bit to … … 86 83 # in opencl; or both in opencl, but one in single precision and the 87 84 # other in double precision). 88 kernels = [part .make_kernel(q_vectors) for part in self.parts]85 kernels = [part(q_vectors) for part in self.parts] 89 86 return MixtureKernel(self.info, kernels) 90 87 91 88 def release(self): 92 # type: () -> None93 89 """ 94 90 Free resources associated with the model. … … 98 94 99 95 100 class MixtureKernel( Kernel):96 class MixtureKernel(object): 101 97 def __init__(self, model_info, kernels): 102 # type: (ModelInfo, List[Kernel]) -> None 103 self.dim = kernels[0].dim 104 self.info = model_info 98 dim = '2d' if kernels[0].q_input.is_2d else '1d' 99 100 # fixed offsets starts at 2 for scale and background 101 fixed_pars, pd_pars = [], [] 102 offsets = [[2, 0]] 103 #vol_index = [] 104 def accumulate(fixed, pd, volume): 105 # subtract 1 from fixed since we are removing background 106 fixed_offset, pd_offset = offsets[-1] 107 #vol_index.extend(k+pd_offset for k,v in pd if v in volume) 108 offsets.append([fixed_offset + len(fixed) - 1, pd_offset + len(pd)]) 109 pd_pars.append(pd) 110 if dim == '2d': 111 for p in kernels: 112 partype = p.info['partype'] 113 accumulate(partype['fixed-2d'], partype['pd-2d'], partype['volume']) 114 else: 115 for p in kernels: 116 partype = p.info['partype'] 117 accumulate(partype['fixed-1d'], partype['pd-1d'], partype['volume']) 118 119 #self.vol_index = vol_index 120 self.offsets = offsets 121 self.fixed_pars = fixed_pars 122 self.pd_pars = pd_pars 123 self.info = model_info 105 124 self.kernels = kernels 125 self.results = None 106 126 107 def __call__(self, call_details, value, weight, cutoff): 108 # type: (CallDetails, np.ndarray, np.ndarry, float) -> np.ndarray 109 scale, background = value[0:2] 127 def __call__(self, fixed_pars, pd_pars, cutoff=1e-5): 128 scale, background = fixed_pars[0:2] 110 129 total = 0.0 111 # remember the parts for plotting later 112 self.results = [] 113 for kernel, kernel_details in zip(self.kernels, call_details.parts): 114 part_result = kernel(kernel_details, value, weight, cutoff) 130 self.results = [] # remember the parts for plotting later 131 for k in range(len(self.offsets)-1): 132 start_fixed, start_pd = self.offsets[k] 133 end_fixed, end_pd = self.offsets[k+1] 134 part_fixed = [fixed_pars[start_fixed], 0.0] + fixed_pars[start_fixed+1:end_fixed] 135 part_pd = [pd_pars[start_pd], 0.0] + pd_pars[start_pd+1:end_pd] 136 part_result = self.kernels[k](part_fixed, part_pd) 115 137 total += part_result 116 self.results.append( part_result)138 self.results.append(scale*sum+background) 117 139 118 140 return scale*total + background 119 141 120 142 def release(self): 121 # type: () -> None 122 for k in self.kernels: 123 k.release() 143 self.p_kernel.release() 144 self.q_kernel.release() 124 145 -
sasmodels/model_test.py
r7ae2b7f r4d76711 43 43 Precision defaults to 5 digits (relative). 44 44 """ 45 #TODO: rename to tests so that tab completion works better for models directory46 47 45 from __future__ import print_function 48 46 … … 50 48 import unittest 51 49 52 import numpy as np # type: ignore50 import numpy as np 53 51 54 52 from .core import list_models, load_model_info, build_model, HAVE_OPENCL 55 from .details import dispersion_mesh 56 from .direct_model import call_kernel, get_weights 53 from .core import call_kernel, call_ER, call_VR 57 54 from .exception import annotate_exception 58 from .modelinfo import expand_pars 59 60 try: 61 from typing import List, Iterator, Callable 62 except ImportError: 63 pass 64 else: 65 from .modelinfo import ParameterTable, ParameterSet, TestCondition, ModelInfo 66 from .kernelpy import PyModel, PyInput, PyKernel, DType 67 68 def call_ER(model_info, pars): 69 # type: (ModelInfo, ParameterSet) -> float 70 """ 71 Call the model ER function using *values*. 72 73 *model_info* is either *model.info* if you have a loaded model, 74 or *kernel.info* if you have a model kernel prepared for evaluation. 75 """ 76 if model_info.ER is None: 77 return 1.0 78 else: 79 value, weight = _vol_pars(model_info, pars) 80 individual_radii = model_info.ER(*value) 81 return np.sum(weight*individual_radii) / np.sum(weight) 82 83 def call_VR(model_info, pars): 84 # type: (ModelInfo, ParameterSet) -> float 85 """ 86 Call the model VR function using *pars*. 87 88 *model_info* is either *model.info* if you have a loaded model, 89 or *kernel.info* if you have a model kernel prepared for evaluation. 90 """ 91 if model_info.VR is None: 92 return 1.0 93 else: 94 value, weight = _vol_pars(model_info, pars) 95 whole, part = model_info.VR(*value) 96 return np.sum(weight*part)/np.sum(weight*whole) 97 98 def _vol_pars(model_info, pars): 99 # type: (ModelInfo, ParameterSet) -> Tuple[np.ndarray, np.ndarray] 100 vol_pars = [get_weights(p, pars) 101 for p in model_info.parameters.call_parameters 102 if p.type == 'volume'] 103 value, weight = dispersion_mesh(model_info, vol_pars) 104 return value, weight 105 55 56 #TODO: rename to tests so that tab completion works better for models directory 106 57 107 58 def make_suite(loaders, models): 108 # type: (List[str], List[str]) -> unittest.TestSuite109 59 """ 110 60 Construct the pyunit test suite. … … 116 66 *models* is the list of models to test, or *["all"]* to test all models. 117 67 """ 118 ModelTestCase = _hide_model_case_from_nose() 68 69 ModelTestCase = _hide_model_case_from_nosetests() 119 70 suite = unittest.TestSuite() 120 71 … … 135 86 # don't try to call cl kernel since it will not be 136 87 # available in some environmentes. 137 is_py = callable(model_info .Iq)88 is_py = callable(model_info['Iq']) 138 89 139 90 if is_py: # kernel implemented in python … … 173 124 174 125 175 def _hide_model_case_from_nose(): 176 # type: () -> type 126 def _hide_model_case_from_nosetests(): 177 127 class ModelTestCase(unittest.TestCase): 178 128 """ … … 185 135 def __init__(self, test_name, model_info, test_method_name, 186 136 platform, dtype): 187 # type: (str, ModelInfo, str, str, DType) -> None188 137 self.test_name = test_name 189 138 self.info = model_info … … 191 140 self.dtype = dtype 192 141 193 setattr(self, test_method_name, self. run_all)142 setattr(self, test_method_name, self._runTest) 194 143 unittest.TestCase.__init__(self, test_method_name) 195 144 196 def run_all(self): 197 # type: () -> None 145 def _runTest(self): 198 146 smoke_tests = [ 199 ({}, 0.1, None),200 ({}, (0.1, 0.1), None),201 ({}, 'ER', None),202 ({}, 'VR', None),147 [{}, 0.1, None], 148 [{}, (0.1, 0.1), None], 149 [{}, 'ER', None], 150 [{}, 'VR', None], 203 151 ] 204 152 205 tests = self.info .tests153 tests = self.info['tests'] 206 154 try: 207 155 model = build_model(self.info, dtype=self.dtype, 208 156 platform=self.platform) 209 157 for test in smoke_tests + tests: 210 self. run_one(model, test)158 self._run_one_test(model, test) 211 159 212 160 if not tests and self.platform == "dll": … … 222 170 raise 223 171 224 def run_one(self, model, test): 225 # type: (PyModel, TestCondition) -> None 226 user_pars, x, y = test 227 pars = expand_pars(self.info.parameters, user_pars) 172 def _run_one_test(self, model, test): 173 pars, x, y = test 228 174 229 175 if not isinstance(y, list): … … 239 185 actual = [call_VR(model.info, pars)] 240 186 elif isinstance(x[0], tuple): 241 qx, qy = zip(*x)242 q_vectors = [np.array( qx), np.array(qy)]187 Qx, Qy = zip(*x) 188 q_vectors = [np.array(Qx), np.array(Qy)] 243 189 kernel = model.make_kernel(q_vectors) 244 190 actual = call_kernel(kernel, pars) … … 248 194 actual = call_kernel(kernel, pars) 249 195 250 self.assert True(len(actual) >0)196 self.assertGreater(len(actual), 0) 251 197 self.assertEqual(len(y), len(actual)) 252 198 … … 264 210 265 211 def is_near(target, actual, digits=5): 266 # type: (float, float, int) -> bool267 212 """ 268 213 Returns true if *actual* is within *digits* significant digits of *target*. … … 273 218 274 219 def main(): 275 # type: () -> int276 220 """ 277 221 Run tests given is sys.argv. … … 307 251 python -m sasmodels.model_test [-v] [opencl|dll] model1 model2 ... 308 252 309 If -v is included on the command line, then use verbo se output.253 If -v is included on the command line, then use verboe output. 310 254 311 255 If neither opencl nor dll is specified, then models will be tested with 312 both OpenCLand dll; the compute target is ignored for pure python models.256 both opencl and dll; the compute target is ignored for pure python models. 313 257 314 258 If model1 is 'all', then all except the remaining models will be tested. … … 325 269 326 270 def model_tests(): 327 # type: () -> Iterator[Callable[[], None]]328 271 """ 329 272 Test runner visible to nosetests. … … 333 276 tests = make_suite(['opencl', 'dll'], ['all']) 334 277 for test_i in tests: 335 yield test_i. run_all278 yield test_i._runTest 336 279 337 280 -
sasmodels/models/cylinder.c
r03cac08 r26141cb 3 3 double Iqxy(double qx, double qy, double sld, double solvent_sld, 4 4 double radius, double length, double theta, double phi); 5 6 #define INVALID(v) (v.radius<0 || v.length<0)7 5 8 6 double form_volume(double radius, double length) … … 17 15 double length) 18 16 { 17 // TODO: return NaN if radius<0 or length<0? 19 18 // precompute qr and qh to save time in the loop 20 19 const double qr = q*radius; … … 48 47 double phi) 49 48 { 49 // TODO: return NaN if radius<0 or length<0? 50 50 double sn, cn; // slots to hold sincos function output 51 51 -
sasmodels/models/cylinder.py
r7ae2b7f rf247314 82 82 """ 83 83 84 import numpy as np # type: ignore85 from numpy import pi, inf # type: ignore84 import numpy as np 85 from numpy import pi, inf 86 86 87 87 name = "cylinder" -
sasmodels/models/flexible_cylinder.c
re6408d0 re6408d0 1 static double 2 form_volume(length, kuhn_length, radius) 1 double form_volume(double length, double kuhn_length, double radius); 2 double Iq(double q, double length, double kuhn_length, double radius, 3 double sld, double solvent_sld); 4 double Iqxy(double qx, double qy, double length, double kuhn_length, 5 double radius, double sld, double solvent_sld); 6 double flexible_cylinder_kernel(double q, double length, double kuhn_length, 7 double radius, double sld, double solvent_sld); 8 9 10 double form_volume(double length, double kuhn_length, double radius) 3 11 { 4 12 return 1.0; 5 13 } 6 14 7 static double 8 Iq(double q, 9 double length, 10 double kuhn_length, 11 double radius, 12 double sld, 13 double solvent_sld) 15 double flexible_cylinder_kernel(double q, 16 double length, 17 double kuhn_length, 18 double radius, 19 double sld, 20 double solvent_sld) 14 21 { 15 const double contrast = sld - solvent_sld; 16 const double cross_section = sas_J1c(q*radius); 17 const double volume = M_PI*radius*radius*length; 18 const double flex = Sk_WR(q, length, kuhn_length); 19 return 1.0e-4 * volume * square(contrast*cross_section) * flex; 22 23 const double cont = sld-solvent_sld; 24 const double qr = q*radius; 25 //const double crossSect = (2.0*J1(qr)/qr)*(2.0*J1(qr)/qr); 26 const double crossSect = sas_J1c(qr); 27 double flex = Sk_WR(q,length,kuhn_length); 28 flex *= crossSect*crossSect; 29 flex *= M_PI*radius*radius*length; 30 flex *= cont*cont; 31 flex *= 1.0e-4; 32 return flex; 20 33 } 34 35 double Iq(double q, 36 double length, 37 double kuhn_length, 38 double radius, 39 double sld, 40 double solvent_sld) 41 { 42 43 double result = flexible_cylinder_kernel(q, length, kuhn_length, radius, sld, solvent_sld); 44 return result; 45 } 46 47 double Iqxy(double qx, double qy, 48 double length, 49 double kuhn_length, 50 double radius, 51 double sld, 52 double solvent_sld) 53 { 54 double q; 55 q = sqrt(qx*qx+qy*qy); 56 double result = flexible_cylinder_kernel(q, length, kuhn_length, radius, sld, solvent_sld); 57 58 return result; 59 } -
sasmodels/models/gel_fit.c
r03cac08 r30b4ddf 1 static double Iq(double q, 1 double form_volume(void); 2 3 double Iq(double q, 4 double guinier_scale, 5 double lorentzian_scale, 6 double gyration_radius, 7 double fractal_exp, 8 double cor_length); 9 10 double Iqxy(double qx, double qy, 11 double guinier_scale, 12 double lorentzian_scale, 13 double gyration_radius, 14 double fractal_exp, 15 double cor_length); 16 17 static double _gel_fit_kernel(double q, 2 18 double guinier_scale, 3 19 double lorentzian_scale, … … 8 24 // Lorentzian Term 9 25 ////////////////////////double a(x[i]*x[i]*zeta*zeta); 10 double lorentzian_term = square(q*cor_length);26 double lorentzian_term = q*q*cor_length*cor_length; 11 27 lorentzian_term = 1.0 + ((fractal_exp + 1.0)/3.0)*lorentzian_term; 12 28 lorentzian_term = pow(lorentzian_term, (fractal_exp/2.0) ); … … 14 30 // Exponential Term 15 31 ////////////////////////double d(x[i]*x[i]*rg*rg); 16 double exp_term = square(q*gyration_radius);32 double exp_term = q*q*gyration_radius*gyration_radius; 17 33 exp_term = exp(-1.0*(exp_term/3.0)); 18 34 … … 21 37 return result; 22 38 } 39 double form_volume(void){ 40 // Unused, so free to return garbage. 41 return NAN; 42 } 43 44 double Iq(double q, 45 double guinier_scale, 46 double lorentzian_scale, 47 double gyration_radius, 48 double fractal_exp, 49 double cor_length) 50 { 51 return _gel_fit_kernel(q, 52 guinier_scale, 53 lorentzian_scale, 54 gyration_radius, 55 fractal_exp, 56 cor_length); 57 } 58 59 // Iqxy is never called since no orientation or magnetic parameters. 60 double Iqxy(double qx, double qy, 61 double guinier_scale, 62 double lorentzian_scale, 63 double gyration_radius, 64 double fractal_exp, 65 double cor_length) 66 { 67 double q = sqrt(qx*qx + qy*qy); 68 return _gel_fit_kernel(q, 69 guinier_scale, 70 lorentzian_scale, 71 gyration_radius, 72 fractal_exp, 73 cor_length); 74 } 75 -
sasmodels/models/hardsphere.py
rd2bb604 rec45c4f 149 149 """ 150 150 151 Iqxy = """ 152 // never called since no orientation or magnetic parameters. 153 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 154 """ 155 151 156 # ER defaults to 0.0 152 157 # VR defaults to 1.0 -
sasmodels/models/hayter_msa.py
rd2bb604 rec45c4f 87 87 return 1.0; 88 88 """ 89 Iqxy = """ 90 // never called since no orientation or magnetic parameters. 91 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 92 """ 89 93 # ER defaults to 0.0 90 94 # VR defaults to 1.0 -
sasmodels/models/lamellar.py
rd2bb604 rec45c4f 82 82 """ 83 83 84 Iqxy = """ 85 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 86 """ 87 84 88 # ER defaults to 0.0 85 89 # VR defaults to 1.0 -
sasmodels/models/lamellar_hg.py
rd2bb604 rec45c4f 101 101 """ 102 102 103 Iqxy = """ 104 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 105 """ 106 103 107 # ER defaults to 0.0 104 108 # VR defaults to 1.0 -
sasmodels/models/lamellar_hg_stack_caille.py
rd2bb604 rec45c4f 120 120 """ 121 121 122 Iqxy = """ 123 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 124 """ 125 122 126 # ER defaults to 0.0 123 127 # VR defaults to 1.0 -
sasmodels/models/lamellar_stack_caille.py
rd2bb604 rec45c4f 104 104 """ 105 105 106 Iqxy = """ 107 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 108 """ 109 106 110 # ER defaults to 0.0 107 111 # VR defaults to 1.0 -
sasmodels/models/lamellar_stack_paracrystal.py
rd2bb604 rec45c4f 132 132 """ 133 133 134 Iqxy = """ 135 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 136 """ 137 134 138 # ER defaults to 0.0 135 139 # VR defaults to 1.0 -
sasmodels/models/lib/sas_JN.c
re6408d0 re6408d0 48 48 */ 49 49 50 double sas_JN( int n, double x ); 51 52 double sas_JN( int n, double x ) { 50 static double 51 sas_JN( int n, double x ) { 53 52 54 53 const double MACHEP = 1.11022302462515654042E-16; -
sasmodels/models/lib/sph_j1c.c
rba32cdd re6f1410 7 7 * using double precision that are the source. 8 8 */ 9 double sph_j1c(double q);10 9 11 10 // The choice of the number of terms in the series and the cutoff value for … … 44 43 #endif 45 44 45 double sph_j1c(double q); 46 46 double sph_j1c(double q) 47 47 { -
sasmodels/models/lib/sphere_form.c
rba32cdd rad90df9 1 double sphere_volume(double radius); 2 double sphere_form(double q, double radius, double sld, double solvent_sld); 3 4 double sphere_volume(double radius) 1 inline double 2 sphere_volume(double radius) 5 3 { 6 4 return M_4PI_3*cube(radius); 7 5 } 8 6 9 double sphere_form(double q, double radius, double sld, double solvent_sld) 7 inline double 8 sphere_form(double q, double radius, double sld, double solvent_sld) 10 9 { 11 10 const double fq = sphere_volume(radius) * sph_j1c(q*radius); -
sasmodels/models/lib/wrc_cyl.c
rba32cdd re7678b2 2 2 Functions for WRC implementation of flexible cylinders 3 3 */ 4 double Sk_WR(double q, double L, double b);5 6 7 4 static double 8 5 AlphaSquare(double x) … … 366 363 } 367 364 365 double Sk_WR(double q, double L, double b); 368 366 double Sk_WR(double q, double L, double b) 369 367 { -
sasmodels/models/onion.c
rce896fd rfdb1487 4 4 double thickness, double A) 5 5 { 6 const double vol = M_4PI_3 * cube(r);6 const double vol = 4.0/3.0 * M_PI * r * r * r; 7 7 const double qr = q * r; 8 8 const double alpha = A * r/thickness; … … 19 19 double sinqr, cosqr; 20 20 SINCOS(qr, sinqr, cosqr); 21 fun = -3.0 *(21 fun = -3.0( 22 22 ((alphasq - qrsq)*sinqr/qr - 2.0*alpha*cosqr) / sumsq 23 23 - (alpha*sinqr/qr - cosqr) … … 32 32 f_linear(double q, double r, double sld, double slope) 33 33 { 34 const double vol = M_4PI_3 * cube(r);34 const double vol = 4.0/3.0 * M_PI * r * r * r; 35 35 const double qr = q * r; 36 36 const double bes = sph_j1c(qr); … … 52 52 { 53 53 const double bes = sph_j1c(q * r); 54 const double vol = M_4PI_3 * cube(r);54 const double vol = 4.0/3.0 * M_PI * r * r * r; 55 55 return sld * vol * bes; 56 56 } … … 64 64 r += thickness[i]; 65 65 } 66 return M_4PI_3*cube(r);66 return 4.0/3.0 * M_PI * r * r * r; 67 67 } 68 68 69 69 static double 70 Iq(double q, double sld_core, double core_radius, double sld_solvent,71 double n, double sld_in[], double sld_out[], double thickness[],70 Iq(double q, double core_sld, double core_radius, double solvent_sld, 71 double n, double in_sld[], double out_sld[], double thickness[], 72 72 double A[]) 73 73 { 74 74 int i; 75 doubler = core_radius;76 double f = f_constant(q, r, sld_core);75 r = core_radius; 76 f = f_constant(q, r, core_sld); 77 77 for (i=0; i<n; i++){ 78 78 const double r0 = r; … … 92 92 } 93 93 } 94 f -= f_constant(q, r, s ld_solvent);95 const doublef2 = f * f * 1.0e-4;94 f -= f_constant(q, r, solvent_sld); 95 f2 = f * f * 1.0e-4; 96 96 97 97 return f2; -
sasmodels/models/onion.py
rfa5fd8d r416609b 293 293 294 294 # ["name", "units", default, [lower, upper], "type","description"], 295 parameters = [[" sld_core", "1e-6/Ang^2", 1.0, [-inf, inf], "",295 parameters = [["core_sld", "1e-6/Ang^2", 1.0, [-inf, inf], "", 296 296 "Core scattering length density"], 297 297 ["core_radius", "Ang", 200., [0, inf], "volume", 298 298 "Radius of the core"], 299 ["s ld_solvent", "1e-6/Ang^2", 6.4, [-inf, inf], "",299 ["solvent_sld", "1e-6/Ang^2", 6.4, [-inf, inf], "", 300 300 "Solvent scattering length density"], 301 ["n _shells", "", 1, [0, 10], "volume",301 ["n", "", 1, [0, 10], "volume", 302 302 "number of shells"], 303 [" sld_in[n_shells]", "1e-6/Ang^2", 1.7, [-inf, inf], "",303 ["in_sld[n]", "1e-6/Ang^2", 1.7, [-inf, inf], "", 304 304 "scattering length density at the inner radius of shell k"], 305 [" sld_out[n_shells]", "1e-6/Ang^2", 2.0, [-inf, inf], "",305 ["out_sld[n]", "1e-6/Ang^2", 2.0, [-inf, inf], "", 306 306 "scattering length density at the outer radius of shell k"], 307 ["thickness[n _shells]", "Ang", 40., [0, inf], "volume",307 ["thickness[n]", "Ang", 40., [0, inf], "volume", 308 308 "Thickness of shell k"], 309 ["A[n _shells]", "", 1.0, [-inf, inf], "",309 ["A[n]", "", 1.0, [-inf, inf], "", 310 310 "Decay rate of shell k"], 311 311 ] 312 312 313 source = ["lib/sph_j1c.c", "onion.c"] 314 315 #def Iq(q, *args, **kw): 316 # return q 317 318 profile_axes = ['Radius (A)', 'SLD (1e-6/A^2)'] 319 def profile(core_sld, core_radius, solvent_sld, n_shells, 320 in_sld, out_sld, thickness, A): 313 #source = ["lib/sph_j1c.c", "onion.c"] 314 315 def Iq(q, *args, **kw): 316 return q 317 318 def Iqxy(qx, *args, **kw): 319 return qx 320 321 322 def shape(core_sld, core_radius, solvent_sld, n, in_sld, out_sld, thickness, A): 321 323 """ 322 Returns shape profile with x=radius, y=SLD.324 SLD profile 323 325 """ 324 326 325 total_radius = 1.25*(sum(thickness[:n _shells]) + core_radius + 1)327 total_radius = 1.25*(sum(thickness[:n]) + core_radius + 1) 326 328 dr = total_radius/400 # 400 points for a smooth plot 327 329 … … 336 338 337 339 # add in the shells 338 for k in range(n _shells):340 for k in range(n): 339 341 # Left side of each shells 340 342 r0 = r[-1] … … 363 365 beta.append(solvent_sld) 364 366 365 return np.asarray(r), np.asarray(beta) *1e-6367 return np.asarray(r), np.asarray(beta) 366 368 367 369 def ER(core_radius, n, thickness): … … 372 374 373 375 demo = { 374 "s ld_solvent": 2.2,375 " sld_core": 1.0,376 "solvent_sld": 2.2, 377 "core_sld": 1.0, 376 378 "core_radius": 100, 377 379 "n": 4, 378 " sld_in": [0.5, 1.5, 0.9, 2.0],379 " sld_out": [nan, 0.9, 1.2, 1.6],380 "in_sld": [0.5, 1.5, 0.9, 2.0], 381 "out_sld": [nan, 0.9, 1.2, 1.6], 380 382 "thickness": [50, 75, 150, 75], 381 383 "A": [0, -1, 1e-4, 1], -
sasmodels/models/rpa.c
rd2bb604 r13ed84c 1 1 double Iq(double q, double case_num, 2 double N[], double Phi[], double v[], double L[], double b[], 2 double Na, double Phia, double va, double a_sld, double ba, 3 double Nb, double Phib, double vb, double b_sld, double bb, 4 double Nc, double Phic, double vc, double c_sld, double bc, 5 double Nd, double Phid, double vd, double d_sld, double bd, 3 6 double Kab, double Kac, double Kad, 4 7 double Kbc, double Kbd, double Kcd 5 8 ); 6 9 10 double Iqxy(double qx, double qy, double case_num, 11 double Na, double Phia, double va, double a_sld, double ba, 12 double Nb, double Phib, double vb, double b_sld, double bb, 13 double Nc, double Phic, double vc, double c_sld, double bc, 14 double Nd, double Phid, double vd, double d_sld, double bd, 15 double Kab, double Kac, double Kad, 16 double Kbc, double Kbd, double Kcd 17 ); 18 19 double form_volume(void); 20 21 double form_volume(void) 22 { 23 return 1.0; 24 } 25 7 26 double Iq(double q, double case_num, 8 double N[], double Phi[], double v[], double L[], double b[], 27 double Na, double Phia, double va, double La, double ba, 28 double Nb, double Phib, double vb, double Lb, double bb, 29 double Nc, double Phic, double vc, double Lc, double bc, 30 double Nd, double Phid, double vd, double Ld, double bd, 9 31 double Kab, double Kac, double Kad, 10 32 double Kbc, double Kbd, double Kcd … … 14 36 #if 0 // Sasview defaults 15 37 if (icase <= 1) { 16 N [0]=N[1]=1000.0;17 Phi [0]=Phi[1]=0.0000001;38 Na=Nb=1000.0; 39 Phia=Phib=0.0000001; 18 40 Kab=Kac=Kad=Kbc=Kbd=-0.0004; 19 L [0]=L[1]=1e-12;20 v [0]=v[1]=100.0;21 b [0]=b[1]=5.0;41 La=Lb=1e-12; 42 va=vb=100.0; 43 ba=bb=5.0; 22 44 } else if (icase <= 4) { 23 Phi [0]=0.0000001;45 Phia=0.0000001; 24 46 Kab=Kac=Kad=-0.0004; 25 L [0]=1e-12;26 v [0]=100.0;27 b [0]=5.0;47 La=1e-12; 48 va=100.0; 49 ba=5.0; 28 50 } 29 51 #else 30 52 if (icase <= 1) { 31 N [0]=N[1]=0.0;32 Phi [0]=Phi[1]=0.0;53 Na=Nb=0.0; 54 Phia=Phib=0.0; 33 55 Kab=Kac=Kad=Kbc=Kbd=0.0; 34 L [0]=L[1]=L[3];35 v [0]=v[1]=v[3];36 b [0]=b[1]=0.0;56 La=Lb=Ld; 57 va=vb=vd; 58 ba=bb=0.0; 37 59 } else if (icase <= 4) { 38 N [0]= 0.0;39 Phi [0]=0.0;60 Na = 0.0; 61 Phia=0.0; 40 62 Kab=Kac=Kad=0.0; 41 L [0]=L[3];42 v [0]=v[3];43 b [0]=0.0;63 La=Ld; 64 va=vd; 65 ba=0.0; 44 66 } 45 67 #endif 46 68 47 const double Xa = q*q*b [0]*b[0]*N[0]/6.0;48 const double Xb = q*q*b [1]*b[1]*N[1]/6.0;49 const double Xc = q*q*b [2]*b[2]*N[2]/6.0;50 const double Xd = q*q*b [3]*b[3]*N[3]/6.0;69 const double Xa = q*q*ba*ba*Na/6.0; 70 const double Xb = q*q*bb*bb*Nb/6.0; 71 const double Xc = q*q*bc*bc*Nc/6.0; 72 const double Xd = q*q*bd*bd*Nd/6.0; 51 73 52 74 // limit as Xa goes to 0 is 1 … … 76 98 #if 0 77 99 const double S0aa = icase<5 78 ? 1.0 : N [0]*Phi[0]*v[0]*Paa;100 ? 1.0 : Na*Phia*va*Paa; 79 101 const double S0bb = icase<2 80 ? 1.0 : N [1]*Phi[1]*v[1]*Pbb;81 const double S0cc = N [2]*Phi[2]*v[2]*Pcc;82 const double S0dd = N [3]*Phi[3]*v[3]*Pdd;102 ? 1.0 : Nb*Phib*vb*Pbb; 103 const double S0cc = Nc*Phic*vc*Pcc; 104 const double S0dd = Nd*Phid*vd*Pdd; 83 105 const double S0ab = icase<8 84 ? 0.0 : sqrt(N [0]*v[0]*Phi[0]*N[1]*v[1]*Phi[1])*Pa*Pb;106 ? 0.0 : sqrt(Na*va*Phia*Nb*vb*Phib)*Pa*Pb; 85 107 const double S0ac = icase<9 86 ? 0.0 : sqrt(N [0]*v[0]*Phi[0]*N[2]*v[2]*Phi[2])*Pa*Pc*exp(-Xb);108 ? 0.0 : sqrt(Na*va*Phia*Nc*vc*Phic)*Pa*Pc*exp(-Xb); 87 109 const double S0ad = icase<9 88 ? 0.0 : sqrt(N [0]*v[0]*Phi[0]*N[3]*v[3]*Phi[3])*Pa*Pd*exp(-Xb-Xc);110 ? 0.0 : sqrt(Na*va*Phia*Nd*vd*Phid)*Pa*Pd*exp(-Xb-Xc); 89 111 const double S0bc = (icase!=4 && icase!=7 && icase!= 9) 90 ? 0.0 : sqrt(N [1]*v[1]*Phi[1]*N[2]*v[2]*Phi[2])*Pb*Pc;112 ? 0.0 : sqrt(Nb*vb*Phib*Nc*vc*Phic)*Pb*Pc; 91 113 const double S0bd = (icase!=4 && icase!=7 && icase!= 9) 92 ? 0.0 : sqrt(N [1]*v[1]*Phi[1]*N[3]*v[3]*Phi[3])*Pb*Pd*exp(-Xc);114 ? 0.0 : sqrt(Nb*vb*Phib*Nd*vd*Phid)*Pb*Pd*exp(-Xc); 93 115 const double S0cd = (icase==0 || icase==2 || icase==5) 94 ? 0.0 : sqrt(N [2]*v[2]*Phi[2]*N[3]*v[3]*Phi[3])*Pc*Pd;116 ? 0.0 : sqrt(Nc*vc*Phic*Nd*vd*Phid)*Pc*Pd; 95 117 #else // sasview equivalent 96 //printf("Xc=%g, S0cc=%g*%g*%g*%g\n",Xc,N [2],Phi[2],v[2],Pcc);97 double S0aa = N [0]*Phi[0]*v[0]*Paa;98 double S0bb = N [1]*Phi[1]*v[1]*Pbb;99 double S0cc = N [2]*Phi[2]*v[2]*Pcc;100 double S0dd = N [3]*Phi[3]*v[3]*Pdd;101 double S0ab = sqrt(N [0]*v[0]*Phi[0]*N[1]*v[1]*Phi[1])*Pa*Pb;102 double S0ac = sqrt(N [0]*v[0]*Phi[0]*N[2]*v[2]*Phi[2])*Pa*Pc*exp(-Xb);103 double S0ad = sqrt(N [0]*v[0]*Phi[0]*N[3]*v[3]*Phi[3])*Pa*Pd*exp(-Xb-Xc);104 double S0bc = sqrt(N [1]*v[1]*Phi[1]*N[2]*v[2]*Phi[2])*Pb*Pc;105 double S0bd = sqrt(N [1]*v[1]*Phi[1]*N[3]*v[3]*Phi[3])*Pb*Pd*exp(-Xc);106 double S0cd = sqrt(N [2]*v[2]*Phi[2]*N[3]*v[3]*Phi[3])*Pc*Pd;118 //printf("Xc=%g, S0cc=%g*%g*%g*%g\n",Xc,Nc,Phic,vc,Pcc); 119 double S0aa = Na*Phia*va*Paa; 120 double S0bb = Nb*Phib*vb*Pbb; 121 double S0cc = Nc*Phic*vc*Pcc; 122 double S0dd = Nd*Phid*vd*Pdd; 123 double S0ab = sqrt(Na*va*Phia*Nb*vb*Phib)*Pa*Pb; 124 double S0ac = sqrt(Na*va*Phia*Nc*vc*Phic)*Pa*Pc*exp(-Xb); 125 double S0ad = sqrt(Na*va*Phia*Nd*vd*Phid)*Pa*Pd*exp(-Xb-Xc); 126 double S0bc = sqrt(Nb*vb*Phib*Nc*vc*Phic)*Pb*Pc; 127 double S0bd = sqrt(Nb*vb*Phib*Nd*vd*Phid)*Pb*Pd*exp(-Xc); 128 double S0cd = sqrt(Nc*vc*Phic*Nd*vd*Phid)*Pc*Pd; 107 129 switch(icase){ 108 130 case 0: … … 289 311 // Note: 1e-13 to convert from fm to cm for scattering length 290 312 const double sqrt_Nav=sqrt(6.022045e+23) * 1.0e-13; 291 const double Lad = icase<5 ? 0.0 : (L [0]/v[0] - L[3]/v[3])*sqrt_Nav;292 const double Lbd = icase<2 ? 0.0 : (L [1]/v[1] - L[3]/v[3])*sqrt_Nav;293 const double Lcd = (L [2]/v[2] - L[3]/v[3])*sqrt_Nav;313 const double Lad = icase<5 ? 0.0 : (La/va - Ld/vd)*sqrt_Nav; 314 const double Lbd = icase<2 ? 0.0 : (Lb/vb - Ld/vd)*sqrt_Nav; 315 const double Lcd = (Lc/vc - Ld/vd)*sqrt_Nav; 294 316 295 317 const double result=Lad*Lad*S11 + Lbd*Lbd*S22 + Lcd*Lcd*S33 … … 299 321 300 322 } 323 324 double Iqxy(double qx, double qy, 325 double case_num, 326 double Na, double Phia, double va, double a_sld, double ba, 327 double Nb, double Phib, double vb, double b_sld, double bb, 328 double Nc, double Phic, double vc, double c_sld, double bc, 329 double Nd, double Phid, double vd, double d_sld, double bd, 330 double Kab, double Kac, double Kad, 331 double Kbc, double Kbd, double Kcd 332 ) 333 { 334 double q = sqrt(qx*qx + qy*qy); 335 return Iq(q, 336 case_num, 337 Na, Phia, va, a_sld, ba, 338 Nb, Phib, vb, b_sld, bb, 339 Nc, Phic, vc, c_sld, bc, 340 Nd, Phid, vd, d_sld, bd, 341 Kab, Kac, Kad, 342 Kbc, Kbd, Kcd); 343 } -
sasmodels/models/rpa.py
ra5b8477 rec45c4f 86 86 # ["name", "units", default, [lower, upper], "type","description"], 87 87 parameters = [ 88 ["case_num", "", 1, [CASES], "", "Component organization"],88 ["case_num", CASES, 0, [0, 10], "", "Component organization"], 89 89 90 ["N[4]", "", 1000.0, [1, inf], "", "Degree of polymerization"], 91 ["Phi[4]", "", 0.25, [0, 1], "", "volume fraction"], 92 ["v[4]", "mL/mol", 100.0, [0, inf], "", "specific volume"], 93 ["L[4]", "fm", 10.0, [-inf, inf], "", "scattering length"], 94 ["b[4]", "Ang", 5.0, [0, inf], "", "segment length"], 90 ["Na", "", 1000.0, [1, inf], "", "Degree of polymerization"], 91 ["Phia", "", 0.25, [0, 1], "", "volume fraction"], 92 ["va", "mL/mol", 100.0, [0, inf], "", "specific volume"], 93 ["La", "fm", 10.0, [-inf, inf], "", "scattering length"], 94 ["ba", "Ang", 5.0, [0, inf], "", "segment length"], 95 96 ["Nb", "", 1000.0, [1, inf], "", "Degree of polymerization"], 97 ["Phib", "", 0.25, [0, 1], "", "volume fraction"], 98 ["vb", "mL/mol", 100.0, [0, inf], "", "specific volume"], 99 ["Lb", "fm", 10.0, [-inf, inf], "", "scattering length"], 100 ["bb", "Ang", 5.0, [0, inf], "", "segment length"], 101 102 ["Nc", "", 1000.0, [1, inf], "", "Degree of polymerization"], 103 ["Phic", "", 0.25, [0, 1], "", "volume fraction"], 104 ["vc", "mL/mol", 100.0, [0, inf], "", "specific volume"], 105 ["Lc", "fm", 10.0, [-inf, inf], "", "scattering length"], 106 ["bc", "Ang", 5.0, [0, inf], "", "segment length"], 107 108 ["Nd", "", 1000.0, [1, inf], "", "Degree of polymerization"], 109 ["Phid", "", 0.25, [0, 1], "", "volume fraction"], 110 ["vd", "mL/mol", 100.0, [0, inf], "", "specific volume"], 111 ["Ld", "fm", 10.0, [-inf, inf], "", "scattering length"], 112 ["bd", "Ang", 5.0, [0, inf], "", "segment length"], 95 113 96 114 ["Kab", "", -0.0004, [-inf, inf], "", "Interaction parameter"], -
sasmodels/models/spherical_sld.py
rd2bb604 rec45c4f 170 170 # pylint: disable=bad-whitespace, line-too-long 171 171 # ["name", "units", default, [lower, upper], "type", "description"], 172 parameters = [["n ","", 1, [0, 9], "", "number of shells"],172 parameters = [["n_shells", "", 1, [0, 9], "", "number of shells"], 173 173 ["radius_core", "Ang", 50.0, [0, inf], "", "intern layer thickness"], 174 174 ["sld_core", "1e-6/Ang^2", 2.07, [-inf, inf], "", "sld function flat"], … … 192 192 193 193 demo = dict( 194 n =4,194 n_shells=4, 195 195 scale=1.0, 196 196 solvent_sld=1.0, -
sasmodels/models/squarewell.py
rd2bb604 rec45c4f 123 123 """ 124 124 125 Iqxy = """ 126 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 127 """ 128 125 129 # ER defaults to 0.0 126 130 # VR defaults to 1.0 -
sasmodels/models/stickyhardsphere.py
rd2bb604 rec45c4f 171 171 """ 172 172 173 Iqxy = """ 174 return Iq(sqrt(qx*qx+qy*qy), IQ_PARAMETERS); 175 """ 176 173 177 # ER defaults to 0.0 174 178 # VR defaults to 1.0 -
sasmodels/product.py
r7ae2b7f rf247314 11 11 *ProductModel(P, S)*. 12 12 """ 13 import numpy as np # type: ignore13 import numpy as np 14 14 15 from .details import dispersion_mesh 16 from .modelinfo import suffix_parameter, ParameterTable, ModelInfo 17 from .kernel import KernelModel, Kernel 15 from .core import call_ER_VR 16 from .generate import process_parameters 18 17 19 try: 20 from typing import Tuple 21 from .modelinfo import ParameterSet 22 from .details import CallDetails 23 except ImportError: 24 pass 25 26 # TODO: make estimates available to constraints 27 #ESTIMATED_PARAMETERS = [ 28 # ["est_effect_radius", "A", 0.0, [0, np.inf], "", "Estimated effective radius"], 29 # ["est_volume_ratio", "", 1.0, [0, np.inf], "", "Estimated volume ratio"], 30 #] 18 SCALE=0 19 BACKGROUND=1 20 RADIUS_EFFECTIVE=2 21 VOLFRACTION=3 31 22 32 23 # TODO: core_shell_sphere model has suppressed the volume ratio calculation 33 24 # revert it after making VR and ER available at run time as constraints. 34 25 def make_product_info(p_info, s_info): 35 # type: (ModelInfo, ModelInfo) -> ModelInfo36 26 """ 37 27 Create info block for product model. 38 28 """ 39 p_id, p_name, p_pars = p_info .id, p_info.name, p_info.parameters40 s_id, s_name, s_pars = s_info .id, s_info.name, s_info.parameters41 p_set = set(p.id for p in p_pars.call_parameters)42 s_set = set(p.id for p in s_pars.call_parameters)43 44 if p_set & s_set:45 # there is some overlap between the parameter names; tag the46 # overlapping S parameters with name_S47 s_list = [(suffix_parameter(par, "_S") if par.id in p_set else par)48 for par in s_pars.kernel_parameters]49 combined_pars = p_pars.kernel_parameters + s_list50 else:51 combined_pars = p_pars.kernel_parameters + s_pars.kernel_parameters52 parameters = ParameterTable(combined_pars)53 54 model_info = ModelInfo()55 model_info .id= '*'.join((p_id, s_id))56 model_info .name= ' X '.join((p_name, s_name))57 model_info .filename= None58 model_info .title = 'Product of %s and%s'%(p_name, s_name)59 model_info .description = model_info.title60 model_info .docs = model_info.title61 model_info .category= "custom"62 model_info .parameters = parameters63 #model_info .single = p_info.single and s_info.single64 model_info .structure_factor= False65 model_info .variant_info= None66 #model_info .tests= []67 #model_info .source= []29 p_id, p_name, p_pars = p_info['id'], p_info['name'], p_info['parameters'] 30 s_id, s_name, s_pars = s_info['id'], s_info['name'], s_info['parameters'] 31 # We require models to start with scale and background 32 assert s_pars[SCALE].name == 'scale' 33 assert s_pars[BACKGROUND].name == 'background' 34 # We require structure factors to start with effect radius and volfraction 35 assert s_pars[RADIUS_EFFECTIVE].name == 'radius_effective' 36 assert s_pars[VOLFRACTION].name == 'volfraction' 37 # Combine the parameter sets. We are skipping the first three 38 # parameters of S since scale, background are defined in P and 39 # effect_radius is set from P.ER(). 40 pars = p_pars + s_pars[3:] 41 # check for duplicates; can't use assertion since they may never be checked 42 if len(set(p.name for p in pars)) != len(pars): 43 raise ValueError("Duplicate parameters in %s and %s"%(p_id)) 44 model_info = {} 45 model_info['id'] = '*'.join((p_id, s_id)) 46 model_info['name'] = ' X '.join((p_name, s_name)) 47 model_info['filename'] = None 48 model_info['title'] = 'Product of %s and structure factor %s'%(p_name, s_name) 49 model_info['description'] = model_info['title'] 50 model_info['docs'] = model_info['title'] 51 model_info['category'] = "custom" 52 model_info['parameters'] = pars 53 #model_info['single'] = p_info['single'] and s_info['single'] 54 model_info['structure_factor'] = False 55 model_info['variant_info'] = None 56 #model_info['tests'] = [] 57 #model_info['source'] = [] 68 58 # Iq, Iqxy, form_volume, ER, VR and sesans 69 model_info.composition = ('product', [p_info, s_info]) 59 model_info['composition'] = ('product', [p_info, s_info]) 60 process_parameters(model_info) 70 61 return model_info 71 62 72 class ProductModel( KernelModel):63 class ProductModel(object): 73 64 def __init__(self, model_info, P, S): 74 # type: (ModelInfo, KernelModel, KernelModel) -> None75 65 self.info = model_info 76 66 self.P = P … … 78 68 79 69 def __call__(self, q_vectors): 80 # type: (List[np.ndarray]) -> Kernel81 70 # Note: may be sending the q_vectors to the GPU twice even though they 82 71 # are only needed once. It would mess up modularity quite a bit to … … 85 74 # in opencl; or both in opencl, but one in single precision and the 86 75 # other in double precision). 87 p_kernel = self.P .make_kernel(q_vectors)88 s_kernel = self.S .make_kernel(q_vectors)76 p_kernel = self.P(q_vectors) 77 s_kernel = self.S(q_vectors) 89 78 return ProductKernel(self.info, p_kernel, s_kernel) 90 79 91 80 def release(self): 92 # type: (None) -> None93 81 """ 94 82 Free resources associated with the model. … … 98 86 99 87 100 class ProductKernel( Kernel):88 class ProductKernel(object): 101 89 def __init__(self, model_info, p_kernel, s_kernel): 102 # type: (ModelInfo, Kernel, Kernel) -> None 90 dim = '2d' if p_kernel.q_input.is_2d else '1d' 91 92 # Need to know if we want 2D and magnetic parameters when constructing 93 # a parameter map. 94 par_map = {} 95 p_info = p_kernel.info['partype'] 96 s_info = s_kernel.info['partype'] 97 vol_pars = set(p_info['volume']) 98 if dim == '2d': 99 num_p_fixed = len(p_info['fixed-2d']) 100 num_p_pd = len(p_info['pd-2d']) 101 num_s_fixed = len(s_info['fixed-2d']) 102 num_s_pd = len(s_info['pd-2d']) - 1 # exclude effect_radius 103 # volume parameters are amongst the pd pars for P, not S 104 vol_par_idx = [k for k,v in enumerate(p_info['pd-2d']) 105 if v in vol_pars] 106 else: 107 num_p_fixed = len(p_info['fixed-1d']) 108 num_p_pd = len(p_info['pd-1d']) 109 num_s_fixed = len(s_info['fixed-1d']) 110 num_s_pd = len(s_info['pd-1d']) - 1 # exclude effect_radius 111 # volume parameters are amongst the pd pars for P, not S 112 vol_par_idx = [k for k,v in enumerate(p_info['pd-1d']) 113 if v in vol_pars] 114 115 start = 0 116 par_map['p_fixed'] = np.arange(start, start+num_p_fixed) 117 # User doesn't set scale, background or effect_radius for S in P*S, 118 # so borrow values from end of p_fixed. This makes volfraction the 119 # first S parameter. 120 start += num_p_fixed 121 par_map['s_fixed'] = np.hstack(([start,start], 122 np.arange(start, start+num_s_fixed-2))) 123 par_map['volfraction'] = num_p_fixed 124 start += num_s_fixed-2 125 # vol pars offset from the start of pd pars 126 par_map['vol_pars'] = [start+k for k in vol_par_idx] 127 par_map['p_pd'] = np.arange(start, start+num_p_pd) 128 start += num_p_pd-1 129 par_map['s_pd'] = np.hstack((start, 130 np.arange(start, start+num_s_pd-1))) 131 132 self.fixed_pars = model_info['partype']['fixed-' + dim] 133 self.pd_pars = model_info['partype']['pd-' + dim] 103 134 self.info = model_info 104 135 self.p_kernel = p_kernel 105 136 self.s_kernel = s_kernel 137 self.par_map = par_map 106 138 107 def __call__(self, details, weights, values, cutoff): 108 # type: (CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray 139 def __call__(self, fixed_pars, pd_pars, cutoff=1e-5): 140 pars = fixed_pars + pd_pars 141 scale = pars[SCALE] 142 background = pars[BACKGROUND] 143 s_volfraction = pars[self.par_map['volfraction']] 144 p_fixed = [pars[k] for k in self.par_map['p_fixed']] 145 s_fixed = [pars[k] for k in self.par_map['s_fixed']] 146 p_pd = [pars[k] for k in self.par_map['p_pd']] 147 s_pd = [pars[k] for k in self.par_map['s_pd']] 148 vol_pars = [pars[k] for k in self.par_map['vol_pars']] 149 109 150 effect_radius, vol_ratio = call_ER_VR(self.p_kernel.info, vol_pars) 110 151 111 p_details, s_details = details.parts 112 p_res = self.p_kernel(p_details, weights, values, cutoff) 113 s_res = self.s_kernel(s_details, weights, values, cutoff) 152 p_fixed[SCALE] = s_volfraction 153 p_fixed[BACKGROUND] = 0.0 154 s_fixed[SCALE] = scale 155 s_fixed[BACKGROUND] = 0.0 156 s_fixed[2] = s_volfraction/vol_ratio 157 s_pd[0] = [effect_radius], [1.0] 158 159 p_res = self.p_kernel(p_fixed, p_pd, cutoff) 160 s_res = self.s_kernel(s_fixed, s_pd, cutoff) 114 161 #print s_fixed, s_pd, p_fixed, p_pd 115 162 116 return values[0]*(p_res*s_res) + values[1]163 return p_res*s_res + background 117 164 118 165 def release(self): 119 # type: () -> None120 166 self.p_kernel.release() 121 self. s_kernel.release()167 self.q_kernel.release() 122 168 123 def call_ER_VR(model_info, pars):124 """125 Return effect radius and volume ratio for the model.126 """127 if model_info.ER is None and model_info.VR is None:128 return 1.0, 1.0129 130 value, weight = _vol_pars(model_info, pars)131 132 if model_info.ER is not None:133 individual_radii = model_info.ER(*value)134 effect_radius = np.sum(weight*individual_radii) / np.sum(weight)135 else:136 effect_radius = 1.0137 138 if model_info.VR is not None:139 whole, part = model_info.VR(*value)140 volume_ratio = np.sum(weight*part)/np.sum(weight*whole)141 else:142 volume_ratio = 1.0143 144 return effect_radius, volume_ratio145 146 def _vol_pars(model_info, pars):147 # type: (ModelInfo, ParameterSet) -> Tuple[np.ndarray, np.ndarray]148 vol_pars = [get_weights(p, pars)149 for p in model_info.parameters.call_parameters150 if p.type == 'volume']151 value, weight = dispersion_mesh(model_info, vol_pars)152 return value, weight153 -
sasmodels/resolution.py
r7ae2b7f r4d76711 6 6 from __future__ import division 7 7 8 from scipy.special import erf # type: ignore9 from numpy import sqrt, log, log10 , exp, pi # type: ignore10 import numpy as np # type: ignore8 from scipy.special import erf 9 from numpy import sqrt, log, log10 10 import numpy as np 11 11 12 12 __all__ = ["Resolution", "Perfect1D", "Pinhole1D", "Slit1D", … … 35 35 smeared theory I(q). 36 36 """ 37 q = None # type: np.ndarray38 q_calc = None # type: np.ndarray37 q = None 38 q_calc = None 39 39 def apply(self, theory): 40 40 """ … … 476 476 *pars* are the parameter values to use when evaluating. 477 477 """ 478 from sasmodels import direct_model478 from sasmodels import core 479 479 kernel = form.make_kernel([q]) 480 theory = direct_model.call_kernel(kernel, pars)480 theory = core.call_kernel(kernel, pars) 481 481 kernel.release() 482 482 return theory … … 489 489 *q0* is the center, *dq* is the width and *q* are the points to evaluate. 490 490 """ 491 from numpy import exp, pi 491 492 return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq) 492 493 … … 499 500 that make it slow to evaluate but give it good accuracy. 500 501 """ 501 from scipy.integrate import romberg # type: ignore 502 503 par_set = set([p.name for p in form.info.parameters.call_parameters]) 504 if any(k not in par_set for k in pars.keys()): 505 extra = set(pars.keys()) - par_set 506 raise ValueError("bad parameters: [%s] not in [%s]" 507 % (", ".join(sorted(extra)), 508 ", ".join(sorted(pars.keys())))) 502 from scipy.integrate import romberg 503 504 if any(k not in form.info['defaults'] for k in pars.keys()): 505 keys = set(form.info['defaults'].keys()) 506 extra = set(pars.keys()) - keys 507 raise ValueError("bad parameters: [%s] not in [%s]"% 508 (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 509 509 510 510 if np.isscalar(width): … … 554 554 that make it slow to evaluate but give it good accuracy. 555 555 """ 556 from scipy.integrate import romberg # type: ignore 557 558 par_set = set([p.name for p in form.info.parameters.call_parameters]) 559 if any(k not in par_set for k in pars.keys()): 560 extra = set(pars.keys()) - par_set 561 raise ValueError("bad parameters: [%s] not in [%s]" 562 % (", ".join(sorted(extra)), 563 ", ".join(sorted(pars.keys())))) 556 from scipy.integrate import romberg 557 558 if any(k not in form.info['defaults'] for k in pars.keys()): 559 keys = set(form.info['defaults'].keys()) 560 extra = set(pars.keys()) - keys 561 raise ValueError("bad parameters: [%s] not in [%s]"% 562 (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 564 563 565 564 _fn = lambda q, q0, dq: eval_form(q, form, pars)*gaussian(q, q0, dq) … … 693 692 694 693 def _eval_sphere(self, pars, resolution): 695 from sasmodels import direct_model694 from sasmodels import core 696 695 kernel = self.model.make_kernel([resolution.q_calc]) 697 theory = direct_model.call_kernel(kernel, pars)696 theory = core.call_kernel(kernel, pars) 698 697 result = resolution.apply(theory) 699 698 kernel.release() … … 751 750 #tol = 0.001 752 751 ## The default 3 sigma and no extra points gets 1% 753 q_calc = None # type: np.ndarray 754 tol = 0.01 752 q_calc, tol = None, 0.01 755 753 resolution = Pinhole1D(q, q_width, q_calc=q_calc) 756 754 output = self._eval_sphere(pars, resolution) 757 755 if 0: # debug plot 758 import matplotlib.pyplot as plt # type: ignore756 import matplotlib.pyplot as plt 759 757 resolution = Perfect1D(q) 760 758 source = self._eval_sphere(pars, resolution) … … 1028 1026 """ 1029 1027 import sys 1030 import xmlrunner # type: ignore1028 import xmlrunner 1031 1029 1032 1030 suite = unittest.TestSuite() … … 1045 1043 import sys 1046 1044 from sasmodels import core 1047 from sasmodels import direct_model1048 1045 name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder' 1049 1046 … … 1066 1063 1067 1064 kernel = model.make_kernel([resolution.q_calc]) 1068 theory = direct_model.call_kernel(kernel, pars)1065 theory = core.call_kernel(kernel, pars) 1069 1066 Iq = resolution.apply(theory) 1070 1067 … … 1076 1073 Iq_romb = romberg_pinhole_1d(resolution.q, dq, model, pars) 1077 1074 1078 import matplotlib.pyplot as plt # type: ignore1075 import matplotlib.pyplot as plt 1079 1076 plt.loglog(resolution.q_calc, theory, label='unsmeared') 1080 1077 plt.loglog(resolution.q, Iq, label='smeared', hold=True) … … 1111 1108 Run the resolution demos. 1112 1109 """ 1113 import matplotlib.pyplot as plt # type: ignore1110 import matplotlib.pyplot as plt 1114 1111 plt.subplot(121) 1115 1112 demo_pinhole_1d() -
sasmodels/resolution2d.py
r7ae2b7f rd6f5da6 7 7 from __future__ import division 8 8 9 import numpy as np # type: ignore10 from numpy import pi, cos, sin, sqrt # type: ignore9 import numpy as np 10 from numpy import pi, cos, sin, sqrt 11 11 12 12 from . import resolution -
sasmodels/sasview_model.py
r60f03de r81ec7c8 21 21 import logging 22 22 23 import numpy as np # type: ignore23 import numpy as np 24 24 25 25 from . import core 26 26 from . import custom 27 27 from . import generate 28 from . import weights29 from . import details30 from . import modelinfo31 28 32 29 try: 33 from typing import Dict, Mapping, Any, Sequence, Tuple, NamedTuple, List, Optional, Union, Callable 34 from .modelinfo import ModelInfo, Parameter 30 from typing import Dict, Mapping, Any, Sequence, Tuple, NamedTuple, List, Optional 35 31 from .kernel import KernelModel 36 32 MultiplicityInfoType = NamedTuple( … … 38 34 [("number", int), ("control", str), ("choices", List[str]), 39 35 ("x_axis_label", str)]) 40 SasviewModelType = Callable[[int], "SasviewModel"]41 36 except ImportError: 42 37 pass 43 38 44 39 # TODO: separate x_axis_label from multiplicity info 45 # The profilex-axis label belongs with the profile generating function40 # The x-axis label belongs with the profile generating function 46 41 MultiplicityInfo = collections.namedtuple( 47 42 'MultiplicityInfo', … … 49 44 ) 50 45 51 # TODO: figure out how to say that the return type is a subclass52 46 def load_standard_models(): 53 # type: () -> List[SasviewModelType]54 47 """ 55 48 Load and return the list of predefined models. … … 62 55 try: 63 56 models.append(_make_standard_model(name)) 64 except Exception:57 except: 65 58 logging.error(traceback.format_exc()) 66 59 return models … … 68 61 69 62 def load_custom_model(path): 70 # type: (str) -> SasviewModelType71 63 """ 72 64 Load a custom model given the model path. 73 65 """ 74 66 kernel_module = custom.load_custom_kernel_module(path) 75 model_info = modelinfo.make_model_info(kernel_module)67 model_info = generate.make_model_info(kernel_module) 76 68 return _make_model_from_info(model_info) 77 69 78 70 79 71 def _make_standard_model(name): 80 # type: (str) -> SasviewModelType81 72 """ 82 73 Load the sasview model defined by *name*. … … 87 78 """ 88 79 kernel_module = generate.load_kernel_module(name) 89 model_info = modelinfo.make_model_info(kernel_module)80 model_info = generate.make_model_info(kernel_module) 90 81 return _make_model_from_info(model_info) 91 82 92 83 93 84 def _make_model_from_info(model_info): 94 # type: (ModelInfo) -> SasviewModelType95 85 """ 96 86 Convert *model_info* into a SasView model wrapper. 97 87 """ 98 def __init__(self, multiplicity=None): 88 model_info['variant_info'] = None # temporary hack for older sasview 89 def __init__(self, multiplicity=1): 99 90 SasviewModel.__init__(self, multiplicity=multiplicity) 100 91 attrs = _generate_model_attributes(model_info) 101 92 attrs['__init__'] = __init__ 102 ConstructedModel = type(model_info .name, (SasviewModel,), attrs) # type: SasviewModelType93 ConstructedModel = type(model_info['id'], (SasviewModel,), attrs) 103 94 return ConstructedModel 104 95 … … 113 104 All the attributes should be immutable to avoid accidents. 114 105 """ 115 116 # TODO: allow model to override axis labels input/output name/unit117 118 # Process multiplicity119 non_fittable = [] # type: List[str]120 xlabel = model_info.profile_axes[0] if model_info.profile is not None else ""121 variants = MultiplicityInfo(0, "", [], xlabel)122 for p in model_info.parameters.kernel_parameters:123 if p.name == model_info.control:124 non_fittable.append(p.name)125 variants = MultiplicityInfo(126 len(p.choices), p.name, p.choices, xlabel127 )128 break129 elif p.is_control:130 non_fittable.append(p.name)131 variants = MultiplicityInfo(132 int(p.limits[1]), p.name, p.choices, xlabel133 )134 break135 136 # Organize parameter sets137 orientation_params = []138 magnetic_params = []139 fixed = []140 for p in model_info.parameters.user_parameters():141 if p.type == 'orientation':142 orientation_params.append(p.name)143 orientation_params.append(p.name+".width")144 fixed.append(p.name+".width")145 if p.type == 'magnetic':146 orientation_params.append(p.name)147 magnetic_params.append(p.name)148 fixed.append(p.name+".width")149 150 # Build class dictionary151 106 attrs = {} # type: Dict[str, Any] 152 107 attrs['_model_info'] = model_info 153 attrs['name'] = model_info.name 154 attrs['id'] = model_info.id 155 attrs['description'] = model_info.description 156 attrs['category'] = model_info.category 157 attrs['is_structure_factor'] = model_info.structure_factor 158 attrs['is_form_factor'] = model_info.ER is not None 108 attrs['name'] = model_info['name'] 109 attrs['id'] = model_info['id'] 110 attrs['description'] = model_info['description'] 111 attrs['category'] = model_info['category'] 112 113 # TODO: allow model to override axis labels input/output name/unit 114 115 #self.is_multifunc = False 116 non_fittable = [] # type: List[str] 117 variants = MultiplicityInfo(0, "", [], "") 118 attrs['is_structure_factor'] = model_info['structure_factor'] 119 attrs['is_form_factor'] = model_info['ER'] is not None 159 120 attrs['is_multiplicity_model'] = variants[0] > 1 160 121 attrs['multiplicity_info'] = variants 122 123 partype = model_info['partype'] 124 orientation_params = ( 125 partype['orientation'] 126 + [n + '.width' for n in partype['orientation']] 127 + partype['magnetic']) 128 magnetic_params = partype['magnetic'] 129 fixed = [n + '.width' for n in partype['pd-2d']] 130 161 131 attrs['orientation_params'] = tuple(orientation_params) 162 132 attrs['magnetic_params'] = tuple(magnetic_params) 163 133 attrs['fixed'] = tuple(fixed) 134 164 135 attrs['non_fittable'] = tuple(non_fittable) 165 136 … … 221 192 dispersion = None # type: Dict[str, Any] 222 193 #: units and limits for each parameter 223 details = None # type: Dict[str, Sequence[Any]] 224 # # actual type is Dict[str, List[str, float, float]] 225 #: multiplicity value, or None if no multiplicity on the model 194 details = None # type: Mapping[str, Tuple(str, float, float)] 195 #: multiplicity used, or None if no multiplicity controls 226 196 multiplicity = None # type: Optional[int] 227 #: memory for polydispersity array if using ArrayDispersion (used by sasview). 228 _persistency_dict = None # type: Dict[str, Tuple[np.ndarray, np.ndarray]] 229 230 def __init__(self, multiplicity=None): 231 # type: (Optional[int]) -> None 232 233 # TODO: _persistency_dict to persistency_dict throughout sasview 234 # TODO: refactor multiplicity to encompass variants 235 # TODO: dispersion should be a class 236 # TODO: refactor multiplicity info 237 # TODO: separate profile view from multiplicity 238 # The button label, x and y axis labels and scale need to be under 239 # the control of the model, not the fit page. Maximum flexibility, 240 # the fit page would supply the canvas and the profile could plot 241 # how it wants, but this assumes matplotlib. Next level is that 242 # we provide some sort of data description including title, labels 243 # and lines to plot. 244 245 # Get the list of hidden parameters given the mulitplicity 246 # Don't include multiplicity in the list of parameters 197 198 def __init__(self, multiplicity): 199 # type: () -> None 200 print("initializing", self.name) 201 #raise Exception("first initialization") 202 self._model = None 203 204 ## _persistency_dict is used by sas.perspectives.fitting.basepage 205 ## to store dispersity reference. 206 self._persistency_dict = {} 207 247 208 self.multiplicity = multiplicity 248 if multiplicity is not None: 249 hidden = self._model_info.get_hidden_parameters(multiplicity) 250 hidden |= set([self.multiplicity_info.control]) 251 else: 252 hidden = set() 253 254 self._persistency_dict = {} 209 255 210 self.params = collections.OrderedDict() 256 211 self.dispersion = {} 257 212 self.details = {} 258 for p in self._model_info.parameters.user_parameters(): 259 if p.name in hidden: 260 continue 213 214 for p in self._model_info['parameters']: 261 215 self.params[p.name] = p.default 262 self.details[p.id] = [p.units, p.limits[0], p.limits[1]] 263 if p.polydisperse: 264 self.details[p.id+".width"] = [ 265 "", 0.0, 1.0 if p.relative_pd else np.inf 266 ] 267 self.dispersion[p.name] = { 268 'width': 0, 269 'npts': 35, 270 'nsigmas': 3, 271 'type': 'gaussian', 272 } 216 self.details[p.name] = [p.units] + p.limits 217 218 for name in self._model_info['partype']['pd-2d']: 219 self.dispersion[name] = { 220 'width': 0, 221 'npts': 35, 222 'nsigmas': 3, 223 'type': 'gaussian', 224 } 273 225 274 226 def __get_state__(self): 275 # type: () -> Dict[str, Any]276 227 state = self.__dict__.copy() 277 228 state.pop('_model') … … 282 233 283 234 def __set_state__(self, state): 284 # type: (Dict[str, Any]) -> None285 235 self.__dict__ = state 286 236 self._model = None 287 237 288 238 def __str__(self): 289 # type: () -> str290 239 """ 291 240 :return: string representation … … 294 243 295 244 def is_fittable(self, par_name): 296 # type: (str) -> bool297 245 """ 298 246 Check if a given parameter is fittable or not … … 305 253 306 254 255 # pylint: disable=no-self-use 307 256 def getProfile(self): 308 # type: () -> (np.ndarray, np.ndarray)309 257 """ 310 258 Get SLD profile … … 313 261 beta is a list of the corresponding SLD values 314 262 """ 315 args = [] # type: List[Union[float, np.ndarray]] 316 for p in self._model_info.parameters.kernel_parameters: 317 if p.id == self.multiplicity_info.control: 318 args.append(float(self.multiplicity)) 319 elif p.length == 1: 320 args.append(self.params.get(p.id, np.NaN)) 321 else: 322 args.append([self.params.get(p.id+str(k), np.NaN) 323 for k in range(1,p.length+1)]) 324 return self._model_info.profile(*args) 263 return None, None 325 264 326 265 def setParam(self, name, value): 327 # type: (str, float) -> None328 266 """ 329 267 Set the value of a model parameter … … 352 290 353 291 def getParam(self, name): 354 # type: (str) -> float355 292 """ 356 293 Set the value of a model parameter … … 376 313 377 314 def getParamList(self): 378 # type: () -> Sequence[str]379 315 """ 380 316 Return a list of all available parameters for the model 381 317 """ 382 param_list = list(self.params.keys())318 param_list = self.params.keys() 383 319 # WARNING: Extending the list with the dispersion parameters 384 320 param_list.extend(self.getDispParamList()) … … 386 322 387 323 def getDispParamList(self): 388 # type: () -> Sequence[str]389 324 """ 390 325 Return a list of polydispersity parameters for the model 391 326 """ 392 327 # TODO: fix test so that parameter order doesn't matter 393 ret = ['%s.%s' % (p.name.lower(), ext) 394 for p in self._model_info.parameters.user_parameters() 395 for ext in ('npts', 'nsigmas', 'width') 396 if p.polydisperse] 328 ret = ['%s.%s' % (d.lower(), p) 329 for d in self._model_info['partype']['pd-2d'] 330 for p in ('npts', 'nsigmas', 'width')] 397 331 #print(ret) 398 332 return ret 399 333 400 334 def clone(self): 401 # type: () -> "SasviewModel"402 335 """ Return a identical copy of self """ 403 336 return deepcopy(self) 404 337 405 338 def run(self, x=0.0): 406 # type: (Union[float, (float, float), List[float]]) -> float407 339 """ 408 340 Evaluate the model … … 417 349 # pylint: disable=unpacking-non-sequence 418 350 q, phi = x 419 return self.calculate_Iq([q*math.cos(phi)], [q*math.sin(phi)])[0] 420 else: 421 return self.calculate_Iq([x])[0] 351 return self.calculate_Iq([q * math.cos(phi)], 352 [q * math.sin(phi)])[0] 353 else: 354 return self.calculate_Iq([float(x)])[0] 422 355 423 356 424 357 def runXY(self, x=0.0): 425 # type: (Union[float, (float, float), List[float]]) -> float426 358 """ 427 359 Evaluate the model in cartesian coordinates … … 434 366 """ 435 367 if isinstance(x, (list, tuple)): 436 return self.calculate_Iq([ x[0]], [x[1]])[0]437 else: 438 return self.calculate_Iq([ x])[0]368 return self.calculate_Iq([float(x[0])], [float(x[1])])[0] 369 else: 370 return self.calculate_Iq([float(x)])[0] 439 371 440 372 def evalDistribution(self, qdist): 441 # type: (Union[np.ndarray, Tuple[np.ndarray, np.ndarray], List[np.ndarray]]) -> np.ndarray442 373 r""" 443 374 Evaluate a distribution of q-values. … … 470 401 # Check whether we have a list of ndarrays [qx,qy] 471 402 qx, qy = qdist 472 if not self._model_info.parameters.has_2d: 403 partype = self._model_info['partype'] 404 if not partype['orientation'] and not partype['magnetic']: 473 405 return self.calculate_Iq(np.sqrt(qx ** 2 + qy ** 2)) 474 406 else: … … 483 415 % type(qdist)) 484 416 485 def calculate_Iq(self, qx, qy=None): 486 # type: (Sequence[float], Optional[Sequence[float]]) -> np.ndarray 417 def calculate_Iq(self, *args): 487 418 """ 488 419 Calculate Iq for one set of q with the current parameters. … … 495 426 if self._model is None: 496 427 self._model = core.build_model(self._model_info) 497 if qy is not None: 498 q_vectors = [np.asarray(qx), np.asarray(qy)] 499 else: 500 q_vectors = [np.asarray(qx)] 501 kernel = self._model.make_kernel(q_vectors) 502 pairs = [self._get_weights(p) 503 for p in self._model_info.parameters.call_parameters] 504 call_details, weight, value = details.build_details(kernel, pairs) 505 result = kernel(call_details, weight, value, cutoff=self.cutoff) 506 kernel.release() 428 q_vectors = [np.asarray(q) for q in args] 429 fn = self._model.make_kernel(q_vectors) 430 pars = [self.params[v] for v in fn.fixed_pars] 431 pd_pars = [self._get_weights(p) for p in fn.pd_pars] 432 result = fn(pars, pd_pars, self.cutoff) 433 fn.q_input.release() 434 fn.release() 507 435 return result 508 436 509 437 def calculate_ER(self): 510 # type: () -> float511 438 """ 512 439 Calculate the effective radius for P(q)*S(q) … … 514 441 :return: the value of the effective radius 515 442 """ 516 if self._model_info.ER is None: 443 ER = self._model_info.get('ER', None) 444 if ER is None: 517 445 return 1.0 518 446 else: 519 value , weight= self._dispersion_mesh()520 fv = self._model_info.ER(*value)447 values, weights = self._dispersion_mesh() 448 fv = ER(*values) 521 449 #print(values[0].shape, weights.shape, fv.shape) 522 return np.sum(weight * fv) / np.sum(weight)450 return np.sum(weights * fv) / np.sum(weights) 523 451 524 452 def calculate_VR(self): 525 # type: () -> float526 453 """ 527 454 Calculate the volf ratio for P(q)*S(q) … … 529 456 :return: the value of the volf ratio 530 457 """ 531 if self._model_info.VR is None: 458 VR = self._model_info.get('VR', None) 459 if VR is None: 532 460 return 1.0 533 461 else: 534 value , weight= self._dispersion_mesh()535 whole, part = self._model_info.VR(*value)536 return np.sum(weight * part) / np.sum(weight* whole)462 values, weights = self._dispersion_mesh() 463 whole, part = VR(*values) 464 return np.sum(weights * part) / np.sum(weights * whole) 537 465 538 466 def set_dispersion(self, parameter, dispersion): 539 # type: (str, weights.Dispersion) -> Dict[str, Any]540 467 """ 541 468 Set the dispersion object for a model parameter … … 560 487 from . import weights 561 488 disperser = weights.dispersers[dispersion.__class__.__name__] 562 dispersion = weights. MODELS[disperser]()489 dispersion = weights.models[disperser]() 563 490 self.dispersion[parameter] = dispersion.get_pars() 564 491 else: … … 566 493 567 494 def _dispersion_mesh(self): 568 # type: () -> List[Tuple[np.ndarray, np.ndarray]]569 495 """ 570 496 Create a mesh grid of dispersion parameters and weights. … … 574 500 parameter set in the vector. 575 501 """ 576 pars = [self._get_weights(p) 577 for p in self._model_info.parameters.call_parameters 578 if p.type == 'volume'] 579 return details.dispersion_mesh(self._model_info, pars) 502 pars = self._model_info['partype']['volume'] 503 return core.dispersion_mesh([self._get_weights(p) for p in pars]) 580 504 581 505 def _get_weights(self, par): 582 # type: (Parameter) -> Tuple[np.ndarray, np.ndarray]583 506 """ 584 507 Return dispersion weights for parameter 585 508 """ 586 if par.name not in self.params: 587 if par.name == self.multiplicity_info.control: 588 return [self.multiplicity], [] 589 else: 590 return [np.NaN], [] 591 elif par.polydisperse: 592 dis = self.dispersion[par.name] 593 value, weight = weights.get_weights( 594 dis['type'], dis['npts'], dis['width'], dis['nsigmas'], 595 self.params[par.name], par.limits, par.relative_pd) 596 return value, weight / np.sum(weight) 597 else: 598 return [self.params[par.name]], [] 509 from . import weights 510 relative = self._model_info['partype']['pd-rel'] 511 limits = self._model_info['limits'] 512 dis = self.dispersion[par] 513 value, weight = weights.get_weights( 514 dis['type'], dis['npts'], dis['width'], dis['nsigmas'], 515 self.params[par], limits[par], par in relative) 516 return value, weight / np.sum(weight) 517 599 518 600 519 def test_model(): 601 # type: () -> float602 520 """ 603 521 Test that a sasview model (cylinder) can be run. … … 607 525 return cylinder.evalDistribution([0.1,0.1]) 608 526 609 def test_rpa():610 # type: () -> float611 """612 Test that a sasview model (cylinder) can be run.613 """614 RPA = _make_standard_model('rpa')615 rpa = RPA(3)616 return rpa.evalDistribution([0.1,0.1])617 618 527 619 528 def test_model_list(): 620 # type: () -> None621 529 """ 622 530 Make sure that all models build as sasview models. -
sasmodels/sesans.py
r7ae2b7f r2684d45 12 12 from __future__ import division 13 13 14 import numpy as np # type: ignore15 from numpy import pi, exp # type: ignore14 import numpy as np 15 from numpy import pi, exp 16 16 from scipy.special import jv as besselj 17 17 #import direct_model.DataMixin as model -
sasmodels/weights.py
ra936688 ra936688 3 3 """ 4 4 # TODO: include dispersion docs with the disperser models 5 from math import sqrt # type: ignore6 import numpy as np # type: ignore7 from scipy.special import gammaln # type: ignore5 from math import sqrt 6 import numpy as np 7 from scipy.special import gammaln 8 8 9 9 class Dispersion(object):
Note: See TracChangeset
for help on using the changeset viewer.