Changeset e526a9d in sasmodels
- Timestamp:
- Feb 4, 2018 9:40:01 PM (7 years ago)
- Branches:
- master, core_shell_microgels, magnetic_model, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
- Children:
- aadec17
- Parents:
- 032646d (diff), 72e41a0 (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. - git-author:
- Paul Butler <butlerpd@…> (02/04/18 21:40:01)
- git-committer:
- GitHub <noreply@…> (02/04/18 21:40:01)
- Files:
-
- 3 added
- 10 edited
- 2 moved
Legend:
- Unmodified
- Added
- Removed
-
doc/guide/pd/polydispersity.rst
reda8b30 r92d330fd 42 42 calculations are generally more robust with more data points or more angles. 43 43 44 The following fivedistribution functions are provided:44 The following distribution functions are provided: 45 45 46 46 * *Rectangular Distribution* 47 * *Uniform Distribution* 47 48 * *Gaussian Distribution* 48 49 * *Lognormal Distribution* 49 50 * *Schulz Distribution* 50 51 * *Array Distribution* 52 * *Boltzmann Distribution* 51 53 52 54 These are all implemented as *number-average* distributions. … … 85 87 Rectangular distribution. 86 88 89 90 91 Uniform Distribution 92 ^^^^^^^^^^^^^^^^^^^^^^^^ 93 94 The Uniform Distribution is defined as 95 96 .. math:: 97 98 f(x) = \frac{1}{\text{Norm}} 99 \begin{cases} 100 1 & \text{for } |x - \bar x| \leq \sigma \\ 101 0 & \text{for } |x - \bar x| > \sigma 102 \end{cases} 103 104 where $\bar x$ is the mean of the distribution, $\sigma$ is the half-width, and 105 *Norm* is a normalization factor which is determined during the numerical 106 calculation. 107 108 Note that the polydispersity is given by 109 110 .. math:: \text{PD} = \sigma / \bar x 111 112 .. figure:: pd_uniform.jpg 113 114 Uniform distribution. 115 116 The value $N_\sigma$ is ignored for this distribution. 117 87 118 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 88 119 … … 183 214 ^^^^^^^^^^^^^^^^^^ 184 215 185 This user-definable distribution should be given as a s asimple ASCII text216 This user-definable distribution should be given as a simple ASCII text 186 217 file where the array is defined by two columns of numbers: $x$ and $f(x)$. 187 218 The $f(x)$ will be normalized to 1 during the computation. … … 202 233 given for the model will have no affect, and will be ignored when computing 203 234 the average. This means that any parameter with an array distribution will 204 not be fittable. 235 not be fitable. 236 237 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 238 239 Boltzmann Distribution 240 ^^^^^^^^^^^^^^^^^^^^^^ 241 242 The Boltzmann Distribution is defined as 243 244 .. math:: 245 246 f(x) = \frac{1}{\text{Norm}} 247 \exp\left(-\frac{ | x - \bar x | }{\sigma}\right) 248 249 where $\bar x$ is the mean of the distribution and *Norm* is a normalization 250 factor which is determined during the numerical calculation. 251 The width is defined as 252 253 .. math:: \sigma=\frac{k T}{E} 254 255 which is the inverse Boltzmann factor, 256 where $k$ is the Boltzmann constant, $T$ the temperature in Kelvin and $E$ a 257 characteristic energy per particle. 258 259 .. figure:: pd_boltzmann.jpg 260 261 Boltzmann distribution. 205 262 206 263 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ -
sasmodels/weights.py
r2d81cfe r3d58247 97 97 return x, px 98 98 99 class UniformDispersion(Dispersion): 100 r""" 101 Uniform dispersion, with width $\sigma$. 102 103 .. math:: 104 105 w = 1 106 """ 107 type = "uniform" 108 default = dict(npts=35, width=0, nsigmas=None) 109 def _weights(self, center, sigma, lb, ub): 110 x = np.linspace(center-sigma, center+sigma, self.npts) 111 x = x[(x >= lb) & (x <= ub)] 112 return x, np.ones_like(x) 99 113 100 114 class RectangleDispersion(Dispersion): … … 107 121 """ 108 122 type = "rectangle" 109 default = dict(npts=35, width=0, nsigmas=1.70325) 110 def _weights(self, center, sigma, lb, ub): 111 x = self._linspace(center, sigma, lb, ub) 112 x = x[np.fabs(x-center) <= np.fabs(sigma)*sqrt(3.0)] 113 return x, np.ones_like(x) 114 123 default = dict(npts=35, width=0, nsigmas=1.73205) 124 def _weights(self, center, sigma, lb, ub): 125 x = self._linspace(center, sigma, lb, ub) 126 x = x[np.fabs(x-center) <= np.fabs(sigma)*sqrt(3.0)] 127 return x, np.ones_like(x) 115 128 116 129 class LogNormalDispersion(Dispersion): … … 190 203 return x, px 191 204 205 class BoltzmannDispersion(Dispersion): 206 r""" 207 Boltzmann dispersion, with $\sigma=k T/E$. 208 209 .. math:: 210 211 w = \exp\left( -|x - c|/\sigma\right) 212 """ 213 type = "boltzmann" 214 default = dict(npts=35, width=0, nsigmas=3) 215 def _weights(self, center, sigma, lb, ub): 216 x = self._linspace(center, sigma, lb, ub) 217 px = np.exp(-np.abs(x-center) / np.abs(sigma)) 218 return x, px 192 219 193 220 # dispersion name -> disperser lookup table. … … 196 223 MODELS = OrderedDict((d.type, d) for d in ( 197 224 RectangleDispersion, 225 UniformDispersion, 198 226 ArrayDispersion, 199 227 LogNormalDispersion, 200 228 GaussianDispersion, 201 229 SchulzDispersion, 230 BoltzmannDispersion 202 231 )) 203 232 -
appveyor.yml
r1258e32 rfefc185 4 4 # /E:ON and /V:ON options are not enabled in the batch script interpreter 5 5 # See: http://stackoverflow.com/a/13751649/163740 6 CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd" 6 # 2018-01-19 PAK: probably irrelevant now that we are using tinycc rather than msvc 7 #CMD_IN_ENV: "cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd" 7 8 8 9 # Workaround for https://github.com/conda/conda-build/issues/636 … … 22 23 - TARGET_ARCH: "x86" 23 24 CONDA_PY: "35" 24 PY_CONDITION: "python >=3.5 "25 PY_CONDITION: "python >=3.5,<3.6" 25 26 CONDA_INSTALL_LOCN: "C:\\Miniconda35" 26 27 - TARGET_ARCH: "x64" … … 34 35 - TARGET_ARCH: "x64" 35 36 CONDA_PY: "35" 36 PY_CONDITION: "python >=3.5 "37 PY_CONDITION: "python >=3.5,<3.6" 37 38 CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" 38 39 … … 43 44 44 45 install: 45 # Set the CONDA_NPY, although it has no impact on the actual build. We need this because of a test within conda-build. 46 # Set the CONDA_NPY, although it has no impact on the actual build. 47 # We need this because of a test within conda-build. 46 48 - cmd: set CONDA_NPY=19 47 49 48 50 # Remove cygwin (and therefore the git that comes with it). 49 - cmd: rmdir C:\cygwin /s /q 51 # 2018-01-19 PAK: irrelevant since we already pulled the repo 52 #- cmd: rmdir C:\cygwin /s /q 53 54 # Set the conda path; would be nice to do this 55 - cmd: path %CONDA_INSTALL_LOCN%\Scripts;%PATH% 50 56 51 57 # Use the pre-installed Miniconda for the desired arch … … 56 62 # so that we can update it. Then we remove it so that 57 63 # we can do a proper activation. 58 - cmd: set "OLDPATH=%PATH%"59 - cmd: set "PATH=%CONDA_INSTALL_LOCN%\\Scripts;%CONDA_INSTALL_LOCN%\\Library\\bin;%PATH%"60 64 - cmd: conda update --yes --quiet conda python 61 - cmd: set "PATH=%OLDPATH%"62 65 - cmd: call %CONDA_INSTALL_LOCN%\Scripts\activate.bat 63 64 - cmd: conda config --add channels conda-forge65 - cmd: conda config --set show_channel_urls true66 - cmd: conda update --yes --quiet conda67 - cmd: conda install --yes --quiet obvious-ci68 - cmd: conda install --yes --quiet numpy toolchain scipy cython cffi66 #- cmd: conda config --add channels conda-forge 67 #- cmd: conda config --set show_channel_urls true 68 #- cmd: conda install --yes --quiet obvious-ci 69 # 2018-01-19 PAK: skipping toolchain cython and cffi (these were for pyopencl?) 70 - cmd: conda install --yes --quiet numpy scipy 71 # 2018-01-19 PAK: skipping pyopencl; this would be needed for deploy but maybe not for test 69 72 #- cmd: conda install --yes --channel conda-forge pyopencl 73 # 2018-01-19 PAK: 3rd party packages might need msvc, so %CMD_IN_ENV% may be needed for pip 70 74 - cmd: pip install bumps unittest-xml-reporting tinycc 71 75 72 76 build_script: 73 77 # Build the project 74 - "%CMD_IN_ENV% python setup.py build" 78 # 2018-01-19 PAK: maybe need one of this if using msvc? 79 #- "%CMD_IN_ENV% python setup.py build" 80 #- cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd python setup.py build 81 - python setup.py build 75 82 76 83 test_script: 77 84 # Run the project tests 78 - "%CMD_IN_ENV% python -m sasmodels.model_test dll all" 85 # 2018-01-19 PAK: maybe need one of this if using msvc? 86 #- "%CMD_IN_ENV% python -m sasmodels.model_test dll all" 87 #- cmd /E:ON /V:ON /C obvci_appveyor_python_build_env.cmd python -m sasmoels.model_test dll all 88 - python -m sasmodels.model_test dll all -
doc/developer/overview.rst
r2a7e20e r0d5a655 190 190 *sasmodels.generate.PROJECTION*, with the default *PROJECTION=1* for 191 191 equirectangular and *PROJECTION=2* for sinusoidal. The more complicated 192 Guyou and Postel projections are not implemented. See explore.jitter.draw_mesh192 Guyou and Postel projections are not implemented. See jitter.draw_mesh 193 193 for details. 194 194 … … 274 274 code. 275 275 276 * explore/jitter.py* is for exploring different options for handling277 orientation and orientation dispersity. It uses * explore/guyou.py* to276 *sasmodels/jitter.py* is for exploring different options for handling 277 orientation and orientation dispersity. It uses *sasmodels/guyou.py* to 278 278 generate the Guyou projection. 279 279 -
doc/genapi.py
r706f466 r0d5a655 69 69 ('exception', 'Annotate exceptions'), 70 70 ('generate', 'Model parser'), 71 ('jitter', 'Orientation explorer'), 72 ('guyou', 'Guyou map projection'), 71 73 ('kernel', 'Evaluator type definitions'), 72 74 ('kernelcl', 'OpenCL model evaluator'), -
doc/guide/orientation/orientation.rst
r5fb0634 r0d5a655 52 52 yaw angle $\theta$ about the $b$-axis and pitch angle $\phi$ about the 53 53 $a$-axis. 54 55 You can explore the view and jitter angles interactively using 56 :func:`sasmodels.jitter.run`. Enter the following into the python 57 interpreter:: 58 59 from sasmodels import jitter 60 jitter.run() 54 61 55 62 More formally, starting with axes $a$-$b$-$c$ of the particle aligned -
explore/realspace.py
r8fb2a94 r032646d 3 3 import time 4 4 from copy import copy 5 import os 6 import argparse 7 from collections import OrderedDict 5 8 6 9 import numpy as np 7 10 from numpy import pi, radians, sin, cos, sqrt 8 from numpy.random import poisson, uniform 11 from numpy.random import poisson, uniform, randn, rand 9 12 from numpy.polynomial.legendre import leggauss 10 13 from scipy.integrate import simps 11 14 from scipy.special import j1 as J1 15 16 try: 17 import numba 18 USE_NUMBA = True 19 except ImportError: 20 USE_NUMBA = False 12 21 13 22 # Definition of rotation matrices comes from wikipedia: … … 82 91 raise NotImplementedError() 83 92 93 def dims(self): 94 # type: () -> float, float, float 95 raise NotImplementedError() 96 84 97 def rotate(self, theta, phi, psi): 85 98 self.rotation = rotation(theta, phi, psi) * self.rotation … … 116 129 for s2 in shapes] 117 130 self.r_max = max(distances + [s.r_max for s in shapes]) 118 119 def volume(self): 120 return sum(shape.volume() for shape in self.shapes) 131 self.volume = sum(shape.volume for shape in self.shapes) 121 132 122 133 def sample(self, density): … … 133 144 self._scale = np.array([a/2, b/2, c/2])[None, :] 134 145 self.r_max = sqrt(a**2 + b**2 + c**2) 135 136 def volume(self): 137 return self.a*self.b*self.c 146 self.dims = a, b, c 147 self.volume = a*b*c 138 148 139 149 def sample(self, density): 140 num_points = poisson(density*self. a*self.b*self.c)150 num_points = poisson(density*self.volume) 141 151 points = self._scale*uniform(-1, 1, size=(num_points, 3)) 142 152 values = self.value.repeat(points.shape[0]) … … 152 162 self._scale = np.array([ra, rb, length/2])[None, :] 153 163 self.r_max = sqrt(4*max(ra, rb)**2 + length**2) 154 155 def volume(self): 156 return pi*self.ra*self.rb*self.length 164 self.dims = 2*ra, 2*rb, length 165 self.volume = pi*ra*rb*length 157 166 158 167 def sample(self, density): 159 # density of the bounding box 168 # randomly sample from a box of side length 2*r, excluding anything 169 # not in the cylinder 160 170 num_points = poisson(density*4*self.ra*self.rb*self.length) 161 171 points = uniform(-1, 1, size=(num_points, 3)) … … 174 184 self._scale = np.array([ra, rb, rc])[None, :] 175 185 self.r_max = 2*max(ra, rb, rc) 176 177 def volume(self): 178 return 4*pi/3 * self.ra * self.rb * self.rc 186 self.dims = 2*ra, 2*rb, 2*rc 187 self.volume = 4*pi/3 * ra * rb * rc 179 188 180 189 def sample(self, density): … … 194 203 self.rotate(*orientation) 195 204 self.shift(*center) 205 helix_length = helix_pitch * tube_length/sqrt(helix_radius**2 + helix_pitch**2) 206 total_radius = self.helix_radius + self.tube_radius 196 207 self.helix_radius, self.helix_pitch = helix_radius, helix_pitch 197 208 self.tube_radius, self.tube_length = tube_radius, tube_length 198 helix_length = helix_pitch * tube_length/sqrt(helix_radius**2 + helix_pitch**2) 199 self.r_max = sqrt((2*helix_radius + 2*tube_radius)*2 200 + (helix_length + 2*tube_radius)**2) 201 202 def volume(self): 209 self.r_max = sqrt(4*total_radius + (helix_length + 2*tube_radius)**2) 210 self.dims = 2*total_radius, 2*total_radius, helix_length 203 211 # small tube radius approximation; for larger tubes need to account 204 212 # for the fact that the inner length is much shorter than the outer 205 213 # length 206 returnpi*self.tube_radius**2*self.tube_length214 self.volume = pi*self.tube_radius**2*self.tube_length 207 215 208 216 def points(self, density): … … 244 252 return values, self._adjust(points) 245 253 246 NUMBA = False 247 if NUMBA: 254 def csbox(a=10, b=20, c=30, da=1, db=2, dc=3, slda=1, sldb=2, sldc=3, sld_core=4): 255 core = Box(a, b, c, sld_core) 256 side_a = Box(da, b, c, slda, center=((a+da)/2, 0, 0)) 257 side_b = Box(a, db, c, sldb, center=(0, (b+db)/2, 0)) 258 side_c = Box(a, b, dc, sldc, center=(0, 0, (c+dc)/2)) 259 side_a2 = copy(side_a).shift(-a-da, 0, 0) 260 side_b2 = copy(side_b).shift(0, -b-db, 0) 261 side_c2 = copy(side_c).shift(0, 0, -c-dc) 262 shape = Composite((core, side_a, side_b, side_c, side_a2, side_b2, side_c2)) 263 shape.dims = 2*da+a, 2*db+b, 2*dc+c 264 return shape 265 266 def _Iqxy(values, x, y, z, qa, qb, qc): 267 """I(q) = |sum V(r) rho(r) e^(1j q.r)|^2 / sum V(r)""" 268 Iq = [abs(np.sum(values*np.exp(1j*(qa_k*x + qb_k*y + qc_k*z))))**2 269 for qa_k, qb_k, qc_k in zip(qa.flat, qb.flat, qc.flat)] 270 return Iq 271 272 if USE_NUMBA: 273 # Override simple numpy solution with numba if available 248 274 from numba import njit 249 275 @njit("f8[:](f8[:],f8[:],f8[:],f8[:],f8[:],f8[:],f8[:])") … … 252 278 for j in range(len(Iq)): 253 279 total = 0. + 0j 254 for k in range(len( Iq)):280 for k in range(len(values)): 255 281 total += values[k]*np.exp(1j*(qa[j]*x[k] + qb[j]*y[k] + qc[j]*z[k])) 256 282 Iq[j] = abs(total)**2 … … 263 289 values = rho*volume 264 290 x, y, z = points.T 291 values, x, y, z, qa, qb, qc = [np.asarray(v, 'd') 292 for v in (values, x, y, z, qa, qb, qc)] 265 293 266 294 # I(q) = |sum V(r) rho(r) e^(1j q.r)|^2 / sum V(r) 267 if NUMBA: 268 Iq = _Iqxy(values, x, y, z, qa.flatten(), qb.flatten(), qc.flatten()) 269 else: 270 Iq = [abs(np.sum(values*np.exp(1j*(qa_k*x + qb_k*y + qc_k*z))))**2 271 for qa_k, qb_k, qc_k in zip(qa.flat, qb.flat, qc.flat)] 295 Iq = _Iqxy(values, x, y, z, qa.flatten(), qb.flatten(), qc.flatten()) 272 296 return np.asarray(Iq).reshape(qx.shape) / np.sum(volume) 273 297 … … 338 362 """ 339 363 340 if NUMBA: 364 if USE_NUMBA: 365 # Override simple numpy solution with numba if available 341 366 @njit("f8[:](f8[:], f8[:], f8[:,:])") 342 def _calc_Pr_uniform _jit(r, rho, points):367 def _calc_Pr_uniform(r, rho, points): 343 368 dr = r[0] 344 369 n_max = len(r) … … 362 387 # P(r) with uniform steps in r is 3x faster; check if we are uniform 363 388 # before continuing 389 r, rho, points = [np.asarray(v, 'd') for v in (r, rho, points)] 364 390 if np.max(np.abs(np.diff(r) - r[0])) > r[0]*0.01: 365 391 Pr = _calc_Pr_nonuniform(r, rho, points) 366 392 else: 367 if NUMBA: 368 Pr = _calc_Pr_uniform_jit(r, rho, points) 369 else: 370 Pr = _calc_Pr_uniform(r, rho, points) 393 Pr = _calc_Pr_uniform(r, rho, points) 371 394 return Pr / Pr.max() 372 395 … … 399 422 return edges 400 423 424 # -------------- plotters ---------------- 401 425 def plot_calc(r, Pr, q, Iq, theory=None): 402 426 import matplotlib.pyplot as plt … … 410 434 plt.ylabel('Iq') 411 435 if theory is not None: 412 plt.loglog(theory[0], theory[1] , '-', label='analytic')436 plt.loglog(theory[0], theory[1]/theory[1][0], '-', label='analytic') 413 437 plt.legend() 414 438 … … 444 468 ax.autoscale(True) 445 469 446 def check_shape(shape, fn=None): 447 rho_solvent = 0 448 q = np.logspace(-3, 0, 200) 449 r = shape.r_bins(q, r_step=0.01) 450 sampling_density = 6*5000 / shape.volume() 451 rho, points = shape.sample(sampling_density) 452 t0 = time.time() 453 Pr = calc_Pr(r, rho-rho_solvent, points) 454 print("calc Pr time", time.time() - t0) 455 Iq = calc_Iq(q, r, Pr) 456 theory = (q, fn(q)) if fn is not None else None 457 458 import pylab 459 #plot_points(rho, points); pylab.figure() 460 plot_calc(r, Pr, q, Iq, theory=theory) 461 pylab.show() 462 463 def check_shape_2d(shape, fn=None, view=(0, 0, 0)): 464 rho_solvent = 0 465 nq, qmax = 100, 1.0 466 qx = np.linspace(0.0, qmax, nq) 467 qy = np.linspace(0.0, qmax, nq) 468 Qx, Qy = np.meshgrid(qx, qy) 469 sampling_density = 50000 / shape.volume() 470 #t0 = time.time() 471 rho, points = shape.sample(sampling_density) 472 #print("sample time", time.time() - t0) 473 t0 = time.time() 474 Iqxy = calc_Iqxy(Qx, Qy, rho, points, view=view) 475 print("calc time", time.time() - t0) 476 theory = fn(Qx, Qy) if fn is not None else None 477 Iqxy += 0.001 * Iqxy.max() 478 if theory is not None: 479 theory += 0.001 * theory.max() 480 481 import pylab 482 #plot_points(rho, points); pylab.figure() 483 plot_calc_2d(qx, qy, Iqxy, theory=theory) 484 pylab.show() 485 470 # ----------- Analytic models -------------- 486 471 def sas_sinx_x(x): 487 472 with np.errstate(all='ignore'): … … 510 495 for k, qk in enumerate(q): 511 496 qab, qc = qk*sin_alpha, qk*cos_alpha 512 Fq = sas_2J1x_x(qab*radius) * j0(qc*length/2)497 Fq = sas_2J1x_x(qab*radius) * sas_sinx_x(qc*length/2) 513 498 Iq[k] = np.sum(w*Fq**2) 514 Iq = Iq /Iq[0]499 Iq = Iq 515 500 return Iq 516 501 517 502 def cylinder_Iqxy(qx, qy, radius, length, view=(0, 0, 0)): 518 503 qa, qb, qc = invert_view(qx, qy, view) 519 qab = np.sqrt(qa**2 + qb**2)520 Fq = sas_2J1x_x(qab*radius) * j0(qc*length/2)504 qab = sqrt(qa**2 + qb**2) 505 Fq = sas_2J1x_x(qab*radius) * sas_sinx_x(qc*length/2) 521 506 Iq = Fq**2 522 507 return Iq.reshape(qx.shape) … … 524 509 def sphere_Iq(q, radius): 525 510 Iq = sas_3j1x_x(q*radius)**2 526 return Iq/Iq[0] 511 return Iq 512 513 def box_Iq(q, a, b, c): 514 z, w = leggauss(76) 515 outer_sum = np.zeros_like(q) 516 for cos_alpha, outer_w in zip((z+1)/2, w): 517 sin_alpha = sqrt(1.0-cos_alpha*cos_alpha) 518 qc = q*cos_alpha 519 siC = c*sas_sinx_x(c*qc/2) 520 inner_sum = np.zeros_like(q) 521 for beta, inner_w in zip((z + 1)*pi/4, w): 522 qa, qb = q*sin_alpha*sin(beta), q*sin_alpha*cos(beta) 523 siA = a*sas_sinx_x(a*qa/2) 524 siB = b*sas_sinx_x(b*qb/2) 525 Fq = siA*siB*siC 526 inner_sum += inner_w * Fq**2 527 outer_sum += outer_w * inner_sum 528 Iq = outer_sum / 4 # = outer*um*zm*8.0/(4.0*M_PI) 529 return Iq 530 531 def box_Iqxy(qx, qy, a, b, c, view=(0, 0, 0)): 532 qa, qb, qc = invert_view(qx, qy, view) 533 sia = sas_sinx_x(qa*a/2) 534 sib = sas_sinx_x(qb*b/2) 535 sic = sas_sinx_x(qc*c/2) 536 Fq = sia*sib*sic 537 Iq = Fq**2 538 return Iq.reshape(qx.shape) 527 539 528 540 def csbox_Iq(q, a, b, c, da, db, dc, slda, sldb, sldc, sld_core): … … 539 551 sin_alpha = sqrt(1.0-cos_alpha*cos_alpha) 540 552 qc = q*cos_alpha 541 siC = c* j0(c*qc/2)542 siCt = tC* j0(tC*qc/2)553 siC = c*sas_sinx_x(c*qc/2) 554 siCt = tC*sas_sinx_x(tC*qc/2) 543 555 inner_sum = np.zeros_like(q) 544 556 for beta, inner_w in zip((z + 1)*pi/4, w): 545 557 qa, qb = q*sin_alpha*sin(beta), q*sin_alpha*cos(beta) 546 siA = a* j0(a*qa/2)547 siB = b* j0(b*qb/2)548 siAt = tA* j0(tA*qa/2)549 siBt = tB* j0(tB*qb/2)558 siA = a*sas_sinx_x(a*qa/2) 559 siB = b*sas_sinx_x(b*qb/2) 560 siAt = tA*sas_sinx_x(tA*qa/2) 561 siBt = tB*sas_sinx_x(tB*qb/2) 550 562 if overlapping: 551 563 Fq = (dr0*siA*siB*siC … … 584 596 return Iq.reshape(qx.shape) 585 597 586 def check_cylinder(radius=25, length=125, rho=2.): 598 # --------- Test cases ----------- 599 600 def build_cylinder(radius=25, length=125, rho=2.): 587 601 shape = EllipticalCylinder(radius, radius, length, rho) 588 fn = lambda q: cylinder_Iq(q, radius, length) 589 check_shape(shape, fn) 590 591 def check_cylinder_2d(radius=25, length=125, rho=2., view=(0, 0, 0)): 592 shape = EllipticalCylinder(radius, radius, length, rho) 593 fn = lambda qx, qy, view=view: cylinder_Iqxy(qx, qy, radius, length, view=view) 594 check_shape_2d(shape, fn, view=view) 595 596 def check_cylinder_2d_lattice(radius=25, length=125, rho=2., 597 view=(0, 0, 0)): 598 nx, dx = 1, 2*radius 599 ny, dy = 30, 2*radius 600 nz, dz = 30, length 601 dx, dy, dz = 2*dx, 2*dy, 2*dz 602 def center(*args): 603 sigma = 0.333 604 space = 2 605 return [(space*n+np.random.randn()*sigma)*x for n, x in args] 606 shapes = [EllipticalCylinder(radius, radius, length, rho, 607 #center=(ix*dx, iy*dy, iz*dz) 608 orientation=np.random.randn(3)*0, 609 center=center((ix, dx), (iy, dy), (iz, dz)) 610 ) 602 fn = lambda q: cylinder_Iq(q, radius, length)*rho**2 603 fn_xy = lambda qx, qy, view: cylinder_Iqxy(qx, qy, radius, length, view=view)*rho**2 604 return shape, fn, fn_xy 605 606 def build_sphere(radius=125, rho=2): 607 shape = TriaxialEllipsoid(radius, radius, radius, rho) 608 fn = lambda q: sphere_Iq(q, radius)*rho**2 609 fn_xy = lambda qx, qy, view: sphere_Iq(np.sqrt(qx**2+qy**2), radius)*rho**2 610 return shape, fn, fn_xy 611 612 def build_box(a=10, b=20, c=30, rho=2.): 613 shape = Box(a, b, c, rho) 614 fn = lambda q: box_Iq(q, a, b, c)*rho**2 615 fn_xy = lambda qx, qy, view: box_Iqxy(qx, qy, a, b, c, view=view)*rho**2 616 return shape, fn, fn_xy 617 618 def build_csbox(a=10, b=20, c=30, da=1, db=2, dc=3, slda=1, sldb=2, sldc=3, sld_core=4): 619 shape = csbox(a, b, c, da, db, dc, slda, sldb, sldc, sld_core) 620 fn = lambda q: csbox_Iq(q, a, b, c, da, db, dc, slda, sldb, sldc, sld_core) 621 fn_xy = lambda qx, qy, view: csbox_Iqxy(qx, qy, a, b, c, da, db, dc, 622 slda, sldb, sldc, sld_core, view=view) 623 return shape, fn, fn_xy 624 625 def build_cubic_lattice(shape, nx=1, ny=1, nz=1, dx=2, dy=2, dz=2, 626 shuffle=0, rotate=0): 627 a, b, c = shape.dims 628 shapes = [copy(shape) 629 .shift((ix+(randn() if shuffle < 0.3 else rand())*shuffle)*dx*a, 630 (iy+(randn() if shuffle < 0.3 else rand())*shuffle)*dy*b, 631 (iz+(randn() if shuffle < 0.3 else rand())*shuffle)*dz*c) 632 .rotate(*((randn(3) if rotate < 30 else rand(3))*rotate)) 611 633 for ix in range(nx) 612 634 for iy in range(ny) 613 635 for iz in range(nz)] 614 shape = Composite(shapes) 615 fn = lambda qx, qy, view=view: cylinder_Iqxy(qx, qy, radius, length, view=view) 616 check_shape_2d(shape, fn, view=view) 617 618 def check_sphere(radius=125, rho=2): 619 shape = TriaxialEllipsoid(radius, radius, radius, rho) 620 fn = lambda q: sphere_Iq(q, radius) 621 check_shape(shape, fn) 622 623 def check_csbox(a=10, b=20, c=30, da=1, db=2, dc=3, slda=1, sldb=2, sldc=3, sld_core=4): 624 core = Box(a, b, c, sld_core) 625 side_a = Box(da, b, c, slda, center=((a+da)/2, 0, 0)) 626 side_b = Box(a, db, c, sldb, center=(0, (b+db)/2, 0)) 627 side_c = Box(a, b, dc, sldc, center=(0, 0, (c+dc)/2)) 628 side_a2 = copy(side_a).shift(-a-da, 0, 0) 629 side_b2 = copy(side_b).shift(0, -b-db, 0) 630 side_c2 = copy(side_c).shift(0, 0, -c-dc) 631 shape = Composite((core, side_a, side_b, side_c, side_a2, side_b2, side_c2)) 632 def fn(q): 633 return csbox_Iq(q, a, b, c, da, db, dc, slda, sldb, sldc, sld_core) 634 #check_shape(shape, fn) 635 636 view = (20, 30, 40) 637 def fn_xy(qx, qy): 638 return csbox_Iqxy(qx, qy, a, b, c, da, db, dc, 639 slda, sldb, sldc, sld_core, view=view) 640 check_shape_2d(shape, fn_xy, view=view) 636 lattice = Composite(shapes) 637 return lattice 638 639 640 SHAPE_FUNCTIONS = OrderedDict([ 641 ("cylinder", build_cylinder), 642 ("sphere", build_sphere), 643 ("box", build_box), 644 ("csbox", build_csbox), 645 ]) 646 SHAPES = list(SHAPE_FUNCTIONS.keys()) 647 648 def check_shape(title, shape, fn=None, show_points=False, 649 mesh=100, qmax=1.0, r_step=0.01, samples=5000): 650 rho_solvent = 0 651 qmin = qmax/100. 652 q = np.logspace(np.log10(qmin), np.log10(qmax), mesh) 653 r = shape.r_bins(q, r_step=r_step) 654 sampling_density = samples / shape.volume 655 rho, points = shape.sample(sampling_density) 656 t0 = time.time() 657 Pr = calc_Pr(r, rho-rho_solvent, points) 658 print("calc Pr time", time.time() - t0) 659 Iq = calc_Iq(q, r, Pr) 660 theory = (q, fn(q)) if fn is not None else None 661 662 import pylab 663 if show_points: 664 plot_points(rho, points); pylab.figure() 665 plot_calc(r, Pr, q, Iq, theory=theory) 666 pylab.gcf().canvas.set_window_title(title) 667 pylab.show() 668 669 def check_shape_2d(title, shape, fn=None, view=(0, 0, 0), show_points=False, 670 mesh=100, qmax=1.0, samples=5000): 671 rho_solvent = 0 672 qx = np.linspace(0.0, qmax, mesh) 673 qy = np.linspace(0.0, qmax, mesh) 674 Qx, Qy = np.meshgrid(qx, qy) 675 sampling_density = samples / shape.volume 676 t0 = time.time() 677 rho, points = shape.sample(sampling_density) 678 print("point generation time", time.time() - t0) 679 t0 = time.time() 680 Iqxy = calc_Iqxy(Qx, Qy, rho, points, view=view) 681 print("calc Iqxy time", time.time() - t0) 682 theory = fn(Qx, Qy, view) if fn is not None else None 683 Iqxy += 0.001 * Iqxy.max() 684 if theory is not None: 685 theory += 0.001 * theory.max() 686 687 import pylab 688 if show_points: 689 plot_points(rho, points); pylab.figure() 690 plot_calc_2d(qx, qy, Iqxy, theory=theory) 691 pylab.gcf().canvas.set_window_title(title) 692 pylab.show() 693 694 def main(): 695 parser = argparse.ArgumentParser( 696 description="Compute scattering from realspace sampling", 697 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 698 ) 699 parser.add_argument('-d', '--dim', type=int, default=1, 700 help='dimension 1 or 2') 701 parser.add_argument('-m', '--mesh', type=int, default=100, 702 help='number of mesh points') 703 parser.add_argument('-s', '--samples', type=int, default=5000, 704 help="number of sample points") 705 parser.add_argument('-q', '--qmax', type=float, default=0.5, 706 help='max q') 707 parser.add_argument('-v', '--view', type=str, default='0,0,0', 708 help='theta,phi,psi angles') 709 parser.add_argument('-n', '--lattice', type=str, default='1,1,1', 710 help='lattice size') 711 parser.add_argument('-z', '--spacing', type=str, default='2,2,2', 712 help='lattice spacing') 713 parser.add_argument('-r', '--rotate', type=float, default=0., 714 help="rotation relative to lattice, gaussian < 30 degrees, uniform otherwise") 715 parser.add_argument('-w', '--shuffle', type=float, default=0., 716 help="position relative to lattice, gaussian < 0.3, uniform otherwise") 717 parser.add_argument('-p', '--plot', action='store_true', 718 help='plot points') 719 parser.add_argument('shape', choices=SHAPES, nargs='?', default=SHAPES[0], 720 help='oriented shape') 721 parser.add_argument('pars', type=str, nargs='*', help='shape parameters') 722 opts = parser.parse_args() 723 pars = {key: float(value) for p in opts.pars for key, value in [p.split('=')]} 724 nx, ny, nz = [int(v) for v in opts.lattice.split(',')] 725 dx, dy, dz = [float(v) for v in opts.spacing.split(',')] 726 shuffle, rotate = opts.shuffle, opts.rotate 727 shape, fn, fn_xy = SHAPE_FUNCTIONS[opts.shape](**pars) 728 if nx > 1 or ny > 1 or nz > 1: 729 shape = build_cubic_lattice(shape, nx, ny, nz, dx, dy, dz, shuffle, rotate) 730 title = "%s(%s)" % (opts.shape, " ".join(opts.pars)) 731 if opts.dim == 1: 732 check_shape(title, shape, fn, show_points=opts.plot, 733 mesh=opts.mesh, qmax=opts.qmax, samples=opts.samples) 734 else: 735 view = tuple(float(v) for v in opts.view.split(',')) 736 check_shape_2d(title, shape, fn_xy, view=view, show_points=opts.plot, 737 mesh=opts.mesh, qmax=opts.qmax, samples=opts.samples) 738 641 739 642 740 if __name__ == "__main__": 643 check_cylinder(radius=10, length=40) 644 #check_cylinder_2d(radius=10, length=40, view=(90,30,0)) 645 #check_cylinder_2d_lattice(radius=10, length=50, view=(90,30,0)) 646 #check_sphere() 647 #check_csbox() 648 #check_csbox(da=100, db=200, dc=300) 741 main() -
sasmodels/core.py
r2d81cfe r7a516d0 148 148 used with functions in generate to build the docs or extract model info. 149 149 """ 150 if '@' in model_string: 151 parts = model_string.split('@') 152 if len(parts) != 2: 153 raise ValueError("Use P@S to apply a structure factor S to model P") 154 P_info, Q_info = [load_model_info(part) for part in parts] 155 return product.make_product_info(P_info, Q_info) 156 157 product_parts = [] 158 addition_parts = [] 159 160 addition_parts_names = model_string.split('+') 161 if len(addition_parts_names) >= 2: 162 addition_parts = [load_model_info(part) for part in addition_parts_names] 163 elif len(addition_parts_names) == 1: 164 product_parts_names = model_string.split('*') 165 if len(product_parts_names) >= 2: 166 product_parts = [load_model_info(part) for part in product_parts_names] 167 elif len(product_parts_names) == 1: 168 if "custom." in product_parts_names[0]: 169 # Extract ModelName from "custom.ModelName" 170 pattern = "custom.([A-Za-z0-9_-]+)" 171 result = re.match(pattern, product_parts_names[0]) 172 if result is None: 173 raise ValueError("Model name in invalid format: " + product_parts_names[0]) 174 model_name = result.group(1) 175 # Use ModelName to find the path to the custom model file 176 model_path = joinpath(CUSTOM_MODEL_PATH, model_name + ".py") 177 if not os.path.isfile(model_path): 178 raise ValueError("The model file {} doesn't exist".format(model_path)) 179 kernel_module = custom.load_custom_kernel_module(model_path) 180 return modelinfo.make_model_info(kernel_module) 181 # Model is a core model 182 kernel_module = generate.load_kernel_module(product_parts_names[0]) 183 return modelinfo.make_model_info(kernel_module) 184 185 model = None 186 if len(product_parts) > 1: 187 model = mixture.make_mixture_info(product_parts, operation='*') 188 if len(addition_parts) > 1: 189 if model is not None: 190 addition_parts.append(model) 191 model = mixture.make_mixture_info(addition_parts, operation='+') 192 return model 150 if "+" in model_string: 151 parts = [load_model_info(part) 152 for part in model_string.split("+")] 153 return mixture.make_mixture_info(parts, operation='+') 154 elif "*" in model_string: 155 parts = [load_model_info(part) 156 for part in model_string.split("*")] 157 return mixture.make_mixture_info(parts, operation='*') 158 elif "@" in model_string: 159 p_info, q_info = [load_model_info(part) 160 for part in model_string.split("@")] 161 return product.make_product_info(p_info, q_info) 162 # We are now dealing with a pure model 163 elif "custom." in model_string: 164 pattern = "custom.([A-Za-z0-9_-]+)" 165 result = re.match(pattern, model_string) 166 if result is None: 167 raise ValueError("Model name in invalid format: " + model_string) 168 model_name = result.group(1) 169 # Use ModelName to find the path to the custom model file 170 model_path = joinpath(CUSTOM_MODEL_PATH, model_name + ".py") 171 if not os.path.isfile(model_path): 172 raise ValueError("The model file {} doesn't exist".format(model_path)) 173 kernel_module = custom.load_custom_kernel_module(model_path) 174 return modelinfo.make_model_info(kernel_module) 175 kernel_module = generate.load_kernel_module(model_string) 176 return modelinfo.make_model_info(kernel_module) 193 177 194 178 … … 336 320 print("\n".join(list_models(kind))) 337 321 322 def test_load(): 323 # type: () -> None 324 """Check that model load works""" 325 def test_models(fst, snd): 326 """Confirm that two models produce the same parameters""" 327 fst = load_model(fst) 328 snd = load_model(snd) 329 # Remove the upper case characters frin the parameters, since 330 # they lasndel the order of events and we specfically are 331 # changin that aspect 332 fst = [[x for x in p.name if x == x.lower()] for p in fst.info.parameters.kernel_parameters] 333 snd = [[x for x in p.name if x == x.lower()] for p in snd.info.parameters.kernel_parameters] 334 assert sorted(fst) == sorted(snd), "{} != {}".format(fst, snd) 335 336 337 test_models( 338 "cylinder+sphere", 339 "sphere+cylinder") 340 test_models( 341 "cylinder*sphere", 342 "sphere*cylinder") 343 test_models( 344 "cylinder@hardsphere*sphere", 345 "sphere*cylinder@hardsphere") 346 test_models( 347 "barbell+sphere*cylinder@hardsphere", 348 "sphere*cylinder@hardsphere+barbell") 349 test_models( 350 "barbell+cylinder@hardsphere*sphere", 351 "cylinder@hardsphere*sphere+barbell") 352 test_models( 353 "barbell+sphere*cylinder@hardsphere", 354 "barbell+cylinder@hardsphere*sphere") 355 test_models( 356 "sphere*cylinder@hardsphere+barbell", 357 "cylinder@hardsphere*sphere+barbell") 358 test_models( 359 "barbell+sphere*cylinder@hardsphere", 360 "cylinder@hardsphere*sphere+barbell") 361 test_models( 362 "barbell+cylinder@hardsphere*sphere", 363 "sphere*cylinder@hardsphere+barbell") 364 365 #Test the the model produces the parameters that we would expect 366 model = load_model("cylinder@hardsphere*sphere") 367 actual = [p.name for p in model.info.parameters.kernel_parameters] 368 target = ("sld sld_solvent radius length theta phi volfraction" 369 " A_sld A_sld_solvent A_radius").split() 370 assert target == actual, "%s != %s"%(target, actual) 371 338 372 if __name__ == "__main__": 339 373 list_models_main() -
sasmodels/jitter.py
r8cfb486 r0d5a655 1 1 #!/usr/bin/env python 2 2 """ 3 Application to explore the difference between sasview 3.x orientation 4 dispersity and possible replacement algorithms. 3 Jitter Explorer 4 =============== 5 6 Application to explore orientation angle and angular dispersity. 5 7 """ 6 8 from __future__ import division, print_function … … 124 126 def draw_parallelepiped(ax, size, view, jitter, steps=None, alpha=1): 125 127 """Draw a parallelepiped.""" 126 a, b,c = size128 a, b, c = size 127 129 x = a*np.array([ 1,-1, 1,-1, 1,-1, 1,-1]) 128 130 y = b*np.array([ 1, 1,-1,-1, 1, 1,-1,-1]) … … 141 143 x, y, z = transform_xyz(view, jitter, x, y, z) 142 144 ax.plot_trisurf(x, y, triangles=tri, Z=z, color='w', alpha=alpha) 145 146 # Draw pink face on box. 147 # Since I can't control face color, instead draw a thin box situated just 148 # in front of the "a+" face. 149 if 1: 150 x = a*np.array([ 1,-1, 1,-1, 1,-1, 1,-1]) 151 y = b*np.array([ 1, 1,-1,-1, 1, 1,-1,-1]) 152 z = c*np.array([ 1, 1, 1, 1,-1,-1,-1,-1]) 153 x, y, z = transform_xyz(view, jitter, abs(x)*1.05, y, z) 154 ax.plot_trisurf(x, y, triangles=tri, Z=z, color=[1,0.6,0.6], alpha=alpha) 143 155 144 156 draw_labels(ax, view, jitter, [ … … 608 620 Show an interactive orientation and jitter demo. 609 621 610 *model_name* is one of the models available in :func:`select_model`. 622 *model_name* is one of: sphere, cylinder, ellipsoid, parallelepiped, 623 triaxial_ellipsoid, or bcc_paracrystal. 624 625 *size* gives the dimensions (a, b, c) of the shape. 626 627 *dist* is the type of dispersition: gaussian, rectangle, or uniform. 628 629 *mesh* is the number of points in the dispersion mesh. 630 631 *projection* is the map projection to use for the mesh: equirectangular, 632 sinusoidal, guyou, azimuthal_equidistance, or azimuthal_equal_area. 611 633 """ 612 634 # projection number according to 1-order position in list, but -
sasmodels/mixture.py
r2d81cfe recf895e 94 94 # If model is a sum model, each constituent model gets its own scale parameter 95 95 scale_prefix = prefix 96 if prefix == '' and part.operation== '*':96 if prefix == '' and getattr(part, "operation", '') == '*': 97 97 # `part` is a composition product model. Find the prefixes of 98 # it's parameters to form a new prefix for the scale , eg:99 # a model with A*B*C will have ABC_scale98 # it's parameters to form a new prefix for the scale. 99 # For example, a model with A*B*C will have ABC_scale. 100 100 sub_prefixes = [] 101 101 for param in part.parameters.kernel_parameters: … … 233 233 return self 234 234 235 def next(self):235 def __next__(self): 236 236 # type: () -> Tuple[List[Callable], CallDetails, np.ndarray] 237 237 if self.part_num >= len(self.parts): … … 251 251 252 252 return kernel, call_details, values 253 254 # CRUFT: py2 support 255 next = __next__ 253 256 254 257 def _part_details(self, info, par_index): -
sasmodels/modelinfo.py
r108e70e r95498a3 69 69 processed.append(parse_parameter(*p)) 70 70 partable = ParameterTable(processed) 71 partable.check_angles() 71 72 return partable 72 73 … … 421 422 # type: (List[Parameter]) -> None 422 423 self.kernel_parameters = parameters 423 self._check_angles()424 424 self._set_vector_lengths() 425 425 … … 471 471 self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse) 472 472 473 def _check_angles(self): 473 def check_angles(self): 474 """ 475 Check that orientation angles are theta, phi and possibly psi. 476 """ 474 477 theta = phi = psi = -1 475 478 for k, p in enumerate(self.kernel_parameters): … … 494 497 if psi >= 0 and psi != phi+1: 495 498 raise TypeError("psi must follow phi") 496 #if (psi >= 0 and psi != last_par) or (psi < 0 and phi != last_par):497 #raise TypeError("orientation parameters must appear at the "498 #"end of the parameter table")499 if (psi >= 0 and psi != last_par) or (psi < 0 and phi != last_par): 500 raise TypeError("orientation parameters must appear at the " 501 "end of the parameter table") 499 502 elif theta >= 0 or phi >= 0 or psi >= 0: 500 503 raise TypeError("oriented shapes must have both theta and phi and maybe psi")
Note: See TracChangeset
for help on using the changeset viewer.