Changeset 9150036 in sasmodels
- Timestamp:
- Mar 6, 2019 4:05:28 PM (5 years ago)
- Branches:
- master, core_shell_microgels, magnetic_model, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
- Children:
- f64b154, bd91e8f, c11d09f, 02226a2
- Parents:
- e589e9a (diff), 37f38ff (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent. - Files:
-
- 50 added
- 116 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/guide/plugin.rst
raa8c6e0 r9150036 272 272 structure factor to account for interactions between particles. See 273 273 `Form_Factors`_ for more details. 274 275 **model_info = ...** lets you define a model directly, for example, by 276 loading and modifying existing models. This is done implicitly by 277 :func:`sasmodels.core.load_model_info`, which can create a mixture model 278 from a pair of existing models. For example:: 279 280 from sasmodels.core import load_model_info 281 model_info = load_model_info('sphere+cylinder') 282 283 See :class:`sasmodels.modelinfo.ModelInfo` for details about the model 284 attributes that are defined. 274 285 275 286 Model Parameters … … 894 905 - \frac{\sin(x)}{x}\left(\frac{1}{x} - \frac{3!}{x^3} + \frac{5!}{x^5} - \frac{7!}{x^7}\right) 895 906 896 For small arguments 907 For small arguments, 897 908 898 909 .. math:: -
example/multiscatfit.py
r49d1f8b8 r2c4a190 15 15 16 16 # Show the model without fitting 17 PYTHONPATH=..:../ explore:../../bumps:../../sasview/src python multiscatfit.py17 PYTHONPATH=..:../../bumps:../../sasview/src python multiscatfit.py 18 18 19 19 # Run the fit 20 PYTHONPATH=..:../ explore:../../bumps:../../sasview/src ../../bumps/run.py \20 PYTHONPATH=..:../../bumps:../../sasview/src ../../bumps/run.py \ 21 21 multiscatfit.py --store=/tmp/t1 22 22 … … 55 55 ) 56 56 57 # Tie the model to the data 58 M = Experiment(data=data, model=model) 59 60 # Stack mulitple scattering on top of the existing resolution function. 61 M.resolution = MultipleScattering(resolution=M.resolution, probability=0.) 62 57 63 # SET THE FITTING PARAMETERS 58 64 model.radius_polar.range(15, 3000) … … 65 71 model.scale.range(0, 0.1) 66 72 67 # Mulitple scattering probability parameter 68 # HACK: the probability is stuffed in as an extra parameter to the experiment. 69 probability = Parameter(name="probability", value=0.0) 70 probability.range(0.0, 0.9) 73 # The multiple scattering probability parameter is in the resolution function 74 # instead of the scattering function, so access it through M.resolution 75 M.scattering_probability.range(0.0, 0.9) 71 76 72 M = Experiment(data=data, model=model, extra_pars={'probability': probability}) 73 74 # Stack mulitple scattering on top of the existing resolution function. 75 # Because resolution functions in sasview don't have fitting parameters, 76 # we instead allow the multiple scattering calculator to take a function 77 # instead of a probability. This function returns the current value of 78 # the parameter. ** THIS IS TEMPORARY ** when multiple scattering is 79 # properly integrated into sasmodels and sasview, its fittable parameter 80 # will be treated like the model parameters. 81 M.resolution = MultipleScattering(resolution=M.resolution, 82 probability=lambda: probability.value, 83 ) 84 M._kernel_inputs = M.resolution.q_calc 77 # Let bumps know that we are fitting this experiment 85 78 problem = FitProblem(M) 86 79 -
sasmodels/__init__.py
ra1ec908 r37f38ff 14 14 defining new models. 15 15 """ 16 __version__ = "0.9 8"16 __version__ = "0.99" 17 17 18 18 def data_files(): -
sasmodels/bumps_model.py
r49d1f8b8 r2c4a190 35 35 # when bumps is not on the path. 36 36 from bumps.names import Parameter # type: ignore 37 from bumps.parameter import Reference # type: ignore 37 38 except ImportError: 38 39 pass … … 139 140 def __init__(self, data, model, cutoff=1e-5, name=None, extra_pars=None): 140 141 # type: (Data, Model, float) -> None 142 # Allow resolution function to define fittable parameters. We do this 143 # by creating reference parameters within the resolution object rather 144 # than modifying the object itself to use bumps parameters. We need 145 # to reset the parameters each time the object has changed. These 146 # additional parameters need to be returned from the fitting engine. 147 # To make them available to the user, they are added as top-level 148 # attributes to the experiment object. The only change to the 149 # resolution function is that it needs an optional 'fittable' attribute 150 # which maps the internal name to the user visible name for the 151 # for the parameter. 152 self._resolution = None 153 self._resolution_pars = {} 141 154 # remember inputs so we can inspect from outside 142 155 self.name = data.filename if name is None else name … … 145 158 self._interpret_data(data, model.sasmodel) 146 159 self._cache = {} 160 # CRUFT: no longer need extra parameters 161 # Multiple scattering probability is now retrieved directly from the 162 # multiple scattering resolution function. 147 163 self.extra_pars = extra_pars 148 164 … … 162 178 return len(self.Iq) 163 179 180 @property 181 def resolution(self): 182 return self._resolution 183 184 @resolution.setter 185 def resolution(self, value): 186 self._resolution = value 187 188 # Remove old resolution fitting parameters from experiment 189 for name in self._resolution_pars: 190 delattr(self, name) 191 192 # Create new resolution fitting parameters 193 res_pars = getattr(self._resolution, 'fittable', {}) 194 self._resolution_pars = { 195 name: Reference(self._resolution, refname, name=name) 196 for refname, name in res_pars.items() 197 } 198 199 # Add new resolution fitting parameters as experiment attributes 200 for name, ref in self._resolution_pars.items(): 201 setattr(self, name, ref) 202 164 203 def parameters(self): 165 204 # type: () -> Dict[str, Parameter] … … 168 207 """ 169 208 pars = self.model.parameters() 170 if self.extra_pars :209 if self.extra_pars is not None: 171 210 pars.update(self.extra_pars) 211 pars.update(self._resolution_pars) 172 212 return pars 173 213 -
sasmodels/direct_model.py
rc1799d3 r9150036 224 224 else: 225 225 Iq, dIq = None, None 226 #self._theory = np.zeros_like(q)227 q_vectors = [res.q_calc]228 226 elif self.data_type == 'Iqxy': 229 227 #if not model.info.parameters.has_2d: … … 242 240 res = resolution2d.Pinhole2D(data=data, index=index, 243 241 nsigma=3.0, accuracy=accuracy) 244 #self._theory = np.zeros_like(self.Iq)245 q_vectors = res.q_calc246 242 elif self.data_type == 'Iq': 247 243 index = (data.x >= data.qmin) & (data.x <= data.qmax) … … 268 264 else: 269 265 res = resolution.Perfect1D(data.x[index]) 270 271 #self._theory = np.zeros_like(self.Iq)272 q_vectors = [res.q_calc]273 266 elif self.data_type == 'Iq-oriented': 274 267 index = (data.x >= data.qmin) & (data.x <= data.qmax) … … 286 279 qx_width=data.dxw[index], 287 280 qy_width=data.dxl[index]) 288 q_vectors = res.q_calc289 281 else: 290 282 raise ValueError("Unknown data type") # never gets here … … 292 284 # Remember function inputs so we can delay loading the function and 293 285 # so we can save/restore state 294 self._kernel_inputs = q_vectors295 286 self._kernel = None 296 287 self.Iq, self.dIq, self.index = Iq, dIq, index … … 329 320 # type: (ParameterSet, float) -> np.ndarray 330 321 if self._kernel is None: 331 self._kernel = self._model.make_kernel(self._kernel_inputs) 322 # TODO: change interfaces so that resolution returns kernel inputs 323 # Maybe have resolution always return a tuple, or maybe have 324 # make_kernel accept either an ndarray or a pair of ndarrays. 325 kernel_inputs = self.resolution.q_calc 326 if isinstance(kernel_inputs, np.ndarray): 327 kernel_inputs = (kernel_inputs,) 328 self._kernel = self._model.make_kernel(kernel_inputs) 332 329 333 330 # Need to pull background out of resolution for multiple scattering -
sasmodels/multiscat.py
rb3703f5 r2c4a190 342 342 343 343 *probability* is related to the expected number of scattering 344 events in the sample $\lambda$ as $p = 1 = e^{-\lambda}$. As a 345 hack to allow probability to be a fitted parameter, the "value" 346 can be a function that takes no parameters and returns the current 347 value of the probability. *coverage* determines how many scattering 348 steps to consider. The default is 0.99, which sets $n$ such that 349 $1 \ldots n$ covers 99% of the Poisson probability mass function. 344 events in the sample $\lambda$ as $p = 1 - e^{-\lambda}$. 345 *coverage* determines how many scattering steps to consider. The 346 default is 0.99, which sets $n$ such that $1 \ldots n$ covers 99% 347 of the Poisson probability mass function. 350 348 351 349 *is2d* is True then 2D scattering is used, otherwise it accepts … … 399 397 self.qmin = qmin 400 398 self.nq = nq 401 self.probability = probability399 self.probability = 0. if probability is None else probability 402 400 self.coverage = coverage 403 401 self.is2d = is2d … … 456 454 self.Iqxy = None # type: np.ndarray 457 455 456 # Label probability as a fittable parameter, and give its external name 457 # Note that the external name must be a valid python identifier, since 458 # is will be set as an experiment attribute. 459 self.fittable = {'probability': 'scattering_probability'} 460 458 461 def apply(self, theory): 459 462 if self.is2d: … … 463 466 Iq_calc = Iq_calc.reshape(self.nq, self.nq) 464 467 468 # CRUFT: don't need probability as a function anymore 465 469 probability = self.probability() if callable(self.probability) else self.probability 466 470 coverage = self.coverage -
sasmodels/sasview_model.py
ra8a1d48 r9150036 25 25 from . import core 26 26 from . import custom 27 from . import kernelcl 27 28 from . import product 28 29 from . import generate … … 30 31 from . import modelinfo 31 32 from .details import make_kernel_args, dispersion_mesh 33 from .kernelcl import reset_environment 32 34 33 35 # pylint: disable=unused-import … … 68 70 #: has changed since we last reloaded. 69 71 _CACHED_MODULE = {} # type: Dict[str, "module"] 72 73 def reset_environment(): 74 # type: () -> None 75 """ 76 Clear the compute engine context so that the GUI can change devices. 77 78 This removes all compiled kernels, even those that are active on fit 79 pages, but they will be restored the next time they are needed. 80 """ 81 kernelcl.reset_environment() 82 for model in MODELS.values(): 83 model._model = None 70 84 71 85 def find_model(modelname): … … 696 710 def _calculate_Iq(self, qx, qy=None): 697 711 if self._model is None: 698 self._model = core.build_model(self._model_info) 712 # Only need one copy of the compiled kernel regardless of how many 713 # times it is used, so store it in the class. Also, to reset the 714 # compute engine, need to clear out all existing compiled kernels, 715 # which is much easier to do if we store them in the class. 716 self.__class__._model = core.build_model(self._model_info) 699 717 if qy is not None: 700 718 q_vectors = [np.asarray(qx), np.asarray(qy)] -
README.rst
re30d645 r2a64722 10 10 is available. 11 11 12 Example 12 Install 13 13 ------- 14 15 The easiest way to use sasmodels is from `SasView <http://www.sasview.org/>`_. 16 17 You can also install sasmodels as a standalone package in python. Use 18 `miniconda <https://docs.conda.io/en/latest/miniconda.html>`_ 19 or `anaconda <https://www.anaconda.com/>`_ 20 to create a python environment with the sasmodels dependencies:: 21 22 $ conda create -n sasmodels -c conda-forge numpy scipy matplotlib pyopencl 23 24 The option ``-n sasmodels`` names the environment sasmodels, and the option 25 ``-c conda-forge`` selects the conda-forge package channel because pyopencl 26 is not part of the base anaconda distribution. 27 28 Activate the environment and install sasmodels:: 29 30 $ conda activate sasmodels 31 (sasmodels) $ pip install sasmodels 32 33 Install `bumps <https://github.com/bumps/bumps>`_ if you want to use it to fit 34 your data:: 35 36 (sasmodels) $ pip install bumps 37 38 Usage 39 ----- 40 41 Check that the works:: 42 43 (sasmodels) $ python -m sasmodels.compare cylinder 44 45 To show the orientation explorer:: 46 47 (sasmodels) $ python -m sasmodels.jitter 48 49 Documentation is available online as part of the SasView 50 `fitting perspective <http://www.sasview.org/docs/index.html>`_ 51 as well as separate pages for 52 `individual models <http://www.sasview.org/docs/user/sasgui/perspectives/fitting/models/index.html>`_. 53 Programming details for sasmodels are available in the 54 `developer documentation <http://www.sasview.org/docs/dev/dev.html>`_. 55 56 57 Fitting Example 58 --------------- 14 59 15 60 The example directory contains a radial+tangential data set for an oriented 16 61 rod-like shape. 17 62 18 The data is loaded by sas.dataloader from the sasview package, so sasview 19 is needed to run the example. 63 To load the example data, you will need the SAS data loader from the sasview 64 package. This is not yet available on PyPI, so you will need a copy of the 65 SasView source code to run it. Create a directory somewhere to hold the 66 sasview and sasmodels source code, which we will refer to as $SOURCE. 20 67 21 To run the example, you need sasview, sasmodels and bumps. Assuming these 22 repositories are installed side by side, change to the sasmodels/example 23 directory and enter:: 68 Use the following to install sasview, and the sasmodels examples:: 24 69 25 PYTHONPATH=..:../../sasview/src ../../bumps/run.py fit.py \ 26 cylinder --preview 70 (sasmodels) $ cd $SOURCE 71 (sasmodels) $ conda install git 72 (sasmodels) $ git clone https://github.com/sasview/sasview.git 73 (sasmodels) $ git clone https://github.com/sasview/sasmodels.git 27 74 28 See bumps documentation for instructions on running the fit. With the 29 python packages installed, e.g., into a virtual environment, then the 30 python path need not be set, and the command would be:: 75 Set the path to the sasview source on your python path within the sasmodels 76 environment. On Windows, this will be:: 31 77 32 bumps fit.py cylinder --preview 78 (sasmodels)> set PYTHONPATH="$SOURCE\sasview\src" 79 (sasmodels)> cd $SOURCE/sasmodels/example 80 (sasmodels)> python -m bumps.cli fit.py cylinder --preview 81 82 On Mac/Linux with the standard shell this will be:: 83 84 (sasmodels) $ export PYTHONPATH="$SOURCE/sasview/src" 85 (sasmodels) $ cd $SOURCE/sasmodels/example 86 (sasmodels) $ bumps fit.py cylinder --preview 33 87 34 88 The fit.py model accepts up to two arguments. The first argument is the … … 38 92 both radial and tangential simultaneously, use the word "both". 39 93 40 Notes 41 ----- 42 43 cylinder.c + cylinder.py is the cylinder model with renamed variables and 44 sld scaled by 1e6 so the numbers are nicer. The model name is "cylinder" 45 46 lamellar.py is an example of a single file model with embedded C code. 94 See `bumps documentation <https://bumps.readthedocs.io/>`_ for detailed 95 instructions on running the fit. 47 96 48 97 |TravisStatus|_ -
doc/guide/gpu_setup.rst
r63602b1 r8b31efa 94 94 Device Selection 95 95 ================ 96 **OpenCL drivers** 97 96 98 If you have multiple GPU devices you can tell the program which device to use. 97 99 By default, the program looks for one GPU and one CPU device from available … … 104 106 was used to run the model. 105 107 106 **If you don't want to use OpenCL, you can set** *SAS_OPENCL=None* 107 **in your environment settings, and it will only use normal programs.** 108 109 If you want to use one of the other devices, you can run the following 108 If you want to use a specific driver and devices, you can run the following 110 109 from the python console:: 111 110 … … 115 114 This will provide a menu of different OpenCL drivers available. 116 115 When one is selected, it will say "set PYOPENCL_CTX=..." 117 Use that value as the value of *SAS_OPENCL*. 116 Use that value as the value of *SAS_OPENCL=driver:device*. 117 118 To use the default OpenCL device (rather than CUDA or None), 119 set *SAS_OPENCL=opencl*. 120 121 In batch queues, you may need to set *XDG_CACHE_HOME=~/.cache* 122 (Linux only) to a different directory, depending on how the filesystem 123 is configured. You should also set *SAS_DLL_PATH* for CPU-only modules. 124 125 -DSAS_MODELPATH=path sets directory containing custom models 126 -DSAS_OPENCL=vendor:device|cuda:device|none sets the target GPU device 127 -DXDG_CACHE_HOME=~/.cache sets the pyopencl cache root (linux only) 128 -DSAS_COMPILER=tinycc|msvc|mingw|unix sets the DLL compiler 129 -DSAS_OPENMP=1 turns on OpenMP for the DLLs 130 -DSAS_DLL_PATH=path sets the path to the compiled modules 131 132 133 **CUDA drivers** 134 135 If OpenCL drivers are not available on your system, but NVidia CUDA 136 drivers are available, then set *SAS_OPENCL=cuda* or 137 *SAS_OPENCL=cuda:n* for a particular device number *n*. If no device 138 number is specified, then the CUDA drivers looks for look for 139 *CUDA_DEVICE=n* or a file ~/.cuda-device containing n for the device number. 140 141 In batch queues, the SLURM command *sbatch --gres=gpu:1 ...* will set 142 *CUDA_VISIBLE_DEVICES=n*, which ought to set the correct device 143 number for *SAS_OPENCL=cuda*. If not, then set 144 *CUDA_DEVICE=$CUDA_VISIBLE_DEVICES* within the batch script. You may 145 need to set the CUDA cache directory to a folder accessible across the 146 cluster with *PYCUDA_CACHE_DIR* (or *PYCUDA_DISABLE_CACHE* to disable 147 caching), and you may need to set environment specific compiler flags 148 with *PYCUDA_DEFAULT_NVCC_FLAGS*. You should also set *SAS_DLL_PATH* 149 for CPU-only modules. 150 151 **No GPU support** 152 153 If you don't want to use OpenCL or CUDA, you can set *SAS_OPENCL=None* 154 in your environment settings, and it will only use normal programs. 155 156 In batch queues, you may need to set *SAS_DLL_PATH* to a directory 157 accessible on the compute node. 158 118 159 119 160 Device Testing … … 154 195 *Document History* 155 196 156 | 201 7-09-27Paul Kienzle197 | 2018-10-15 Paul Kienzle -
doc/guide/scripting.rst
rbd7630d r23df833 188 188 python kernel. Once the kernel is in hand, we can then marshal a set of 189 189 parameters into a :class:`sasmodels.details.CallDetails` object and ship it to 190 the kernel using the :func:`sansmodels.direct_model.call_kernel` function. An 191 example should help, *example/cylinder_eval.py*:: 192 193 from numpy import logspace 190 the kernel using the :func:`sansmodels.direct_model.call_kernel` function. To 191 accesses the underlying $<F(q)>$ and $<F^2(q)>$, use 192 :func:`sasmodels.direct_model.call_Fq` instead. 193 194 The following example should 195 help, *example/cylinder_eval.py*:: 196 197 from numpy import logspace, sqrt 194 198 from matplotlib import pyplot as plt 195 199 from sasmodels.core import load_model 196 from sasmodels.direct_model import call_kernel 200 from sasmodels.direct_model import call_kernel, call_Fq 197 201 198 202 model = load_model('cylinder') 199 203 q = logspace(-3, -1, 200) 200 204 kernel = model.make_kernel([q]) 201 Iq = call_kernel(kernel, dict(radius=200.)) 202 plt.loglog(q, Iq) 205 pars = {'radius': 200, 'radius_pd': 0.1, 'scale': 2} 206 Iq = call_kernel(kernel, pars) 207 F, Fsq, Reff, V, Vratio = call_Fq(kernel, pars) 208 209 plt.loglog(q, Iq, label='2 I(q)') 210 plt.loglog(q, F**2/V, label='<F(q)>^2/V') 211 plt.loglog(q, Fsq/V, label='<F^2(q)>/V') 212 plt.xlabel('q (1/A)') 213 plt.ylabel('I(q) (1/cm)') 214 plt.title('Cylinder with radius 200.') 215 plt.legend() 203 216 plt.show() 204 217 205 On windows, this can be called from the cmd prompt using sasview as:: 218 .. figure:: direct_call.png 219 220 Comparison between $I(q)$, $<F(q)>$ and $<F^2(q)>$ for cylinder model. 221 222 This compares $I(q)$ with $<F(q)>$ and $<F^2(q)>$ for a cylinder 223 with *radius=200 +/- 20* and *scale=2*. Note that *call_Fq* does not 224 include scale and background, nor does it normalize by the average volume. 225 The definition of $F = \rho V \hat F$ scaled by the contrast and 226 volume, compared to the canonical cylinder $\hat F$, with $\hat F(0) = 1$. 227 Integrating over polydispersity and orientation, the returned values are 228 $\sum_{r,w\in N(r_o, r_o/10)} \sum_\theta w F(q,r_o,\theta)\sin\theta$ and 229 $\sum_{r,w\in N(r_o, r_o/10)} \sum_\theta w F^2(q,r_o,\theta)\sin\theta$. 230 231 On windows, this example can be called from the cmd prompt using sasview as 232 as the python interpreter:: 206 233 207 234 SasViewCom example/cylinder_eval.py -
example/cylinder_eval.py
r2e66ef5 r23df833 3 3 """ 4 4 5 from numpy import logspace 5 from numpy import logspace, sqrt 6 6 from matplotlib import pyplot as plt 7 7 from sasmodels.core import load_model 8 from sasmodels.direct_model import call_kernel 8 from sasmodels.direct_model import call_kernel, call_Fq 9 9 10 10 model = load_model('cylinder') 11 11 q = logspace(-3, -1, 200) 12 12 kernel = model.make_kernel([q]) 13 Iq = call_kernel(kernel, dict(radius=200.)) 14 plt.loglog(q, Iq) 13 pars = {'radius': 200, 'radius_pd': 0.1, 'scale': 2} 14 Iq = call_kernel(kernel, pars) 15 F, Fsq, Reff, V, Vratio = call_Fq(kernel, pars) 16 plt.loglog(q, Iq, label='2 I(q)') 17 plt.loglog(q, F**2/V, label='<F(q)>^2/V') 18 plt.loglog(q, Fsq/V, label='<F^2(q)>/V') 15 19 plt.xlabel('q (1/A)') 16 plt.ylabel('I(q) ')20 plt.ylabel('I(q) (1/cm)') 17 21 plt.title('Cylinder with radius 200.') 22 plt.legend() 18 23 plt.show() -
explore/precision.py
rfba9ca0 rcd28947 207 207 return model_info 208 208 209 # Hack to allow second parameter A in two parameter functions 209 # Hack to allow second parameter A in the gammainc and gammaincc functions. 210 # Create a 2-D variant of the precision test if we need to handle other two 211 # parameter functions. 210 212 A = 1 211 213 def parse_extra_pars(): 214 """ 215 Parse the command line looking for the second parameter "A=..." for the 216 gammainc/gammaincc functions. 217 """ 212 218 global A 213 219 … … 333 339 ) 334 340 add_function( 341 # Note: "a" is given as A=... on the command line via parse_extra_pars 335 342 name="gammainc(x)", 336 343 mp_function=lambda x, a=A: mp.gammainc(a, a=0, b=x)/mp.gamma(a), … … 339 346 ) 340 347 add_function( 348 # Note: "a" is given as A=... on the command line via parse_extra_pars 341 349 name="gammaincc(x)", 342 350 mp_function=lambda x, a=A: mp.gammainc(a, a=x, b=mp.inf)/mp.gamma(a), … … 403 411 ) 404 412 add_function( 405 name=" debye",413 name="gauss_coil", 406 414 mp_function=lambda x: 2*(mp.exp(-x**2) + x**2 - 1)/x**4, 407 415 np_function=lambda x: 2*(np.expm1(-x**2) + x**2)/x**4, 408 416 ocl_function=make_ocl(""" 409 417 const double qsq = q*q; 410 if (qsq < 1.0) { // Pade approximation 418 // For double: use O(5) Pade with 0.5 cutoff (10 mad + 1 divide) 419 // For single: use O(7) Taylor with 0.8 cutoff (7 mad) 420 if (qsq < 0.0) { 411 421 const double x = qsq; 412 422 if (0) { // 0.36 single … … 418 428 const double B1=3./8., B2=3./56., B3=1./336.; 419 429 return (((A3*x + A2)*x + A1)*x + 1.)/(((B3*x + B2)*x + B1)*x + 1.); 420 } else if ( 1) { // 1.0 for single, 0.25 for double430 } else if (0) { // 1.0 for single, 0.25 for double 421 431 // PadeApproximant[2*Exp[-x^2] + x^2-1)/x^4, {x, 0, 8}] 422 432 const double A1=1./15., A2=1./60, A3=0., A4=1./75600.; … … 431 441 /(((((B5*x + B4)*x + B3)*x + B2)*x + B1)*x + 1.); 432 442 } 433 } else if (qsq < 1.) { // Taylor series; 0.9 for single, 0.25 for double443 } else if (qsq < 0.8) { 434 444 const double x = qsq; 435 445 const double C0 = +1.; -
sasmodels/compare.py
r610ef23 rc1799d3 41 41 from . import kerneldll 42 42 from . import kernelcl 43 from . import kernelcuda 43 44 from .data import plot_theory, empty_data1D, empty_data2D, load_data 44 45 from .direct_model import DirectModel, get_mesh … … 115 116 === environment variables === 116 117 -DSAS_MODELPATH=path sets directory containing custom models 117 -DSAS_OPENCL=vendor:device| none sets the target OpenCLdevice118 -DSAS_OPENCL=vendor:device|cuda:device|none sets the target GPU device 118 119 -DXDG_CACHE_HOME=~/.cache sets the pyopencl cache root (linux only) 119 120 -DSAS_COMPILER=tinycc|msvc|mingw|unix sets the DLL compiler … … 352 353 353 354 # If it is a list of choices, pick one at random with equal probability 354 # In practice, the model specific random generator will override.355 355 par = model_info.parameters[name] 356 if len(par.limits) > 2: # choice list357 return np.random.randint(len(par. limits))356 if par.choices: # choice list 357 return np.random.randint(len(par.choices)) 358 358 359 359 # If it is a fixed range, pick from it with equal probability. … … 725 725 set_integration_size(model_info, ngauss) 726 726 727 if dtype != "default" and not dtype.endswith('!') and not kernelcl.use_opencl(): 727 if (dtype != "default" and not dtype.endswith('!') 728 and not (kernelcl.use_opencl() or kernelcuda.use_cuda())): 728 729 raise RuntimeError("OpenCL not available " + kernelcl.OPENCL_ERROR) 729 730 … … 1151 1152 'rel_err' : True, 1152 1153 'explore' : False, 1153 'use_demo' : True,1154 'use_demo' : False, 1154 1155 'zero' : False, 1155 1156 'html' : False, -
sasmodels/core.py
r2dcd6e7 r07646b6 21 21 from . import mixture 22 22 from . import kernelpy 23 from . import kernelcuda 23 24 from . import kernelcl 24 25 from . import kerneldll … … 205 206 206 207 numpy_dtype, fast, platform = parse_dtype(model_info, dtype, platform) 207 208 208 source = generate.make_source(model_info) 209 209 if platform == "dll": 210 210 #print("building dll", numpy_dtype) 211 211 return kerneldll.load_dll(source['dll'], model_info, numpy_dtype) 212 elif platform == "cuda": 213 return kernelcuda.GpuModel(source, model_info, numpy_dtype, fast=fast) 212 214 else: 213 215 #print("building ocl", numpy_dtype) … … 245 247 # type: (ModelInfo, str, str) -> (np.dtype, bool, str) 246 248 """ 247 Interpret dtype string, returning np.dtype and fast flag.249 Interpret dtype string, returning np.dtype, fast flag and platform. 248 250 249 251 Possible types include 'half', 'single', 'double' and 'quad'. If the … … 253 255 default for the model and platform. 254 256 255 Platform preference can be specfied ("ocl" vs "dll"), with the default 256 being OpenCL if it is availabe. If the dtype name ends with '!' then 257 platform is forced to be DLL rather than OpenCL. 257 Platform preference can be specfied ("ocl", "cuda", "dll"), with the 258 default being OpenCL or CUDA if available, otherwise DLL. If the dtype 259 name ends with '!' then platform is forced to be DLL rather than GPU. 260 The default platform is set by the environment variable SAS_OPENCL, 261 SAS_OPENCL=driver:device for OpenCL, SAS_OPENCL=cuda:device for CUDA 262 or SAS_OPENCL=none for DLL. 258 263 259 264 This routine ignores the preferences within the model definition. This … … 265 270 # Assign default platform, overriding ocl with dll if OpenCL is unavailable 266 271 # If opencl=False OpenCL is switched off 267 268 272 if platform is None: 269 273 platform = "ocl" 270 if not kernelcl.use_opencl() or not model_info.opencl:271 platform = "dll"272 274 273 275 # Check if type indicates dll regardless of which platform is given … … 275 277 platform = "dll" 276 278 dtype = dtype[:-1] 279 280 # Make sure model allows opencl/gpu 281 if not model_info.opencl: 282 platform = "dll" 283 284 # Make sure opencl is available, or fallback to cuda then to dll 285 if platform == "ocl" and not kernelcl.use_opencl(): 286 platform = "cuda" if kernelcuda.use_cuda() else "dll" 277 287 278 288 # Convert special type names "half", "fast", and "quad" … … 285 295 dtype = "float16" 286 296 287 # Convert dtype string to numpy dtype. 297 # Convert dtype string to numpy dtype. Use single precision for GPU 298 # if model allows it, otherwise use double precision. 288 299 if dtype is None or dtype == "default": 289 numpy_dtype = (generate.F32 if platform == "ocl" and model_info.single300 numpy_dtype = (generate.F32 if model_info.single and platform in ("ocl", "cuda") 290 301 else generate.F64) 291 302 else: 292 303 numpy_dtype = np.dtype(dtype) 293 304 294 # Make sure that the type is supported by opencl, otherwise use dll305 # Make sure that the type is supported by GPU, otherwise use dll 295 306 if platform == "ocl": 296 307 env = kernelcl.environment() 297 if not env.has_type(numpy_dtype): 298 platform = "dll" 299 if dtype is None: 300 numpy_dtype = generate.F64 308 elif platform == "cuda": 309 env = kernelcuda.environment() 310 else: 311 env = None 312 if env is not None and not env.has_type(numpy_dtype): 313 platform = "dll" 314 if dtype is None: 315 numpy_dtype = generate.F64 301 316 302 317 return numpy_dtype, fast, platform … … 365 380 model = load_model("cylinder@hardsphere*sphere") 366 381 actual = [p.name for p in model.info.parameters.kernel_parameters] 367 target = ("sld sld_solvent radius length theta phi volfraction" 382 target = ("sld sld_solvent radius length theta phi" 383 " radius_effective volfraction " 384 " structure_factor_mode radius_effective_mode" 368 385 " A_sld A_sld_solvent A_radius").split() 369 386 assert target == actual, "%s != %s"%(target, actual) -
sasmodels/data.py
rbd7630d rc88f983 337 337 else: 338 338 dq = resolution * q 339 340 339 data = Data1D(q, Iq, dx=dq, dy=dIq) 341 340 data.filename = "fake data" -
sasmodels/generate.py
r6e45516 ra8a1d48 24 24 dimension, or 1.0 if no volume normalization is required. 25 25 26 *ER(p1, p2, ...)* returns the effective radius of the form with 27 particular dimensions. 28 29 *VR(p1, p2, ...)* returns the volume ratio for core-shell style forms. 26 *shell_volume(p1, p2, ...)* returns the volume of the shell for forms 27 which are hollow. 28 29 *effective_radius(mode, p1, p2, ...)* returns the effective radius of 30 the form with particular dimensions. Mode determines the type of 31 effective radius returned, with mode=1 for equivalent volume. 30 32 31 33 #define INVALID(v) (expr) returns False if v.parameter is invalid … … 72 74 background. This will work correctly even when polydispersity is off. 73 75 74 *ER* and *VR* are python functions which operate on parameter vectors.75 The constructor code will generate the necessary vectors for computing76 them with the desired polydispersity.77 76 The kernel module must set variables defining the kernel meta data: 78 77 … … 106 105 create the OpenCL kernel functions. The files defining the functions 107 106 need to be listed before the files which use the functions. 108 109 *ER* is a python function defining the effective radius. If it is110 not present, the effective radius is 0.111 112 *VR* is a python function defining the volume ratio. If it is not113 present, the volume ratio is 1.114 107 115 108 *form_volume*, *Iq*, *Iqac*, *Iqabc* are strings containing … … 671 664 672 665 673 # type in IQXY pattern could be single, float, double, long double, ... 674 _IQXY_PATTERN = re.compile(r"(^|\s)double\s+I(?P<mode>q(ab?c|xy))\s*[(]", 666 _IQXY_PATTERN = re.compile(r"(^|\s)double\s+I(?P<mode>q(ac|abc|xy))\s*[(]", 675 667 flags=re.MULTILINE) 676 668 def find_xy_mode(source): … … 701 693 return 'qa' 702 694 695 # Note: not presently used. Need to know whether Fq is available before 696 # trying to compile the source. Leave the code here in case we decide that 697 # define have_Fq for each form factor is too tedious and error prone. 698 _FQ_PATTERN = re.compile(r"(^|\s)void\s+Fq[(]", flags=re.MULTILINE) 699 def contains_Fq(source): 700 # type: (List[str]) -> bool 701 """ 702 Return True if C source defines "void Fq(". 703 """ 704 for code in source: 705 if _FQ_PATTERN.search(code) is not None: 706 return True 707 return False 708 709 _SHELL_VOLUME_PATTERN = re.compile(r"(^|\s)double\s+shell_volume[(]", flags=re.MULTILINE) 710 def contains_shell_volume(source): 711 # type: (List[str]) -> bool 712 """ 713 Return True if C source defines "double shell_volume(". 714 """ 715 for code in source: 716 if _SHELL_VOLUME_PATTERN.search(code) is not None: 717 return True 718 return False 703 719 704 720 def _add_source(source, code, path, lineno=1): … … 730 746 # dispersion. Need to be careful that necessary parameters are available 731 747 # for computing volume even if we allow non-disperse volume parameters. 732 733 748 partable = model_info.parameters 734 749 … … 743 758 for path, code in user_code: 744 759 _add_source(source, code, path) 745 746 760 if model_info.c_code: 747 761 _add_source(source, model_info.c_code, model_info.filename, … … 751 765 q, qx, qy, qab, qa, qb, qc \ 752 766 = [Parameter(name=v) for v in 'q qx qy qab qa qb qc'.split()] 767 753 768 # Generate form_volume function, etc. from body only 754 769 if isinstance(model_info.form_volume, str): 755 770 pars = partable.form_volume_parameters 756 771 source.append(_gen_fn(model_info, 'form_volume', pars)) 772 if isinstance(model_info.shell_volume, str): 773 pars = partable.form_volume_parameters 774 source.append(_gen_fn(model_info, 'shell_volume', pars)) 757 775 if isinstance(model_info.Iq, str): 758 776 pars = [q] + partable.iq_parameters … … 767 785 pars = [qa, qb, qc] + partable.iq_parameters 768 786 source.append(_gen_fn(model_info, 'Iqabc', pars)) 787 788 # Check for shell_volume in source 789 is_hollow = contains_shell_volume(source) 769 790 770 791 # What kind of 2D model do we need? Is it consistent with the parameters? … … 789 810 source.append("\\\n".join(p.as_definition() 790 811 for p in partable.kernel_parameters)) 791 792 812 # Define the function calls 813 call_effective_radius = "#define CALL_EFFECTIVE_RADIUS(_mode, _v) 0.0" 793 814 if partable.form_volume_parameters: 794 815 refs = _call_pars("_v.", partable.form_volume_parameters) 795 call_volume = "#define CALL_VOLUME(_v) form_volume(%s)"%(",".join(refs)) 816 if is_hollow: 817 call_volume = "#define CALL_VOLUME(_form, _shell, _v) do { _form = form_volume(%s); _shell = shell_volume(%s); } while (0)"%((",".join(refs),)*2) 818 else: 819 call_volume = "#define CALL_VOLUME(_form, _shell, _v) do { _form = _shell = form_volume(%s); } while (0)"%(",".join(refs)) 820 if model_info.effective_radius_type: 821 call_effective_radius = "#define CALL_EFFECTIVE_RADIUS(_mode, _v) effective_radius(_mode, %s)"%(",".join(refs)) 796 822 else: 797 823 # Model doesn't have volume. We could make the kernel run a little 798 824 # faster by not using/transferring the volume normalizations, but 799 825 # the ifdef's reduce readability more than is worthwhile. 800 call_volume = "#define CALL_VOLUME( v) 1.0"826 call_volume = "#define CALL_VOLUME(_form, _shell, _v) do { _form = _shell = 1.0; } while (0)" 801 827 source.append(call_volume) 802 828 source.append(call_effective_radius) 803 829 model_refs = _call_pars("_v.", partable.iq_parameters) 804 pars = ",".join(["_q"] + model_refs) 805 call_iq = "#define CALL_IQ(_q, _v) Iq(%s)" % pars 830 831 if model_info.have_Fq: 832 pars = ",".join(["_q", "&_F1", "&_F2",] + model_refs) 833 call_iq = "#define CALL_FQ(_q, _F1, _F2, _v) Fq(%s)" % pars 834 clear_iq = "#undef CALL_FQ" 835 else: 836 pars = ",".join(["_q"] + model_refs) 837 call_iq = "#define CALL_IQ(_q, _v) Iq(%s)" % pars 838 clear_iq = "#undef CALL_IQ" 806 839 if xy_mode == 'qabc': 807 840 pars = ",".join(["_qa", "_qb", "_qc"] + model_refs) … … 812 845 call_iqxy = "#define CALL_IQ_AC(_qa,_qc,_v) Iqac(%s)" % pars 813 846 clear_iqxy = "#undef CALL_IQ_AC" 814 elif xy_mode == 'qa' :847 elif xy_mode == 'qa' and not model_info.have_Fq: 815 848 pars = ",".join(["_qa"] + model_refs) 816 849 call_iqxy = "#define CALL_IQ_A(_qa,_v) Iq(%s)" % pars 817 850 clear_iqxy = "#undef CALL_IQ_A" 851 elif xy_mode == 'qa' and model_info.have_Fq: 852 pars = ",".join(["_qa", "&_F1", "&_F2",] + model_refs) 853 # Note: uses rare C construction (expr1, expr2) which computes 854 # expr1 then expr2 and evaluates to expr2. This allows us to 855 # leave it looking like a function even though it is returning 856 # its values by reference. 857 call_iqxy = "#define CALL_FQ_A(_qa,_F1,_F2,_v) (Fq(%s),_F2)" % pars 858 clear_iqxy = "#undef CALL_FQ_A" 818 859 elif xy_mode == 'qxy': 819 860 orientation_refs = _call_pars("_v.", partable.orientation_parameters) … … 831 872 magpars = [k-2 for k, p in enumerate(partable.call_parameters) 832 873 if p.type == 'sld'] 833 834 874 # Fill in definitions for numbers of parameters 835 875 source.append("#define MAX_PD %s"%partable.max_pd) … … 839 879 source.append("#define MAGNETIC_PARS %s"%",".join(str(k) for k in magpars)) 840 880 source.append("#define PROJECTION %d"%PROJECTION) 841 842 881 # TODO: allow mixed python/opencl kernels? 843 844 ocl = _kernels(kernel_code, call_iq, call_iqxy, clear_iqxy, model_info.name)845 dll = _kernels(kernel_code, call_iq, call_iqxy, clear_iqxy, model_info.name) 882 ocl = _kernels(kernel_code, call_iq, clear_iq, call_iqxy, clear_iqxy, model_info.name) 883 dll = _kernels(kernel_code, call_iq, clear_iq, call_iqxy, clear_iqxy, model_info.name) 884 846 885 result = { 847 886 'dll': '\n'.join(source+dll[0]+dll[1]+dll[2]), 848 887 'opencl': '\n'.join(source+ocl[0]+ocl[1]+ocl[2]), 849 888 } 850 851 889 return result 852 890 853 891 854 def _kernels(kernel, call_iq, c all_iqxy, clear_iqxy, name):892 def _kernels(kernel, call_iq, clear_iq, call_iqxy, clear_iqxy, name): 855 893 # type: ([str,str], str, str, str) -> List[str] 856 894 code = kernel[0] … … 862 900 '#line 1 "%s Iq"' % path, 863 901 code, 864 "#undef CALL_IQ",902 clear_iq, 865 903 "#undef KERNEL_NAME", 866 904 ] … … 968 1006 pars = model_info.parameters.kernel_parameters 969 1007 else: 970 pars = model_info.parameters.COMMON + model_info.parameters.kernel_parameters 1008 pars = (model_info.parameters.common_parameters 1009 + model_info.parameters.kernel_parameters) 971 1010 partable = make_partable(pars) 972 1011 subst = dict(id=model_info.id.replace('_', '-'), -
sasmodels/jitter.py
r1198f90 r7d97437 15 15 pass 16 16 17 import matplotlib as mpl 17 18 import matplotlib.pyplot as plt 18 19 from matplotlib.widgets import Slider … … 746 747 pass 747 748 748 axcolor = 'lightgoldenrodyellow' 749 # CRUFT: use axisbg instead of facecolor for matplotlib<2 750 facecolor_prop = 'facecolor' if mpl.__version__ > '2' else 'axisbg' 751 props = {facecolor_prop: 'lightgoldenrodyellow'} 749 752 750 753 ## add control widgets to plot 751 axes_theta = plt.axes([0.1, 0.15, 0.45, 0.04], axisbg=axcolor)752 axes_phi = plt.axes([0.1, 0.1, 0.45, 0.04], axisbg=axcolor)753 axes_psi = plt.axes([0.1, 0.05, 0.45, 0.04], axisbg=axcolor)754 axes_theta = plt.axes([0.1, 0.15, 0.45, 0.04], **props) 755 axes_phi = plt.axes([0.1, 0.1, 0.45, 0.04], **props) 756 axes_psi = plt.axes([0.1, 0.05, 0.45, 0.04], **props) 754 757 stheta = Slider(axes_theta, 'Theta', -90, 90, valinit=theta) 755 758 sphi = Slider(axes_phi, 'Phi', -180, 180, valinit=phi) 756 759 spsi = Slider(axes_psi, 'Psi', -180, 180, valinit=psi) 757 760 758 axes_dtheta = plt.axes([0.75, 0.15, 0.15, 0.04], axisbg=axcolor)759 axes_dphi = plt.axes([0.75, 0.1, 0.15, 0.04], axisbg=axcolor)760 axes_dpsi = plt.axes([0.75, 0.05, 0.15, 0.04], axisbg=axcolor)761 axes_dtheta = plt.axes([0.75, 0.15, 0.15, 0.04], **props) 762 axes_dphi = plt.axes([0.75, 0.1, 0.15, 0.04], **props) 763 axes_dpsi = plt.axes([0.75, 0.05, 0.15, 0.04], **props) 761 764 # Note: using ridiculous definition of rectangle distribution, whose width 762 765 # in sasmodels is sqrt(3) times the given width. Divide by sqrt(3) to keep -
sasmodels/kernel.py
r2d81cfe rcd28947 41 41 dtype = None # type: np.dtype 42 42 43 def __call__(self, call_details, values, cutoff, magnetic):43 def Iq(self, call_details, values, cutoff, magnetic): 44 44 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 45 raise NotImplementedError("need to implement __call__") 45 r""" 46 Returns I(q) from the polydisperse average scattering. 47 48 .. math:: 49 50 I(q) = \text{scale} \cdot P(q) + \text{background} 51 52 With the correct choice of model and contrast, setting *scale* to 53 the volume fraction $V_f$ of particles should match the measured 54 absolute scattering. Some models (e.g., vesicle) have volume fraction 55 built into the model, and do not need an additional scale. 56 """ 57 _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, magnetic, 58 effective_radius_type=0) 59 combined_scale = values[0]/shell_volume 60 background = values[1] 61 return combined_scale*F2 + background 62 __call__ = Iq 63 64 def Fq(self, call_details, values, cutoff, magnetic, effective_radius_type=0): 65 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray 66 r""" 67 Returns <F(q)>, <F(q)^2>, effective radius, shell volume and 68 form:shell volume ratio. The <F(q)> term may be None if the 69 form factor does not support direct computation of $F(q)$ 70 71 $P(q) = <F^2(q)>/<V>$ is used for structure factor calculations, 72 73 .. math:: 74 75 I(q) = \text{scale} \cdot P(q) \cdot S(q) + \text{background} 76 77 For the beta approximation, this becomes 78 79 .. math:: 80 81 I(q) = \text{scale} * P (1 + <F>^2/<F^2> (S - 1)) + \text{background} 82 = \text{scale}/<V> (<F^2> + <F>^2 (S - 1)) + \text{background} 83 84 $<F(q)>$ and $<F^2(q)>$ are averaged by polydispersity in shape 85 and orientation, with each configuration $x_k$ having form factor 86 $F(q, x_k)$, weight $w_k$ and volume $V_k$. The result is: 87 88 .. math:: 89 90 P(q) = \frac{\sum w_k F^2(q, x_k) / \sum w_k}{\sum w_k V_k / \sum w_k} 91 92 The form factor itself is scaled by volume and contrast to compute the 93 total scattering. This is then squared, and the volume weighted 94 F^2 is then normalized by volume F. For a given density, the number 95 of scattering centers is assumed to scale linearly with volume. Later 96 scaling the resulting $P(q)$ by the volume fraction of particles 97 gives the total scattering on an absolute scale. Most models 98 incorporate the volume fraction into the overall scale parameter. An 99 exception is vesicle, which includes the volume fraction parameter in 100 the model itself, scaling $F$ by $\surd V_f$ so that the math for 101 the beta approximation works out. 102 103 By scaling $P(q)$ by total weight $\sum w_k$, there is no need to make 104 sure that the polydisperisity distributions normalize to one. In 105 particular, any distibution values $x_k$ outside the valid domain 106 of $F$ will not be included, and the distribution will be implicitly 107 truncated. This is controlled by the parameter limits defined in the 108 model (which truncate the distribution before calling the kernel) as 109 well as any region excluded using the *INVALID* macro defined within 110 the model itself. 111 112 The volume used in the polydispersity calculation is the form volume 113 for solid objects or the shell volume for hollow objects. Shell 114 volume should be used within $F$ so that the normalizing scale 115 represents the volume fraction of the shell rather than the entire 116 form. This corresponds to the volume fraction of shell-forming 117 material added to the solvent. 118 119 The calculation of $S$ requires the effective radius and the 120 volume fraction of the particles. The model can have several 121 different ways to compute effective radius, with the 122 *effective_radius_type* parameter used to select amongst them. The 123 volume fraction of particles should be determined from the total 124 volume fraction of the form, not just the shell volume fraction. 125 This makes a difference for hollow shapes, which need to scale 126 the volume fraction by the returned volume ratio when computing $S$. 127 For solid objects, the shell volume is set to the form volume so 128 this scale factor evaluates to one and so can be used for both 129 hollow and solid shapes. 130 """ 131 self._call_kernel(call_details, values, cutoff, magnetic, effective_radius_type) 132 #print("returned",self.q_input.q, self.result) 133 nout = 2 if self.info.have_Fq and self.dim == '1d' else 1 134 total_weight = self.result[nout*self.q_input.nq + 0] 135 # Note: total_weight = sum(weight > cutoff), with cutoff >= 0, so it 136 # is okay to test directly against zero. If weight is zero then I(q), 137 # etc. must also be zero. 138 if total_weight == 0.: 139 total_weight = 1. 140 # Note: shell_volume == form_volume for solid objects 141 form_volume = self.result[nout*self.q_input.nq + 1]/total_weight 142 shell_volume = self.result[nout*self.q_input.nq + 2]/total_weight 143 effective_radius = self.result[nout*self.q_input.nq + 3]/total_weight 144 if shell_volume == 0.: 145 shell_volume = 1. 146 F1 = self.result[1:nout*self.q_input.nq:nout]/total_weight if nout == 2 else None 147 F2 = self.result[0:nout*self.q_input.nq:nout]/total_weight 148 return F1, F2, effective_radius, shell_volume, form_volume/shell_volume 46 149 47 150 def release(self): 48 151 # type: () -> None 49 152 pass 153 154 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 155 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray 156 """ 157 Call the kernel. Subclasses defining kernels for particular execution 158 engines need to provide an implementation for this. 159 """ 160 raise NotImplementedError() -
sasmodels/kernel_header.c
r108e70e r07646b6 1 1 #ifdef __OPENCL_VERSION__ 2 2 # define USE_OPENCL 3 #elif defined(__CUDACC__) 4 # define USE_CUDA 3 5 #elif defined(_OPENMP) 4 6 # define USE_OPENMP 5 7 #endif 8 9 // Use SAS_DOUBLE to force the use of double even for float kernels 10 #define SAS_DOUBLE dou ## ble 6 11 7 12 // If opencl is not available, then we are compiling a C function 8 13 // Note: if using a C++ compiler, then define kernel as extern "C" 9 14 #ifdef USE_OPENCL 15 16 #define USE_GPU 17 #define pglobal global 18 #define pconstant constant 19 10 20 typedef int int32_t; 11 # if defined(USE_SINCOS) 12 # define SINCOS(angle,svar,cvar) svar=sincos(angle,&cvar) 13 # else 14 # define SINCOS(angle,svar,cvar) do {const double _t_=angle; svar=sin(_t_);cvar=cos(_t_);} while (0) 15 # endif 21 22 #if defined(USE_SINCOS) 23 # define SINCOS(angle,svar,cvar) svar=sincos(angle,&cvar) 24 #else 25 # define SINCOS(angle,svar,cvar) do {const double _t_=angle; svar=sin(_t_);cvar=cos(_t_);} while (0) 26 #endif 16 27 // Intel CPU on Mac gives strange values for erf(); on the verified 17 28 // platforms (intel, nvidia, amd), the cephes erf() is significantly … … 24 35 # define erfcf erfc 25 36 #endif 26 #else // !USE_OPENCL 27 // Use SAS_DOUBLE to force the use of double even for float kernels 28 # define SAS_DOUBLE dou ## ble 29 # ifdef __cplusplus 37 38 #elif defined(USE_CUDA) 39 40 #define USE_GPU 41 #define local __shared__ 42 #define pglobal 43 #define constant __constant__ 44 #define pconstant const 45 #define kernel extern "C" __global__ 46 47 // OpenCL powr(a,b) = C99 pow(a,b), b >= 0 48 // OpenCL pown(a,b) = C99 pow(a,b), b integer 49 #define powr(a,b) pow(a,b) 50 #define pown(a,b) pow(a,b) 51 //typedef int int32_t; 52 #if defined(USE_SINCOS) 53 # define SINCOS(angle,svar,cvar) sincos(angle,&svar,&cvar) 54 #else 55 # define SINCOS(angle,svar,cvar) do {const double _t_=angle; svar=sin(_t_);cvar=cos(_t_);} while (0) 56 #endif 57 58 #else // !USE_OPENCL && !USE_CUDA 59 60 #define local 61 #define pglobal 62 #define constant const 63 #define pconstant const 64 65 #ifdef __cplusplus 30 66 #include <cstdio> 31 67 #include <cmath> … … 51 87 #endif 52 88 inline void SINCOS(double angle, double &svar, double &cvar) { svar=sin(angle); cvar=cos(angle); } 53 #else // !__cplusplus89 #else // !__cplusplus 54 90 #include <inttypes.h> // C99 guarantees that int32_t types is here 55 91 #include <stdio.h> … … 68 104 #define NEED_EXPM1 69 105 #define NEED_TGAMMA 106 #define NEED_CBRT 70 107 // expf missing from windows? 71 108 #define expf exp … … 76 113 #define kernel 77 114 #define SINCOS(angle,svar,cvar) do {const double _t_=angle; svar=sin(_t_);cvar=cos(_t_);} while (0) 78 # endif // !__cplusplus 79 # define global 80 # define local 81 # define constant const 82 // OpenCL powr(a,b) = C99 pow(a,b), b >= 0 83 // OpenCL pown(a,b) = C99 pow(a,b), b integer 84 # define powr(a,b) pow(a,b) 85 # define pown(a,b) pow(a,b) 115 #endif // !__cplusplus 116 // OpenCL powr(a,b) = C99 pow(a,b), b >= 0 117 // OpenCL pown(a,b) = C99 pow(a,b), b integer 118 #define powr(a,b) pow(a,b) 119 #define pown(a,b) pow(a,b) 120 86 121 #endif // !USE_OPENCL 122 123 #if defined(NEED_CBRT) 124 #define cbrt(_x) pow(_x, 0.33333333333333333333333) 125 #endif 87 126 88 127 #if defined(NEED_EXPM1) -
sasmodels/kernel_iq.c
r70530778 r12f4c19 26 26 // MAGNETIC_PARS : a comma-separated list of indices to the sld 27 27 // parameters in the parameter table. 28 // CALL_VOLUME(table) : call the form volume function 28 // CALL_VOLUME(form, shell, table) : assign form and shell values 29 // CALL_EFFECTIVE_RADIUS(type, table) : call the R_eff function 29 30 // CALL_IQ(q, table) : call the Iq function for 1D calcs. 30 31 // CALL_IQ_A(q, table) : call the Iq function with |q| for 2D data. 32 // CALL_FQ(q, F1, F2, table) : call the Fq function for 1D calcs. 33 // CALL_FQ_A(q, F1, F2, table) : call the Iq function with |q| for 2D data. 31 34 // CALL_IQ_AC(qa, qc, table) : call the Iqxy function for symmetric shapes 32 35 // CALL_IQ_ABC(qa, qc, table) : call the Iqxy function for asymmetric shapes … … 36 39 // PROJECTION : equirectangular=1, sinusoidal=2 37 40 // see explore/jitter.py for definitions. 41 38 42 39 43 #ifndef _PAR_BLOCK_ // protected block so we can include this code twice. … … 270 274 #endif // _QABC_SECTION 271 275 272 273 276 // ==================== KERNEL CODE ======================== 274 275 277 kernel 276 278 void KERNEL_NAME( 277 int32_t nq, // number of q values 278 const int32_t pd_start, // where we are in the dispersity loop 279 const int32_t pd_stop, // where we are stopping in the dispersity loop 280 global const ProblemDetails *details, 281 global const double *values, 282 global const double *q, // nq q values, with padding to boundary 283 global double *result, // nq+1 return values, again with padding 284 const double cutoff // cutoff in the dispersity weight product 279 int32_t nq, // number of q values 280 const int32_t pd_start, // where we are in the dispersity loop 281 const int32_t pd_stop, // where we are stopping in the dispersity loop 282 pglobal const ProblemDetails *details, 283 pglobal const double *values, // parameter values and distributions 284 pglobal const double *q, // nq q values, with padding to boundary 285 pglobal double *result, // nq+1 return values, again with padding 286 const double cutoff, // cutoff in the dispersity weight product 287 int32_t effective_radius_type // which effective radius to compute 285 288 ) 286 289 { 287 #if def USE_OPENCL290 #if defined(USE_GPU) 288 291 // who we are and what element we are working with 292 #if defined(USE_OPENCL) 289 293 const int q_index = get_global_id(0); 294 #else // USE_CUDA 295 const int q_index = threadIdx.x + blockIdx.x * blockDim.x; 296 #endif 290 297 if (q_index >= nq) return; 291 298 #else … … 338 345 // 339 346 // The code differs slightly between opencl and dll since opencl is only 340 // seeing one q value (stored in the variable "this_ result") while the dll347 // seeing one q value (stored in the variable "this_F2") while the dll 341 348 // version must loop over all q. 342 #ifdef USE_OPENCL 343 double pd_norm = (pd_start == 0 ? 0.0 : result[nq]); 344 double this_result = (pd_start == 0 ? 0.0 : result[q_index]); 345 #else // !USE_OPENCL 346 double pd_norm = (pd_start == 0 ? 0.0 : result[nq]); 349 #if defined(CALL_FQ) 350 double weight_norm = (pd_start == 0 ? 0.0 : result[2*nq]); 351 double weighted_form = (pd_start == 0 ? 0.0 : result[2*nq+1]); 352 double weighted_shell = (pd_start == 0 ? 0.0 : result[2*nq+2]); 353 double weighted_radius = (pd_start == 0 ? 0.0 : result[2*nq+3]); 354 #else 355 double weight_norm = (pd_start == 0 ? 0.0 : result[nq]); 356 double weighted_form = (pd_start == 0 ? 0.0 : result[nq+1]); 357 double weighted_shell = (pd_start == 0 ? 0.0 : result[nq+2]); 358 double weighted_radius = (pd_start == 0 ? 0.0 : result[nq+3]); 359 #endif 360 #if defined(USE_GPU) 361 #if defined(CALL_FQ) 362 double this_F2 = (pd_start == 0 ? 0.0 : result[2*q_index+0]); 363 double this_F1 = (pd_start == 0 ? 0.0 : result[2*q_index+1]); 364 #else 365 double this_F2 = (pd_start == 0 ? 0.0 : result[q_index]); 366 #endif 367 #else // !USE_GPU 347 368 if (pd_start == 0) { 348 369 #ifdef USE_OPENMP 349 370 #pragma omp parallel for 350 371 #endif 351 for (int q_index=0; q_index < nq; q_index++) result[q_index] = 0.0; 372 #if defined(CALL_FQ) 373 // 2*nq for F^2,F pairs 374 for (int q_index=0; q_index < 2*nq; q_index++) result[q_index] = 0.0; 375 #else 376 for (int q_index=0; q_index < nq; q_index++) result[q_index] = 0.0; 377 #endif 352 378 } 353 379 //if (q_index==0) printf("start %d %g %g\n", pd_start, pd_norm, result[0]); 354 #endif // !USE_ OPENCL380 #endif // !USE_GPU 355 381 356 382 … … 375 401 const int n4 = pd_length[4]; 376 402 const int p4 = pd_par[4]; 377 global const double *v4 = pd_value + pd_offset[4];378 global const double *w4 = pd_weight + pd_offset[4];403 pglobal const double *v4 = pd_value + pd_offset[4]; 404 pglobal const double *w4 = pd_weight + pd_offset[4]; 379 405 int i4 = (pd_start/pd_stride[4])%n4; // position in level 4 at pd_start 380 406 … … 411 437 FETCH_Q // set qx,qy from the q input vector 412 438 APPLY_ROTATION // convert qx,qy to qa,qb,qc 413 CALL_KERNEL // scattering= Iqxy(qa, qb, qc, p1, p2, ...)439 CALL_KERNEL // F2 = Iqxy(qa, qb, qc, p1, p2, ...) 414 440 415 441 ++step; // increment counter representing position in dispersity mesh … … 442 468 // inner loop and defines the macros that use them. 443 469 444 #if defined(CALL_IQ) 445 // unoriented 1D 470 471 #if defined(CALL_FQ) // COMPUTE_F1_F2 is true 472 // unoriented 1D returning <F> and <F^2> 473 // Note that F1 and F2 are returned from CALL_FQ by reference, and the 474 // user of the CALL_KERNEL macro below is assuming that F1 and F2 are defined. 475 double qk; 476 double F1, F2; 477 #define FETCH_Q() do { qk = q[q_index]; } while (0) 478 #define BUILD_ROTATION() do {} while(0) 479 #define APPLY_ROTATION() do {} while(0) 480 #define CALL_KERNEL() CALL_FQ(qk,F1,F2,local_values.table) 481 482 #elif defined(CALL_FQ_A) 483 // unoriented 2D return <F> and <F^2> 484 // Note that the CALL_FQ_A macro is computing _F1_slot and _F2_slot by 485 // reference then returning _F2_slot. We are calling them _F1_slot and 486 // _F2_slot here so they don't conflict with _F1 and _F2 in the macro 487 // expansion, or with the use of F2 = CALL_KERNEL() when it is used below. 488 double qx, qy; 489 double _F1_slot, _F2_slot; 490 #define FETCH_Q() do { qx = q[2*q_index]; qy = q[2*q_index+1]; } while (0) 491 #define BUILD_ROTATION() do {} while(0) 492 #define APPLY_ROTATION() do {} while(0) 493 #define CALL_KERNEL() CALL_FQ_A(sqrt(qx*qx+qy*qy),_F1_slot,_F2_slot,local_values.table) 494 495 #elif defined(CALL_IQ) 496 // unoriented 1D return <F^2> 446 497 double qk; 447 498 #define FETCH_Q() do { qk = q[q_index]; } while (0) 448 499 #define BUILD_ROTATION() do {} while(0) 449 500 #define APPLY_ROTATION() do {} while(0) 450 #define CALL_KERNEL() CALL_IQ(qk, 501 #define CALL_KERNEL() CALL_IQ(qk,local_values.table) 451 502 452 503 #elif defined(CALL_IQ_A) … … 482 533 #define APPLY_ROTATION() qabc_apply(&rotation, qx, qy, &qa, &qb, &qc) 483 534 #define CALL_KERNEL() CALL_IQ_ABC(qa, qb, qc, local_values.table) 535 484 536 #elif defined(CALL_IQ_XY) 485 537 // direct call to qx,qy calculator … … 495 547 // logic in the IQ_AC and IQ_ABC branches. This will become more important 496 548 // if we implement more projections, or more complicated projections. 497 #if defined(CALL_IQ) || defined(CALL_IQ_A) // no orientation 549 #if defined(CALL_IQ) || defined(CALL_IQ_A) || defined(CALL_FQ) || defined(CALL_FQ_A) 550 // no orientation 498 551 #define APPLY_PROJECTION() const double weight=weight0 499 552 #elif defined(CALL_IQ_XY) // pass orientation to the model … … 562 615 const int n##_LOOP = details->pd_length[_LOOP]; \ 563 616 const int p##_LOOP = details->pd_par[_LOOP]; \ 564 global const double *v##_LOOP = pd_value + details->pd_offset[_LOOP]; \565 global const double *w##_LOOP = pd_weight + details->pd_offset[_LOOP]; \617 pglobal const double *v##_LOOP = pd_value + details->pd_offset[_LOOP]; \ 618 pglobal const double *w##_LOOP = pd_weight + details->pd_offset[_LOOP]; \ 566 619 int i##_LOOP = (pd_start/details->pd_stride[_LOOP])%n##_LOOP; 567 620 … … 587 640 // Pointers to the start of the dispersity and weight vectors, if needed. 588 641 #if MAX_PD>0 589 global const double *pd_value = values + NUM_VALUES;590 global const double *pd_weight = pd_value + details->num_weights;642 pglobal const double *pd_value = values + NUM_VALUES; 643 pglobal const double *pd_weight = pd_value + details->num_weights; 591 644 #endif 592 645 … … 645 698 // Note: weight==0 must always be excluded 646 699 if (weight > cutoff) { 647 pd_norm += weight * CALL_VOLUME(local_values.table); 700 double form, shell; 701 CALL_VOLUME(form, shell, local_values.table); 702 weight_norm += weight; 703 weighted_form += weight * form; 704 weighted_shell += weight * shell; 705 if (effective_radius_type != 0) { 706 weighted_radius += weight * CALL_EFFECTIVE_RADIUS(effective_radius_type, local_values.table); 707 } 648 708 BUILD_ROTATION(); 649 709 650 #if ndef USE_OPENCL710 #if !defined(USE_GPU) 651 711 // DLL needs to explicitly loop over the q values. 652 712 #ifdef USE_OPENMP … … 654 714 #endif 655 715 for (q_index=0; q_index<nq; q_index++) 656 #endif // !USE_ OPENCL716 #endif // !USE_GPU 657 717 { 658 718 … … 663 723 #if defined(MAGNETIC) && NUM_MAGNETIC > 0 664 724 // Compute the scattering from the magnetic cross sections. 665 double scattering= 0.0;725 double F2 = 0.0; 666 726 const double qsq = qx*qx + qy*qy; 667 727 if (qsq > 1.e-16) { … … 688 748 // q_index, qx, qy, xs, sk, local_values.vector[sld_index], px, py, mx, my, mz); 689 749 } 690 scattering+= xs_weight * CALL_KERNEL();750 F2 += xs_weight * CALL_KERNEL(); 691 751 } 692 752 } 693 753 } 694 754 #else // !MAGNETIC 695 const double scattering = CALL_KERNEL(); 755 #if defined(CALL_FQ) 756 CALL_KERNEL(); // sets F1 and F2 by reference 757 #else 758 const double F2 = CALL_KERNEL(); 759 #endif 696 760 #endif // !MAGNETIC 697 //printf("q_index:%d %g %g %g %g\n", q_index, scattering, weight0); 698 699 #ifdef USE_OPENCL 700 this_result += weight * scattering; 761 //printf("q_index:%d %g %g %g %g\n", q_index, F2, weight0); 762 763 #if defined(USE_GPU) 764 #if defined(CALL_FQ) 765 this_F2 += weight * F2; 766 this_F1 += weight * F1; 767 #else 768 this_F2 += weight * F2; 769 #endif 701 770 #else // !USE_OPENCL 702 result[q_index] += weight * scattering; 771 #if defined(CALL_FQ) 772 result[2*q_index+0] += weight * F2; 773 result[2*q_index+1] += weight * F1; 774 #else 775 result[q_index] += weight * F2; 776 #endif 703 777 #endif // !USE_OPENCL 704 778 } 705 779 } 706 780 } 707 708 781 // close nested loops 709 782 ++step; … … 724 797 #endif 725 798 726 // Remember the current result and the updated norm. 727 #ifdef USE_OPENCL 728 result[q_index] = this_result; 729 if (q_index == 0) result[nq] = pd_norm; 730 //if (q_index == 0) printf("res: %g/%g\n", result[0], pd_norm); 731 #else // !USE_OPENCL 732 result[nq] = pd_norm; 733 //printf("res: %g/%g\n", result[0], pd_norm); 734 #endif // !USE_OPENCL 799 // Remember the results and the updated norm. 800 #if defined(USE_GPU) 801 #if defined(CALL_FQ) 802 result[2*q_index+0] = this_F2; 803 result[2*q_index+1] = this_F1; 804 #else 805 result[q_index] = this_F2; 806 #endif 807 if (q_index == 0) 808 #endif 809 { 810 #if defined(CALL_FQ) 811 result[2*nq] = weight_norm; 812 result[2*nq+1] = weighted_form; 813 result[2*nq+2] = weighted_shell; 814 result[2*nq+3] = weighted_radius; 815 #else 816 result[nq] = weight_norm; 817 result[nq+1] = weighted_form; 818 result[nq+2] = weighted_shell; 819 result[nq+3] = weighted_radius; 820 #endif 821 } 735 822 736 823 // ** clear the macros in preparation for the next kernel ** -
sasmodels/kernelcl.py
rd86f0fc r8064d5e 1 1 """ 2 2 GPU driver for C kernels 3 4 TODO: docs are out of date 3 5 4 6 There should be a single GPU environment running on the system. This … … 56 58 import time 57 59 60 try: 61 from time import perf_counter as clock 62 except ImportError: # CRUFT: python < 3.3 63 import sys 64 if sys.platform.count("darwin") > 0: 65 from time import time as clock 66 else: 67 from time import clock 68 58 69 import numpy as np # type: ignore 59 70 60 61 # Attempt to setup opencl. This may fail if the opencl package is not 71 # Attempt to setup opencl. This may fail if the pyopencl package is not 62 72 # installed or if it is installed but there are no devices available. 63 73 try: … … 74 84 75 85 from . import generate 86 from .generate import F32, F64 76 87 from .kernel import KernelModel, Kernel 77 88 … … 131 142 132 143 def use_opencl(): 133 return HAVE_OPENCL and os.environ.get("SAS_OPENCL", "").lower() != "none" 144 sas_opencl = os.environ.get("SAS_OPENCL", "OpenCL").lower() 145 return HAVE_OPENCL and sas_opencl != "none" and not sas_opencl.startswith("cuda") 134 146 135 147 ENV = None … … 162 174 Return true if device supports the requested precision. 163 175 """ 164 if dtype == generate.F32:176 if dtype == F32: 165 177 return True 166 elif dtype == generate.F64:178 elif dtype == F64: 167 179 return "cl_khr_fp64" in device.extensions 168 elif dtype == generate.F16:169 return "cl_khr_fp16" in device.extensions170 180 else: 181 # Not supporting F16 type since it isn't accurate enough 171 182 return False 172 183 … … 179 190 cl.kernel_work_group_info.PREFERRED_WORK_GROUP_SIZE_MULTIPLE, 180 191 queue.device) 181 182 def _stretch_input(vector, dtype, extra=1e-3, boundary=32):183 # type: (np.ndarray, np.dtype, float, int) -> np.ndarray184 """185 Stretch an input vector to the correct boundary.186 187 Performance on the kernels can drop by a factor of two or more if the188 number of values to compute does not fall on a nice power of two189 boundary. The trailing additional vector elements are given a190 value of *extra*, and so f(*extra*) will be computed for each of191 them. The returned array will thus be a subset of the computed array.192 193 *boundary* should be a power of 2 which is at least 32 for good194 performance on current platforms (as of Jan 2015). It should195 probably be the max of get_warp(kernel,queue) and196 device.min_data_type_align_size//4.197 """198 remainder = vector.size % boundary199 if remainder != 0:200 size = vector.size + (boundary - remainder)201 vector = np.hstack((vector, [extra] * (size - vector.size)))202 return np.ascontiguousarray(vector, dtype=dtype)203 204 192 205 193 def compile_model(context, source, dtype, fast=False): … … 239 227 """ 240 228 GPU context, with possibly many devices, and one queue per device. 229 230 Because the environment can be reset during a live program (e.g., if the 231 user changes the active GPU device in the GUI), everything associated 232 with the device context must be cached in the environment and recreated 233 if the environment changes. The *cache* attribute is a simple dictionary 234 which holds keys and references to objects, such as compiled kernels and 235 allocated buffers. The running program should check in the cache for 236 long lived objects and create them if they are not there. The program 237 should not hold onto cached objects, but instead only keep them active 238 for the duration of a function call. When the environment is destroyed 239 then the *release* method for each active cache item is called before 240 the environment is freed. This means that each cl buffer should be 241 in its own cache entry. 241 242 """ 242 243 def __init__(self): 243 244 # type: () -> None 244 245 # find gpu context 245 #self.context = cl.create_some_context() 246 247 self.context = None 248 if 'SAS_OPENCL' in os.environ: 249 #Setting PYOPENCL_CTX as a SAS_OPENCL to create cl context 250 os.environ["PYOPENCL_CTX"] = os.environ["SAS_OPENCL"] 251 if 'PYOPENCL_CTX' in os.environ: 252 self._create_some_context() 253 254 if not self.context: 255 self.context = _get_default_context() 246 context_list = _create_some_context() 247 248 # Find a context for F32 and for F64 (maybe the same one). 249 # F16 isn't good enough. 250 self.context = {} 251 for dtype in (F32, F64): 252 for context in context_list: 253 if has_type(context.devices[0], dtype): 254 self.context[dtype] = context 255 break 256 else: 257 self.context[dtype] = None 258 259 # Build a queue for each context 260 self.queue = {} 261 context = self.context[F32] 262 self.queue[F32] = cl.CommandQueue(context, context.devices[0]) 263 if self.context[F64] == self.context[F32]: 264 self.queue[F64] = self.queue[F32] 265 else: 266 context = self.context[F64] 267 self.queue[F64] = cl.CommandQueue(context, context.devices[0]) 256 268 257 269 # Byte boundary for data alignment 258 #self.data_boundary = max( d.min_data_type_align_size259 # for d in self.context.devices)260 self.queues = [cl.CommandQueue(context, context.devices[0]) 261 for context in self.context]270 #self.data_boundary = max(context.devices[0].min_data_type_align_size 271 # for context in self.context.values()) 272 273 # Cache for compiled programs, and for items in context 262 274 self.compiled = {} 275 self.cache = {} 263 276 264 277 def has_type(self, dtype): … … 267 280 Return True if all devices support a given type. 268 281 """ 269 return any(has_type(d, dtype) 270 for context in self.context 271 for d in context.devices) 272 273 def get_queue(self, dtype): 274 # type: (np.dtype) -> cl.CommandQueue 275 """ 276 Return a command queue for the kernels of type dtype. 277 """ 278 for context, queue in zip(self.context, self.queues): 279 if all(has_type(d, dtype) for d in context.devices): 280 return queue 281 282 def get_context(self, dtype): 283 # type: (np.dtype) -> cl.Context 284 """ 285 Return a OpenCL context for the kernels of type dtype. 286 """ 287 for context in self.context: 288 if all(has_type(d, dtype) for d in context.devices): 289 return context 290 291 def _create_some_context(self): 292 # type: () -> cl.Context 293 """ 294 Protected call to cl.create_some_context without interactivity. Use 295 this if SAS_OPENCL is set in the environment. Sets the *context* 296 attribute. 297 """ 298 try: 299 self.context = [cl.create_some_context(interactive=False)] 300 except Exception as exc: 301 warnings.warn(str(exc)) 302 warnings.warn("pyopencl.create_some_context() failed") 303 warnings.warn("the environment variable 'SAS_OPENCL' might not be set correctly") 282 return self.context.get(dtype, None) is not None 304 283 305 284 def compile_program(self, name, source, dtype, fast, timestamp): … … 318 297 del self.compiled[key] 319 298 if key not in self.compiled: 320 context = self. get_context(dtype)299 context = self.context[dtype] 321 300 logging.info("building %s for OpenCL %s", key, 322 301 context.devices[0].name.strip()) 323 program = compile_model(self. get_context(dtype),302 program = compile_model(self.context[dtype], 324 303 str(source), dtype, fast) 325 304 self.compiled[key] = (program, timestamp) 326 305 return program 306 307 def free_buffer(self, key): 308 if key in self.cache: 309 self.cache[key].release() 310 del self.cache[key] 311 312 def __del__(self): 313 for v in self.cache.values(): 314 release = getattr(v, 'release', lambda: None) 315 release() 316 self.cache = {} 317 318 _CURRENT_ID = 0 319 def unique_id(): 320 global _CURRENT_ID 321 _CURRENT_ID += 1 322 return _CURRENT_ID 323 324 def _create_some_context(): 325 # type: () -> cl.Context 326 """ 327 Protected call to cl.create_some_context without interactivity. 328 329 Uses SAS_OPENCL or PYOPENCL_CTX if they are set in the environment, 330 otherwise scans for the most appropriate device using 331 :func:`_get_default_context`. Ignore *SAS_OPENCL=OpenCL*, which 332 indicates that an OpenCL device should be used without specifying 333 which one (and not a CUDA device, or no GPU). 334 """ 335 # Assume we do not get here if SAS_OPENCL is None or CUDA 336 sas_opencl = os.environ.get('SAS_OPENCL', 'opencl') 337 if sas_opencl.lower() != 'opencl': 338 # Setting PYOPENCL_CTX as a SAS_OPENCL to create cl context 339 os.environ["PYOPENCL_CTX"] = sas_opencl 340 341 if 'PYOPENCL_CTX' in os.environ: 342 try: 343 return [cl.create_some_context(interactive=False)] 344 except Exception as exc: 345 warnings.warn(str(exc)) 346 warnings.warn("pyopencl.create_some_context() failed") 347 warnings.warn("the environment variable 'SAS_OPENCL' or 'PYOPENCL_CTX' might not be set correctly") 348 349 return _get_default_context() 327 350 328 351 def _get_default_context(): … … 404 427 self.dtype = dtype 405 428 self.fast = fast 406 self. program = None # delay program creation407 self._ kernels = None429 self.timestamp = generate.ocl_timestamp(self.info) 430 self._cache_key = unique_id() 408 431 409 432 def __getstate__(self): … … 414 437 # type: (Tuple[ModelInfo, str, np.dtype, bool]) -> None 415 438 self.info, self.source, self.dtype, self.fast = state 416 self.program = None417 439 418 440 def make_kernel(self, q_vectors): 419 441 # type: (List[np.ndarray]) -> "GpuKernel" 420 if self.program is None: 421 compile_program = environment().compile_program 422 timestamp = generate.ocl_timestamp(self.info) 423 self.program = compile_program( 442 return GpuKernel(self, q_vectors) 443 444 @property 445 def Iq(self): 446 return self._fetch_kernel('Iq') 447 448 def fetch_kernel(self, name): 449 # type: (str) -> cl.Kernel 450 """ 451 Fetch the kernel from the environment by name, compiling it if it 452 does not already exist. 453 """ 454 gpu = environment() 455 key = self._cache_key 456 if key not in gpu.cache: 457 program = gpu.compile_program( 424 458 self.info.name, 425 459 self.source['opencl'], 426 460 self.dtype, 427 461 self.fast, 428 timestamp)462 self.timestamp) 429 463 variants = ['Iq', 'Iqxy', 'Imagnetic'] 430 464 names = [generate.kernel_name(self.info, k) for k in variants] 431 kernels = [getattr( self.program, k) for k in names]432 self._kernels= dict((k, v) for k, v in zip(variants, kernels))433 is_2d = len(q_vectors) == 2434 if is_2d:435 kernel = [self._kernels['Iqxy'], self._kernels['Imagnetic']]465 kernels = [getattr(program, k) for k in names] 466 data = dict((k, v) for k, v in zip(variants, kernels)) 467 # keep a handle to program so GC doesn't collect 468 data['program'] = program 469 gpu.cache[key] = data 436 470 else: 437 kernel = [self._kernels['Iq']]*2 438 return GpuKernel(kernel, self.dtype, self.info, q_vectors) 439 440 def release(self): 441 # type: () -> None 442 """ 443 Free the resources associated with the model. 444 """ 445 if self.program is not None: 446 self.program = None 447 448 def __del__(self): 449 # type: () -> None 450 self.release() 471 data = gpu.cache[key] 472 return data[name] 451 473 452 474 # TODO: check that we don't need a destructor for buffers which go out of scope … … 473 495 # type: (List[np.ndarray], np.dtype) -> None 474 496 # TODO: do we ever need double precision q? 475 env = environment()476 497 self.nq = q_vectors[0].size 477 498 self.dtype = np.dtype(dtype) … … 482 503 # architectures tested so far. 483 504 if self.is_2d: 484 # Note: 16 rather than 15 because result is 1 longer than input. 485 width = ((self.nq+16)//16)*16 505 width = ((self.nq+15)//16)*16 486 506 self.q = np.empty((width, 2), dtype=dtype) 487 507 self.q[:self.nq, 0] = q_vectors[0] 488 508 self.q[:self.nq, 1] = q_vectors[1] 489 509 else: 490 # Note: 32 rather than 31 because result is 1 longer than input. 491 width = ((self.nq+32)//32)*32 510 width = ((self.nq+31)//32)*32 492 511 self.q = np.empty(width, dtype=dtype) 493 512 self.q[:self.nq] = q_vectors[0] 494 513 self.global_size = [self.q.shape[0]] 495 context = env.get_context(self.dtype) 496 #print("creating inputs of size", self.global_size) 497 self.q_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 498 hostbuf=self.q) 514 self._cache_key = unique_id() 515 516 @property 517 def q_b(self): 518 """Lazy creation of q buffer so it can survive context reset""" 519 env = environment() 520 key = self._cache_key 521 if key not in env.cache: 522 context = env.context[self.dtype] 523 #print("creating inputs of size", self.global_size) 524 buffer = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 525 hostbuf=self.q) 526 env.cache[key] = buffer 527 return env.cache[key] 499 528 500 529 def release(self): 501 530 # type: () -> None 502 531 """ 503 Free the memory. 504 """ 505 if self.q_b is not None: 506 self.q_b.release() 507 self.q_b = None 532 Free the buffer associated with the q value 533 """ 534 environment().free_buffer(id(self)) 508 535 509 536 def __del__(self): … … 515 542 Callable SAS kernel. 516 543 517 * kernel* is the GpuKernel object to call518 519 *model_info* is the module information520 521 * q_vectors* is the q vectors at which the kernel should be evaluated544 *model* is the GpuModel object to call 545 546 The following attributes are defined: 547 548 *info* is the module information 522 549 523 550 *dtype* is the kernel precision 551 552 *dim* is '1d' or '2d' 553 554 *result* is a vector to contain the results of the call 524 555 525 556 The resulting call method takes the *pars*, a list of values for … … 531 562 Call :meth:`release` when done with the kernel instance. 532 563 """ 533 def __init__(self, kernel, dtype, model_info, q_vectors):564 def __init__(self, model, q_vectors): 534 565 # type: (cl.Kernel, np.dtype, ModelInfo, List[np.ndarray]) -> None 535 q_input = GpuInput(q_vectors, dtype) 536 self.kernel = kernel 537 self.info = model_info 538 self.dtype = dtype 539 self.dim = '2d' if q_input.is_2d else '1d' 540 # plus three for the normalization values 541 self.result = np.empty(q_input.nq+1, dtype) 542 543 # Inputs and outputs for each kernel call 544 # Note: res may be shorter than res_b if global_size != nq 566 dtype = model.dtype 567 self.q_input = GpuInput(q_vectors, dtype) 568 self._model = model 569 # F16 isn't sufficient, so don't support it 570 self._as_dtype = np.float64 if dtype == generate.F64 else np.float32 571 self._cache_key = unique_id() 572 573 # attributes accessed from the outside 574 self.dim = '2d' if self.q_input.is_2d else '1d' 575 self.info = model.info 576 self.dtype = model.dtype 577 578 # holding place for the returned value 579 nout = 2 if self.info.have_Fq and self.dim == '1d' else 1 580 extra_q = 4 # total weight, form volume, shell volume and R_eff 581 self.result = np.empty(self.q_input.nq*nout+extra_q, dtype) 582 583 @property 584 def _result_b(self): 585 """Lazy creation of result buffer so it can survive context reset""" 545 586 env = environment() 546 self.queue = env.get_queue(dtype) 547 548 self.result_b = cl.Buffer(self.queue.context, mf.READ_WRITE, 549 q_input.global_size[0] * dtype.itemsize) 550 self.q_input = q_input # allocated by GpuInput above 551 552 self._need_release = [self.result_b, self.q_input] 553 self.real = (np.float32 if dtype == generate.F32 554 else np.float64 if dtype == generate.F64 555 else np.float16 if dtype == generate.F16 556 else np.float32) # will never get here, so use np.float32 557 558 def __call__(self, call_details, values, cutoff, magnetic): 587 key = self._cache_key 588 if key not in env.cache: 589 context = env.context[self.dtype] 590 width = ((self.result.size+31)//32)*32 * self.dtype.itemsize 591 buffer = cl.Buffer(context, mf.READ_WRITE, width) 592 env.cache[key] = buffer 593 return env.cache[key] 594 595 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 559 596 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 560 context = self.queue.context 561 # Arrange data transfer to card 597 env = environment() 598 queue = env.queue[self._model.dtype] 599 context = queue.context 600 601 # Arrange data transfer to/from card 602 q_b = self.q_input.q_b 603 result_b = self._result_b 562 604 details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 563 605 hostbuf=call_details.buffer) … … 565 607 hostbuf=values) 566 608 567 kernel = self.kernel[1 if magnetic else 0] 568 args = [ 609 name = 'Iq' if self.dim == '1d' else 'Imagnetic' if magnetic else 'Iqxy' 610 kernel = self._model.fetch_kernel(name) 611 kernel_args = [ 569 612 np.uint32(self.q_input.nq), None, None, 570 details_b, values_b, self.q_input.q_b, self.result_b, 571 self.real(cutoff), 613 details_b, values_b, q_b, result_b, 614 self._as_dtype(cutoff), 615 np.uint32(effective_radius_type), 572 616 ] 573 617 #print("Calling OpenCL") 574 618 #call_details.show(values) 575 # 619 #Call kernel and retrieve results 576 620 wait_for = None 577 last_nap = time.clock()621 last_nap = clock() 578 622 step = 1000000//self.q_input.nq + 1 579 623 for start in range(0, call_details.num_eval, step): 580 624 stop = min(start + step, call_details.num_eval) 581 625 #print("queuing",start,stop) 582 args[1:3] = [np.int32(start), np.int32(stop)]583 wait_for = [kernel( self.queue, self.q_input.global_size, None,584 * args, wait_for=wait_for)]626 kernel_args[1:3] = [np.int32(start), np.int32(stop)] 627 wait_for = [kernel(queue, self.q_input.global_size, None, 628 *kernel_args, wait_for=wait_for)] 585 629 if stop < call_details.num_eval: 586 630 # Allow other processes to run 587 631 wait_for[0].wait() 588 current_time = time.clock()632 current_time = clock() 589 633 if current_time - last_nap > 0.5: 590 time.sleep(0.0 5)634 time.sleep(0.001) 591 635 last_nap = current_time 592 cl.enqueue_copy( self.queue, self.result, self.result_b)636 cl.enqueue_copy(queue, self.result, result_b, wait_for=wait_for) 593 637 #print("result", self.result) 594 638 … … 598 642 v.release() 599 643 600 pd_norm = self.result[self.q_input.nq]601 scale = values[0]/(pd_norm if pd_norm != 0.0 else 1.0)602 background = values[1]603 #print("scale",scale,values[0],self.result[self.q_input.nq],background)604 return scale*self.result[:self.q_input.nq] + background605 # return self.result[:self.q_input.nq]606 607 644 def release(self): 608 645 # type: () -> None … … 610 647 Release resources associated with the kernel. 611 648 """ 612 for v in self._need_release: 613 v.release() 614 self._need_release = [] 649 environment().free_buffer(id(self)) 650 self.q_input.release() 615 651 616 652 def __del__(self): -
sasmodels/kerneldll.py
r1a3559f re44432d 99 99 pass 100 100 # pylint: enable=unused-import 101 102 # Compiler output is a byte stream that needs to be decode in python 3 103 decode = (lambda s: s) if sys.version_info[0] < 3 else (lambda s: s.decode('utf8')) 101 104 102 105 if "SAS_DLL_PATH" in os.environ: … … 184 187 subprocess.check_output(command, shell=shell, stderr=subprocess.STDOUT) 185 188 except subprocess.CalledProcessError as exc: 186 raise RuntimeError("compile failed.\n%s\n%s"%(command_str, exc.output)) 189 output = decode(exc.output) 190 raise RuntimeError("compile failed.\n%s\n%s"%(command_str, output)) 187 191 if not os.path.exists(output): 188 192 raise RuntimeError("compile failed. File is in %r"%source) … … 315 319 316 320 # int, int, int, int*, double*, double*, double*, double*, double 317 argtypes = [ct.c_int32]*3 + [ct.c_void_p]*4 + [float_type ]321 argtypes = [ct.c_int32]*3 + [ct.c_void_p]*4 + [float_type, ct.c_int32] 318 322 names = [generate.kernel_name(self.info, variant) 319 323 for variant in ("Iq", "Iqxy", "Imagnetic")] … … 375 379 def __init__(self, kernel, model_info, q_input): 376 380 # type: (Callable[[], np.ndarray], ModelInfo, PyInput) -> None 381 #,model_info,q_input) 377 382 self.kernel = kernel 378 383 self.info = model_info … … 380 385 self.dtype = q_input.dtype 381 386 self.dim = '2d' if q_input.is_2d else '1d' 382 self.result = np.empty(q_input.nq+1, q_input.dtype) 387 # leave room for f1/f2 results in case we need to compute beta for 1d models 388 nout = 2 if self.info.have_Fq else 1 389 # +4 for total weight, shell volume, effective radius, form volume 390 self.result = np.empty(q_input.nq*nout + 4, self.dtype) 383 391 self.real = (np.float32 if self.q_input.dtype == generate.F32 384 392 else np.float64 if self.q_input.dtype == generate.F64 385 393 else np.float128) 386 394 387 def __call__(self, call_details, values, cutoff, magnetic): 388 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 389 395 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 396 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray 390 397 kernel = self.kernel[1 if magnetic else 0] 391 398 args = [ … … 394 401 None, # pd_stop pd_stride[MAX_PD] 395 402 call_details.buffer.ctypes.data, # problem 396 values.ctypes.data, # pars397 self.q_input.q.ctypes.data, # q403 values.ctypes.data, # pars 404 self.q_input.q.ctypes.data, # q 398 405 self.result.ctypes.data, # results 399 406 self.real(cutoff), # cutoff 407 effective_radius_type, # cutoff 400 408 ] 401 409 #print("Calling DLL") … … 407 415 kernel(*args) # type: ignore 408 416 409 #print("returned",self.q_input.q, self.result)410 pd_norm = self.result[self.q_input.nq]411 scale = values[0]/(pd_norm if pd_norm != 0.0 else 1.0)412 background = values[1]413 #print("scale",scale,background)414 return scale*self.result[:self.q_input.nq] + background415 416 417 def release(self): 417 418 # type: () -> None -
sasmodels/kernelpy.py
r12eec1e raa8c6e0 12 12 13 13 import numpy as np # type: ignore 14 from numpy import pi 15 try: 16 from numpy import cbrt 17 except ImportError: 18 def cbrt(x): return x ** (1.0/3.0) 14 19 15 20 from .generate import F64 … … 158 163 159 164 # Generate a closure which calls the form_volume if it exists. 160 form_volume = model_info.form_volume 161 self._volume = ((lambda: form_volume(*volume_args)) if form_volume else 162 (lambda: 1.0)) 163 164 def __call__(self, call_details, values, cutoff, magnetic): 165 self._volume_args = volume_args 166 volume = model_info.form_volume 167 shell = model_info.shell_volume 168 radius = model_info.effective_radius 169 self._volume = ((lambda: (shell(*volume_args), volume(*volume_args))) if shell and volume 170 else (lambda: [volume(*volume_args)]*2) if volume 171 else (lambda: (1.0, 1.0))) 172 self._radius = ((lambda mode: radius(mode, *volume_args)) if radius 173 else (lambda mode: cbrt(0.75/pi*volume(*volume_args))) if volume 174 else (lambda mode: 1.0)) 175 176 177 178 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 165 179 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 166 180 if magnetic: … … 168 182 #print("Calling python kernel") 169 183 #call_details.show(values) 170 res = _loops(self._parameter_vector, self._form, self._volume, 171 self.q_input.nq, call_details, values, cutoff) 172 return res 184 radius = ((lambda: 0.0) if effective_radius_type == 0 185 else (lambda: self._radius(effective_radius_type))) 186 self.result = _loops( 187 self._parameter_vector, self._form, self._volume, radius, 188 self.q_input.nq, call_details, values, cutoff) 173 189 174 190 def release(self): … … 183 199 form, # type: Callable[[], np.ndarray] 184 200 form_volume, # type: Callable[[], float] 201 form_radius, # type: Callable[[], float] 185 202 nq, # type: int 186 203 call_details, # type: details.CallDetails 187 204 values, # type: np.ndarray 188 cutoff 205 cutoff, # type: float 189 206 ): 190 207 # type: (...) -> None … … 198 215 # # 199 216 ################################################################ 217 218 # WARNING: Trickery ahead 219 # The parameters[] vector is embedded in the closures for form(), 220 # form_volume() and form_radius(). We set the initial vector from 221 # the values for the model parameters. As we loop through the polydispesity 222 # mesh, we update the components with the polydispersity values before 223 # calling the respective functions. 200 224 n_pars = len(parameters) 201 225 parameters[:] = values[2:n_pars+2] 226 202 227 if call_details.num_active == 0: 203 pd_norm = float(form_volume()) 204 scale = values[0]/(pd_norm if pd_norm != 0.0 else 1.0) 205 background = values[1] 206 return scale*form() + background 207 208 pd_value = values[2+n_pars:2+n_pars + call_details.num_weights] 209 pd_weight = values[2+n_pars + call_details.num_weights:] 210 211 pd_norm = 0.0 212 partial_weight = np.NaN 213 weight = np.NaN 214 215 p0_par = call_details.pd_par[0] 216 p0_length = call_details.pd_length[0] 217 p0_index = p0_length 218 p0_offset = call_details.pd_offset[0] 219 220 pd_par = call_details.pd_par[:call_details.num_active] 221 pd_offset = call_details.pd_offset[:call_details.num_active] 222 pd_stride = call_details.pd_stride[:call_details.num_active] 223 pd_length = call_details.pd_length[:call_details.num_active] 224 225 total = np.zeros(nq, 'd') 226 for loop_index in range(call_details.num_eval): 227 # update polydispersity parameter values 228 if p0_index == p0_length: 229 pd_index = (loop_index//pd_stride)%pd_length 230 parameters[pd_par] = pd_value[pd_offset+pd_index] 231 partial_weight = np.prod(pd_weight[pd_offset+pd_index][1:]) 232 p0_index = loop_index%p0_length 233 234 weight = partial_weight * pd_weight[p0_offset + p0_index] 235 parameters[p0_par] = pd_value[p0_offset + p0_index] 236 p0_index += 1 237 if weight > cutoff: 238 # Call the scattering function 239 # Assume that NaNs are only generated if the parameters are bad; 240 # exclude all q for that NaN. Even better would be to have an 241 # INVALID expression like the C models, but that is too expensive. 242 Iq = np.asarray(form(), 'd') 243 if np.isnan(Iq).any(): 244 continue 245 246 # update value and norm 247 total += weight * Iq 248 pd_norm += weight * form_volume() 249 250 scale = values[0]/(pd_norm if pd_norm != 0.0 else 1.0) 251 background = values[1] 252 return scale*total + background 228 total = form() 229 weight_norm = 1.0 230 weighted_shell, weighted_form = form_volume() 231 weighted_radius = form_radius() 232 233 else: 234 pd_value = values[2+n_pars:2+n_pars + call_details.num_weights] 235 pd_weight = values[2+n_pars + call_details.num_weights:] 236 237 weight_norm = 0.0 238 weighted_form = 0.0 239 weighted_shell = 0.0 240 weighted_radius = 0.0 241 partial_weight = np.NaN 242 weight = np.NaN 243 244 p0_par = call_details.pd_par[0] 245 p0_length = call_details.pd_length[0] 246 p0_index = p0_length 247 p0_offset = call_details.pd_offset[0] 248 249 pd_par = call_details.pd_par[:call_details.num_active] 250 pd_offset = call_details.pd_offset[:call_details.num_active] 251 pd_stride = call_details.pd_stride[:call_details.num_active] 252 pd_length = call_details.pd_length[:call_details.num_active] 253 254 total = np.zeros(nq, 'd') 255 for loop_index in range(call_details.num_eval): 256 # update polydispersity parameter values 257 if p0_index == p0_length: 258 pd_index = (loop_index//pd_stride)%pd_length 259 parameters[pd_par] = pd_value[pd_offset+pd_index] 260 partial_weight = np.prod(pd_weight[pd_offset+pd_index][1:]) 261 p0_index = loop_index%p0_length 262 263 weight = partial_weight * pd_weight[p0_offset + p0_index] 264 parameters[p0_par] = pd_value[p0_offset + p0_index] 265 p0_index += 1 266 if weight > cutoff: 267 # Call the scattering function 268 # Assume that NaNs are only generated if the parameters are bad; 269 # exclude all q for that NaN. Even better would be to have an 270 # INVALID expression like the C models, but that is expensive. 271 Iq = np.asarray(form(), 'd') 272 if np.isnan(Iq).any(): 273 continue 274 275 # update value and norm 276 total += weight * Iq 277 weight_norm += weight 278 shell, form = form_volume() 279 weighted_form += weight * form 280 weighted_shell += weight * shell 281 weighted_radius += weight * form_radius() 282 283 result = np.hstack((total, weight_norm, weighted_form, weighted_shell, weighted_radius)) 284 return result 253 285 254 286 -
sasmodels/mixture.py
recf895e r39a06c9 143 143 #model_info.tests = [] 144 144 #model_info.source = [] 145 # Iq, Iqxy, form_volume, ER, VR and sesans146 145 # Remember the component info blocks so we can build the model 147 146 model_info.composition = ('mixture', parts) -
sasmodels/model_test.py
r12eec1e r5024a56 5 5 Usage:: 6 6 7 python -m sasmodels.model_test [opencl| dll|opencl_and_dll] model1 model2 ...7 python -m sasmodels.model_test [opencl|cuda|dll] model1 model2 ... 8 8 9 9 if model1 is 'all', then all except the remaining models will be tested 10 10 11 11 Each model is tested using the default parameters at q=0.1, (qx, qy)=(0.1, 0.1), 12 and the ER and VR are computed. The return values at these points are not13 considered. The test is only to verify that the models run to completion, 14 and do not produce inf or NaN.12 and Fq is called to make sure R_eff, volume and volume ratio are computed. 13 The return values at these points are not considered. The test is only to 14 verify that the models run to completion, and do not produce inf or NaN. 15 15 16 16 Tests are defined with the *tests* attribute in the model.py file. *tests* 17 17 is a list of individual tests to run, where each test consists of the 18 18 parameter values for the test, the q-values and the expected results. For 19 the effective radius test, the q-value should be 'ER'. For the VR test, 20 the q-value should be 'VR'. For 1-D tests, either specify the q value or 21 a list of q-values, and the corresponding I(q) value, or list of I(q) values. 19 the effective radius test and volume ratio tests, use the extended output 20 form, which checks each output of kernel.Fq. For 1-D tests, either specify 21 the q value or a list of q-values, and the corresponding I(q) value, or 22 list of I(q) values. 22 23 23 24 That is:: … … 32 33 [I(qx1, qy1), I(qx2, qy2), ...]], 33 34 34 [ {parameters}, 'ER', ER(pars) ], 35 [ {parameters}, 'VR', VR(pars) ], 35 [ {parameters}, q, F(q), F^2(q), R_eff, V, V_r ], 36 36 ... 37 37 ] … … 60 60 from . import core 61 61 from .core import list_models, load_model_info, build_model 62 from .direct_model import call_kernel, call_ ER, call_VR62 from .direct_model import call_kernel, call_Fq 63 63 from .exception import annotate_exception 64 64 from .modelinfo import expand_pars 65 65 from .kernelcl import use_opencl 66 from .kernelcuda import use_cuda 67 from . import product 66 68 67 69 # pylint: disable=unused-import … … 80 82 Construct the pyunit test suite. 81 83 82 *loaders* is the list of kernel drivers to use, which is one of 83 *["dll", "opencl"]*, *["dll"]* or *["opencl"]*. For python models, 84 the python driver is always used. 84 *loaders* is the list of kernel drivers to use (dll, opencl or cuda). 85 For python model the python driver is always used. 85 86 86 87 *models* is the list of models to test, or *["all"]* to test all models. … … 160 161 test_method_name, 161 162 platform="ocl", dtype=None, 163 stash=stash) 164 #print("defining", test_name) 165 suite.addTest(test) 166 167 # test using cuda if desired and available 168 if 'cuda' in loaders and use_cuda(): 169 test_name = "%s-cuda"%model_name 170 test_method_name = "test_%s_cuda" % model_info.id 171 # Using dtype=None so that the models that are only 172 # correct for double precision are not tested using 173 # single precision. The choice is determined by the 174 # presence of *single=False* in the model file. 175 test = ModelTestCase(test_name, model_info, 176 test_method_name, 177 platform="cuda", dtype=None, 162 178 stash=stash) 163 179 #print("defining", test_name) … … 202 218 ({}, [0.001, 0.01, 0.1], [None]*3), 203 219 ({}, [(0.1, 0.1)]*2, [None]*2), 204 # test that ER/VR will run if they exist 205 ({}, 'ER', None), 206 ({}, 'VR', None), 220 # test that Fq will run, and return R_eff, V, V_r 221 ({}, 0.1, None, None, None, None, None), 207 222 ] 208 223 tests = smoke_tests … … 210 225 if self.info.tests is not None: 211 226 tests += self.info.tests 227 S_tests = [test for test in tests if '@S' in test[0]] 228 P_tests = [test for test in tests if '@S' not in test[0]] 212 229 try: 213 230 model = build_model(self.info, dtype=self.dtype, 214 231 platform=self.platform) 215 results = [self.run_one(model, test) for test in tests] 232 results = [self.run_one(model, test) for test in P_tests] 233 for test in S_tests: 234 # pull the S model name out of the test defn 235 pars = test[0].copy() 236 s_name = pars.pop('@S') 237 ps_test = [pars] + list(test[1:]) 238 # build the P@S model 239 s_info = load_model_info(s_name) 240 ps_info = product.make_product_info(self.info, s_info) 241 ps_model = build_model(ps_info, dtype=self.dtype, 242 platform=self.platform) 243 # run the tests 244 results.append(self.run_one(ps_model, ps_test)) 245 216 246 if self.stash: 217 247 for test, target, actual in zip(tests, self.stash[0], results): … … 223 253 224 254 # Check for missing tests. Only do so for the "dll" tests 225 # to reduce noise from both opencl and dll, and because255 # to reduce noise from both opencl and cuda, and because 226 256 # python kernels use platform="dll". 227 257 if self.platform == "dll": … … 238 268 def _find_missing_tests(self): 239 269 # type: () -> None 240 """make sure there are 1D, 2D, ER and VR tests as appropriate""" 241 model_has_VR = callable(self.info.VR) 242 model_has_ER = callable(self.info.ER) 270 """make sure there are 1D and 2D tests as appropriate""" 243 271 model_has_1D = True 244 272 model_has_2D = any(p.type == 'orientation' … … 248 276 single = [test for test in self.info.tests 249 277 if not isinstance(test[2], list) and test[2] is not None] 250 tests_has_VR = any(test[1] == 'VR' for test in single)251 tests_has_ER = any(test[1] == 'ER' for test in single)252 278 tests_has_1D_single = any(isinstance(test[1], float) for test in single) 253 279 tests_has_2D_single = any(isinstance(test[1], tuple) for test in single) … … 262 288 263 289 missing = [] 264 if model_has_VR and not tests_has_VR:265 missing.append("VR")266 if model_has_ER and not tests_has_ER:267 missing.append("ER")268 290 if model_has_1D and not (tests_has_1D_single or tests_has_1D_multiple): 269 291 missing.append("1D") … … 276 298 # type: (KernelModel, TestCondition) -> None 277 299 """Run a single test case.""" 278 user_pars, x, y = test 300 user_pars, x, y = test[:3] 279 301 pars = expand_pars(self.info.parameters, user_pars) 280 302 invalid = invalid_pars(self.info.parameters, pars) … … 289 311 self.assertEqual(len(y), len(x)) 290 312 291 if x[0] == 'ER': 292 actual = np.array([call_ER(model.info, pars)]) 293 elif x[0] == 'VR': 294 actual = np.array([call_VR(model.info, pars)]) 295 elif isinstance(x[0], tuple): 313 if isinstance(x[0], tuple): 296 314 qx, qy = zip(*x) 297 315 q_vectors = [np.array(qx), np.array(qy)] 298 kernel = model.make_kernel(q_vectors)299 actual = call_kernel(kernel, pars)300 316 else: 301 317 q_vectors = [np.array(x)] 302 kernel = model.make_kernel(q_vectors) 318 319 kernel = model.make_kernel(q_vectors) 320 if len(test) == 3: 303 321 actual = call_kernel(kernel, pars) 304 305 self.assertTrue(len(actual) > 0) 306 self.assertEqual(len(y), len(actual)) 307 308 for xi, yi, actual_yi in zip(x, y, actual): 322 self._check_vectors(x, y, actual, 'I') 323 return actual 324 else: 325 y1 = y 326 y2 = test[3] if not isinstance(test[3], list) else [test[3]] 327 F1, F2, R_eff, volume, volume_ratio = call_Fq(kernel, pars) 328 if F1 is not None: # F1 is none for models with Iq instead of Fq 329 self._check_vectors(x, y1, F1, 'F') 330 self._check_vectors(x, y2, F2, 'F^2') 331 self._check_scalar(test[4], R_eff, 'R_eff') 332 self._check_scalar(test[5], volume, 'volume') 333 self._check_scalar(test[6], volume_ratio, 'form:shell ratio') 334 return F2 335 336 def _check_scalar(self, target, actual, name): 337 if target is None: 338 # smoke test --- make sure it runs and produces a value 339 self.assertTrue(not np.isnan(actual), 340 'invalid %s: %s' % (name, actual)) 341 elif np.isnan(target): 342 # make sure nans match 343 self.assertTrue(np.isnan(actual), 344 '%s: expected:%s; actual:%s' 345 % (name, target, actual)) 346 else: 347 # is_near does not work for infinite values, so also test 348 # for exact values. 349 self.assertTrue(target == actual or is_near(target, actual, 5), 350 '%s: expected:%s; actual:%s' 351 % (name, target, actual)) 352 353 def _check_vectors(self, x, target, actual, name='I'): 354 self.assertTrue(len(actual) > 0, 355 '%s(...) expected return'%name) 356 if target is None: 357 return 358 self.assertEqual(len(target), len(actual), 359 '%s(...) returned wrong length'%name) 360 for xi, yi, actual_yi in zip(x, target, actual): 309 361 if yi is None: 310 362 # smoke test --- make sure it runs and produces a value 311 363 self.assertTrue(not np.isnan(actual_yi), 312 'invalid f(%s): %s' % (xi, actual_yi))364 'invalid %s(%s): %s' % (name, xi, actual_yi)) 313 365 elif np.isnan(yi): 366 # make sure nans match 314 367 self.assertTrue(np.isnan(actual_yi), 315 ' f(%s): expected:%s; actual:%s'316 % ( xi, yi, actual_yi))368 '%s(%s): expected:%s; actual:%s' 369 % (name, xi, yi, actual_yi)) 317 370 else: 318 371 # is_near does not work for infinite values, so also test 319 # for exact values. Note that this will not372 # for exact values. 320 373 self.assertTrue(yi == actual_yi or is_near(yi, actual_yi, 5), 321 'f(%s); expected:%s; actual:%s' 322 % (xi, yi, actual_yi)) 323 return actual 374 '%s(%s); expected:%s; actual:%s' 375 % (name, xi, yi, actual_yi)) 324 376 325 377 return ModelTestCase … … 333 385 invalid = [] 334 386 for par in sorted(pars.keys()): 387 # special handling of R_eff mode, which is not a usual parameter 388 if par == product.RADIUS_MODE_ID: 389 continue 335 390 parts = par.split('_pd') 336 391 if len(parts) > 1 and parts[1] not in ("", "_n", "nsigma", "type"): … … 383 438 384 439 # Build a test suite containing just the model 385 loaders = ['opencl' ] if use_opencl() else ['dll']440 loaders = ['opencl' if use_opencl() else 'cuda' if use_cuda() else 'dll'] 386 441 suite = unittest.TestSuite() 387 442 _add_model_to_suite(loaders, suite, model_info) … … 444 499 loaders = ['opencl'] 445 500 models = models[1:] 501 elif models and models[0] == 'cuda': 502 if not use_cuda(): 503 print("cuda is not available") 504 return 1 505 loaders = ['cuda'] 506 models = models[1:] 446 507 elif models and models[0] == 'dll': 447 508 # TODO: test if compiler is available? 448 509 loaders = ['dll'] 449 510 models = models[1:] 450 elif models and models[0] == 'opencl_and_dll':451 loaders = ['opencl', 'dll'] if use_opencl() else ['dll']452 models = models[1:]453 511 else: 454 loaders = ['opencl', 'dll'] if use_opencl() else ['dll'] 512 loaders = ['dll'] 513 if use_opencl(): 514 loaders.append('opencl') 515 if use_cuda(): 516 loaders.append('cuda') 455 517 if not models: 456 518 print("""\ 457 519 usage: 458 python -m sasmodels.model_test [-v] [opencl| dll] model1 model2 ...520 python -m sasmodels.model_test [-v] [opencl|cuda|dll] model1 model2 ... 459 521 460 522 If -v is included on the command line, then use verbose output. 461 523 462 If n either opencl nor dll is specified, then models will be tested with463 both OpenCL and dll; the compute target is ignored for pure python models.524 If no platform is specified, then models will be tested with dll, and 525 if available, OpenCL and CUDA; the compute target is ignored for pure python models. 464 526 465 527 If model1 is 'all', then all except the remaining models will be tested. … … 481 543 Run "nosetests sasmodels" on the command line to invoke it. 482 544 """ 483 loaders = ['opencl', 'dll'] if use_opencl() else ['dll'] 545 loaders = ['dll'] 546 if use_opencl(): 547 loaders.append('opencl') 548 if use_cuda(): 549 loaders.append('cuda') 484 550 tests = make_suite(loaders, ['all']) 485 551 def build_test(test): -
sasmodels/modelinfo.py
rbd547d0 rc1799d3 164 164 parameter.length = length 165 165 parameter.length_control = control 166 167 166 return parameter 168 167 … … 265 264 will have a magnitude and a direction, which may be different from 266 265 other sld parameters. The volume parameters are used for calls 267 to form_volume within the kernel (required for volume normalization) 268 and for calls to ER and VR for effective radius and volume ratio269 respectively.266 to form_volume within the kernel (required for volume normalization), 267 to shell_volume (for hollow shapes), and to effective_radius (for 268 structure factor interactions) respectively. 270 269 271 270 *description* is a short description of the parameter. This will … … 405 404 parameters counted as n individual parameters p1, p2, ... 406 405 406 * *common_parameters* is the list of common parameters, with a unique 407 copy for each model so that structure factors can have a default 408 background of 0.0. 409 407 410 * *call_parameters* is the complete list of parameters to the kernel, 408 411 including scale and background, with vector parameters recorded as … … 417 420 parameters don't use vector notation, and instead use p1, p2, ... 418 421 """ 419 # scale and background are implicit parameters420 COMMON = [Parameter(*p) for p in COMMON_PARAMETERS]421 422 422 def __init__(self, parameters): 423 423 # type: (List[Parameter]) -> None 424 425 # scale and background are implicit parameters 426 # Need them to be unique to each model in case they have different 427 # properties, such as default=0.0 for structure factor backgrounds. 428 self.common_parameters = [Parameter(*p) for p in COMMON_PARAMETERS] 429 424 430 self.kernel_parameters = parameters 425 431 self._set_vector_lengths() 426 427 432 self.npars = sum(p.length for p in self.kernel_parameters) 428 433 self.nmagnetic = sum(p.length for p in self.kernel_parameters … … 431 436 if self.nmagnetic: 432 437 self.nvalues += 3 + 3*self.nmagnetic 433 434 438 self.call_parameters = self._get_call_parameters() 435 439 self.defaults = self._get_defaults() … … 471 475 if p.polydisperse and p.type not in ('orientation', 'magnetic')) 472 476 self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse) 477 478 def set_zero_background(self): 479 """ 480 Set the default background to zero for this model. This is done for 481 structure factor models. 482 """ 483 # type: () -> None 484 # Make sure background is the second common parameter. 485 assert self.common_parameters[1].id == "background" 486 self.common_parameters[1].default = 0.0 487 self.defaults = self._get_defaults() 473 488 474 489 def check_angles(self): … … 570 585 def _get_call_parameters(self): 571 586 # type: () -> List[Parameter] 572 full_list = self. COMMON[:]587 full_list = self.common_parameters[:] 573 588 for p in self.kernel_parameters: 574 589 if p.length == 1: … … 673 688 674 689 # Gather the user parameters in order 675 result = control + self. COMMON690 result = control + self.common_parameters 676 691 for p in self.kernel_parameters: 677 692 if not is2d and p.type in ('orientation', 'magnetic'): … … 722 737 723 738 #: Set of variables defined in the model that might contain C code 724 C_SYMBOLS = ['Imagnetic', 'Iq', 'Iqxy', 'Iqac', 'Iqabc', 'form_volume', ' c_code']739 C_SYMBOLS = ['Imagnetic', 'Iq', 'Iqxy', 'Iqac', 'Iqabc', 'form_volume', 'shell_volume', 'c_code'] 725 740 726 741 def _find_source_lines(model_info, kernel_module): … … 771 786 # Custom sum/multi models 772 787 return kernel_module.model_info 788 773 789 info = ModelInfo() 790 791 # Build the parameter table 774 792 #print("make parameter table", kernel_module.parameters) 775 793 parameters = make_parameter_table(getattr(kernel_module, 'parameters', [])) 794 795 # background defaults to zero for structure factor models 796 structure_factor = getattr(kernel_module, 'structure_factor', False) 797 if structure_factor: 798 parameters.set_zero_background() 799 800 # TODO: remove demo parameters 801 # The plots in the docs are generated from the model default values. 802 # Sascomp set parameters from the command line, and so doesn't need 803 # demo values for testing. 776 804 demo = expand_pars(parameters, getattr(kernel_module, 'demo', None)) 805 777 806 filename = abspath(kernel_module.__file__).replace('.pyc', '.py') 778 807 kernel_id = splitext(basename(filename))[0] … … 792 821 info.category = getattr(kernel_module, 'category', None) 793 822 info.structure_factor = getattr(kernel_module, 'structure_factor', False) 823 # TODO: find Fq by inspection 824 info.effective_radius_type = getattr(kernel_module, 'effective_radius_type', None) 825 info.have_Fq = getattr(kernel_module, 'have_Fq', False) 794 826 info.profile_axes = getattr(kernel_module, 'profile_axes', ['x', 'y']) 795 827 # Note: custom.load_custom_kernel_module assumes the C sources are defined … … 797 829 info.source = getattr(kernel_module, 'source', []) 798 830 info.c_code = getattr(kernel_module, 'c_code', None) 831 info.effective_radius = getattr(kernel_module, 'effective_radius', None) 799 832 # TODO: check the structure of the tests 800 833 info.tests = getattr(kernel_module, 'tests', []) 801 info.ER = getattr(kernel_module, 'ER', None) # type: ignore802 info.VR = getattr(kernel_module, 'VR', None) # type: ignore803 834 info.form_volume = getattr(kernel_module, 'form_volume', None) # type: ignore 835 info.shell_volume = getattr(kernel_module, 'shell_volume', None) # type: ignore 804 836 info.Iq = getattr(kernel_module, 'Iq', None) # type: ignore 805 837 info.Iqxy = getattr(kernel_module, 'Iqxy', None) # type: ignore … … 825 857 info.lineno = {} 826 858 _find_source_lines(info, kernel_module) 827 828 859 return info 829 860 … … 918 949 #: provided in the file. 919 950 structure_factor = None # type: bool 951 #: True if the model defines an Fq function with signature 952 #: void Fq(double q, double *F1, double *F2, ...) 953 have_Fq = False 954 #: List of options for computing the effective radius of the shape, 955 #: or None if the model is not usable as a form factor model. 956 effective_radius_type = None # type: List[str] 920 957 #: List of C source files used to define the model. The source files 921 958 #: should define the *Iq* function, and possibly *Iqac* or *Iqabc* if the 922 959 #: model defines orientation parameters. Files containing the most basic 923 960 #: functions must appear first in the list, followed by the files that 924 #: use those functions. Form factors are indicated by providing 925 #: an :attr:`ER` function. 961 #: use those functions. 926 962 source = None # type: List[str] 927 #: The set of tests that must pass. The format of the tests is described 928 #: in :mod:`model_test`. 929 tests = None # type: List[TestCondition] 930 #: Returns the effective radius of the model given its volume parameters. 931 #: The presence of *ER* indicates that the model is a form factor model 932 #: that may be used together with a structure factor to form an implicit 933 #: multiplication model. 934 #: 935 #: The parameters to the *ER* function must be marked with type *volume*. 936 #: in the parameter table. They will appear in the same order as they 937 #: do in the table. The values passed to *ER* will be vectors, with one 938 #: value for each polydispersity condition. For example, if the model 939 #: is polydisperse over both length and radius, then both length and 940 #: radius will have the same number of values in the vector, with one 941 #: value for each *length X radius*. If only *radius* is polydisperse, 942 #: then the value for *length* will be repeated once for each value of 943 #: *radius*. The *ER* function should return one effective radius for 944 #: each parameter set. Multiplicity parameters will be received as 945 #: arrays, with one row per polydispersity condition. 946 ER = None # type: Optional[Callable[[np.ndarray], np.ndarray]] 947 #: Returns the occupied volume and the total volume for each parameter set. 948 #: See :attr:`ER` for details on the parameters. 949 VR = None # type: Optional[Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]] 950 #: Arbitrary C code containing supporting functions, etc., to be inserted 951 #: after everything in source. This can include Iq and Iqxy functions with 952 #: the full function signature, including all parameters. 953 c_code = None 963 #: inline source code, added after all elements of source 964 c_code = None # type: Optional[str] 954 965 #: Returns the form volume for python-based models. Form volume is needed 955 966 #: for volume normalization in the polydispersity integral. If no … … 959 970 #: C code, either defined as a string, or in the sources. 960 971 form_volume = None # type: Union[None, str, Callable[[np.ndarray], float]] 972 #: Returns the shell volume for python-based models. Form volume and 973 #: shell volume are needed for volume normalization in the polydispersity 974 #: integral and structure interactions for hollow shapes. If no 975 #: parameters are *volume* parameters, then shell volume is not needed. 976 #: For C-based models, (with :attr:`sources` defined, or with :attr:`Iq` 977 #: defined using a string containing C code), shell_volume must also be 978 #: C code, either defined as a string, or in the sources. 979 shell_volume = None # type: Union[None, str, Callable[[np.ndarray], float]] 961 980 #: Returns *I(q, a, b, ...)* for parameters *a*, *b*, etc. defined 962 981 #: by the parameter table. *Iq* can be defined as a python function, or … … 993 1012 #: Line numbers for symbols defining C code 994 1013 lineno = None # type: Dict[str, int] 1014 #: The set of tests that must pass. The format of the tests is described 1015 #: in :mod:`model_test`. 1016 tests = None # type: List[TestCondition] 995 1017 996 1018 def __init__(self): -
sasmodels/models/_spherepy.py
rca4444f r304c775 71 71 return 1.333333333333333 * pi * radius ** 3 72 72 73 def effective_radius(mode, radius): 74 return radius 75 73 76 def Iq(q, sld, sld_solvent, radius): 74 77 #print "q",q … … 105 108 sesans.vectorized = True # sesans accepts an array of z values 106 109 107 def ER(radius):108 return radius109 110 # VR defaults to 1.0111 112 110 demo = dict(scale=1, background=0, 113 111 sld=6, sld_solvent=1, -
sasmodels/models/barbell.c
r108e70e r99658f6 63 63 64 64 static double 65 Iq(double q, double sld, double solvent_sld, 65 radius_from_excluded_volume(double radius_bell, double radius, double length) 66 { 67 const double hdist = sqrt(square(radius_bell) - square(radius)); 68 const double length_tot = length + 2.0*(hdist+ radius); 69 return 0.5*cbrt(0.75*radius_bell*(2.0*radius_bell*length_tot + (radius_bell + length_tot)*(M_PI*radius_bell + length_tot))); 70 } 71 72 static double 73 radius_from_volume(double radius_bell, double radius, double length) 74 { 75 const double vol_barbell = form_volume(radius_bell,radius,length); 76 return cbrt(vol_barbell/M_4PI_3); 77 } 78 79 static double 80 radius_from_totallength(double radius_bell, double radius, double length) 81 { 82 const double hdist = sqrt(square(radius_bell) - square(radius)); 83 return 0.5*length + hdist + radius_bell; 84 } 85 86 static double 87 effective_radius(int mode, double radius_bell, double radius, double length) 88 { 89 switch (mode) { 90 default: 91 case 1: // equivalent cylinder excluded volume 92 return radius_from_excluded_volume(radius_bell, radius , length); 93 case 2: // equivalent volume sphere 94 return radius_from_volume(radius_bell, radius , length); 95 case 3: // radius 96 return radius; 97 case 4: // half length 98 return 0.5*length; 99 case 5: // half total length 100 return radius_from_totallength(radius_bell,radius,length); 101 } 102 } 103 104 static void 105 Fq(double q,double *F1, double *F2, double sld, double solvent_sld, 66 106 double radius_bell, double radius, double length) 67 107 { … … 72 112 const double zm = M_PI_4; 73 113 const double zb = M_PI_4; 74 double total = 0.0; 114 double total_F1 = 0.0; 115 double total_F2 = 0.0; 75 116 for (int i = 0; i < GAUSS_N; i++){ 76 117 const double alpha= GAUSS_Z[i]*zm + zb; … … 78 119 SINCOS(alpha, sin_alpha, cos_alpha); 79 120 const double Aq = _fq(q*sin_alpha, q*cos_alpha, h, radius_bell, radius, half_length); 80 total += GAUSS_W[i] * Aq * Aq * sin_alpha; 121 total_F1 += GAUSS_W[i] * Aq * sin_alpha; 122 total_F2 += GAUSS_W[i] * Aq * Aq * sin_alpha; 81 123 } 82 124 // translate dx in [-1,1] to dx in [lower,upper] 83 const double form = total*zm; 125 const double form_avg = total_F1*zm; 126 const double form_squared_avg = total_F2*zm; 84 127 85 128 //Contrast 86 129 const double s = (sld - solvent_sld); 87 return 1.0e-4 * s * s * form; 130 *F1 = 1.0e-2 * s * form_avg; 131 *F2 = 1.0e-4 * s * s * form_squared_avg; 88 132 } 89 90 133 91 134 static double -
sasmodels/models/barbell.py
r2d81cfe r99658f6 79 79 .. [#] H Kaya and N R deSouza, *J. Appl. Cryst.*, 37 (2004) 508-509 (addenda 80 80 and errata) 81 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 81 82 82 83 Authorship and Verification … … 116 117 117 118 source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "barbell.c"] 119 have_Fq = True 120 effective_radius_type = [ 121 "equivalent cylinder excluded volume","equivalent volume sphere", "radius", "half length", "half total length", 122 ] 118 123 119 124 def random(): -
sasmodels/models/binary_hard_sphere.c
r925ad6e r71b751d 1 1 double form_volume(void); 2 2 3 double Iq(double q, 3 double Iq(double q, 4 4 double lg_radius, double sm_radius, 5 5 double lg_vol_frac, double sm_vol_frac, 6 6 double lg_sld, double sm_sld, double solvent_sld 7 7 ); 8 8 9 9 void calculate_psfs(double qval, 10 10 double r2, double nf2, … … 22 22 double lg_vol_frac, double sm_vol_frac, 23 23 double lg_sld, double sm_sld, double solvent_sld) 24 25 24 { 26 25 double r2,r1,nf2,phi,aa,rho2,rho1,rhos,inten; //my local names … … 28 27 double phi1,phi2,phr,a3; 29 28 double v1,v2,n1,n2,qr1,qr2,b1,b2,sc1,sc2; 30 29 31 30 r2 = lg_radius; 32 31 r1 = sm_radius; … … 36 35 rho1 = sm_sld; 37 36 rhos = solvent_sld; 38 39 37 38 40 39 phi = phi1 + phi2; 41 40 aa = r1/r2; … … 46 45 // calculate the PSF's here 47 46 calculate_psfs(q,r2,nf2,aa,phi,&psf11,&psf22,&psf12); 48 47 49 48 // /* do form factor calculations */ 50 49 51 50 v1 = M_4PI_3*r1*r1*r1; 52 51 v2 = M_4PI_3*r2*r2*r2; 53 52 54 53 n1 = phi1/v1; 55 54 n2 = phi2/v2; 56 55 57 56 qr1 = r1*q; 58 57 qr2 = r2*q; … … 68 67 inten *= 1.0e8; 69 68 ///*convert rho^2 in 10^-6A to A*/ 70 inten *= 1.0e-12; 69 inten *= 1.0e-12; 71 70 return(inten); 72 71 } … … 77 76 double aa, double phi, 78 77 double *s11, double *s22, double *s12) 79 80 78 { 81 79 // variable qval,r2,nf2,aa,phi,&s11,&s22,&s12 82 80 83 81 // calculate constant terms 84 82 double s2,v,a3,v1,v2,g11,g12,g22,wmv,wmv3,wmv4; … … 87 85 double c11,c22,c12,f12,f22,ttt1,ttt2,ttt3,ttt4,yl,y13; 88 86 double t21,t22,t23,t31,t32,t33,t41,t42,yl3,wma3,y1; 89 87 90 88 s2 = 2.0*r2; 91 89 // s1 = aa*s2; why is this never used? check original paper? … … 108 106 gm1=(v1*a1+a3*v2*a2)*.5; 109 107 gm12=2.*gm1*(1.-aa)/aa; 110 //c 108 //c 111 109 //c calculate the direct correlation functions and print results 112 110 //c 113 111 // do 20 j=1,npts 114 112 115 113 yy=qval*s2; 116 114 //c calculate direct correlation functions … … 123 121 t3=gm1*((4.*ay*ay2-24.*ay)*sin(ay)-(ay2*ay2-12.*ay2+24.)*cos(ay)+24.)/ay3; 124 122 f11=24.*v1*(t1+t2+t3)/ay3; 125 123 126 124 //c ------c22 127 125 y2=yy*yy; … … 131 129 tt3=gm1*((4.*y3-24.*yy)*sin(yy)-(y2*y2-12.*y2+24.)*cos(yy)+24.)/ay3; 132 130 f22=24.*v2*(tt1+tt2+tt3)/y3; 133 131 134 132 //c -----c12 135 133 yl=.5*yy*(1.-aa); … … 151 149 ttt4=a1*(t41+t42)/y1; 152 150 f12=ttt1+24.*v*sqrt(nf2)*sqrt(1.-nf2)*a3*(ttt2+ttt3+ttt4)/(nf2+(1.-nf2)*a3); 153 151 154 152 c11=f11; 155 153 c22=f22; 156 154 c12=f12; 157 155 *s11=1./(1.+c11-(c12)*c12/(1.+c22)); 158 *s22=1./(1.+c22-(c12)*c12/(1.+c11)); 159 *s12=-c12/((1.+c11)*(1.+c22)-(c12)*(c12)); 160 156 *s22=1./(1.+c22-(c12)*c12/(1.+c11)); 157 *s12=-c12/((1.+c11)*(1.+c22)-(c12)*(c12)); 158 161 159 return; 162 160 } 163 -
sasmodels/models/capped_cylinder.c
r108e70e r99658f6 85 85 86 86 static double 87 Iq(double q, double sld, double solvent_sld, 87 radius_from_excluded_volume(double radius, double radius_cap, double length) 88 { 89 const double hc = radius_cap - sqrt(radius_cap*radius_cap - radius*radius); 90 const double length_tot = length + 2.0*hc; 91 return 0.5*cbrt(0.75*radius*(2.0*radius*length_tot + (radius + length_tot)*(M_PI*radius + length_tot))); 92 } 93 94 static double 95 radius_from_volume(double radius, double radius_cap, double length) 96 { 97 const double vol_cappedcyl = form_volume(radius,radius_cap,length); 98 return cbrt(vol_cappedcyl/M_4PI_3); 99 } 100 101 static double 102 radius_from_totallength(double radius, double radius_cap, double length) 103 { 104 const double hc = radius_cap - sqrt(radius_cap*radius_cap - radius*radius); 105 return 0.5*length + hc; 106 } 107 108 static double 109 effective_radius(int mode, double radius, double radius_cap, double length) 110 { 111 switch (mode) { 112 default: 113 case 1: // equivalent cylinder excluded volume 114 return radius_from_excluded_volume(radius, radius_cap, length); 115 case 2: // equivalent volume sphere 116 return radius_from_volume(radius, radius_cap, length); 117 case 3: // radius 118 return radius; 119 case 4: // half length 120 return 0.5*length; 121 case 5: // half total length 122 return radius_from_totallength(radius, radius_cap,length); 123 } 124 } 125 126 static void 127 Fq(double q,double *F1, double *F2, double sld, double solvent_sld, 88 128 double radius, double radius_cap, double length) 89 129 { … … 94 134 const double zm = M_PI_4; 95 135 const double zb = M_PI_4; 96 double total = 0.0; 136 double total_F1 = 0.0; 137 double total_F2 = 0.0; 97 138 for (int i=0; i<GAUSS_N ;i++) { 98 139 const double theta = GAUSS_Z[i]*zm + zb; … … 103 144 const double Aq = _fq(qab, qc, h, radius_cap, radius, half_length); 104 145 // scale by sin_theta for spherical coord integration 105 total += GAUSS_W[i] * Aq * Aq * sin_theta; 146 total_F1 += GAUSS_W[i] * Aq * sin_theta; 147 total_F2 += GAUSS_W[i] * Aq * Aq * sin_theta; 106 148 } 107 149 // translate dx in [-1,1] to dx in [lower,upper] 108 const double form = total * zm; 150 const double form_avg = total_F1 * zm; 151 const double form_squared_avg = total_F2 * zm; 109 152 110 153 // Contrast 111 154 const double s = (sld - solvent_sld); 112 return 1.0e-4 * s * s * form; 155 *F1 = 1.0e-2 * s * form_avg; 156 *F2 = 1.0e-4 * s * s * form_squared_avg; 113 157 } 114 158 -
sasmodels/models/capped_cylinder.py
r2d81cfe r99658f6 82 82 .. [#] H Kaya and N-R deSouza, *J. Appl. Cryst.*, 37 (2004) 508-509 (addenda 83 83 and errata) 84 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 84 85 85 86 Authorship and Verification … … 136 137 137 138 source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "capped_cylinder.c"] 139 have_Fq = True 140 effective_radius_type = [ 141 "equivalent cylinder excluded volume", "equivalent volume sphere", "radius", "half length", "half total length", 142 ] 138 143 139 144 def random(): -
sasmodels/models/core_multi_shell.c
rc3ccaec rd42dd4a 9 9 10 10 static double 11 form_volume(double core_radius, double fp_n, double thickness[])11 outer_radius(double core_radius, double fp_n, double thickness[]) 12 12 { 13 13 double r = core_radius; … … 16 16 r += thickness[i]; 17 17 } 18 return M_4PI_3 * cube(r);18 return r; 19 19 } 20 20 21 21 static double 22 Iq(double q, double core_sld, double core_radius, 22 form_volume(double core_radius, double fp_n, double thickness[]) 23 { 24 return M_4PI_3 * cube(outer_radius(core_radius, fp_n, thickness)); 25 } 26 27 static double 28 effective_radius(int mode, double core_radius, double fp_n, double thickness[]) 29 { 30 switch (mode) { 31 default: 32 case 1: // outer radius 33 return outer_radius(core_radius, fp_n, thickness); 34 case 2: // core radius 35 return core_radius; 36 } 37 } 38 39 static void 40 Fq(double q, double *F1, double *F2, double core_sld, double core_radius, 23 41 double solvent_sld, double fp_n, double sld[], double thickness[]) 24 42 { … … 34 52 } 35 53 f += M_4PI_3 * cube(r) * (solvent_sld - last_sld) * sas_3j1x_x(q*r); 36 return f * f * 1.0e-4; 54 *F1 = 1e-2 * f; 55 *F2 = 1e-4 * f * f; 37 56 } -
sasmodels/models/core_multi_shell.py
r2d81cfe ree60aa7 99 99 100 100 source = ["lib/sas_3j1x_x.c", "core_multi_shell.c"] 101 have_Fq = True 102 effective_radius_type = ["outer radius", "core radius"] 101 103 102 104 def random(): … … 143 145 return np.asarray(z), np.asarray(rho) 144 146 145 def ER(radius, n, thickness):146 """Effective radius"""147 n = int(n[0]+0.5) # n is a control parameter and is not polydisperse148 return np.sum(thickness[:n], axis=0) + radius149 150 147 demo = dict(sld_core=6.4, 151 148 radius=60, -
sasmodels/models/core_shell_bicelle.c
r108e70e r99658f6 37 37 38 38 static double 39 Iq(double q, 39 radius_from_excluded_volume(double radius, double thick_rim, double thick_face, double length) 40 { 41 const double radius_tot = radius + thick_rim; 42 const double length_tot = length + 2.0*thick_face; 43 return 0.5*cbrt(0.75*radius_tot*(2.0*radius_tot*length_tot + (radius_tot + length_tot)*(M_PI*radius_tot + length_tot))); 44 } 45 46 static double 47 radius_from_volume(double radius, double thick_rim, double thick_face, double length) 48 { 49 const double volume_bicelle = form_volume(radius,thick_rim,thick_face,length); 50 return cbrt(volume_bicelle/M_4PI_3); 51 } 52 53 static double 54 radius_from_diagonal(double radius, double thick_rim, double thick_face, double length) 55 { 56 const double radius_tot = radius + thick_rim; 57 const double length_tot = length + 2.0*thick_face; 58 return sqrt(radius_tot*radius_tot + 0.25*length_tot*length_tot); 59 } 60 61 static double 62 effective_radius(int mode, double radius, double thick_rim, double thick_face, double length) 63 { 64 switch (mode) { 65 default: 66 case 1: // equivalent cylinder excluded volume 67 return radius_from_excluded_volume(radius, thick_rim, thick_face, length); 68 case 2: // equivalent sphere 69 return radius_from_volume(radius, thick_rim, thick_face, length); 70 case 3: // outer rim radius 71 return radius + thick_rim; 72 case 4: // half outer thickness 73 return 0.5*length + thick_face; 74 case 5: // half diagonal 75 return radius_from_diagonal(radius,thick_rim,thick_face,length); 76 } 77 } 78 79 static void 80 Fq(double q, 81 double *F1, 82 double *F2, 40 83 double radius, 41 84 double thick_radius, … … 51 94 const double halflength = 0.5*length; 52 95 53 double total = 0.0; 96 double total_F1 = 0.0; 97 double total_F2 = 0.0; 54 98 for(int i=0;i<GAUSS_N;i++) { 55 99 double theta = (GAUSS_Z[i] + 1.0)*uplim; 56 100 double sin_theta, cos_theta; // slots to hold sincos function output 57 101 SINCOS(theta, sin_theta, cos_theta); 58 double f q= bicelle_kernel(q*sin_theta, q*cos_theta, radius, thick_radius, thick_face,102 double form = bicelle_kernel(q*sin_theta, q*cos_theta, radius, thick_radius, thick_face, 59 103 halflength, sld_core, sld_face, sld_rim, sld_solvent); 60 total += GAUSS_W[i]*fq*fq*sin_theta; 104 total_F1 += GAUSS_W[i]*form*sin_theta; 105 total_F2 += GAUSS_W[i]*form*form*sin_theta; 61 106 } 107 // Correct for integration range 108 total_F1 *= uplim; 109 total_F2 *= uplim; 62 110 63 // calculate value of integral to return 64 double answer = total*uplim; 65 return 1.0e-4*answer; 111 *F1 = 1.0e-2*total_F1; 112 *F2 = 1.0e-4*total_F2; 66 113 } 67 114 -
sasmodels/models/core_shell_bicelle.py
r2d81cfe r99658f6 89 89 from Proquest <http://search.proquest.com/docview/304915826?accountid 90 90 =26379>`_ 91 92 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 91 93 92 94 Authorship and Verification … … 154 156 source = ["lib/sas_Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", 155 157 "core_shell_bicelle.c"] 158 have_Fq = True 159 effective_radius_type = [ 160 "excluded volume","equivalent volume sphere", "outer rim radius", 161 "half outer thickness", "half diagonal", 162 ] 156 163 157 164 def random(): -
sasmodels/models/core_shell_bicelle_elliptical.c
r108e70e r99658f6 11 11 12 12 static double 13 Iq(double q, 13 radius_from_excluded_volume(double r_minor, double x_core, double thick_rim, double thick_face, double length) 14 { 15 const double r_equiv = sqrt((r_minor + thick_rim)*(r_minor*x_core + thick_rim)); 16 const double length_tot = length + 2.0*thick_face; 17 return 0.5*cbrt(0.75*r_equiv*(2.0*r_equiv*length_tot + (r_equiv + length_tot)*(M_PI*r_equiv + length_tot))); 18 } 19 20 static double 21 radius_from_volume(double r_minor, double x_core, double thick_rim, double thick_face, double length) 22 { 23 const double volume_bicelle = form_volume(r_minor, x_core, thick_rim,thick_face,length); 24 return cbrt(volume_bicelle/M_4PI_3); 25 } 26 27 static double 28 radius_from_diagonal(double r_minor, double x_core, double thick_rim, double thick_face, double length) 29 { 30 const double radius_max = (x_core < 1.0 ? r_minor : x_core*r_minor); 31 const double radius_max_tot = radius_max + thick_rim; 32 const double length_tot = length + 2.0*thick_face; 33 return sqrt(radius_max_tot*radius_max_tot + 0.25*length_tot*length_tot); 34 } 35 36 static double 37 effective_radius(int mode, double r_minor, double x_core, double thick_rim, double thick_face, double length) 38 { 39 switch (mode) { 40 default: 41 case 1: // equivalent cylinder excluded volume 42 return radius_from_excluded_volume(r_minor, x_core, thick_rim, thick_face, length); 43 case 2: // equivalent volume sphere 44 return radius_from_volume(r_minor, x_core, thick_rim, thick_face, length); 45 case 3: // outer rim average radius 46 return 0.5*r_minor*(1.0 + x_core) + thick_rim; 47 case 4: // outer rim min radius 48 return (x_core < 1.0 ? x_core*r_minor+thick_rim : r_minor+thick_rim); 49 case 5: // outer max radius 50 return (x_core > 1.0 ? x_core*r_minor+thick_rim : r_minor+thick_rim); 51 case 6: // half outer thickness 52 return 0.5*length + thick_face; 53 case 7: // half diagonal 54 return radius_from_diagonal(r_minor,x_core,thick_rim,thick_face,length); 55 } 56 } 57 58 static void 59 Fq(double q, 60 double *F1, 61 double *F2, 14 62 double r_minor, 15 63 double x_core, … … 36 84 37 85 //initialize integral 38 double outer_sum = 0.0; 86 double outer_total_F1 = 0.0; 87 double outer_total_F2 = 0.0; 39 88 for(int i=0;i<GAUSS_N;i++) { 40 89 //setup inner integral over the ellipsoidal cross-section 41 //const double va = 0.0;42 //const double vb = 1.0;43 90 //const double cos_theta = ( GAUSS_Z[i]*(vb-va) + va + vb )/2.0; 44 91 const double cos_theta = ( GAUSS_Z[i] + 1.0 )/2.0; … … 48 95 const double si1 = sas_sinx_x(halfheight*qc); 49 96 const double si2 = sas_sinx_x((halfheight+thick_face)*qc); 50 double inner_sum=0.0; 97 double inner_total_F1 = 0; 98 double inner_total_F2 = 0; 51 99 for(int j=0;j<GAUSS_N;j++) { 52 100 //76 gauss points for the inner integral (WAS 20 points,so this may make unecessarily slow, but playing safe) 53 // inner integral limits 54 //const double vaj=0.0; 55 //const double vbj=M_PI; 56 //const double phi = ( GAUSS_Z[j]*(vbj-vaj) + vaj + vbj )/2.0; 57 const double phi = ( GAUSS_Z[j] +1.0)*M_PI_2; 58 const double rr = sqrt(r2A - r2B*cos(phi)); 101 //const double beta = ( GAUSS_Z[j]*(vbj-vaj) + vaj + vbj )/2.0; 102 const double beta = ( GAUSS_Z[j] +1.0)*M_PI_2; 103 const double rr = sqrt(r2A - r2B*cos(beta)); 59 104 const double be1 = sas_2J1x_x(rr*qab); 60 105 const double be2 = sas_2J1x_x((rr+thick_rim)*qab); 61 const double f q= dr1*si1*be1 + dr2*si2*be2 + dr3*si2*be1;106 const double f = dr1*si1*be1 + dr2*si2*be2 + dr3*si2*be1; 62 107 63 inner_sum += GAUSS_W[j] * fq * fq; 108 inner_total_F1 += GAUSS_W[j] * f; 109 inner_total_F2 += GAUSS_W[j] * f * f; 64 110 } 65 111 //now calculate outer integral 66 outer_sum += GAUSS_W[i] * inner_sum; 112 outer_total_F1 += GAUSS_W[i] * inner_total_F1; 113 outer_total_F2 += GAUSS_W[i] * inner_total_F2; 67 114 } 115 // now complete change of integration variables (1-0)/(1-(-1))= 0.5 116 outer_total_F1 *= 0.25; 117 outer_total_F2 *= 0.25; 68 118 69 return outer_sum*2.5e-05; 119 //convert from [1e-12 A-1] to [cm-1] 120 *F1 = 1e-2*outer_total_F1; 121 *F2 = 1e-4*outer_total_F2; 70 122 } 71 123 -
sasmodels/models/core_shell_bicelle_elliptical.py
rfc3ae1b r99658f6 100 100 101 101 .. [#] 102 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 102 103 103 104 Authorship and Verification … … 146 147 source = ["lib/sas_Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", 147 148 "core_shell_bicelle_elliptical.c"] 149 have_Fq = True 150 effective_radius_type = [ 151 "equivalent cylinder excluded volume", "equivalent volume sphere", "outer rim average radius", "outer rim min radius", 152 "outer max radius", "half outer thickness", "half diagonal", 153 ] 148 154 149 155 def random(): … … 179 185 180 186 tests = [ 181 [{'radius': 30.0, 'x_core': 3.0,182 'thick_rim': 8.0, 'thick_face': 14.0, 'length': 50.0}, 'ER', 1],183 [{'radius': 30.0, 'x_core': 3.0,184 'thick_rim': 8.0, 'thick_face': 14.0, 'length': 50.0}, 'VR', 1],187 #[{'radius': 30.0, 'x_core': 3.0, 188 # 'thick_rim': 8.0, 'thick_face': 14.0, 'length': 50.0}, 'ER', 1], 189 #[{'radius': 30.0, 'x_core': 3.0, 190 # 'thick_rim': 8.0, 'thick_face': 14.0, 'length': 50.0}, 'VR', 1], 185 191 186 192 [{'radius': 30.0, 'x_core': 3.0, -
sasmodels/models/core_shell_bicelle_elliptical_belt_rough.c
r108e70e r99658f6 12 12 13 13 static double 14 Iq(double q, 14 radius_from_excluded_volume(double r_minor, double x_core, double thick_rim, double thick_face, double length) 15 { 16 const double r_equiv = sqrt((r_minor + thick_rim)*(r_minor*x_core + thick_rim)); 17 const double length_tot = length + 2.0*thick_face; 18 return 0.5*cbrt(0.75*r_equiv*(2.0*r_equiv*length_tot + (r_equiv + length_tot)*(M_PI*r_equiv + length_tot))); 19 } 20 21 static double 22 radius_from_volume(double r_minor, double x_core, double thick_rim, double thick_face, double length) 23 { 24 const double volume_bicelle = form_volume(r_minor, x_core, thick_rim,thick_face,length); 25 return cbrt(volume_bicelle/M_4PI_3); 26 } 27 28 static double 29 radius_from_diagonal(double r_minor, double x_core, double thick_rim, double thick_face, double length) 30 { 31 const double radius_max = (x_core < 1.0 ? r_minor : x_core*r_minor); 32 const double radius_max_tot = radius_max + thick_rim; 33 const double length_tot = length + 2.0*thick_face; 34 return sqrt(radius_max_tot*radius_max_tot + 0.25*length_tot*length_tot); 35 } 36 37 static double 38 effective_radius(int mode, double r_minor, double x_core, double thick_rim, double thick_face, double length) 39 { 40 switch (mode) { 41 default: 42 case 1: // equivalent cylinder excluded volume 43 return radius_from_excluded_volume(r_minor, x_core, thick_rim, thick_face, length); 44 case 2: // equivalent sphere 45 return radius_from_volume(r_minor, x_core, thick_rim, thick_face, length); 46 case 3: // outer rim average radius 47 return 0.5*r_minor*(1.0 + x_core) + thick_rim; 48 case 4: // outer rim min radius 49 return (x_core < 1.0 ? x_core*r_minor+thick_rim : r_minor+thick_rim); 50 case 5: // outer max radius 51 return (x_core > 1.0 ? x_core*r_minor+thick_rim : r_minor+thick_rim); 52 case 6: // half outer thickness 53 return 0.5*length + thick_face; 54 case 7: // half diagonal (this ignores the missing "corners", so may give unexpected answer if thick_face 55 // or thick_rim is extremely large) 56 return radius_from_diagonal(r_minor,x_core,thick_rim,thick_face,length); 57 } 58 } 59 60 static void 61 Fq(double q, 62 double *F1, 63 double *F2, 15 64 double r_minor, 16 65 double x_core, … … 24 73 double sigma) 25 74 { 26 double si1,si2,be1,be2;27 75 // core_shell_bicelle_elliptical_belt, RKH 5th Oct 2017, core_shell_bicelle_elliptical 28 76 // tested briefly against limiting cases of cylinder, hollow cylinder & elliptical cylinder models … … 38 86 const double r2A = 0.5*(square(r_major) + square(r_minor)); 39 87 const double r2B = 0.5*(square(r_major) - square(r_minor)); 88 const double vol1 = M_PI*r_minor*r_major*(2.0*halfheight); 89 const double vol2 = M_PI*(r_minor+thick_rim)*(r_major+thick_rim)*2.0*halfheight; 90 const double vol3 = M_PI*r_minor*r_major*2.0*(halfheight+thick_face); 40 91 // dr1,2,3 are now for Vcore, Vcore+rim, Vcore+face, 41 const double dr1 = (-rhor - rhoh + rhoc + rhosolv) *M_PI*r_minor*r_major* 42 2.0*halfheight; 43 const double dr2 = (rhor-rhosolv) *M_PI*(r_minor+thick_rim)*( 44 r_major+thick_rim)* 2.0*halfheight; 45 const double dr3 = (rhoh-rhosolv) *M_PI*r_minor*r_major* 46 2.0*(halfheight+thick_face); 92 const double dr1 = vol1*(-rhor - rhoh + rhoc + rhosolv); 93 const double dr2 = vol2*(rhor-rhosolv); 94 const double dr3 = vol3*(rhoh-rhosolv); 95 47 96 //initialize integral 48 double outer_sum = 0.0; 97 double outer_total_F1 = 0.0; 98 double outer_total_F2 = 0.0; 49 99 for(int i=0;i<GAUSS_N;i++) { 50 100 //setup inner integral over the ellipsoidal cross-section 51 101 // since we generate these lots of times, why not store them somewhere? 52 //const double cos_alpha = ( GAUSS_Z[i]*(vb-va) + va + vb )/2.0; 53 const double cos_alpha = ( GAUSS_Z[i] + 1.0 )/2.0; 54 const double sin_alpha = sqrt(1.0 - cos_alpha*cos_alpha); 55 double inner_sum=0; 56 double sinarg1 = q*halfheight*cos_alpha; 57 double sinarg2 = q*(halfheight+thick_face)*cos_alpha; 58 si1 = sas_sinx_x(sinarg1); 59 si2 = sas_sinx_x(sinarg2); 102 //const double cos_theta = ( GAUSS_Z[i]*(vb-va) + va + vb )/2.0; 103 const double cos_theta = ( GAUSS_Z[i] + 1.0 )/2.0; 104 const double sin_theta = sqrt(1.0 - cos_theta*cos_theta); 105 const double qab = q*sin_theta; 106 const double qc = q*cos_theta; 107 const double si1 = sas_sinx_x(halfheight*qc); 108 const double si2 = sas_sinx_x((halfheight+thick_face)*qc); 109 double inner_total_F1 = 0; 110 double inner_total_F2 = 0; 60 111 for(int j=0;j<GAUSS_N;j++) { 61 112 //76 gauss points for the inner integral (WAS 20 points,so this may make unecessarily slow, but playing safe) … … 63 114 const double beta = ( GAUSS_Z[j] +1.0)*M_PI_2; 64 115 const double rr = sqrt(r2A - r2B*cos(beta)); 65 double besarg1 = q*rr*sin_alpha; 66 double besarg2 = q*(rr+thick_rim)*sin_alpha; 67 be1 = sas_2J1x_x(besarg1); 68 be2 = sas_2J1x_x(besarg2); 69 inner_sum += GAUSS_W[j] *square(dr1*si1*be1 + 70 dr2*si1*be2 + 71 dr3*si2*be1); 116 const double be1 = sas_2J1x_x(rr*qab); 117 const double be2 = sas_2J1x_x((rr+thick_rim)*qab); 118 const double f = dr1*si1*be1 + dr2*si1*be2 + dr3*si2*be1; 119 120 inner_total_F1 += GAUSS_W[j] * f; 121 inner_total_F2 += GAUSS_W[j] * f * f; 72 122 } 73 123 //now calculate outer integral 74 outer_sum += GAUSS_W[i] * inner_sum; 124 outer_total_F1 += GAUSS_W[i] * inner_total_F1; 125 outer_total_F2 += GAUSS_W[i] * inner_total_F2; 75 126 } 127 // now complete change of integration variables (1-0)/(1-(-1))= 0.5 128 outer_total_F1 *= 0.25; 129 outer_total_F2 *= 0.25; 76 130 77 return outer_sum*2.5e-05*exp(-0.5*square(q*sigma)); 131 //convert from [1e-12 A-1] to [cm-1] 132 *F1 = 1e-2*outer_total_F1*exp(-0.25*square(q*sigma)); 133 *F2 = 1e-4*outer_total_F2*exp(-0.5*square(q*sigma)); 78 134 } 79 135 … … 111 167 const double si1 = sas_sinx_x( halfheight*qc ); 112 168 const double si2 = sas_sinx_x( (halfheight + thick_face)*qc ); 113 const double Aq = square( vol1*dr1*si1*be1 + vol2*dr2*si1*be2 + vol3*dr3*si2*be1); 114 return 1.0e-4 * Aq*exp(-0.5*(square(qa) + square(qb) + square(qc) )*square(sigma)); 169 const double fq = vol1*dr1*si1*be1 + vol2*dr2*si1*be2 + vol3*dr3*si2*be1; 170 const double atten = exp(-0.5*(qa*qa + qb*qb + qc*qc)*(sigma*sigma)); 171 return 1.0e-4 * fq*fq * atten; 115 172 } -
sasmodels/models/core_shell_bicelle_elliptical_belt_rough.py
rfc3ae1b r99658f6 112 112 113 113 .. [#] 114 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 114 115 115 116 Authorship and Verification … … 159 160 source = ["lib/sas_Si.c", "lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", 160 161 "core_shell_bicelle_elliptical_belt_rough.c"] 162 have_Fq = True 163 effective_radius_type = [ 164 "equivalent cylinder excluded volume", "equivalent volume sphere", "outer rim average radius", "outer rim min radius", 165 "outer max radius", "half outer thickness", "half diagonal", 166 ] 161 167 162 168 demo = dict(scale=1, background=0, … … 181 187 182 188 tests = [ 183 [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'ER', 1],184 [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'VR', 1],189 #[{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'ER', 1], 190 #[{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0}, 'VR', 1], 185 191 186 192 [{'radius': 30.0, 'x_core': 3.0, 'thick_rim':8.0, 'thick_face':14.0, 'length':50.0, -
sasmodels/models/core_shell_cylinder.c
rd86f0fc r99658f6 14 14 15 15 static double 16 Iq(double q, 16 radius_from_excluded_volume(double radius, double thickness, double length) 17 { 18 const double radius_tot = radius + thickness; 19 const double length_tot = length + 2.0*thickness; 20 return 0.5*cbrt(0.75*radius_tot*(2.0*radius_tot*length_tot + (radius_tot + length_tot)*(M_PI*radius_tot + length_tot))); 21 } 22 23 static double 24 radius_from_volume(double radius, double thickness, double length) 25 { 26 const double volume_outer_cyl = form_volume(radius,thickness,length); 27 return cbrt(volume_outer_cyl/M_4PI_3); 28 } 29 30 static double 31 radius_from_diagonal(double radius, double thickness, double length) 32 { 33 const double radius_outer = radius + thickness; 34 const double length_outer = length + 2.0*thickness; 35 return sqrt(radius_outer*radius_outer + 0.25*length_outer*length_outer); 36 } 37 38 static double 39 effective_radius(int mode, double radius, double thickness, double length) 40 { 41 switch (mode) { 42 default: 43 case 1: //cylinder excluded volume 44 return radius_from_excluded_volume(radius, thickness, length); 45 case 2: // equivalent volume sphere 46 return radius_from_volume(radius, thickness, length); 47 case 3: // outer radius 48 return radius + thickness; 49 case 4: // half outer length 50 return 0.5*length + thickness; 51 case 5: // half min outer length 52 return (radius < 0.5*length ? radius + thickness : 0.5*length + thickness); 53 case 6: // half max outer length 54 return (radius > 0.5*length ? radius + thickness : 0.5*length + thickness); 55 case 7: // half outer diagonal 56 return radius_from_diagonal(radius,thickness,length); 57 } 58 } 59 60 static void 61 Fq(double q, 62 double *F1, 63 double *F2, 17 64 double core_sld, 18 65 double shell_sld, … … 29 76 const double shell_h = (0.5*length + thickness); 30 77 const double shell_vd = form_volume(radius,thickness,length) * (shell_sld-solvent_sld); 31 double total = 0.0; 78 double total_F1 = 0.0; 79 double total_F2 = 0.0; 32 80 for (int i=0; i<GAUSS_N ;i++) { 33 81 // translate a point in [-1,1] to a point in [0, pi/2] … … 40 88 const double fq = _cyl(core_vd, core_r*qab, core_h*qc) 41 89 + _cyl(shell_vd, shell_r*qab, shell_h*qc); 42 total += GAUSS_W[i] * fq * fq * sin_theta; 90 total_F1 += GAUSS_W[i] * fq * sin_theta; 91 total_F2 += GAUSS_W[i] * fq * fq * sin_theta; 43 92 } 44 93 // translate dx in [-1,1] to dx in [lower,upper] 45 94 //const double form = (upper-lower)/2.0*total; 46 return 1.0e-4 * total * M_PI_4; 95 *F1 = 1.0e-2 * total_F1 * M_PI_4; 96 *F2 = 1.0e-4 * total_F2 * M_PI_4; 47 97 } 48 49 98 50 99 static double -
sasmodels/models/core_shell_cylinder.py
re31b19a r99658f6 70 70 1445-1452 71 71 .. [#kline] S R Kline, *J Appl. Cryst.*, 39 (2006) 895 72 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 72 73 73 74 Authorship and Verification … … 131 132 132 133 source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "core_shell_cylinder.c"] 133 134 def ER(radius, thickness, length): 135 """ 136 Returns the effective radius used in the S*P calculation 137 """ 138 radius = radius + thickness 139 length = length + 2 * thickness 140 ddd = 0.75 * radius * (2 * radius * length + (length + radius) * (length + pi * radius)) 141 return 0.5 * (ddd) ** (1. / 3.) 134 have_Fq = True 135 effective_radius_type = [ 136 "excluded volume", "equivalent volume sphere", "outer radius", "half outer length", 137 "half min outer dimension", "half max outer dimension", "half outer diagonal", 138 ] 142 139 143 140 def random(): -
sasmodels/models/core_shell_ellipsoid.c
r108e70e r99658f6 39 39 40 40 static double 41 Iq(double q, 41 radius_from_volume(double radius_equat_core, double x_core, double thick_shell, double x_polar_shell) 42 { 43 const double volume_ellipsoid = form_volume(radius_equat_core, x_core, thick_shell, x_polar_shell); 44 return cbrt(volume_ellipsoid/M_4PI_3); 45 } 46 47 static double 48 radius_from_curvature(double radius_equat_core, double x_core, double thick_shell, double x_polar_shell) 49 { 50 // Trivial cases 51 if (1.0 == x_core && 1.0 == x_polar_shell) return radius_equat_core + thick_shell; 52 if ((radius_equat_core + thick_shell)*(radius_equat_core*x_core + thick_shell*x_polar_shell) == 0.) return 0.; 53 54 // see equation (26) in A.Isihara, J.Chem.Phys. 18(1950)1446-1449 55 const double radius_equat_tot = radius_equat_core + thick_shell; 56 const double radius_polar_tot = radius_equat_core*x_core + thick_shell*x_polar_shell; 57 const double ratio = (radius_polar_tot < radius_equat_tot 58 ? radius_polar_tot / radius_equat_tot 59 : radius_equat_tot / radius_polar_tot); 60 const double e1 = sqrt(1.0 - ratio*ratio); 61 const double b1 = 1.0 + asin(e1) / (e1 * ratio); 62 const double bL = (1.0 + e1) / (1.0 - e1); 63 const double b2 = 1.0 + 0.5 * ratio * ratio / e1 * log(bL); 64 const double delta = 0.75 * b1 * b2; 65 const double ddd = 2.0 * (delta + 1.0) * radius_polar_tot * radius_equat_tot * radius_equat_tot; 66 return 0.5 * cbrt(ddd); 67 } 68 69 static double 70 effective_radius(int mode, double radius_equat_core, double x_core, double thick_shell, double x_polar_shell) 71 { 72 const double radius_equat_tot = radius_equat_core + thick_shell; 73 const double radius_polar_tot = radius_equat_core*x_core + thick_shell*x_polar_shell; 74 75 switch (mode) { 76 default: 77 case 1: // average outer curvature 78 return radius_from_curvature(radius_equat_core, x_core, thick_shell, x_polar_shell); 79 case 2: // equivalent volume sphere 80 return radius_from_volume(radius_equat_core, x_core, thick_shell, x_polar_shell); 81 case 3: // min outer radius 82 return (radius_polar_tot < radius_equat_tot ? radius_polar_tot : radius_equat_tot); 83 case 4: // max outer radius 84 return (radius_polar_tot > radius_equat_tot ? radius_polar_tot : radius_equat_tot); 85 } 86 } 87 88 static void 89 Fq(double q, 90 double *F1, 91 double *F2, 42 92 double radius_equat_core, 43 93 double x_core, … … 58 108 const double m = 0.5; 59 109 const double b = 0.5; 60 double total = 0.0; //initialize intergral 110 double total_F1 = 0.0; //initialize intergral 111 double total_F2 = 0.0; //initialize intergral 61 112 for(int i=0;i<GAUSS_N;i++) { 62 113 const double cos_theta = GAUSS_Z[i]*m + b; … … 66 117 equat_shell, polar_shell, 67 118 sld_core_shell, sld_shell_solvent); 68 total += GAUSS_W[i] * fq * fq; 119 total_F1 += GAUSS_W[i] * fq; 120 total_F2 += GAUSS_W[i] * fq * fq; 69 121 } 70 total *= m; 122 total_F1 *= m; 123 total_F2 *= m; 71 124 72 125 // convert to [cm-1] 73 return 1.0e-4 * total; 126 *F1 = 1.0e-2 * total_F1; 127 *F2 = 1.0e-4 * total_F2; 74 128 } 129 75 130 76 131 static double -
sasmodels/models/core_shell_ellipsoid.py
r2d81cfe r99658f6 145 145 146 146 source = ["lib/sas_3j1x_x.c", "lib/gauss76.c", "core_shell_ellipsoid.c"] 147 148 def ER(radius_equat_core, x_core, thick_shell, x_polar_shell): 149 """ 150 Returns the effective radius used in the S*P calculation 151 """ 152 from .ellipsoid import ER as ellipsoid_ER 153 polar_outer = radius_equat_core*x_core + thick_shell*x_polar_shell 154 equat_outer = radius_equat_core + thick_shell 155 return ellipsoid_ER(polar_outer, equat_outer) 147 have_Fq = True 148 effective_radius_type = [ 149 "average outer curvature", "equivalent volume sphere", 150 "min outer radius", "max outer radius", 151 ] 156 152 157 153 def random(): -
sasmodels/models/core_shell_parallelepiped.c
rdbf1a60 r99658f6 28 28 29 29 static double 30 Iq(double q, 30 radius_from_excluded_volume(double length_a, double length_b, double length_c, 31 double thick_rim_a, double thick_rim_b, double thick_rim_c) 32 { 33 double r_equiv, length; 34 double lengths[3] = {length_a+thick_rim_a, length_b+thick_rim_b, length_c+thick_rim_c}; 35 double lengthmax = fmax(lengths[0],fmax(lengths[1],lengths[2])); 36 double length_1 = lengthmax; 37 double length_2 = lengthmax; 38 double length_3 = lengthmax; 39 40 for(int ilen=0; ilen<3; ilen++) { 41 if (lengths[ilen] < length_1) { 42 length_2 = length_1; 43 length_1 = lengths[ilen]; 44 } else { 45 if (lengths[ilen] < length_2) { 46 length_2 = lengths[ilen]; 47 } 48 } 49 } 50 if(length_2-length_1 > length_3-length_2) { 51 r_equiv = sqrt(length_2*length_3/M_PI); 52 length = length_1; 53 } else { 54 r_equiv = sqrt(length_1*length_2/M_PI); 55 length = length_3; 56 } 57 58 return 0.5*cbrt(0.75*r_equiv*(2.0*r_equiv*length + (r_equiv + length)*(M_PI*r_equiv + length))); 59 } 60 61 static double 62 radius_from_volume(double length_a, double length_b, double length_c, 63 double thick_rim_a, double thick_rim_b, double thick_rim_c) 64 { 65 const double volume = form_volume(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c); 66 return cbrt(volume/M_4PI_3); 67 } 68 69 static double 70 radius_from_crosssection(double length_a, double length_b, double thick_rim_a, double thick_rim_b) 71 { 72 const double area_xsec_paral = length_a*length_b + 2.0*thick_rim_a*length_b + 2.0*thick_rim_b*length_a; 73 return sqrt(area_xsec_paral/M_PI); 74 } 75 76 static double 77 effective_radius(int mode, double length_a, double length_b, double length_c, 78 double thick_rim_a, double thick_rim_b, double thick_rim_c) 79 { 80 switch (mode) { 81 default: 82 case 1: // equivalent cylinder excluded volume 83 return radius_from_excluded_volume(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c); 84 case 2: // equivalent volume sphere 85 return radius_from_volume(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c); 86 case 3: // half outer length a 87 return 0.5 * length_a + thick_rim_a; 88 case 4: // half outer length b 89 return 0.5 * length_b + thick_rim_b; 90 case 5: // half outer length c 91 return 0.5 * length_c + thick_rim_c; 92 case 6: // equivalent circular cross-section 93 return radius_from_crosssection(length_a, length_b, thick_rim_a, thick_rim_b); 94 case 7: // half outer ab diagonal 95 return 0.5*sqrt(square(length_a+ 2.0*thick_rim_a) + square(length_b+ 2.0*thick_rim_b)); 96 case 8: // half outer diagonal 97 return 0.5*sqrt(square(length_a+ 2.0*thick_rim_a) + square(length_b+ 2.0*thick_rim_b) + square(length_c+ 2.0*thick_rim_c)); 98 } 99 } 100 101 static void 102 Fq(double q, 103 double *F1, 104 double *F2, 31 105 double core_sld, 32 106 double arim_sld, … … 60 134 // outer integral (with gauss points), integration limits = 0, 1 61 135 // substitute d_cos_alpha for sin_alpha d_alpha 62 double outer_sum = 0; //initialize integral 136 double outer_sum_F1 = 0; //initialize integral 137 double outer_sum_F2 = 0; //initialize integral 63 138 for( int i=0; i<GAUSS_N; i++) { 64 139 const double cos_alpha = 0.5 * ( GAUSS_Z[i] + 1.0 ); … … 69 144 // inner integral (with gauss points), integration limits = 0, 1 70 145 // substitute beta = PI/2 u (so 2/PI * d_(PI/2 * beta) = d_beta) 71 double inner_sum = 0.0; 146 double inner_sum_F1 = 0.0; 147 double inner_sum_F2 = 0.0; 72 148 for(int j=0; j<GAUSS_N; j++) { 73 149 const double u = 0.5 * ( GAUSS_Z[j] + 1.0 ); … … 91 167 #endif 92 168 93 inner_sum += GAUSS_W[j] * f * f; 169 inner_sum_F1 += GAUSS_W[j] * f; 170 inner_sum_F2 += GAUSS_W[j] * f * f; 94 171 } 95 172 // now complete change of inner integration variable (1-0)/(1-(-1))= 0.5 96 inner_sum *= 0.5;97 // now sum up the outer integral98 outer_sum += GAUSS_W[i] * inner_sum;173 // and sum up the outer integral 174 outer_sum_F1 += GAUSS_W[i] * inner_sum_F1 * 0.5; 175 outer_sum_F2 += GAUSS_W[i] * inner_sum_F2 * 0.5; 99 176 } 100 177 // now complete change of outer integration variable (1-0)/(1-(-1))= 0.5 101 outer_sum *= 0.5; 178 outer_sum_F1 *= 0.5; 179 outer_sum_F2 *= 0.5; 102 180 103 181 //convert from [1e-12 A-1] to [cm-1] 104 return 1.0e-4 * outer_sum; 182 *F1 = 1.0e-2 * outer_sum_F1; 183 *F2 = 1.0e-4 * outer_sum_F2; 105 184 } 106 185 -
sasmodels/models/core_shell_parallelepiped.py
rf89ec96 r99658f6 98 98 is the scattering length of the solvent. 99 99 100 .. note:: 100 .. note:: 101 101 102 102 the code actually implements two substitutions: $d(cos\alpha)$ is … … 173 173 from Proquest <http://search.proquest.com/docview/304915826?accountid 174 174 =26379>`_ 175 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 175 176 176 177 Authorship and Verification … … 226 227 227 228 source = ["lib/gauss76.c", "core_shell_parallelepiped.c"] 228 229 230 def ER(length_a, length_b, length_c, thick_rim_a, thick_rim_b, thick_rim_c): 231 """ 232 Return equivalent radius (ER) 233 """ 234 from .parallelepiped import ER as ER_p 235 236 a = length_a + 2*thick_rim_a 237 b = length_b + 2*thick_rim_b 238 c = length_c + 2*thick_rim_c 239 return ER_p(a, b, c) 240 241 # VR defaults to 1.0 229 have_Fq = True 230 effective_radius_type = [ 231 "equivalent cylinder excluded volume", 232 "equivalent volume sphere", 233 "half outer length_a", "half outer length_b", "half outer length_c", 234 "equivalent circular cross-section", 235 "half outer ab diagonal", "half outer diagonal", 236 ] 242 237 243 238 def random(): -
sasmodels/models/core_shell_sphere.c
r3a48772 rd42dd4a 1 double form_volume(double radius, double thickness); 2 double Iq(double q, double radius, double thickness, double core_sld, double shell_sld, double solvent_sld); 1 static double 2 form_volume(double radius, double thickness) 3 { 4 return M_4PI_3 * cube(radius + thickness); 5 } 3 6 4 double Iq(double q, double radius, double thickness, double core_sld, double shell_sld, double solvent_sld) { 7 static double 8 effective_radius(int mode, double radius, double thickness) 9 { 10 switch (mode) { 11 default: 12 case 1: // outer radius 13 return radius + thickness; 14 case 2: // core radius 15 return radius; 16 } 17 } 5 18 6 7 double intensity = core_shell_kernel(q, 19 static void 20 Fq(double q, double *F1, double *F2, double radius, 21 double thickness, double core_sld, double shell_sld, double solvent_sld) { 22 double form = core_shell_fq(q, 8 23 radius, 9 24 thickness, … … 11 26 shell_sld, 12 27 solvent_sld); 13 return intensity; 28 *F1 = 1.0e-2*form; 29 *F2 = 1.0e-4*form*form; 14 30 } 15 16 double form_volume(double radius, double thickness)17 {18 return M_4PI_3 * cube(radius + thickness);19 } -
sasmodels/models/core_shell_sphere.py
rda1c8d1 r304c775 58 58 title = "Form factor for a monodisperse spherical particle with particle with a core-shell structure." 59 59 description = """ 60 F ^2(q) = 3/V_s [V_c (sld_core-sld_shell)(sin(q*radius)-q*radius*cos(q*radius))/(q*radius)^361 + V_s (sld_shell-sld_solvent)(sin(q*r_s)-q*r_s*cos(q*r_s))/(q*r_s)^3]60 F(q) = [V_c (sld_core-sld_shell) 3 (sin(q*radius)-q*radius*cos(q*radius))/(q*radius)^3 61 + V_s (sld_shell-sld_solvent) 3 (sin(q*r_s)-q*r_s*cos(q*r_s))/(q*r_s)^3] 62 62 63 63 V_s: Volume of the sphere shell … … 77 77 78 78 source = ["lib/sas_3j1x_x.c", "lib/core_shell.c", "core_shell_sphere.c"] 79 have_Fq = True 80 effective_radius_type = ["outer radius", "core radius"] 79 81 80 82 demo = dict(scale=1, background=0, radius=60, thickness=10, 81 83 sld_core=1.0, sld_shell=2.0, sld_solvent=0.0) 82 83 def ER(radius, thickness):84 """85 Equivalent radius86 @param radius: core radius87 @param thickness: shell thickness88 """89 return radius + thickness90 84 91 85 def random(): … … 102 96 103 97 tests = [ 104 [{'radius': 20.0, 'thickness': 10.0}, 'ER', 30.0],98 [{'radius': 20.0, 'thickness': 10.0}, 0.1, None, None, 30.0, 4.*pi/3*30**3, 1.0], 105 99 106 100 # The SasView test result was 0.00169, with a background of 0.001 -
sasmodels/models/cylinder.c
r108e70e r99658f6 8 8 9 9 static double 10 fq(double qab, double qc, double radius, double length)10 _fq(double qab, double qc, double radius, double length) 11 11 { 12 12 return sas_2J1x_x(qab*radius) * sas_sinx_x(qc*0.5*length); … … 14 14 15 15 static double 16 orient_avg_1D(double q, double radius, double length) 16 radius_from_excluded_volume(double radius, double length) 17 { 18 return 0.5*cbrt(0.75*radius*(2.0*radius*length + (radius + length)*(M_PI*radius + length))); 19 } 20 21 static double 22 radius_from_volume(double radius, double length) 23 { 24 return cbrt(M_PI*radius*radius*length/M_4PI_3); 25 } 26 27 static double 28 radius_from_diagonal(double radius, double length) 29 { 30 return sqrt(radius*radius + 0.25*length*length); 31 } 32 33 static double 34 effective_radius(int mode, double radius, double length) 35 { 36 switch (mode) { 37 default: 38 case 1: 39 return radius_from_excluded_volume(radius, length); 40 case 2: 41 return radius_from_volume(radius, length); 42 case 3: 43 return radius; 44 case 4: 45 return 0.5*length; 46 case 5: 47 return (radius < 0.5*length ? radius : 0.5*length); 48 case 6: 49 return (radius > 0.5*length ? radius : 0.5*length); 50 case 7: 51 return radius_from_diagonal(radius,length); 52 } 53 } 54 55 static void 56 Fq(double q, 57 double *F1, 58 double *F2, 59 double sld, 60 double solvent_sld, 61 double radius, 62 double length) 17 63 { 18 64 // translate a point in [-1,1] to a point in [0, pi/2] … … 20 66 const double zb = M_PI_4; 21 67 22 double total = 0.0; 68 double total_F1 = 0.0; 69 double total_F2 = 0.0; 23 70 for (int i=0; i<GAUSS_N ;i++) { 24 71 const double theta = GAUSS_Z[i]*zm + zb; … … 26 73 // theta (theta,phi) the projection of the cylinder on the detector plane 27 74 SINCOS(theta , sin_theta, cos_theta); 28 const double form = fq(q*sin_theta, q*cos_theta, radius, length); 29 total += GAUSS_W[i] * form * form * sin_theta; 75 const double form = _fq(q*sin_theta, q*cos_theta, radius, length); 76 total_F1 += GAUSS_W[i] * form * sin_theta; 77 total_F2 += GAUSS_W[i] * form * form * sin_theta; 30 78 } 31 79 // translate dx in [-1,1] to dx in [lower,upper] 32 return total*zm; 80 total_F1 *= zm; 81 total_F2 *= zm; 82 const double s = (sld - solvent_sld) * form_volume(radius, length); 83 *F1 = 1e-2 * s * total_F1; 84 *F2 = 1e-4 * s * s * total_F2; 33 85 } 34 86 35 static double 36 Iq(double q, 37 double sld, 38 double solvent_sld, 39 double radius, 40 double length) 41 { 42 const double s = (sld - solvent_sld) * form_volume(radius, length); 43 return 1.0e-4 * s * s * orient_avg_1D(q, radius, length); 44 } 87 45 88 46 89 static double … … 51 94 double length) 52 95 { 96 const double form = _fq(qab, qc, radius, length); 53 97 const double s = (sld-solvent_sld) * form_volume(radius, length); 54 const double form = fq(qab, qc, radius, length);55 98 return 1.0e-4 * square(s * form); 56 99 } 100 -
sasmodels/models/cylinder.py
r2d81cfe r99658f6 98 98 J. S. Pedersen, Adv. Colloid Interface Sci. 70, 171-210 (1997). 99 99 G. Fournet, Bull. Soc. Fr. Mineral. Cristallogr. 74, 39-113 (1951). 100 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 100 101 """ 101 102 … … 138 139 139 140 source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "cylinder.c"] 140 141 def ER(radius, length): 142 """ 143 Return equivalent radius (ER) 144 """ 145 ddd = 0.75 * radius * (2 * radius * length + (length + radius) * (length + pi * radius)) 146 return 0.5 * (ddd) ** (1. / 3.) 141 have_Fq = True 142 effective_radius_type = [ 143 "excluded volume", "equivalent volume sphere", "radius", 144 "half length", "half min dimension", "half max dimension", "half diagonal", 145 ] 147 146 148 147 def random(): … … 169 168 phi_pd=10, phi_pd_n=5) 170 169 170 # pylint: disable=bad-whitespace, line-too-long 171 171 qx, qy = 0.2 * np.cos(2.5), 0.2 * np.sin(2.5) 172 172 # After redefinition of angles, find new tests values. Was 10 10 in old coords … … 182 182 ] 183 183 del qx, qy # not necessary to delete, but cleaner 184 185 # Default radius and length 186 radius, length = parameters[2][2], parameters[3][2] 187 tests.extend([ 188 ({'radius_effective_mode': 0}, 0.1, None, None, 0., pi*radius**2*length, 1.0), 189 ({'radius_effective_mode': 1}, 0.1, None, None, 0.5*(0.75*radius*(2.0*radius*length + (radius + length)*(pi*radius + length)))**(1./3.), None, None), 190 ({'radius_effective_mode': 2}, 0.1, None, None, (0.75*radius**2*length)**(1./3.), None, None), 191 ({'radius_effective_mode': 3}, 0.1, None, None, radius, None, None), 192 ({'radius_effective_mode': 4}, 0.1, None, None, length/2., None, None), 193 ({'radius_effective_mode': 5}, 0.1, None, None, min(radius, length/2.), None, None), 194 ({'radius_effective_mode': 6}, 0.1, None, None, max(radius, length/2.), None, None), 195 ({'radius_effective_mode': 7}, 0.1, None, None, np.sqrt(4*radius**2 + length**2)/2., None, None), 196 ]) 197 del radius, length 198 # pylint: enable=bad-whitespace, line-too-long 199 184 200 # ADDED by: RKH ON: 18Mar2016 renamed sld's etc -
sasmodels/models/dab.py
r2d81cfe r304c775 74 74 return pars 75 75 76 # ER defaults to 1.077 78 # VR defaults to 1.079 80 76 demo = dict(scale=1, background=0, cor_length=50) -
sasmodels/models/ellipsoid.c
r108e70e r99658f6 5 5 } 6 6 7 static double 8 Iq(double q, 7 static double 8 radius_from_volume(double radius_polar, double radius_equatorial) 9 { 10 return cbrt(radius_polar*radius_equatorial*radius_equatorial); 11 } 12 13 static double 14 radius_from_curvature(double radius_polar, double radius_equatorial) 15 { 16 // Trivial cases 17 if (radius_polar == radius_equatorial) return radius_polar; 18 if (radius_polar * radius_equatorial == 0.) return 0.; 19 20 // see equation (26) in A.Isihara, J.Chem.Phys. 18(1950)1446-1449 21 const double ratio = (radius_polar < radius_equatorial 22 ? radius_polar / radius_equatorial 23 : radius_equatorial / radius_polar); 24 const double e1 = sqrt(1.0 - ratio*ratio); 25 const double b1 = 1.0 + asin(e1) / (e1 * ratio); 26 const double bL = (1.0 + e1) / (1.0 - e1); 27 const double b2 = 1.0 + 0.5 * ratio * ratio / e1 * log(bL); 28 const double delta = 0.75 * b1 * b2; 29 const double ddd = 2.0 * (delta + 1.0) * radius_polar * radius_equatorial * radius_equatorial; 30 return 0.5 * cbrt(ddd); 31 } 32 33 static double 34 effective_radius(int mode, double radius_polar, double radius_equatorial) 35 { 36 switch (mode) { 37 default: 38 case 1: // average curvature 39 return radius_from_curvature(radius_polar, radius_equatorial); 40 case 2: // equivalent volume sphere 41 return radius_from_volume(radius_polar, radius_equatorial); 42 case 3: // min radius 43 return (radius_polar < radius_equatorial ? radius_polar : radius_equatorial); 44 case 4: // max radius 45 return (radius_polar > radius_equatorial ? radius_polar : radius_equatorial); 46 } 47 } 48 49 50 static void 51 Fq(double q, 52 double *F1, 53 double *F2, 9 54 double sld, 10 55 double sld_solvent, … … 20 65 // i(h) = int_0^1 Phi^2(h a sqrt(1 + u^2(v^2-1)) du 21 66 const double v_square_minus_one = square(radius_polar/radius_equatorial) - 1.0; 22 23 67 // translate a point in [-1,1] to a point in [0, 1] 24 68 // const double u = GAUSS_Z[i]*(upper-lower)/2 + (upper+lower)/2; 25 69 const double zm = 0.5; 26 70 const double zb = 0.5; 27 double total = 0.0; 71 double total_F2 = 0.0; 72 double total_F1 = 0.0; 28 73 for (int i=0;i<GAUSS_N;i++) { 29 74 const double u = GAUSS_Z[i]*zm + zb; 30 75 const double r = radius_equatorial*sqrt(1.0 + u*u*v_square_minus_one); 31 76 const double f = sas_3j1x_x(q*r); 32 total += GAUSS_W[i] * f * f; 77 total_F2 += GAUSS_W[i] * f * f; 78 total_F1 += GAUSS_W[i] * f; 33 79 } 34 80 // translate dx in [-1,1] to dx in [lower,upper] 35 const double form = total*zm; 81 total_F1 *= zm; 82 total_F2 *= zm; 36 83 const double s = (sld - sld_solvent) * form_volume(radius_polar, radius_equatorial); 37 return 1.0e-4 * s * s * form; 84 *F1 = 1e-2 * s * total_F1; 85 *F2 = 1e-4 * s * s * total_F2; 38 86 } 39 87 -
sasmodels/models/ellipsoid.py
r0168844 r99658f6 166 166 ] 167 167 168 168 169 source = ["lib/sas_3j1x_x.c", "lib/gauss76.c", "ellipsoid.c"] 169 170 def ER(radius_polar, radius_equatorial): 171 # see equation (26) in A.Isihara, J.Chem.Phys. 18(1950)1446-1449 172 ee = np.empty_like(radius_polar) 173 idx = radius_polar > radius_equatorial 174 ee[idx] = (radius_polar[idx] ** 2 - radius_equatorial[idx] ** 2) / radius_polar[idx] ** 2 175 idx = radius_polar < radius_equatorial 176 ee[idx] = (radius_equatorial[idx] ** 2 - radius_polar[idx] ** 2) / radius_equatorial[idx] ** 2 177 valid = (radius_polar * radius_equatorial != 0) & (radius_polar != radius_equatorial) 178 bd = 1.0 - ee[valid] 179 e1 = np.sqrt(ee[valid]) 180 b1 = 1.0 + np.arcsin(e1) / (e1 * np.sqrt(bd)) 181 bL = (1.0 + e1) / (1.0 - e1) 182 b2 = 1.0 + bd / 2 / e1 * np.log(bL) 183 delta = 0.75 * b1 * b2 184 ddd = 2.0 * (delta + 1.0) * (radius_polar * radius_equatorial**2)[valid] 185 186 r = np.zeros_like(radius_polar) 187 r[valid] = 0.5 * cbrt(ddd) 188 idx = radius_polar == radius_equatorial 189 r[idx] = radius_polar[idx] 190 return r 170 have_Fq = True 171 effective_radius_type = [ 172 "average curvature", "equivalent volume sphere", "min radius", "max radius", 173 ] 191 174 192 175 def random(): -
sasmodels/models/elliptical_cylinder.c
r108e70e r99658f6 6 6 7 7 static double 8 Iq(double q, double radius_minor, double r_ratio, double length, 8 radius_from_excluded_volume(double radius_minor, double r_ratio, double length) 9 { 10 const double r_equiv = sqrt(radius_minor*radius_minor*r_ratio); 11 return 0.5*cbrt(0.75*r_equiv*(2.0*r_equiv*length + (r_equiv + length)*(M_PI*r_equiv + length))); 12 } 13 14 static double 15 radius_from_volume(double radius_minor, double r_ratio, double length) 16 { 17 const double volume_ellcyl = form_volume(radius_minor,r_ratio,length); 18 return cbrt(volume_ellcyl/M_4PI_3); 19 } 20 21 static double 22 radius_from_min_dimension(double radius_minor, double r_ratio, double hlength) 23 { 24 const double rad_min = (r_ratio > 1.0 ? radius_minor : r_ratio*radius_minor); 25 return (rad_min < hlength ? rad_min : hlength); 26 } 27 28 static double 29 radius_from_max_dimension(double radius_minor, double r_ratio, double hlength) 30 { 31 const double rad_max = (r_ratio < 1.0 ? radius_minor : r_ratio*radius_minor); 32 return (rad_max > hlength ? rad_max : hlength); 33 } 34 35 static double 36 radius_from_diagonal(double radius_minor, double r_ratio, double length) 37 { 38 const double radius_max = (r_ratio > 1.0 ? radius_minor*r_ratio : radius_minor); 39 return sqrt(radius_max*radius_max + 0.25*length*length); 40 } 41 42 static double 43 effective_radius(int mode, double radius_minor, double r_ratio, double length) 44 { 45 switch (mode) { 46 default: 47 case 1: // equivalent cylinder excluded volume 48 return radius_from_excluded_volume(radius_minor, r_ratio, length); 49 case 2: // equivalent volume sphere 50 return radius_from_volume(radius_minor, r_ratio, length); 51 case 3: // average radius 52 return 0.5*radius_minor*(1.0 + r_ratio); 53 case 4: // min radius 54 return (r_ratio > 1.0 ? radius_minor : r_ratio*radius_minor); 55 case 5: // max radius 56 return (r_ratio < 1.0 ? radius_minor : r_ratio*radius_minor); 57 case 6: // equivalent circular cross-section 58 return sqrt(radius_minor*radius_minor*r_ratio); 59 case 7: // half length 60 return 0.5*length; 61 case 8: // half min dimension 62 return radius_from_min_dimension(radius_minor,r_ratio,0.5*length); 63 case 9: // half max dimension 64 return radius_from_max_dimension(radius_minor,r_ratio,0.5*length); 65 case 10: // half diagonal 66 return radius_from_diagonal(radius_minor,r_ratio,length); 67 } 68 } 69 70 static void 71 Fq(double q, double *F1, double *F2, double radius_minor, double r_ratio, double length, 9 72 double sld, double solvent_sld) 10 73 { … … 21 84 22 85 //initialize integral 23 double outer_sum = 0.0; 86 double outer_sum_F1 = 0.0; 87 double outer_sum_F2 = 0.0; 24 88 for(int i=0;i<GAUSS_N;i++) { 25 89 //setup inner integral over the ellipsoidal cross-section … … 27 91 const double sin_val = sqrt(1.0 - cos_val*cos_val); 28 92 //const double arg = radius_minor*sin_val; 29 double inner_sum=0; 93 double inner_sum_F1 = 0.0; 94 double inner_sum_F2 = 0.0; 30 95 for(int j=0;j<GAUSS_N;j++) { 31 96 const double theta = ( GAUSS_Z[j]*(vbj-vaj) + vaj + vbj )/2.0; 32 97 const double r = sin_val*sqrt(rA - rB*cos(theta)); 33 98 const double be = sas_2J1x_x(q*r); 34 inner_sum += GAUSS_W[j] * be * be; 99 inner_sum_F1 += GAUSS_W[j] * be; 100 inner_sum_F2 += GAUSS_W[j] * be * be; 35 101 } 36 102 //now calculate the value of the inner integral 37 inner_sum *= 0.5*(vbj-vaj); 103 inner_sum_F1 *= 0.5*(vbj-vaj); 104 inner_sum_F2 *= 0.5*(vbj-vaj); 38 105 39 106 //now calculate outer integral 40 107 const double si = sas_sinx_x(q*0.5*length*cos_val); 41 outer_sum += GAUSS_W[i] * inner_sum * si * si; 108 outer_sum_F1 += GAUSS_W[i] * inner_sum_F1 * si; 109 outer_sum_F2 += GAUSS_W[i] * inner_sum_F2 * si * si; 42 110 } 43 outer_sum *= 0.5*(vb-va); 44 45 //divide integral by Pi 46 const double form = outer_sum/M_PI; 111 // correct limits and divide integral by pi 112 outer_sum_F1 *= 0.5*(vb-va)/M_PI; 113 outer_sum_F2 *= 0.5*(vb-va)/M_PI; 47 114 48 115 // scale by contrast and volume, and convert to to 1/cm units 49 const double vol = form_volume(radius_minor, r_ratio, length); 50 const double delrho = sld - solvent_sld; 51 return 1.0e-4*square(delrho*vol)*form; 116 const double volume = form_volume(radius_minor, r_ratio, length); 117 const double contrast = sld - solvent_sld; 118 const double s = contrast*volume; 119 *F1 = 1.0e-2*s*outer_sum_F1; 120 *F2 = 1.0e-4*s*s*outer_sum_F2; 52 121 } 53 122 … … 63 132 const double be = sas_2J1x_x(qr); 64 133 const double si = sas_sinx_x(qc*0.5*length); 65 const double Aq = be * si;66 const double delrho= sld - solvent_sld;67 const double vol = form_volume(radius_minor, r_ratio, length);68 return 1.0e-4 * square( delrho * vol * Aq);134 const double fq = be * si; 135 const double contrast = sld - solvent_sld; 136 const double volume = form_volume(radius_minor, r_ratio, length); 137 return 1.0e-4 * square(contrast * volume * fq); 69 138 } -
sasmodels/models/elliptical_cylinder.py
ra261a83 r99658f6 88 88 L A Feigin and D I Svergun, *Structure Analysis by Small-Angle X-Ray and 89 89 Neutron Scattering*, Plenum, New York, (1987) [see table 3.4] 90 L. Onsager, Ann. New York Acad. Sci. 51, 627-659 (1949). 90 91 91 92 Authorship and Verification … … 122 123 123 124 source = ["lib/polevl.c", "lib/sas_J1.c", "lib/gauss76.c", "elliptical_cylinder.c"] 125 have_Fq = True 126 effective_radius_type = [ 127 "equivalent cylinder excluded volume", "equivalent volume sphere", "average radius", "min radius", "max radius", 128 "equivalent circular cross-section", 129 "half length", "half min dimension", "half max dimension", "half diagonal", 130 ] 124 131 125 132 demo = dict(scale=1, background=0, radius_minor=100, axis_ratio=1.5, length=400.0, 126 133 sld=4.0, sld_solvent=1.0, theta=10.0, phi=20, psi=30, 127 134 theta_pd=10, phi_pd=2, psi_pd=3) 128 129 def ER(radius_minor, axis_ratio, length):130 """131 Equivalent radius132 @param radius_minor: Ellipse minor radius133 @param axis_ratio: Ratio of major radius over minor radius134 @param length: Length of the cylinder135 """136 radius = sqrt(radius_minor * radius_minor * axis_ratio)137 ddd = 0.75 * radius * (2 * radius * length138 + (length + radius) * (length + pi * radius))139 return 0.5 * (ddd) ** (1. / 3.)140 135 141 136 def random(): … … 161 156 162 157 tests = [ 163 [{'radius_minor': 20.0, 'axis_ratio': 1.5, 'length':400.0}, 'ER', 79.89245454155024],164 [{'radius_minor': 20.0, 'axis_ratio': 1.2, 'length':300.0}, 'VR', 1],158 # [{'radius_minor': 20.0, 'axis_ratio': 1.5, 'length':400.0}, 'ER', 79.89245454155024], 159 # [{'radius_minor': 20.0, 'axis_ratio': 1.2, 'length':300.0}, 'VR', 1], 165 160 166 161 # The SasView test result was 0.00169, with a background of 0.001 -
sasmodels/models/fcc_paracrystal.c
r108e70e r71b751d 79 79 } 80 80 81 82 81 static double Iqabc(double qa, double qb, double qc, 83 82 double dnn, double d_factor, double radius, -
sasmodels/models/flexible_cylinder_elliptical.c
r74768cb r71b751d 72 72 return result; 73 73 } 74 -
sasmodels/models/fractal.c
r4788822 r71b751d 19 19 20 20 //calculate P(q) for the spherical subunits 21 const double pq = square(form_volume(radius) * (sld_block-sld_solvent)22 *sas_3j1x_x(q*radius) );21 const double fq = form_volume(radius) * (sld_block-sld_solvent) 22 *sas_3j1x_x(q*radius); 23 23 24 24 // scale to units cm-1 sr-1 (assuming data on absolute scale) … … 27 27 // combined: 1e-4 * I(q) 28 28 29 return 1.e-4 * volfraction * sq * pq;29 return 1.e-4 * volfraction * sq * fq * fq; 30 30 } 31 -
sasmodels/models/fractal_core_shell.c
rbdd08df rbad3093 16 16 double cor_length) 17 17 { 18 //The radius for the building block of the core shell particle that is 18 //The radius for the building block of the core shell particle that is 19 19 //needed by the Teixeira fractal S(q) is the radius of the whole particle. 20 20 const double cs_radius = radius + thickness; 21 21 const double sq = fractal_sq(q, cs_radius, fractal_dim, cor_length); 22 const double pq = core_shell_kernel(q, radius, thickness,23 22 const double fq = core_shell_fq(q, radius, thickness, 23 core_sld, shell_sld, solvent_sld); 24 24 25 // Note: core_shell_kernel already performs the 1e-4 unit conversion 26 return volfraction * sq * pq; 25 return 1.0e-4 * volfraction * sq * fq * fq; 27 26 } 28 -
sasmodels/models/fractal_core_shell.py
reb3eb38 r2cc8aa2 134 134 return radius + thickness 135 135 136 tests = [[{'radius': 20.0, 'thickness': 10.0}, 'ER', 30.0],137 136 #tests = [[{'radius': 20.0, 'thickness': 10.0}, 'ER', 30.0], 137 tests = [ 138 138 # # At some point the SasView 3.x test result was deemed incorrect. The 139 139 #following tests were verified against NIST IGOR macros ver 7.850. -
sasmodels/models/fuzzy_sphere.py
r2d81cfe ree60aa7 78 78 ["sld_solvent", "1e-6/Ang^2", 3, [-inf, inf], "sld", "Solvent scattering length density"], 79 79 ["radius", "Ang", 60, [0, inf], "volume", "Sphere radius"], 80 ["fuzziness", "Ang", 10, [0, inf], " ", "std deviation of Gaussian convolution for interface (must be << radius)"],80 ["fuzziness", "Ang", 10, [0, inf], "volume", "std deviation of Gaussian convolution for interface (must be << radius)"], 81 81 ] 82 82 # pylint: enable=bad-whitespace,line-too-long 83 83 84 source = ["lib/sas_3j1x_x.c"] 85 86 # No volume normalization despite having a volume parameter 87 # This should perhaps be volume normalized? 88 form_volume = """ 89 return M_4PI_3*cube(radius); 90 """ 91 92 Iq = """ 93 const double qr = q*radius; 94 const double bes = sas_3j1x_x(qr); 95 const double qf = q*fuzziness; 96 const double fq = bes * (sld - sld_solvent) * form_volume(radius) * exp(-0.5*qf*qf); 97 return 1.0e-4*fq*fq; 98 """ 99 100 def ER(radius): 101 """ 102 Return radius 103 """ 104 return radius 105 106 # VR defaults to 1.0 84 source = ["lib/sas_3j1x_x.c","fuzzy_sphere.c"] 85 have_Fq = True 86 effective_radius_type = ["radius", "radius + fuzziness"] 107 87 108 88 def random(): -
sasmodels/models/gaussian_peak.py
r2d81cfe r304c775 50 50 """ 51 51 52 # VR defaults to 1.053 54 52 def random(): 55 53 peak_pos = 10**np.random.uniform(-3, -1) -
sasmodels/models/hardsphere.py
r2d81cfe rc1799d3 62 62 63 63 # ["name", "units", default, [lower, upper], "type","description"], 64 parameters = [["radius_effective", "Ang", 50.0, [0, inf], " volume",64 parameters = [["radius_effective", "Ang", 50.0, [0, inf], "", 65 65 "effective radius of hard sphere"], 66 66 ["volfraction", "", 0.2, [0, 0.74], "", 67 67 "volume fraction of hard spheres"], 68 68 ] 69 70 # No volume normalization despite having a volume parameter71 # This should perhaps be volume normalized?72 form_volume = """73 return 1.0;74 """75 69 76 70 Iq = r""" … … 168 162 return pars 169 163 170 # ER defaults to 0.0171 # VR defaults to 1.0172 173 demo = dict(radius_effective=200, volfraction=0.2,174 radius_effective_pd=0.1, radius_effective_pd_n=40)175 164 # Q=0.001 is in the Taylor series, low Q part, so add Q=0.1, 176 165 # assuming double precision sasview is correct -
sasmodels/models/hayter_msa.py
r2d81cfe r304c775 89 89 return 1.0; 90 90 """ 91 # ER defaults to 0.092 # VR defaults to 1.093 91 94 92 def random(): -
sasmodels/models/hollow_cylinder.c
r108e70e r99658f6 1 1 //#define INVALID(v) (v.radius_core >= v.radius) 2 2 3 // From Igor library4 3 static double 5 _hollow_cylinder_scaling(double integrand, double delrho, double volume)4 shell_volume(double radius, double thickness, double length) 6 5 { 7 return 1.0e-4 * square(volume * delrho) * integrand; 6 return M_PI*length*(square(radius+thickness) - radius*radius); 7 } 8 9 static double 10 form_volume(double radius, double thickness, double length) 11 { 12 return M_PI*length*square(radius+thickness); 13 } 14 15 static double 16 radius_from_excluded_volume(double radius, double thickness, double length) 17 {