Changeset bd91e8f in sasmodels
- Timestamp:
- Mar 30, 2019 4:59:53 PM (6 years ago)
- Branches:
- ticket_1156
- Children:
- 345cc8f
- Parents:
- a3412a6 (diff), e15a822 (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:
-
- 2 added
- 150 edited
Legend:
- Unmodified
- Added
- Removed
-
.travis.yml
r5c36bf1 rf3767c2 9 9 - os: linux 10 10 env: 11 - PY=3. 611 - PY=3.7 12 12 - os: osx 13 13 language: generic … … 17 17 language: generic 18 18 env: 19 - PY=3. 519 - PY=3.7 20 20 branches: 21 21 only: -
doc/guide/index.rst
rda5536f rbc69321 12 12 pd/polydispersity.rst 13 13 resolution.rst 14 plugin.rst 15 fitting_sq.rst 14 16 magnetism/magnetism.rst 15 17 orientation/orientation.rst 16 18 sesans/sans_to_sesans.rst 17 19 sesans/sesans_fitting.rst 18 plugin.rst19 20 scripting.rst 20 21 refs.rst -
doc/guide/plugin.rst
r9150036 re15a822 303 303 **Note: The order of the parameters in the definition will be the order of the 304 304 parameters in the user interface and the order of the parameters in Fq(), Iq(), 305 Iqac(), Iqabc(), form_volume() and shell_volume().305 Iqac(), Iqabc(), radius_effective(), form_volume() and shell_volume(). 306 306 And** *scale* **and** *background* **parameters are implicit to all models, 307 307 so they do not need to be included in the parameter table.** … … 387 387 can take arbitrary values, even for integer parameters, so your model should 388 388 round the incoming parameter value to the nearest integer inside your model 389 you should round to the nearest integer. In C code, you can do this using:: 389 you should round to the nearest integer. In C code, you can do this using: 390 391 .. code-block:: c 390 392 391 393 static double … … 454 456 ............. 455 457 458 .. note:: 459 460 Pure python models do not yet support direct computation of $<F(Q)^2>$ or 461 $<F(Q)>^2$. Neither do they support orientational distributions or magnetism 462 (use C models if these are required). 463 456 464 For pure python models, define the *Iq* function:: 457 465 … … 499 507 Models should define *form_volume(par1, par2, ...)* where the parameter 500 508 list includes the *volume* parameters in order. This is used for a weighted 501 volume normalization so that scattering is on an absolute scale. If 502 *form_volume* is not defined, then the default *form_volume = 1.0* will be 503 used. 509 volume normalization so that scattering is on an absolute scale. For 510 solid shapes, the *I(q)* function should use *form_volume* squared 511 as its scale factor. If *form_volume* is not defined, then the default 512 *form_volume = 1.0* will be used. 504 513 505 514 Hollow shapes, where the volume fraction of particle corresponds to the 506 515 material in the shell rather than the volume enclosed by the shape, must 507 516 also define a *shell_volume(par1, par2, ...)* function. The parameters 508 are the same as for *form_volume*. The *I(q)* calculation should use 509 *shell_volume* squared as its scale factor for the volume normalization. 510 The structure factor calculation needs *form_volume* in order to properly 511 scale the volume fraction parameter, so both functions are required for 512 hollow shapes. 513 514 Note: Pure python models do not yet support direct computation of the 515 average of $F(q)$ and $F^2(q)$. 517 are the same as for *form_volume*. Here the *I(q)* function should use 518 *shell_volume* squared instead of *form_volume* squared so that the scale 519 parameter corresponds to the volume fraction of material in the sample. 520 The structure factor calculation needs the volume fraction of the filled 521 shapes for its calculation, so the volume fraction parameter in the model 522 is automatically scaled by *form_volume/shell_volume* prior to calling the 523 structure factor. 516 524 517 525 Embedded C Models … … 524 532 """ 525 533 526 This expands into the equivalent C code:: 534 This expands into the equivalent C code: 535 536 .. code-block:: c 527 537 528 538 double Iq(double q, double par1, double par2, ...); … … 535 545 includes only the volume parameters. 536 546 537 * form_volume* defines the volume of the shell for hollow shapes. As in547 *shell_volume* defines the volume of the shell for hollow shapes. As in 538 548 python models, it includes only the volume parameters. 539 549 … … 567 577 Rather than returning NAN from Iq, you must define the *INVALID(v)*. The 568 578 *v* parameter lets you access all the parameters in the model using 569 *v.par1*, *v.par2*, etc. For example:: 579 *v.par1*, *v.par2*, etc. For example: 580 581 .. code-block:: c 570 582 571 583 #define INVALID(v) (v.bell_radius < v.radius) … … 582 594 583 595 Instead of defining the *Iq* function, models can define *Fq* as 584 something like:: 596 something like: 597 598 .. code-block:: c 585 599 586 600 double Fq(double q, double *F1, double *F2, double par1, double par2, ...); … … 614 628 laboratory frame and beam travelling along $-z$. 615 629 616 The oriented C model is called using *Iqabc(qa, qb, qc, par1, par2, ...)* where 630 The oriented C model (oriented pure Python models are not supported) 631 is called using *Iqabc(qa, qb, qc, par1, par2, ...)* where 617 632 *par1*, etc. are the parameters to the model. If the shape is rotationally 618 633 symmetric about *c* then *psi* is not needed, and the model is called … … 642 657 643 658 Using the $z, w$ values for Gauss-Legendre integration in "lib/gauss76.c", the 644 numerical integration is then:: 659 numerical integration is then: 660 661 .. code-block:: c 645 662 646 663 double outer_sum = 0.0; … … 718 735 to compute the proper magnetism and orientation, which you can implement 719 736 using *Iqxy(qx, qy, par1, par2, ...)*. 737 738 **Note: Magnetism is not supported in pure Python models.** 720 739 721 740 Special Functions … … 983 1002 memory, and wrong answers computed. The conclusion from a very long and 984 1003 strange debugging session was that any arrays that you declare in your 985 model should be a multiple of four. For example:: 1004 model should be a multiple of four. For example: 1005 1006 .. code-block:: c 986 1007 987 1008 double Iq(q, p1, p2, ...) … … 1015 1036 structure factor is the *hardsphere* interaction, which 1016 1037 uses the effective radius of the form factor as an input to the structure 1017 factor model. The effective radius is the average radius of the 1018 form averaged over all the polydispersity values. 1019 1020 :: 1021 1022 def ER(radius, thickness): 1023 """Effective radius of a core-shell sphere.""" 1024 return radius + thickness 1025 1026 Now consider the *core_shell_sphere*, which has a simple effective radius 1027 equal to the radius of the core plus the thickness of the shell, as 1028 shown above. Given polydispersity over *(r1, r2, ..., rm)* in radius and 1029 *(t1, t2, ..., tn)* in thickness, *ER* is called with a mesh 1030 grid covering all possible combinations of radius and thickness. 1031 That is, *radius* is *(r1, r2, ..., rm, r1, r2, ..., rm, ...)* 1032 and *thickness* is *(t1, t1, ... t1, t2, t2, ..., t2, ...)*. 1033 The *ER* function returns one effective radius for each combination. 1034 The effective radius calculator weights each of these according to 1035 the polydispersity distributions and calls the structure factor 1036 with the average *ER*. 1037 1038 :: 1039 1040 def VR(radius, thickness): 1041 """Sphere and shell volumes for a core-shell sphere.""" 1042 whole = 4.0/3.0 * pi * (radius + thickness)**3 1043 core = 4.0/3.0 * pi * radius**3 1044 return whole, whole - core 1045 1046 Core-shell type models have an additional volume ratio which scales 1047 the structure factor. The *VR* function returns the volume of 1048 the whole sphere and the volume of the shell. Like *ER*, there is 1049 one return value for each point in the mesh grid. 1050 1051 *NOTE: we may be removing or modifying this feature soon. As of the 1052 time of writing, core-shell sphere returns (1., 1.) for VR, giving a volume 1053 ratio of 1.0.* 1038 factor model. The effective radius is the weighted average over all 1039 values of the shape in polydisperse systems. 1040 1041 There can be many notions of effective radius, depending on the shape. For 1042 a sphere it is clearly just the radius, but for an ellipsoid of revolution 1043 we provide average curvature, equivalent sphere radius, minimum radius and 1044 maximum radius. These options are listed as *radius_effective_modes* in 1045 the python model defintion, and must be computed by the *radius_effective* 1046 function which takes the *radius_effective_mode* parameter as an integer, 1047 along with the various model parameters. Unlike normal C/Python arrays, 1048 the first mode is 1, the second is 2, etc. Mode 0 indicates that the 1049 effective radius from the shape is to be ignored in favour of the the 1050 effective radius parameter in the structure factor model. 1051 1052 1053 Consider the core-shell sphere, which defines the following effective radius 1054 modes in the python model:: 1055 1056 radius_effective_modes = [ 1057 "outer radius", 1058 "core radius", 1059 ] 1060 1061 and the following function in the C-file for the model: 1062 1063 .. code-block:: c 1064 1065 static double 1066 radius_effective(int mode, double radius, double thickness) 1067 { 1068 switch (mode) { 1069 case 0: return radius + thickness; 1070 case 1: return radius; 1071 default: return 0.; 1072 } 1073 } 1074 1075 static double 1076 form_volume(double radius, double thickness) 1077 { 1078 return M_4PI_3 * cube(radius + thickness); 1079 } 1080 1081 Given polydispersity over *(r1, r2, ..., rm)* in radius and *(t1, t2, ..., tn)* 1082 in thickness, *radius_effective* is called over a mesh grid covering all 1083 possible combinations of radius and thickness, with a single *(ri, tj)* pair 1084 in each call. The weights each of these results according to the 1085 polydispersity distributions and calls the structure factor with the average 1086 effective radius. Similarly, for *form_volume*. 1087 1088 Hollow models have an additional volume ratio which is needed to scale the 1089 structure factor. The structure factor uses the volume fraction of the filled 1090 particles as part of its density estimate, but the scale factor for the 1091 scattering intensity (as non-solvent volume fraction / volume) is determined 1092 by the shell volume only. Therefore the *shell_volume* function is 1093 needed to compute the form:shell volume ratio, which then scales the 1094 *volfraction* parameter prior to calling the structure factor calculator. 1095 In the case of a hollow sphere, this would be: 1096 1097 .. code-block:: c 1098 1099 static double 1100 shell_volume(double radius, double thickness) 1101 { 1102 double whole = M_4PI_3 * cube(radius + thickness); 1103 double core = M_4PI_3 * cube(radius); 1104 return whole - core; 1105 } 1106 1107 If *shell_volume* is not present, then *form_volume* and *shell_volume* are 1108 assumed to be equal, and the shape is considered solid. 1054 1109 1055 1110 Unit Tests … … 1108 1163 and a check that the model runs. 1109 1164 1110 If you are not using sasmodels from SasView, skip this step.1111 1112 1165 Recommended Testing 1113 1166 ................... 1167 1168 **NB: For now, this more detailed testing is only possible if you have a 1169 SasView build environment available!** 1114 1170 1115 1171 If the model compiles and runs, you can next run the unit tests that … … 1248 1304 | 2016-10-25 Steve King 1249 1305 | 2017-05-07 Paul Kienzle - Moved from sasview to sasmodels docs 1306 | 2019-03-28 Paul Kienzle - Update docs for radius_effective and shell_volume -
doc/guide/resolution.rst
r0db85af rdb1d9d5 1 .. sm_help.rst2 3 .. This is a port of the original SasView html help file to ReSTructured text4 .. by S King, ISIS, during SasView CodeCamp-III in Feb 2015.1 .. resolution.rst 2 3 .. This is a port of the original SasView html help file sm_help to ReSTructured 4 .. text by S King, ISIS, during SasView CodeCamp-III in Feb 2015. 5 5 6 6 … … 17 17 resolution contribution into a model calculation/simulation (which by definition 18 18 will be exact) to make it more representative of what has been measured 19 experimentally - a process called *smearing*. Sasmodels does the latter. 19 experimentally - a process called *smearing*. The Sasmodels component of SasView 20 does the latter. 20 21 21 22 Both smearing and desmearing rely on functions to describe the resolution … … 29 30 for the instrument and stored with the data file. If not, they will need to 30 31 be set manually before fitting. 32 33 .. note:: 34 Problems may be encountered if the data set loaded by SasView is a 35 concatenation of SANS data from several detector distances where, of 36 course, the worst Q resolution is next to the beam stop at each detector 37 distance. (This will also be noticeable in the residuals plot where 38 there will be poor overlap). SasView sensibly orders all the input 39 data points by increasing Q for nicer-looking plots, however, the dQ 40 data can then vary considerably from point to point. If 'Use dQ data' 41 smearing is selected then spikes may appear in the model fits, whereas 42 if 'None' or 'Custom Pinhole Smear' are selected the fits look normal. 43 44 In such instances, possible solutions are to simply remove the data 45 with poor Q resolution from the shorter detector distances, or to fit 46 the data from different detector distances simultaneously. 31 47 32 48 -
explore/asymint.py
rc462169 r66dbbfb 27 27 import os, sys 28 28 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) 29 import warnings 29 30 30 31 import numpy as np … … 36 37 37 38 import sasmodels.special as sp 38 39 # Need to parse shape early since it determines the kernel function40 # that will be used for the various integrators41 shape = 'parallelepiped' if len(sys.argv) < 2 else sys.argv[1]42 Qstr = '0.005' if len(sys.argv) < 3 else sys.argv[2]43 39 44 40 DTYPE = 'd' … … 201 197 return norm, Fq 202 198 203 if shape == 'sphere': 204 RADIUS = 50 # integer for the sake of mpf 205 NORM, KERNEL = make_sphere(radius=RADIUS) 206 NORM_MP, KERNEL_MP = make_sphere(radius=RADIUS, env=MPenv) 207 elif shape == 'cylinder': 208 #RADIUS, LENGTH = 10, 100000 209 RADIUS, LENGTH = 10, 300 # integer for the sake of mpf 210 NORM, KERNEL = make_cylinder(radius=RADIUS, length=LENGTH) 211 NORM_MP, KERNEL_MP = make_cylinder(radius=RADIUS, length=LENGTH, env=MPenv) 212 elif shape == 'triaxial_ellipsoid': 213 #A, B, C = 4450, 14000, 47 214 A, B, C = 445, 140, 47 # integer for the sake of mpf 215 NORM, KERNEL = make_triaxial_ellipsoid(A, B, C) 216 NORM_MP, KERNEL_MP = make_triaxial_ellipsoid(A, B, C, env=MPenv) 217 elif shape == 'parallelepiped': 218 #A, B, C = 4450, 14000, 47 219 A, B, C = 445, 140, 47 # integer for the sake of mpf 220 NORM, KERNEL = make_parallelepiped(A, B, C) 221 NORM_MP, KERNEL_MP = make_parallelepiped(A, B, C, env=MPenv) 222 elif shape == 'core_shell_parallelepiped': 223 #A, B, C = 4450, 14000, 47 224 #A, B, C = 445, 140, 47 # integer for the sake of mpf 225 A, B, C = 114, 1380, 6800 226 DA, DB, DC = 21, 58, 2300 227 SLDA, SLDB, SLDC = "5", "-0.3", "11.5" 228 ## default parameters from sasmodels 229 #A,B,C,DA,DB,DC,SLDA,SLDB,SLDC = 400,75,35,10,10,10,2,4,2 230 ## swap A-B-C to C-B-A 231 #A, B, C, DA, DB, DC, SLDA, SLDB, SLDC = C, B, A, DC, DB, DA, SLDC, SLDB, SLDA 232 #A,B,C,DA,DB,DC,SLDA,SLDB,SLDC = 10,20,30,100,200,300,1,2,3 233 #SLD_SOLVENT,CONTRAST = 0, 4 234 if 1: # C shortest 235 B, C = C, B 236 DB, DC = DC, DB 237 SLDB, SLDC = SLDC, SLDB 238 elif 0: # C longest 239 A, C = C, A 240 DA, DC = DC, DA 241 SLDA, SLDC = SLDC, SLDA 242 #NORM, KERNEL = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC) 243 NORM, KERNEL = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC) 244 NORM_MP, KERNEL_MP = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC, env=MPenv) 245 elif shape == 'paracrystal': 246 LATTICE = 'bcc' 247 #LATTICE = 'fcc' 248 #LATTICE = 'sc' 249 DNN, D_FACTOR = 220, '0.06' # mpmath needs to initialize floats from string 250 RADIUS = 40 # integer for the sake of mpf 251 NORM, KERNEL = make_paracrystal( 252 radius=RADIUS, dnn=DNN, d_factor=D_FACTOR, lattice=LATTICE) 253 NORM_MP, KERNEL_MP = make_paracrystal( 254 radius=RADIUS, dnn=DNN, d_factor=D_FACTOR, lattice=LATTICE, env=MPenv) 255 else: 256 raise ValueError("Unknown shape %r"%shape) 199 NORM = 1.0 # type: float 200 KERNEL = None # type: CALLABLE[[ndarray, ndarray, ndarray], ndarray] 201 NORM_MP = 1 # type: mpf 202 KERNEL = None # type: CALLABLE[[mpf, mpf, mpf], mpf] 203 204 SHAPES = [ 205 'sphere', 206 'cylinder', 207 'triaxial_ellipsoid', 208 'parallelepiped', 209 'core_shell_parallelepiped', 210 'fcc_paracrystal', 211 'bcc_paracrystal', 212 'sc_paracrystal', 213 ] 214 def build_shape(shape, **pars): 215 global NORM, KERNEL 216 global NORM_MP, KERNEL_MP 217 218 # Note: using integer or string defaults for the sake of mpf 219 if shape == 'sphere': 220 RADIUS = pars.get('radius', 50) 221 NORM, KERNEL = make_sphere(radius=RADIUS) 222 NORM_MP, KERNEL_MP = make_sphere(radius=RADIUS, env=MPenv) 223 elif shape == 'cylinder': 224 #RADIUS, LENGTH = 10, 100000 225 RADIUS = pars.get('radius', 10) 226 LENGTH = pars.get('radius', 300) 227 NORM, KERNEL = make_cylinder(radius=RADIUS, length=LENGTH) 228 NORM_MP, KERNEL_MP = make_cylinder(radius=RADIUS, length=LENGTH, env=MPenv) 229 elif shape == 'triaxial_ellipsoid': 230 #A, B, C = 4450, 14000, 47 231 A = pars.get('a', 445) 232 B = pars.get('b', 140) 233 C = pars.get('c', 47) 234 NORM, KERNEL = make_triaxial_ellipsoid(A, B, C) 235 NORM_MP, KERNEL_MP = make_triaxial_ellipsoid(A, B, C, env=MPenv) 236 elif shape == 'parallelepiped': 237 #A, B, C = 4450, 14000, 47 238 A = pars.get('a', 445) 239 B = pars.get('b', 140) 240 C = pars.get('c', 47) 241 NORM, KERNEL = make_parallelepiped(A, B, C) 242 NORM_MP, KERNEL_MP = make_parallelepiped(A, B, C, env=MPenv) 243 elif shape == 'core_shell_parallelepiped': 244 #A, B, C = 4450, 14000, 47 245 #A, B, C = 445, 140, 47 # integer for the sake of mpf 246 A = pars.get('a', 114) 247 B = pars.get('b', 1380) 248 C = pars.get('c', 6800) 249 DA = pars.get('da', 21) 250 DB = pars.get('db', 58) 251 DC = pars.get('dc', 2300) 252 SLDA = pars.get('slda', "5") 253 SLDB = pars.get('sldb', "-0.3") 254 SLDC = pars.get('sldc', "11.5") 255 ## default parameters from sasmodels 256 #A,B,C,DA,DB,DC,SLDA,SLDB,SLDC = 400,75,35,10,10,10,2,4,2 257 ## swap A-B-C to C-B-A 258 #A, B, C, DA, DB, DC, SLDA, SLDB, SLDC = C, B, A, DC, DB, DA, SLDC, SLDB, SLDA 259 #A,B,C,DA,DB,DC,SLDA,SLDB,SLDC = 10,20,30,100,200,300,1,2,3 260 #SLD_SOLVENT,CONTRAST = 0, 4 261 if 1: # C shortest 262 B, C = C, B 263 DB, DC = DC, DB 264 SLDB, SLDC = SLDC, SLDB 265 elif 0: # C longest 266 A, C = C, A 267 DA, DC = DC, DA 268 SLDA, SLDC = SLDC, SLDA 269 #NORM, KERNEL = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC) 270 NORM, KERNEL = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC) 271 NORM_MP, KERNEL_MP = make_core_shell_parallelepiped(A, B, C, DA, DB, DC, SLDA, SLDB, SLDC, env=MPenv) 272 elif shape.endswith('paracrystal'): 273 LATTICE, _ = shape.split('_') 274 DNN = pars.get('dnn', 220) 275 D_FACTOR = pars.get('d_factor', '0.06') 276 RADIUS = pars.get('radius', 40) 277 NORM, KERNEL = make_paracrystal( 278 radius=RADIUS, dnn=DNN, d_factor=D_FACTOR, lattice=LATTICE) 279 NORM_MP, KERNEL_MP = make_paracrystal( 280 radius=RADIUS, dnn=DNN, d_factor=D_FACTOR, lattice=LATTICE, env=MPenv) 281 else: 282 raise ValueError("Unknown shape %r"%shape) 257 283 258 284 # Note: hardcoded in mp_quad … … 273 299 274 300 # 2D integration functions 275 def mp_quad_2d(q , shape):301 def mp_quad_2d(q): 276 302 evals = [0] 277 303 def integrand(theta, phi): … … 393 419 print("simpson-%d"%n, n**2, simps(simps(Zq, dx=dx), dx=dy)*SCALE/(4*pi)) 394 420 print("romb-%d"%n, n**2, romb(romb(Zq, dx=dx), dx=dy)*SCALE/(4*pi)) 421 422 def quadpy_method(q, rule): 423 """ 424 Use *rule*="name:index" where name and index are chosen from below. 425 426 Available rule names and the corresponding indices:: 427 428 AlbrechtCollatz: [1-5] 429 BazantOh: 9, 11, 13 430 HeoXu: 13, 15, 17, 19-[1-2], 21-[1-6], 23-[1-3], 25-[1-2], 27-[1-3], 431 29, 31, 33, 35, 37, 39-[1-2] 432 FliegeMaier: 4, 9, 16, 25 433 Lebedev: 3[a-c], 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 35, 434 41, 47, 53, 59, 65, 71, 77 83, 89, 95, 101, 107, 113, 119, 125, 131 435 McLaren: [1-10] 436 Stroud: U3 3-1, U3 5-[1-5], U3 7-[1-2], U3 8-1, U3 9-[1-3], 437 U3 11-[1-3], U3 14-1 438 """ 439 try: 440 import quadpy 441 except ImportError: 442 warnings.warn("use 'pip install quadpy' to enable quadpy.sphere tests") 443 return 444 445 from quadpy.sphere import (AlbrechtCollatz, BazantOh, HeoXu, 446 FliegeMaier, Lebedev, McLaren, Stroud, integrate_spherical) 447 RULES = { 448 'AlbrechtCollatz': AlbrechtCollatz, 449 'BazantOh': BazantOh, 450 'HeoXu': HeoXu, 451 'FliegeMaier': FliegeMaier, 452 'Lebedev': Lebedev, 453 'McLaren': McLaren, 454 'Stroud': Stroud, 455 } 456 int_index = 'AlbrechtCollatz', 'McLaren' 457 458 rule_name, rule_index = rule.split(':') 459 index = int(rule_index) if rule_name in int_index else rule_index 460 rule_obj = RULES[rule_name](index) 461 fn = lambda azimuthal, polar: kernel_2d(q=q, theta=polar, phi=azimuthal) 462 Iq = integrate_spherical(fn, rule=rule_obj)/(4*pi) 463 print("%s degree=%d points=%s => %.15g" 464 % (rule, rule_obj.degree, len(rule_obj.points), Iq)) 395 465 396 466 def plot_2d(q, n=300): … … 414 484 pylab.show() 415 485 416 def main(Qstr): 417 Q = float(Qstr) 418 if shape == 'sphere': 486 def main(): 487 import argparse 488 489 parser = argparse.ArgumentParser( 490 description="asymmetric integration explorer", 491 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 492 ) 493 parser.add_argument('-s', '--shape', choices=SHAPES, 494 default='parallelepiped', 495 help='oriented shape') 496 parser.add_argument('-q', '--q_value', type=str, default='0.005', 497 help='Q value to evaluate') 498 parser.add_argument('pars', type=str, nargs='*', default=[], 499 help='p=val for p in shape parameters') 500 opts = parser.parse_args() 501 pars = {k: v for par in opts.pars for k, v in [par.split('=')]} 502 build_shape(opts.shape, **pars) 503 504 Q = float(opts.q_value) 505 if opts.shape == 'sphere': 419 506 print("exact", NORM*sp.sas_3j1x_x(Q*RADIUS)**2) 420 print("gauss-20", *gauss_quad_2d(Q, n=20)) 421 print("gauss-76", *gauss_quad_2d(Q, n=76)) 422 print("gauss-150", *gauss_quad_2d(Q, n=150)) 423 print("gauss-500", *gauss_quad_2d(Q, n=500)) 424 print("gauss-1025", *gauss_quad_2d(Q, n=1025)) 425 print("gauss-2049", *gauss_quad_2d(Q, n=2049)) 426 print("gauss-20 usub", *gauss_quad_usub(Q, n=20)) 427 print("gauss-76 usub", *gauss_quad_usub(Q, n=76)) 428 print("gauss-150 usub", *gauss_quad_usub(Q, n=150)) 507 508 # Methods from quadpy, if quadpy is available 509 # AlbrechtCollatz: [1-5] 510 # BazantOh: 9, 11, 13 511 # HeoXu: 13, 15, 17, 19-[1-2], 21-[1-6], 23-[1-3], 25-[1-2], 27-[1-3], 512 # 29, 31, 33, 35, 37, 39-[1-2] 513 # FliegeMaier: 4, 9, 16, 25 514 # Lebedev: 3[a-c], 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 35, 515 # 41, 47, 53, 59, 65, 71, 77 83, 89, 95, 101, 107, 113, 119, 125, 131 516 # McLaren: [1-10] 517 # Stroud: U3 3-1, U3 5-[1-5], U3 7-[1-2], U3 8-1, U3 9-[1-3], 518 # U3 11-[1-3], U3 14-1 519 quadpy_method(Q, "AlbrechtCollatz:5") 520 quadpy_method(Q, "HeoXu:39-2") 521 quadpy_method(Q, "FliegeMaier:25") 522 quadpy_method(Q, "Lebedev:19") 523 quadpy_method(Q, "Lebedev:131") 524 quadpy_method(Q, "McLaren:10") 525 quadpy_method(Q, "Stroud:U3 14-1") 526 527 print("gauss-20 points=%d => %.15g" % gauss_quad_2d(Q, n=20)) 528 print("gauss-76 points=%d => %.15g" % gauss_quad_2d(Q, n=76)) 529 print("gauss-150 points=%d => %.15g" % gauss_quad_2d(Q, n=150)) 530 print("gauss-500 points=%d => %.15g" % gauss_quad_2d(Q, n=500)) 531 print("gauss-1025 points=%d => %.15g" % gauss_quad_2d(Q, n=1025)) 532 print("gauss-2049 points=%d => %.15g" % gauss_quad_2d(Q, n=2049)) 533 print("gauss-20 usub points=%d => %.15g" % gauss_quad_usub(Q, n=20)) 534 print("gauss-76 usub points=%d => %.15g" % gauss_quad_usub(Q, n=76)) 535 print("gauss-150 usub points=%d => %.15g" % gauss_quad_usub(Q, n=150)) 536 429 537 #gridded_2d(Q, n=2**8+1) 430 538 gridded_2d(Q, n=2**10+1) 431 539 #gridded_2d(Q, n=2**12+1) 432 540 #gridded_2d(Q, n=2**15+1) 433 if shape not in ('paracrystal', 'core_shell_parallelepiped'): 434 # adaptive forms on models for which the calculations are fast enough 541 # adaptive forms on models for which the calculations are fast enough 542 SLOW_SHAPES = { 543 'fcc_paracrystal', 'bcc_paracrystal', 'sc_paracrystal', 544 'core_shell_parallelepiped', 545 } 546 if opts.shape not in SLOW_SHAPES: 435 547 print("dblquad", *scipy_dblquad_2d(Q)) 436 548 print("semi-romberg-100", *semi_romberg_2d(Q, n=100)) 437 549 print("romberg", *scipy_romberg_2d(Q)) 438 550 with mp.workprec(100): 439 print("mpmath", *mp_quad_2d(mp.mpf( Qstr), shape))551 print("mpmath", *mp_quad_2d(mp.mpf(opts.q_value))) 440 552 plot_2d(Q, n=200) 441 553 442 554 if __name__ == "__main__": 443 main( Qstr)555 main() -
explore/beta/sasfit_compare.py
r119073a ra34b811 408 408 #radius_effective=12.59921049894873, 409 409 ) 410 target = sasmodels_theory(q, model, effective_radius_mode=0, structure_factor_mode=1, **pars)410 target = sasmodels_theory(q, model, radius_effective_mode=0, structure_factor_mode=1, **pars) 411 411 actual = ellipsoid_pe(q, norm='sasview', **pars) 412 412 title = " ".join(("sasmodels", model, pd_type)) -
explore/beta/sasfit_compare_new.py
r2586ab72 ra34b811 21 21 #Used to calculate F(q) for the cylinder, sphere, ellipsoid models 22 22 # RKH adding vesicle and hollow_cylinder to test sasview special cases of ER and VR 23 # BEWARE there are issues here if you run this from python3 (i.e. qt5 version of sasview), so do "source activate sasview"23 # There were issues here from python3 (i.e. qt5 version of sasview),fixed by Paul K (else do "source activate sasview") 24 24 def sas_sinx_x(x): 25 25 with np.errstate(all='ignore'): … … 275 275 if radius_effective is None: 276 276 radius_effective = radius 277 print("radius_effective ",radius_effective)278 277 average_volume = total_volume/total_weight 279 278 if norm == 'sasfit': … … 285 284 F2 *= 1e-12 286 285 IQD = F2/average_volume*1e8*volfraction 286 print("in sphere_r : radius_effective ",radius_effective," volfraction",volfraction) 287 287 SQ = hardsphere_simple(q, radius_effective, volfraction) 288 288 beta = F1**2/F2 … … 332 332 if radius_effective is None: 333 333 radius_effective = radius_eff/total_weight 334 print(" radius_effective ",radius_effective)334 print("in vesicle_pe : radius_effective ",radius_effective) 335 335 if norm == 'sasfit': 336 336 IQD = F2 … … 348 348 F2 *= 1e-12 349 349 IQD = F2/average_volume*1e8*volfraction 350 # RKH SORT THIS OUT WHEN HAVE NEW MODEL 351 vfff=volfraction*( 30./20.)**3352 print(" radius_effective, fudged volfaction for S(Q) ",radius_effective,vfff)350 # RKH SORT THIS OUT WHEN HAVE NEW MODEL Vtot/Vshell = (R+T)^3/ ( (R+T)^3 -R^3 ) 351 vfff=volfraction*( 1.0/(1.0 - ( (radius/(radius+thickness))**3 ))) 352 print("in vesicle_pe : radius_effective, fudged volfraction for S(Q) ",radius_effective,vfff) 353 353 SQ = hardsphere_simple(q, radius_effective, vfff) 354 354 beta = F1**2/F2 … … 359 359 # 360 360 #polydispersity for hollow_cylinder 361 def hollow_cylinder_pe(q,radius=20, thickness=10, sld=4, sld_solvent=1, volfraction=0.15,362 radius_pd=0.1, thickness_pd=0.2, length_pd=0.05, radius_pd_type="gaussian", thickness_pd_type="gaussian",363 radius_effective=None, background=0, scale=1,361 def hollow_cylinder_pe(q,radius=20, thickness=10, length=80, sld=4, sld_solvent=1, volfraction=0.15, 362 radius_pd=0.1, thickness_pd=0.2, length_pd=0.05, radius_pd_type="gaussian", length_pd_type="gaussian", 363 thickness_pd_type="gaussian", radius_effective=None, background=0, scale=1, 364 364 norm='sasview'): 365 365 if norm not in ['sasview', 'sasfit', 'yun']: … … 374 374 T_val, T_prob = schulz_distribution(thickness, thickness_pd*thickness, 0, inf) 375 375 if length_pd_type == 'gaussian': 376 T_val, T_prob = gaussian_distribution(length, length_pd*length, 0, inf)376 L_val, L_prob = gaussian_distribution(length, length_pd*length, 0, inf) 377 377 elif length_pd_type == 'schulz': 378 378 L_val, L_prob = schulz_distribution(length, length_pd*length, 0, inf) … … 402 402 if radius_effective is None: 403 403 radius_effective = radius_eff/total_weight 404 print(" radius_effective ",radius_effective)404 print("in hollow_cylinder : radius_effective ",radius_effective) 405 405 if norm == 'sasfit': 406 406 IQD = F2 … … 420 420 # RKH SORT THIS OUT WHEN HAVE NEW MODEL 421 421 vfff=volfraction 422 print(" radius_effective, volfaction for S(Q) ",radius_effective,vfff)422 print("in hollow_cylinder : radius_effective, volfaction for S(Q) ",radius_effective,vfff) 423 423 SQ = hardsphere_simple(q, radius_effective, vfff) 424 424 beta = F1**2/F2 … … 548 548 #Iq = None#= Sq = None 549 549 r = dict(I._kernel.results()) 550 #print(r)550 print(r) 551 551 return Theory(Q=q, F1=None, F2=None, P=Pq, S=None, I=None, Seff=r["S_eff(Q)"], Ibeta=Iq) 552 552 … … 591 591 radius_pd=.1, radius_pd_type=pd_type, 592 592 volfraction=0.15, 593 radius_effective=12.59921049894873, # equivalent average sphere radius 593 radius_effective=20 # equivalent average sphere radius 594 # NOTE sasview computes its own radius_effective in "target" (the print(r) at end of sasmodels_theory will reveal its value), 595 # this one is only used locally for "actual" 594 596 ) 595 target = sasmodels_theory(q, model, effective_radius_mode=0, structure_factor_mode=1, **pars)597 target = sasmodels_theory(q, model, radius_effective_mode=0, structure_factor_mode=1, **pars) 596 598 actual = sphere_r(q, norm='sasview', **pars) 597 599 title = " ".join(("sasmodels", model, pd_type)) … … 604 606 q = np.logspace(-5, 0, 250) 605 607 model = 'vesicle' 606 print("F2 seems OK, but big issues with S(Q), wo work in progress")607 # NOTE parame ers for vesicle model are soon to be changed to remove "volfraction"608 print("F2 seems OK, but big issues with S(Q), so work in progress") 609 # NOTE parameters for vesicle model are soon to be changed to remove "volfraction" (though still there in 5.0) 608 610 pars = dict( 609 611 radius=20, thickness=10, sld=4, sld_solvent=1, volfraction=0.03, 610 612 background=0, 611 radius_pd=0.1, thickness_pd=0.0, radius_pd_type=pd_type, thickness_pd_type=pd_type 612 #,radius_effective=12.59921049894873, # equivalent average sphere radius 613 ) 614 target = sasmodels_theory(q, model, effective_radius_mode=0, structure_factor_mode=1, **pars) 613 radius_pd=0.01, thickness_pd=0.01, radius_pd_type=pd_type, thickness_pd_type=pd_type, 614 radius_effective=30. ) 615 # equivalent average sphere radius for local "actual" to match what sasview uses, use None to compute average outer radius here, 616 617 target = sasmodels_theory(q, model, radius_effective_mode=0, structure_factor_mode=1, **pars) 615 618 actual = vesicle_pe(q, norm='sasview', **pars) 616 619 title = " ".join(("sasmodels", model, pd_type)) … … 629 632 radius=20, thickness=10, length=80, sld=4, sld_solvent=1, 630 633 background=0, 631 radius_pd=0.1, thickness_pd=0.0, length_pd=0.0, radius_pd_type=pd_type, thickness_pd_type=pd_type, length_pd_type=pd_type 632 #,radius_effective=12.59921049894873, # equivalent average sphere radius633 )634 target = sasmodels_theory(q, model, effective_radius_mode=0, structure_factor_mode=1, **pars)634 radius_pd=0.1, thickness_pd=0.0, length_pd=0.0, radius_pd_type=pd_type, thickness_pd_type=pd_type, length_pd_type=pd_type, 635 radius_effective=40.687) 636 # equivalent average sphere radius for local "actual" to match what sasview uses 637 target = sasmodels_theory(q, model, radius_effective_mode=0, structure_factor_mode=1, **pars) 635 638 actual = hollow_cylinder_pe(q, norm='sasview', **pars) 636 639 # RKH monodisp was OK, actual = hollow_cylinder_theta(q,radius=20, thickness=10, length=80, sld=4, sld_solvent=1 ) … … 651 654 volfraction=0.15, 652 655 radius_effective=270.7543927018, 653 #radius_effective=12.59921049894873, 656 # if change radius_effective to some other value, the S(Q) from sasview does not agree 654 657 ) 655 target = sasmodels_theory(q, model, effective_radius_mode=0, structure_factor_mode=1, **pars)658 target = sasmodels_theory(q, model, radius_effective_mode=0, structure_factor_mode=1, **pars) 656 659 actual = ellipsoid_pe(q, norm='sasview', **pars) 657 660 # RKH test actual = ellipsoid_theta(q, radius_polar=20, radius_equatorial=400, sld=4, sld_solvent=1, volfraction=0.15, radius_effective=270.) … … 751 754 } 752 755 753 Q, IQ = load_sasfit(data_file(' richard_test.txt'))754 Q, IQSD = load_sasfit(data_file(' richard_test2.txt'))755 Q, IQBD = load_sasfit(data_file(' richard_test3.txt'))756 Q, IQ = load_sasfit(data_file('sasfit_sphere_schulz_IQD.txt')) 757 Q, IQSD = load_sasfit(data_file('sasfit_sphere_schulz_IQSD.txt')) 758 Q, IQBD = load_sasfit(data_file('sasfit_sphere_schulz_IQBD.txt')) 756 759 target = Theory(Q=Q, F1=None, F2=None, P=IQ, S=None, I=IQSD, Seff=None, Ibeta=IQBD) 757 760 actual = sphere_r(Q, norm="sasfit", **pars) … … 772 775 } 773 776 774 Q, IQ = load_sasfit(data_file(' richard_test4.txt'))775 Q, IQSD = load_sasfit(data_file(' richard_test5.txt'))776 Q, IQBD = load_sasfit(data_file(' richard_test6.txt'))777 Q, IQ = load_sasfit(data_file('sasfit_ellipsoid_schulz_IQD.txt')) 778 Q, IQSD = load_sasfit(data_file('sasfit_ellipsoid_schulz_IQSD.txt')) 779 Q, IQBD = load_sasfit(data_file('sasfit_ellipsoid_schulz_IQBD.txt')) 777 780 target = Theory(Q=Q, F1=None, F2=None, P=IQ, S=None, I=IQSD, Seff=None, Ibeta=IQBD) 778 781 actual = ellipsoid_pe(Q, norm="sasfit", **pars) -
extra/build_linux.sh
rdb8756e r0d362b7 14 14 # Run tests 15 15 STATUS=0 16 python -m sasmodels.model_test opencl_and_dllall || STATUS=$?16 python -m sasmodels.model_test -v all || STATUS=$? 17 17 python -m sasmodels.resolution || STATUS=$? 18 18 -
extra/pylint.rc
r6e68289 rdf20715 67 67 locally-enabled, 68 68 locally-disabled, 69 #no-else-return, 70 #invalid-name, 71 fixme, 72 too-many-lines, 73 too-many-locals, 74 too-many-branches, 75 too-many-statements, 76 #too-many-instance-attributes, 77 #too-many-return-statements, 78 #no-self-use, 69 79 70 80 [REPORTS] … … 109 119 good-names=_, 110 120 input, id, 111 h, i, j, k, n, p, v, w, x, y, z,112 q, qx, qy, qz, Q, Qx, Qy, Qz, 113 dt, dx, dy, dz, dq, 121 h, i, j, k, n, p, u, v, w, x, y, z, r, dr, 122 q, qx, qy, qz, Q, Qx, Qy, Qz, nx, ny, nz, nq, nr, 123 dt, dx, dy, dz, dq, S, P, a, b, c, 114 124 Iq, dIq, Iqxy, Iq_calc, 115 125 ER, call_ER, VR, call_VR, … … 133 143 134 144 # Regular expression matching correct variable names 135 variable-rgx=[a-z_][a-z0-9_{2D}{1D}]{ 2,30}$145 variable-rgx=[a-z_][a-z0-9_{2D}{1D}]{1,30}$ 136 146 137 147 # Naming hint for variable names … … 151 161 152 162 # Regular expression matching correct argument names 153 argument-rgx=[a-z_][a-z0-9_]{ 2,30}$163 argument-rgx=[a-z_][a-z0-9_]{1,30}$ 154 164 155 165 # Naming hint for argument names … … 280 290 # (useful for modules/projects where namespaces are manipulated during runtime 281 291 # and thus existing member attributes cannot be deduced by static analysis 282 ignored-modules=bumps,sas,numpy,numpy.random,scipy,scipy.special 292 ignored-modules=bumps,sas,numpy,numpy.random,scipy,scipy.special,pyopencl,pycuda,matplotlib.cm,ipyvolume,ipywidget 283 293 284 294 # List of classes names for which member attributes should not be checked -
sasmodels/bumps_model.py
r2c4a190 rb297ba9 26 26 from .kernel import KernelModel 27 27 from .modelinfo import ModelInfo 28 from .resolution import Resolution 28 29 Data = Union[Data1D, Data2D] 29 30 except ImportError: … … 180 181 @property 181 182 def resolution(self): 183 # type: () -> Union[None, Resolution] 184 """ 185 :class:`sasmodels.Resolution` applied to the data, if any. 186 """ 182 187 return self._resolution 183 188 184 189 @resolution.setter 185 190 def resolution(self, value): 191 # type: (Resolution) -> None 192 """ 193 :class:`sasmodels.Resolution` applied to the data, if any. 194 """ 186 195 self._resolution = value 187 196 … … 270 279 Save the model parameters and data into a file. 271 280 272 Not Implemented .281 Not Implemented except for sesans fits. 273 282 """ 274 283 if self.data_type == "sesans": -
sasmodels/compare.py
rc1799d3 rb297ba9 725 725 set_integration_size(model_info, ngauss) 726 726 727 if (dtype != "default" and not dtype.endswith('!') 727 if (dtype != "default" and not dtype.endswith('!') 728 728 and not (kernelcl.use_opencl() or kernelcuda.use_cuda())): 729 729 raise RuntimeError("OpenCL not available " + kernelcl.OPENCL_ERROR) … … 772 772 opts['pars'] = parse_pars(opts, maxdim=maxdim) 773 773 if opts['pars'] is None: 774 return 774 return limits 775 775 result = run_models(opts, verbose=True) 776 776 if opts['plot']: … … 822 822 args = dict((k, v) for k, v in args.items() 823 823 if "_pd" not in k 824 825 824 and ":" not in k 825 and k not in ("background", "scale", "theta", "phi", "psi")) 826 826 args = args.copy() 827 827 … … 896 896 # work with trimmed data, not the full set 897 897 sorted_err = np.sort(abs(err.compressed())) 898 if len(sorted_err)== 0:898 if sorted_err.size == 0: 899 899 print(label + " no valid values") 900 900 return -
sasmodels/convert.py
rb3f4831 rbd91e8f 105 105 translation[newid+str(k)] = oldid+str(k) 106 106 # Remove control parameter from the result 107 if model_info.control: 108 translation[model_info.control] = "CONTROL" 107 control_pars = [p.id for p in model_info.parameters.kernel_parameters 108 if p.is_control] 109 if control_pars: 110 control_id = control_pars[0] 111 translation[control_id] = "CONTROL" 109 112 return translation 110 113 … … 388 391 389 392 def revert_name(model_info): 393 """Translate model name back to the name used in SasView 3.x""" 390 394 oldname, _ = CONVERSION_TABLE.get(model_info.id, [None, {}]) 391 395 return oldname … … 657 661 658 662 def test_backward_forward(): 663 """ 664 Test conversion of model parameters from 4.x to 3.x and back. 665 """ 659 666 from .core import list_models 660 667 L = lambda name: _check_one(name, seed=1) -
sasmodels/core.py
r07646b6 r9562dd2 64 64 * all: all models 65 65 * py: python models only 66 * c: compiled models only 67 * single: models which support single precision 68 * double: models which require double precision 69 * opencl: controls if OpenCL is supperessed 70 * 1d: models which are 1D only, or 2D using abs(q) 71 * 2d: models which can be 2D 72 * magnetic: models with an sld 73 * nommagnetic: models without an sld 66 * c: c models only 67 * single: c models which support single precision 68 * double: c models which require double precision 69 * opencl: c models which run in opencl 70 * dll: c models which do not run in opencl 71 * 1d: models without orientation 72 * 2d: models with orientation 73 * magnetic: models supporting magnetic sld 74 * nommagnetic: models without magnetic parameter 74 75 75 76 For multiple conditions, combine with plus. For example, *c+single+2d* … … 95 96 info = load_model_info(name) 96 97 pars = info.parameters.kernel_parameters 97 if kind == "py" and callable(info.Iq): 98 return True 99 elif kind == "c" and not callable(info.Iq): 100 return True 101 elif kind == "double" and not info.single: 102 return True 103 elif kind == "single" and info.single: 104 return True 105 elif kind == "opencl" and info.opencl: 106 return True 107 elif kind == "2d" and any(p.type == 'orientation' for p in pars): 108 return True 109 elif kind == "1d" and all(p.type != 'orientation' for p in pars): 110 return True 111 elif kind == "magnetic" and any(p.type == 'sld' for p in pars): 112 return True 113 elif kind == "nonmagnetic" and any(p.type != 'sld' for p in pars): 114 return True 98 # TODO: may be adding Fq to the list at some point 99 is_pure_py = callable(info.Iq) 100 if kind == "py": 101 return is_pure_py 102 elif kind == "c": 103 return not is_pure_py 104 elif kind == "double": 105 return not info.single and not is_pure_py 106 elif kind == "single": 107 return info.single and not is_pure_py 108 elif kind == "opencl": 109 return info.opencl 110 elif kind == "dll": 111 return not info.opencl and not is_pure_py 112 elif kind == "2d": 113 return any(p.type == 'orientation' for p in pars) 114 elif kind == "1d": 115 return all(p.type != 'orientation' for p in pars) 116 elif kind == "magnetic": 117 return any(p.type == 'sld' for p in pars) 118 elif kind == "nonmagnetic": 119 return not any(p.type == 'sld' for p in pars) 115 120 return False 116 121 … … 317 322 return numpy_dtype, fast, platform 318 323 319 def list_models_main():320 # type: () -> None321 """322 Run list_models as a main program. See :func:`list_models` for the323 kinds of models that can be requested on the command line.324 """325 import sys326 kind = sys.argv[1] if len(sys.argv) > 1 else "all"327 print("\n".join(list_models(kind)))328 329 324 def test_composite_order(): 325 """ 326 Check that mixture models produce the same result independent of ordder. 327 """ 330 328 def test_models(fst, snd): 331 329 """Confirm that two models produce the same parameters""" … … 342 340 343 341 def build_test(first, second): 342 """Construct pair model test""" 344 343 test = lambda description: test_models(first, second) 345 344 description = first + " vs. " + second … … 377 376 # type: () -> None 378 377 """Check that model load works""" 378 from .product import RADIUS_ID, VOLFRAC_ID, STRUCTURE_MODE_ID, RADIUS_MODE_ID 379 379 #Test the the model produces the parameters that we would expect 380 380 model = load_model("cylinder@hardsphere*sphere") 381 381 actual = [p.name for p in model.info.parameters.kernel_parameters] 382 target = ("sld sld_solvent radius length theta phi" 383 " radius_effective volfraction " 384 " structure_factor_mode radius_effective_mode" 385 " A_sld A_sld_solvent A_radius").split() 382 target = ["sld", "sld_solvent", "radius", "length", "theta", "phi", 383 RADIUS_ID, VOLFRAC_ID, STRUCTURE_MODE_ID, RADIUS_MODE_ID, 384 "A_sld", "A_sld_solvent", "A_radius"] 386 385 assert target == actual, "%s != %s"%(target, actual) 386 387 def list_models_main(): 388 # type: () -> int 389 """ 390 Run list_models as a main program. See :func:`list_models` for the 391 kinds of models that can be requested on the command line. 392 """ 393 import sys 394 kind = sys.argv[1] if len(sys.argv) > 1 else "all" 395 try: 396 models = list_models(kind) 397 print("\n".join(models)) 398 except Exception: 399 print(list_models.__doc__) 400 return 1 401 return 0 387 402 388 403 if __name__ == "__main__": -
sasmodels/data.py
rc88f983 rb297ba9 500 500 mdata = masked_array(data.y, data.mask.copy()) 501 501 mdata[~np.isfinite(mdata)] = masked 502 if view is'log':502 if view == 'log': 503 503 mdata[mdata <= 0] = masked 504 504 plt.errorbar(data.x, scale*mdata, yerr=data.dy, fmt='.') … … 514 514 mtheory = masked_array(theory) 515 515 mtheory[~np.isfinite(mtheory)] = masked 516 if view is'log':516 if view == 'log': 517 517 mtheory[mtheory <= 0] = masked 518 518 plt.plot(theory_x, scale*mtheory, '-') -
sasmodels/details.py
r885753a rb297ba9 236 236 npars = kernel.info.parameters.npars 237 237 nvalues = kernel.info.parameters.nvalues 238 scalars = [value for value, _dispersity, _weight in mesh]238 scalars = [value for value, dispersity, weight in mesh] 239 239 # skipping scale and background when building values and weights 240 _values, dispersity, weights= zip(*mesh[2:npars+2]) if npars else ((), (), ())241 #weight s = correct_theta_weights(kernel.info.parameters, dispersity, weights)242 length = np.array([len(w) for w in weight s])240 value, dispersity, weight = zip(*mesh[2:npars+2]) if npars else ((), (), ()) 241 #weight = correct_theta_weights(kernel.info.parameters, dispersity, weight) 242 length = np.array([len(w) for w in weight]) 243 243 offset = np.cumsum(np.hstack((0, length))) 244 244 call_details = make_details(kernel.info, length, offset[:-1], offset[-1]) … … 246 246 data_len = nvalues + 2*sum(len(v) for v in dispersity) 247 247 extra = (32 - data_len%32)%32 248 data = np.hstack((scalars,) + dispersity + weight s+ ZEROS[:extra])248 data = np.hstack((scalars,) + dispersity + weight + ZEROS[:extra]) 249 249 data = data.astype(kernel.dtype) 250 250 is_magnetic = convert_magnetism(kernel.info.parameters, data) -
sasmodels/direct_model.py
r9150036 re220acc 31 31 from . import resolution2d 32 32 from .details import make_kernel_args, dispersion_mesh 33 from .modelinfo import DEFAULT_BACKGROUND34 33 from .product import RADIUS_MODE_ID 35 34 … … 60 59 """ 61 60 mesh = get_mesh(calculator.info, pars, dim=calculator.dim, mono=mono) 62 #print(" pars", list(zip(*mesh))[0])61 #print("in call_kernel: pars:", list(zip(*mesh))[0]) 63 62 call_details, values, is_magnetic = make_kernel_args(calculator, mesh) 64 #print(" values:", values)63 #print("in call_kernel: values:", values) 65 64 return calculator(call_details, values, cutoff, is_magnetic) 66 65 … … 73 72 74 73 Use parameter *radius_effective_mode* to select the effective radius 75 calculation. 74 calculation to use amongst the *radius_effective_modes* list given in the 75 model. 76 76 """ 77 77 R_eff_type = int(pars.pop(RADIUS_MODE_ID, 1.0)) 78 78 mesh = get_mesh(calculator.info, pars, dim=calculator.dim, mono=mono) 79 #print(" pars", list(zip(*mesh))[0])79 #print("in call_Fq: pars", list(zip(*mesh))[0]) 80 80 call_details, values, is_magnetic = make_kernel_args(calculator, mesh) 81 #print(" values:", values)81 #print("in call_Fq: values:", values) 82 82 return calculator.Fq(call_details, values, cutoff, is_magnetic, R_eff_type) 83 83 … … 119 119 active = lambda name: True 120 120 121 #print(" pars",[p.id for p in parameters.call_parameters])121 #print("in get_mesh: pars:",[p.id for p in parameters.call_parameters]) 122 122 mesh = [_get_par_weights(p, values, active(p.name)) 123 123 for p in parameters.call_parameters] -
sasmodels/generate.py
ra8a1d48 ra34b811 27 27 which are hollow. 28 28 29 * effective_radius(mode, p1, p2, ...)* returns the effective radius of29 *radius_effective(mode, p1, p2, ...)* returns the effective radius of 30 30 the form with particular dimensions. Mode determines the type of 31 31 effective radius returned, with mode=1 for equivalent volume. … … 190 190 191 191 def get_data_path(external_dir, target_file): 192 """ 193 Search for the target file relative in the installed application. 194 195 Search first in the location of the generate module in case we are 196 running directly from the distribution. Search next to the python 197 executable for windows installs. Search in the ../Resources directory 198 next to the executable for Mac OS/X installs. 199 """ 192 200 path = abspath(dirname(__file__)) 193 201 if exists(joinpath(path, target_file)): … … 225 233 # 226 234 # NOTE: there is an RST_PROLOG at the end of this file which is NOT 227 # used for the bundled documentation. Still as long as we are defining the macros228 # in two places any new addition should define the macro in both places.235 # used for the bundled documentation. Still as long as we are defining the 236 # macros in two places any new addition should define the macro in both places. 229 237 RST_UNITS = { 230 238 "Ang": "|Ang|", … … 530 538 531 539 def test_tag_float(): 532 """ check that floating point constants are properlyidentified and tagged with 'f'"""540 """Check that floating point constants are identified and tagged with 'f'""" 533 541 534 542 cases = """ … … 707 715 return False 708 716 709 _SHELL_VOLUME_PATTERN = re.compile(r"(^|\s)double\s+shell_volume[(]", flags=re.MULTILINE) 717 _SHELL_VOLUME_PATTERN = re.compile(r"(^|\s)double\s+shell_volume[(]", 718 flags=re.MULTILINE) 710 719 def contains_shell_volume(source): 711 720 # type: (List[str]) -> bool … … 801 810 logger.warn("oriented shapes should define Iqac or Iqabc") 802 811 else: 803 raise ValueError("Expected functionIqac or Iqabc for oriented shape")812 raise ValueError("Expected Iqac or Iqabc for oriented shape") 804 813 805 814 # Define the parameter table … … 811 820 for p in partable.kernel_parameters)) 812 821 # Define the function calls 813 call_ effective_radius = "#define CALL_EFFECTIVE_RADIUS(_mode, _v) 0.0"822 call_radius_effective = "#define CALL_RADIUS_EFFECTIVE(_mode, _v) 0.0" 814 823 if partable.form_volume_parameters: 815 824 refs = _call_pars("_v.", partable.form_volume_parameters) 816 825 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) 826 call_volume = ( 827 "#define CALL_VOLUME(_form, _shell, _v) " 828 "do { _form = form_volume(%s); _shell = shell_volume(%s); } " 829 "while (0)") % ((",".join(refs),)*2) 818 830 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)) 831 call_volume = ( 832 "#define CALL_VOLUME(_form, _shell, _v) " 833 "do { _form = _shell = form_volume(%s); } " 834 "while (0)") % (",".join(refs)) 835 if model_info.radius_effective_modes: 836 call_radius_effective = ( 837 "#define CALL_RADIUS_EFFECTIVE(_mode, _v) " 838 "radius_effective(_mode, %s)") % (",".join(refs)) 822 839 else: 823 840 # Model doesn't have volume. We could make the kernel run a little 824 841 # faster by not using/transferring the volume normalizations, but 825 842 # the ifdef's reduce readability more than is worthwhile. 826 call_volume = "#define CALL_VOLUME(_form, _shell, _v) do { _form = _shell = 1.0; } while (0)" 843 call_volume = ( 844 "#define CALL_VOLUME(_form, _shell, _v) " 845 "do { _form = _shell = 1.0; } while (0)") 827 846 source.append(call_volume) 828 source.append(call_ effective_radius)847 source.append(call_radius_effective) 829 848 model_refs = _call_pars("_v.", partable.iq_parameters) 830 849 … … 879 898 source.append("#define MAGNETIC_PARS %s"%",".join(str(k) for k in magpars)) 880 899 source.append("#define PROJECTION %d"%PROJECTION) 881 # TODO: allow mixed python/opencl kernels? 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 885 result = { 886 'dll': '\n'.join(source+dll[0]+dll[1]+dll[2]), 887 'opencl': '\n'.join(source+ocl[0]+ocl[1]+ocl[2]), 888 } 900 wrappers = _kernels(kernel_code, call_iq, clear_iq, 901 call_iqxy, clear_iqxy, model_info.name) 902 code = '\n'.join(source + wrappers[0] + wrappers[1] + wrappers[2]) 903 904 # Note: Identical code for dll and opencl. This may not always be the case 905 # so leave them as separate entries in the returned value. 906 result = {'dll': code, 'opencl': code} 889 907 return result 890 908 -
sasmodels/gengauss.py
rff31782 rb297ba9 1 1 #!/usr/bin/env python 2 """ 3 Generate the Gauss-Legendre integration points and save them as a C file. 4 """ 2 5 from __future__ import division, print_function 3 4 import sys5 6 6 7 import numpy as np … … 8 9 9 10 def gengauss(n, path): 11 """ 12 Save the Gauss-Legendre integration points for length *n* into file *path*. 13 """ 10 14 z, w = leggauss(n) 11 15 -
sasmodels/guyou.py
rb3703f5 rb297ba9 68 68 69 69 def ellipticJi(u, v, m): 70 """Returns [sn, cn, dn](u + iv|m).""" 70 71 scalar = np.isscalar(u) and np.isscalar(v) and np.isscalar(m) 71 72 u, v, m = np.broadcast_arrays(u, v, m) … … 100 101 ] 101 102 102 # calculate F(phi+ipsi|m).103 # see Abramowitz and Stegun, 17.4.11.104 103 def ellipticFi(phi, psi, m): 104 """Returns F(phi+ipsi|m). See Abramowitz and Stegun, 17.4.11.""" 105 105 if np.any(phi == 0): 106 106 scalar = np.isscalar(phi) and np.isscalar(psi) and np.isscalar(m) … … 207 207 plt.plot(x, y, 'g') 208 208 209 for long in range(-limit, limit+1, step):210 x, y = guyou_invert(scale*long , scale*long_line)209 for longitude in range(-limit, limit+1, step): 210 x, y = guyou_invert(scale*longitude, scale*long_line) 211 211 plt.plot(x, y, 'b') 212 212 plt.xlabel('longitude') -
sasmodels/jitter.py
r7d97437 r4e28511 1 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 2 3 """ 3 4 Jitter Explorer … … 5 6 6 7 Application to explore orientation angle and angular dispersity. 8 9 From the command line:: 10 11 # Show docs 12 python -m sasmodels.jitter --help 13 14 # Guyou projection jitter, uniform over 20 degree theta and 10 in phi 15 python -m sasmodels.jitter --projection=guyou --dist=uniform --jitter=20,10,0 16 17 From a jupyter cell:: 18 19 import ipyvolume as ipv 20 from sasmodels import jitter 21 import importlib; importlib.reload(jitter) 22 jitter.set_plotter("ipv") 23 24 size = (10, 40, 100) 25 view = (20, 0, 0) 26 27 #size = (15, 15, 100) 28 #view = (60, 60, 0) 29 30 dview = (0, 0, 0) 31 #dview = (5, 5, 0) 32 #dview = (15, 180, 0) 33 #dview = (180, 15, 0) 34 35 projection = 'equirectangular' 36 #projection = 'azimuthal_equidistance' 37 #projection = 'guyou' 38 #projection = 'sinusoidal' 39 #projection = 'azimuthal_equal_area' 40 41 dist = 'uniform' 42 #dist = 'gaussian' 43 44 jitter.run(size=size, view=view, jitter=dview, dist=dist, projection=projection) 45 #filename = projection+('_theta' if dview[0] == 180 else '_phi' if dview[1] == 180 else '') 46 #ipv.savefig(filename+'.png') 7 47 """ 8 48 from __future__ import division, print_function … … 10 50 import argparse 11 51 12 try: # CRUFT: travis-ci does not support mpl_toolkits.mplot3d13 import mpl_toolkits.mplot3d # Adds projection='3d' option to subplot14 except ImportError:15 pass16 17 import matplotlib as mpl18 import matplotlib.pyplot as plt19 from matplotlib.widgets import Slider20 52 import numpy as np 21 from numpy import pi, cos, sin, sqrt, exp, degrees, radians 22 23 def draw_beam(axes, view=(0, 0)): 53 from numpy import pi, cos, sin, sqrt, exp, log, degrees, radians, arccos, arctan2 54 55 # Too many complaints about variable names from pylint: 56 # a, b, c, u, v, x, y, z, dx, dy, dz, px, py, pz, R, Rx, Ry, Rz, ... 57 # pylint: disable=invalid-name 58 59 def draw_beam(axes, view=(0, 0), alpha=0.5, steps=25): 24 60 """ 25 61 Draw the beam going from source at (0, 0, 1) to detector at (0, 0, -1) 26 62 """ 27 63 #axes.plot([0,0],[0,0],[1,-1]) 28 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=0.8) 29 30 steps = 25 31 u = np.linspace(0, 2 * np.pi, steps) 32 v = np.linspace(-1, 1, steps) 64 #axes.scatter([0]*100,[0]*100,np.linspace(1, -1, 100), alpha=alpha) 65 66 u = np.linspace(0, 2 * pi, steps) 67 v = np.linspace(-1, 1, 2) 33 68 34 69 r = 0.02 35 x = r*np.outer( np.cos(u), np.ones_like(v))36 y = r*np.outer( np.sin(u), np.ones_like(v))70 x = r*np.outer(cos(u), np.ones_like(v)) 71 y = r*np.outer(sin(u), np.ones_like(v)) 37 72 z = 1.3*np.outer(np.ones_like(u), v) 38 73 … … 42 77 points = Rz(phi)*Ry(theta)*points 43 78 x, y, z = [v.reshape(shape) for v in points] 44 45 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='y', alpha=0.5) 79 axes.plot_surface(x, y, z, color='yellow', alpha=alpha) 80 81 # TODO: draw endcaps on beam 82 ## Drawing tiny balls on the end will work 83 #draw_sphere(axes, radius=0.02, center=(0, 0, 1.3), color='yellow', alpha=alpha) 84 #draw_sphere(axes, radius=0.02, center=(0, 0, -1.3), color='yellow', alpha=alpha) 85 ## The following does not work 86 #triangles = [(0, i+1, i+2) for i in range(steps-2)] 87 #x_cap, y_cap = x[:, 0], y[:, 0] 88 #for z_cap in z[:, 0], z[:, -1]: 89 # axes.plot_trisurf(x_cap, y_cap, z_cap, triangles, 90 # color='yellow', alpha=alpha) 91 46 92 47 93 def draw_ellipsoid(axes, size, view, jitter, steps=25, alpha=1): 48 94 """Draw an ellipsoid.""" 49 95 a, b, c = size 50 u = np.linspace(0, 2 * np.pi, steps)51 v = np.linspace(0, np.pi, steps)52 x = a*np.outer( np.cos(u), np.sin(v))53 y = b*np.outer( np.sin(u), np.sin(v))54 z = c*np.outer(np.ones_like(u), np.cos(v))96 u = np.linspace(0, 2 * pi, steps) 97 v = np.linspace(0, pi, steps) 98 x = a*np.outer(cos(u), sin(v)) 99 y = b*np.outer(sin(u), sin(v)) 100 z = c*np.outer(np.ones_like(u), cos(v)) 55 101 x, y, z = transform_xyz(view, jitter, x, y, z) 56 102 57 axes.plot_surface(x, y, z, rstride=4, cstride=4,color='w', alpha=alpha)103 axes.plot_surface(x, y, z, color='w', alpha=alpha) 58 104 59 105 draw_labels(axes, view, jitter, [ … … 68 114 def draw_sc(axes, size, view, jitter, steps=None, alpha=1): 69 115 """Draw points for simple cubic paracrystal""" 116 # pylint: disable=unused-argument 70 117 atoms = _build_sc() 71 118 _draw_crystal(axes, size, view, jitter, atoms=atoms) … … 73 120 def draw_fcc(axes, size, view, jitter, steps=None, alpha=1): 74 121 """Draw points for face-centered cubic paracrystal""" 122 # pylint: disable=unused-argument 75 123 # Build the simple cubic crystal 76 124 atoms = _build_sc() … … 88 136 def draw_bcc(axes, size, view, jitter, steps=None, alpha=1): 89 137 """Draw points for body-centered cubic paracrystal""" 138 # pylint: disable=unused-argument 90 139 # Build the simple cubic crystal 91 140 atoms = _build_sc() … … 124 173 return atoms 125 174 126 def draw_parallelepiped(axes, size, view, jitter, steps=None, alpha=1): 127 """Draw a parallelepiped.""" 175 def draw_box(axes, size, view): 176 """Draw a wireframe box at a particular view.""" 177 a, b, c = size 178 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 179 y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 180 z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 181 x, y, z = transform_xyz(view, None, x, y, z) 182 def _draw(i, j): 183 axes.plot([x[i], x[j]], [y[i], y[j]], [z[i], z[j]], color='black') 184 _draw(0, 1) 185 _draw(0, 2) 186 _draw(0, 3) 187 _draw(7, 4) 188 _draw(7, 5) 189 _draw(7, 6) 190 191 def draw_parallelepiped(axes, size, view, jitter, steps=None, 192 color=(0.6, 1.0, 0.6), alpha=1): 193 """Draw a parallelepiped surface, with view and jitter.""" 194 # pylint: disable=unused-argument 128 195 a, b, c = size 129 196 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) … … 142 209 143 210 x, y, z = transform_xyz(view, jitter, x, y, z) 144 axes.plot_trisurf(x, y, triangles=tri, Z=z, color='w', alpha=alpha) 145 146 # Draw pink face on box. 211 axes.plot_trisurf(x, y, triangles=tri, Z=z, color=color, alpha=alpha, 212 linewidth=0) 213 214 # Colour the c+ face of the box. 147 215 # Since I can't control face color, instead draw a thin box situated just 148 216 # in front of the "c+" face. Use the c face so that rotations about psi 149 217 # rotate that face. 150 if 1: 218 if 0: # pylint: disable=using-constant-test 219 color = (1, 0.6, 0.6) # pink 151 220 x = a*np.array([+1, -1, +1, -1, +1, -1, +1, -1]) 152 221 y = b*np.array([+1, +1, -1, -1, +1, +1, -1, -1]) 153 222 z = c*np.array([+1, +1, +1, +1, -1, -1, -1, -1]) 154 223 x, y, z = transform_xyz(view, jitter, x, y, abs(z)+0.001) 155 axes.plot_trisurf(x, y, triangles=tri, Z=z, color= [1, 0.6, 0.6], alpha=alpha)224 axes.plot_trisurf(x, y, triangles=tri, Z=z, color=color, alpha=alpha) 156 225 157 226 draw_labels(axes, view, jitter, [ … … 164 233 ]) 165 234 166 def draw_sphere(axes, radius=10., steps=100): 235 def draw_sphere(axes, radius=1.0, steps=25, 236 center=(0, 0, 0), color='w', alpha=1.): 167 237 """Draw a sphere""" 168 u = np.linspace(0, 2 * np.pi, steps) 169 v = np.linspace(0, np.pi, steps) 170 171 x = radius * np.outer(np.cos(u), np.sin(v)) 172 y = radius * np.outer(np.sin(u), np.sin(v)) 173 z = radius * np.outer(np.ones(np.size(u)), np.cos(v)) 174 axes.plot_surface(x, y, z, rstride=4, cstride=4, color='w') 175 176 def draw_jitter(axes, view, jitter, dist='gaussian', size=(0.1, 0.4, 1.0), 177 draw_shape=draw_parallelepiped): 238 u = np.linspace(0, 2 * pi, steps) 239 v = np.linspace(0, pi, steps) 240 241 x = radius * np.outer(cos(u), sin(v)) + center[0] 242 y = radius * np.outer(sin(u), sin(v)) + center[1] 243 z = radius * np.outer(np.ones(np.size(u)), cos(v)) + center[2] 244 axes.plot_surface(x, y, z, color=color, alpha=alpha) 245 #axes.plot_wireframe(x, y, z) 246 247 def draw_axes(axes, origin=(-1, -1, -1), length=(2, 2, 2)): 248 """Draw wireframe axes lines, with given origin and length""" 249 x, y, z = origin 250 dx, dy, dz = length 251 axes.plot([x, x+dx], [y, y], [z, z], color='black') 252 axes.plot([x, x], [y, y+dy], [z, z], color='black') 253 axes.plot([x, x], [y, y], [z, z+dz], color='black') 254 255 def draw_person_on_sphere(axes, view, height=0.5, radius=1.0): 256 """ 257 Draw a person on the surface of a sphere. 258 259 *view* indicates (latitude, longitude, orientation) 260 """ 261 limb_offset = height * 0.05 262 head_radius = height * 0.10 263 head_height = height - head_radius 264 neck_length = head_radius * 0.50 265 shoulder_height = height - 2*head_radius - neck_length 266 torso_length = shoulder_height * 0.55 267 torso_radius = torso_length * 0.30 268 leg_length = shoulder_height - torso_length 269 arm_length = torso_length * 0.90 270 271 def _draw_part(y, z): 272 x = np.zeros_like(y) 273 xp, yp, zp = transform_xyz(view, None, x, y, z + radius) 274 axes.plot(xp, yp, zp, color='k') 275 276 # circle for head 277 u = np.linspace(0, 2 * pi, 40) 278 y = head_radius * cos(u) 279 z = head_radius * sin(u) + head_height 280 _draw_part(y, z) 281 282 # rectangle for body 283 y = np.array([-torso_radius, torso_radius, torso_radius, -torso_radius, -torso_radius]) 284 z = np.array([0., 0, torso_length, torso_length, 0]) + leg_length 285 _draw_part(y, z) 286 287 # arms 288 y = np.array([-torso_radius - limb_offset, -torso_radius - limb_offset, -torso_radius]) 289 z = np.array([shoulder_height - arm_length, shoulder_height, shoulder_height]) 290 _draw_part(y, z) 291 _draw_part(-y, z) # pylint: disable=invalid-unary-operand-type 292 293 # legs 294 y = np.array([-torso_radius + limb_offset, -torso_radius + limb_offset]) 295 z = np.array([0, leg_length]) 296 _draw_part(y, z) 297 _draw_part(-y, z) # pylint: disable=invalid-unary-operand-type 298 299 limits = [-radius-height, radius+height] 300 axes.set_xlim(limits) 301 axes.set_ylim(limits) 302 axes.set_zlim(limits) 303 axes.set_axis_off() 304 305 def draw_jitter(axes, view, jitter, dist='gaussian', 306 size=(0.1, 0.4, 1.0), 307 draw_shape=draw_parallelepiped, 308 projection='equirectangular', 309 alpha=0.8, 310 views=None): 178 311 """ 179 312 Represent jitter as a set of shapes at different orientations. 180 313 """ 314 project, project_weight = get_projection(projection) 315 181 316 # set max diagonal to 0.95 182 317 scale = 0.95/sqrt(sum(v**2 for v in size)) 183 318 size = tuple(scale*v for v in size) 184 319 185 #np.random.seed(10)186 #cloud = np.random.randn(10,3)187 cloud = [188 [-1, -1, -1],189 [-1, -1, +0],190 [-1, -1, +1],191 [-1, +0, -1],192 [-1, +0, +0],193 [-1, +0, +1],194 [-1, +1, -1],195 [-1, +1, +0],196 [-1, +1, +1],197 [+0, -1, -1],198 [+0, -1, +0],199 [+0, -1, +1],200 [+0, +0, -1],201 [+0, +0, +0],202 [+0, +0, +1],203 [+0, +1, -1],204 [+0, +1, +0],205 [+0, +1, +1],206 [+1, -1, -1],207 [+1, -1, +0],208 [+1, -1, +1],209 [+1, +0, -1],210 [+1, +0, +0],211 [+1, +0, +1],212 [+1, +1, -1],213 [+1, +1, +0],214 [+1, +1, +1],215 ]216 320 dtheta, dphi, dpsi = jitter 217 if dtheta == 0: 218 cloud = [v for v in cloud if v[0] == 0] 219 if dphi == 0: 220 cloud = [v for v in cloud if v[1] == 0] 221 if dpsi == 0: 222 cloud = [v for v in cloud if v[2] == 0] 223 draw_shape(axes, size, view, [0, 0, 0], steps=100, alpha=0.8) 224 scale = {'gaussian':1, 'rectangle':1/sqrt(3), 'uniform':1/3}[dist] 225 for point in cloud: 226 delta = [scale*dtheta*point[0], scale*dphi*point[1], scale*dpsi*point[2]] 227 draw_shape(axes, size, view, delta, alpha=0.8) 321 base = {'gaussian':3, 'rectangle':sqrt(3), 'uniform':1}[dist] 322 def _steps(delta): 323 if views is None: 324 n = max(3, min(25, 2*int(base*delta/5))) 325 else: 326 n = views 327 return base*delta*np.linspace(-1, 1, n) if delta > 0 else [0.] 328 for theta in _steps(dtheta): 329 for phi in _steps(dphi): 330 for psi in _steps(dpsi): 331 w = project_weight(theta, phi, 1.0, 1.0) 332 if w > 0: 333 dview = project(theta, phi, psi) 334 draw_shape(axes, size, view, dview, alpha=alpha) 228 335 for v in 'xyz': 229 336 a, b, c = size 230 lim = np.sqrt(a**2 + b**2 + c**2)337 lim = sqrt(a**2 + b**2 + c**2) 231 338 getattr(axes, 'set_'+v+'lim')([-lim, lim]) 232 getattr(axes, v+'axis').label.set_text(v)339 #getattr(axes, v+'axis').label.set_text(v) 233 340 234 341 PROJECTIONS = [ … … 238 345 'azimuthal_equal_area', 239 346 ] 240 def draw_mesh(axes, view, jitter, radius=1.2, n=11, dist='gaussian', 241 projection='equirectangular'): 242 """ 243 Draw the dispersion mesh showing the theta-phi orientations at which 244 the model will be evaluated. 245 347 def get_projection(projection): 348 349 """ 246 350 jitter projections 247 351 <https://en.wikipedia.org/wiki/List_of_map_projections> … … 297 401 Should allow free movement in theta, but phi is distorted. 298 402 """ 403 # pylint: disable=unused-argument 299 404 # TODO: try Kent distribution instead of a gaussian warped by projection 300 405 301 dist_x = np.linspace(-1, 1, n)302 weights = np.ones_like(dist_x)303 if dist == 'gaussian':304 dist_x *= 3305 weights = exp(-0.5*dist_x**2)306 elif dist == 'rectangle':307 # Note: uses sasmodels ridiculous definition of rectangle width308 dist_x *= sqrt(3)309 elif dist == 'uniform':310 pass311 else:312 raise ValueError("expected dist to be gaussian, rectangle or uniform")313 314 406 if projection == 'equirectangular': #define PROJECTION 1 315 def _rotate(theta_i, phi_j): 316 return Rx(phi_j)*Ry(theta_i) 407 def _project(theta_i, phi_j, psi): 408 latitude, longitude = theta_i, phi_j 409 return latitude, longitude, psi, 'xyz' 410 #return Rx(phi_j)*Ry(theta_i) 317 411 def _weight(theta_i, phi_j, w_i, w_j): 318 412 return w_i*w_j*abs(cos(radians(theta_i))) 319 413 elif projection == 'sinusoidal': #define PROJECTION 2 320 def _ rotate(theta_i, phi_j):414 def _project(theta_i, phi_j, psi): 321 415 latitude = theta_i 322 416 scale = cos(radians(latitude)) 323 417 longitude = phi_j/scale if abs(phi_j) < abs(scale)*180 else 0 324 418 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 325 return Rx(longitude)*Ry(latitude) 326 def _weight(theta_i, phi_j, w_i, w_j): 419 return latitude, longitude, psi, 'xyz' 420 #return Rx(longitude)*Ry(latitude) 421 def _project(theta_i, phi_j, w_i, w_j): 327 422 latitude = theta_i 328 423 scale = cos(radians(latitude)) … … 330 425 return active*w_i*w_j 331 426 elif projection == 'guyou': #define PROJECTION 3 (eventually?) 332 def _ rotate(theta_i, phi_j):427 def _project(theta_i, phi_j, psi): 333 428 from .guyou import guyou_invert 334 429 #latitude, longitude = guyou_invert([theta_i], [phi_j]) 335 430 longitude, latitude = guyou_invert([phi_j], [theta_i]) 336 return Rx(longitude[0])*Ry(latitude[0]) 431 return latitude, longitude, psi, 'xyz' 432 #return Rx(longitude[0])*Ry(latitude[0]) 337 433 def _weight(theta_i, phi_j, w_i, w_j): 338 434 return w_i*w_j 339 elif projection == 'azimuthal_equidistance': # Note: Rz Ry, not Rx Ry 340 def _rotate(theta_i, phi_j): 435 elif projection == 'azimuthal_equidistance': 436 # Note that calculates angles for Rz Ry rather than Rx Ry 437 def _project(theta_i, phi_j, psi): 341 438 latitude = sqrt(theta_i**2 + phi_j**2) 342 longitude = degrees( np.arctan2(phi_j, theta_i))439 longitude = degrees(arctan2(phi_j, theta_i)) 343 440 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 344 return Rz(longitude)*Ry(latitude) 441 return latitude, longitude, psi-longitude, 'zyz' 442 #R = Rz(longitude)*Ry(latitude)*Rz(psi) 443 #return R_to_xyz(R) 444 #return Rz(longitude)*Ry(latitude) 345 445 def _weight(theta_i, phi_j, w_i, w_j): 346 446 # Weighting for each point comes from the integral: … … 376 476 return weight*w_i*w_j if latitude < 180 else 0 377 477 elif projection == 'azimuthal_equal_area': 378 def _rotate(theta_i, phi_j): 478 # Note that calculates angles for Rz Ry rather than Rx Ry 479 def _project(theta_i, phi_j, psi): 379 480 radius = min(1, sqrt(theta_i**2 + phi_j**2)/180) 380 latitude = 180-degrees(2* np.arccos(radius))381 longitude = degrees( np.arctan2(phi_j, theta_i))481 latitude = 180-degrees(2*arccos(radius)) 482 longitude = degrees(arctan2(phi_j, theta_i)) 382 483 #print("(%+7.2f, %+7.2f) => (%+7.2f, %+7.2f)"%(theta_i, phi_j, latitude, longitude)) 383 return Rz(longitude)*Ry(latitude) 484 return latitude, longitude, psi, 'zyz' 485 #R = Rz(longitude)*Ry(latitude)*Rz(psi) 486 #return R_to_xyz(R) 487 #return Rz(longitude)*Ry(latitude) 384 488 def _weight(theta_i, phi_j, w_i, w_j): 385 489 latitude = sqrt(theta_i**2 + phi_j**2) … … 389 493 raise ValueError("unknown projection %r"%projection) 390 494 495 return _project, _weight 496 497 def R_to_xyz(R): 498 """ 499 Return phi, theta, psi Tait-Bryan angles corresponding to the given rotation matrix. 500 501 Extracting Euler Angles from a Rotation Matrix 502 Mike Day, Insomniac Games 503 https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf 504 Based on: Shoemakeâs "Euler Angle Conversion", Graphics Gems IV, pp. 222-229 505 """ 506 phi = arctan2(R[1, 2], R[2, 2]) 507 theta = arctan2(-R[0, 2], sqrt(R[0, 0]**2 + R[0, 1]**2)) 508 psi = arctan2(R[0, 1], R[0, 0]) 509 return degrees(phi), degrees(theta), degrees(psi) 510 511 def draw_mesh(axes, view, jitter, radius=1.2, n=11, dist='gaussian', 512 projection='equirectangular'): 513 """ 514 Draw the dispersion mesh showing the theta-phi orientations at which 515 the model will be evaluated. 516 """ 517 518 _project, _weight = get_projection(projection) 519 def _rotate(theta, phi, z): 520 dview = _project(theta, phi, 0.) 521 if dview[3] == 'zyz': 522 return Rz(dview[1])*Ry(dview[0])*z 523 else: # dview[3] == 'xyz': 524 return Rx(dview[1])*Ry(dview[0])*z 525 526 527 dist_x = np.linspace(-1, 1, n) 528 weights = np.ones_like(dist_x) 529 if dist == 'gaussian': 530 dist_x *= 3 531 weights = exp(-0.5*dist_x**2) 532 elif dist == 'rectangle': 533 # Note: uses sasmodels ridiculous definition of rectangle width 534 dist_x *= sqrt(3) 535 elif dist == 'uniform': 536 pass 537 else: 538 raise ValueError("expected dist to be gaussian, rectangle or uniform") 539 391 540 # mesh in theta, phi formed by rotating z 392 dtheta, dphi, dpsi = jitter 541 dtheta, dphi, dpsi = jitter # pylint: disable=unused-variable 393 542 z = np.matrix([[0], [0], [radius]]) 394 points = np.hstack([_rotate(theta_i, phi_j )*z543 points = np.hstack([_rotate(theta_i, phi_j, z) 395 544 for theta_i in dtheta*dist_x 396 545 for phi_j in dphi*dist_x]) … … 470 619 Points are stored in a 3 x n numpy matrix, not a numpy array or tuple. 471 620 """ 472 dtheta, dphi, dpsi = jitter 473 points = Rx(dphi)*Ry(dtheta)*Rz(dpsi)*points 621 if jitter is None: 622 return points 623 # Hack to deal with the fact that azimuthal_equidistance uses euler angles 624 if len(jitter) == 4: 625 dtheta, dphi, dpsi, _ = jitter 626 points = Rz(dphi)*Ry(dtheta)*Rz(dpsi)*points 627 else: 628 dtheta, dphi, dpsi = jitter 629 points = Rx(dphi)*Ry(dtheta)*Rz(dpsi)*points 474 630 return points 475 631 … … 481 637 """ 482 638 theta, phi, psi = view 483 points = Rz(phi)*Ry(theta)*Rz(psi)*points 639 points = Rz(phi)*Ry(theta)*Rz(psi)*points # viewing angle 640 #points = Rz(psi)*Ry(pi/2-theta)*Rz(phi)*points # 1-D integration angles 641 #points = Rx(phi)*Ry(theta)*Rz(psi)*points # angular dispersion angle 484 642 return points 643 644 def orient_relative_to_beam_quaternion(view, points): 645 """ 646 Apply the view transform to a set of points. 647 648 Points are stored in a 3 x n numpy matrix, not a numpy array or tuple. 649 650 This variant uses quaternions rather than rotation matrices for the 651 computation. It works but it is not used because it doesn't solve 652 any problems. The challenge of mapping theta/phi/psi to SO(3) does 653 not disappear by calculating the transform differently. 654 """ 655 theta, phi, psi = view 656 x, y, z = [1, 0, 0], [0, 1, 0], [0, 0, 1] 657 q = Quaternion(1, [0, 0, 0]) 658 ## Compose a rotation about the three axes by rotating 659 ## the unit vectors before applying the rotation. 660 #q = Quaternion.from_angle_axis(theta, q.rot(x)) * q 661 #q = Quaternion.from_angle_axis(phi, q.rot(y)) * q 662 #q = Quaternion.from_angle_axis(psi, q.rot(z)) * q 663 ## The above turns out to be equivalent to reversing 664 ## the order of application, so ignore it and use below. 665 q = q * Quaternion.from_angle_axis(theta, x) 666 q = q * Quaternion.from_angle_axis(phi, y) 667 q = q * Quaternion.from_angle_axis(psi, z) 668 ## Reverse the order by post-multiply rather than pre-multiply 669 #q = Quaternion.from_angle_axis(theta, x) * q 670 #q = Quaternion.from_angle_axis(phi, y) * q 671 #q = Quaternion.from_angle_axis(psi, z) * q 672 #print("axes psi", q.rot(np.matrix([x, y, z]).T)) 673 return q.rot(points) 674 #orient_relative_to_beam = orient_relative_to_beam_quaternion 675 676 # === Quaterion class definition === BEGIN 677 # Simple stand-alone quaternion class 678 679 # Note: this code works but isn't unused since quaternions didn't solve the 680 # representation problem. Leave it here in case we want to revisit this later. 681 682 #import numpy as np 683 class Quaternion(object): 684 r""" 685 Quaternion(w, r) = w + ir[0] + jr[1] + kr[2] 686 687 Quaternion.from_angle_axis(theta, r) for a rotation of angle theta about 688 an axis oriented toward the direction r. This defines a unit quaternion, 689 normalizing $r$ to the unit vector $\hat r$, and setting quaternion 690 $Q = \cos \theta + \sin \theta \hat r$ 691 692 Quaternion objects can be multiplied, which applies a rotation about the 693 given axis, allowing composition of rotations without risk of gimbal lock. 694 The resulting quaternion is applied to a set of points using *Q.rot(v)*. 695 """ 696 def __init__(self, w, r): 697 self.w = w 698 self.r = np.asarray(r, dtype='d') 699 700 @staticmethod 701 def from_angle_axis(theta, r): 702 """Build quaternion as rotation theta about axis r""" 703 theta = np.radians(theta)/2 704 r = np.asarray(r) 705 w = np.cos(theta) 706 r = np.sin(theta)*r/np.dot(r, r) 707 return Quaternion(w, r) 708 709 def __mul__(self, other): 710 """Multiply quaterions""" 711 if isinstance(other, Quaternion): 712 w = self.w*other.w - np.dot(self.r, other.r) 713 r = self.w*other.r + other.w*self.r + np.cross(self.r, other.r) 714 return Quaternion(w, r) 715 raise NotImplementedError("Quaternion * non-quaternion not implemented") 716 717 def rot(self, v): 718 """Transform point *v* by quaternion""" 719 v = np.asarray(v).T 720 use_transpose = (v.shape[-1] != 3) 721 if use_transpose: 722 v = v.T 723 v = v + np.cross(2*self.r, np.cross(self.r, v) + self.w*v) 724 #v = v + 2*self.w*np.cross(self.r, v) + np.cross(2*self.r, np.cross(self.r, v)) 725 if use_transpose: 726 v = v.T 727 return v.T 728 729 def conj(self): 730 """Conjugate quaternion""" 731 return Quaternion(self.w, -self.r) 732 733 def inv(self): 734 """Inverse quaternion""" 735 return self.conj()/self.norm()**2 736 737 def norm(self): 738 """Quaternion length""" 739 return np.sqrt(self.w**2 + np.sum(self.r**2)) 740 741 def __str__(self): 742 return "%g%+gi%+gj%+gk"%(self.w, self.r[0], self.r[1], self.r[2]) 743 744 def test_qrot(): 745 """Quaternion checks""" 746 # Define rotation of 60 degrees around an axis in y-z that is 60 degrees 747 # from y. The rotation axis is determined by rotating the point [0, 1, 0] 748 # about x. 749 ax = Quaternion.from_angle_axis(60, [1, 0, 0]).rot([0, 1, 0]) 750 q = Quaternion.from_angle_axis(60, ax) 751 # Set the point to be rotated, and its expected rotated position. 752 p = [1, -1, 2] 753 target = [(10+4*np.sqrt(3))/8, (1+2*np.sqrt(3))/8, (14-3*np.sqrt(3))/8] 754 #print(q, q.rot(p) - target) 755 assert max(abs(q.rot(p) - target)) < 1e-14 756 #test_qrot() 757 #import sys; sys.exit() 758 # === Quaterion class definition === END 485 759 486 760 # translate between number of dimension of dispersity and the number of … … 504 778 or the top of the range, depending on whether *mode* is 'central' or 'top'. 505 779 """ 506 if portion == 1.0: 507 return data.min(), data.max() 508 elif mode == 'central': 509 data = np.sort(data.flatten()) 510 offset = int(portion*len(data)/2 + 0.5) 511 return data[offset], data[-offset] 512 elif mode == 'top': 513 data = np.sort(data.flatten()) 514 offset = int(portion*len(data) + 0.5) 515 return data[offset], data[-1] 780 if portion < 1.0: 781 if mode == 'central': 782 data = np.sort(data.flatten()) 783 offset = int(portion*len(data)/2 + 0.5) 784 return data[offset], data[-offset] 785 if mode == 'top': 786 data = np.sort(data.flatten()) 787 offset = int(portion*len(data) + 0.5) 788 return data[offset], data[-1] 789 # Default: full range 790 return data.min(), data.max() 516 791 517 792 def draw_scattering(calculator, axes, view, jitter, dist='gaussian'): … … 544 819 545 820 # compute the pattern 546 qx, qy = calculator. _data.x_bins, calculator._data.y_bins821 qx, qy = calculator.qxqy 547 822 Iqxy = calculator(**pars).reshape(len(qx), len(qy)) 548 823 549 824 # scale it and draw it 550 Iqxy = np.log(Iqxy)825 Iqxy = log(Iqxy) 551 826 if calculator.limits: 552 827 # use limits from orientation (0,0,0) … … 556 831 vmin = vmax*10**-7 557 832 #vmin, vmax = clipped_range(Iqxy, portion=portion, mode='top') 833 #vmin, vmax = Iqxy.min(), Iqxy.max() 558 834 #print("range",(vmin,vmax)) 559 835 #qx, qy = np.meshgrid(qx, qy) 560 if 0: 836 if 0: # pylint: disable=using-constant-test 561 837 level = np.asarray(255*(Iqxy - vmin)/(vmax - vmin), 'i') 562 838 level[level < 0] = 0 839 from matplotlib import pylab as plt 563 840 colors = plt.get_cmap()(level) 564 axes.plot_surface(qx, qy, -1.1, rstride=1, cstride=1, facecolors=colors) 565 elif 1: 841 #from matplotlib import cm 842 #colors = cm.coolwarm(level) 843 #colors = cm.gist_yarg(level) 844 #colors = cm.Wistia(level) 845 colors[level <= 0, 3] = 0. # set floor to transparent 846 x, y = np.meshgrid(qx/qx.max(), qy/qy.max()) 847 axes.plot_surface(x, y, -1.1*np.ones_like(x), facecolors=colors) 848 elif 1: # pylint: disable=using-constant-test 566 849 axes.contourf(qx/qx.max(), qy/qy.max(), Iqxy, zdir='z', offset=-1.1, 567 850 levels=np.linspace(vmin, vmax, 24)) … … 594 877 calculator = DirectModel(data, model) 595 878 879 # Remember the data axes so we can plot the results 880 calculator.qxqy = (q, q) 881 596 882 # stuff the values for non-orientation parameters into the calculator 597 883 calculator.pars = pars.copy() … … 601 887 # under rotation or angular dispersion 602 888 Iqxy = calculator(theta=0, phi=0, psi=0, **calculator.pars) 603 Iqxy = np.log(Iqxy)889 Iqxy = log(Iqxy) 604 890 vmin, vmax = clipped_range(Iqxy, 0.95, mode='top') 605 891 calculator.limits = vmin, vmax+1 … … 692 978 } 693 979 980 694 981 def run(model_name='parallelepiped', size=(10, 40, 100), 982 view=(0, 0, 0), jitter=(0, 0, 0), 695 983 dist='gaussian', mesh=30, 696 984 projection='equirectangular'): … … 702 990 703 991 *size* gives the dimensions (a, b, c) of the shape. 992 993 *view* gives the initial view (theta, phi, psi) of the shape. 994 995 *view* gives the initial jitter (dtheta, dphi, dpsi) of the shape. 704 996 705 997 *dist* is the type of dispersition: gaussian, rectangle, or uniform. … … 721 1013 calculator, size = select_calculator(model_name, n=150, size=size) 722 1014 draw_shape = DRAW_SHAPES.get(model_name, draw_parallelepiped) 1015 #draw_shape = draw_fcc 723 1016 724 1017 ## uncomment to set an independent the colour range for every view … … 726 1019 calculator.limits = None 727 1020 728 ## initial view 729 #theta, dtheta = 70., 10. 730 #phi, dphi = -45., 3. 731 #psi, dpsi = -45., 3. 732 theta, phi, psi = 0, 0, 0 733 dtheta, dphi, dpsi = 0, 0, 0 1021 PLOT_ENGINE(calculator, draw_shape, size, view, jitter, dist, mesh, projection) 1022 1023 def _mpl_plot(calculator, draw_shape, size, view, jitter, dist, mesh, projection): 1024 # Note: travis-ci does not support mpl_toolkits.mplot3d, but this shouldn't be 1025 # an issue since we are lazy-loading the package on a path that isn't tested. 1026 # Importing mplot3d adds projection='3d' option to subplot 1027 import mpl_toolkits.mplot3d # pylint: disable=unused-variable 1028 import matplotlib as mpl 1029 import matplotlib.pyplot as plt 1030 from matplotlib.widgets import Slider 734 1031 735 1032 ## create the plot window … … 752 1049 753 1050 ## add control widgets to plot 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) 757 stheta = Slider(axes_theta, 'Theta', -90, 90, valinit=theta) 758 sphi = Slider(axes_phi, 'Phi', -180, 180, valinit=phi) 759 spsi = Slider(axes_psi, 'Psi', -180, 180, valinit=psi) 760 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) 1051 axes_theta = plt.axes([0.05, 0.15, 0.50, 0.04], **props) 1052 axes_phi = plt.axes([0.05, 0.10, 0.50, 0.04], **props) 1053 axes_psi = plt.axes([0.05, 0.05, 0.50, 0.04], **props) 1054 stheta = Slider(axes_theta, u'Ξ', -90, 90, valinit=0) 1055 sphi = Slider(axes_phi, u'Ï', -180, 180, valinit=0) 1056 spsi = Slider(axes_psi, u'Ï', -180, 180, valinit=0) 1057 1058 axes_dtheta = plt.axes([0.70, 0.15, 0.20, 0.04], **props) 1059 axes_dphi = plt.axes([0.70, 0.1, 0.20, 0.04], **props) 1060 axes_dpsi = plt.axes([0.70, 0.05, 0.20, 0.04], **props) 1061 764 1062 # Note: using ridiculous definition of rectangle distribution, whose width 765 1063 # in sasmodels is sqrt(3) times the given width. Divide by sqrt(3) to keep 766 1064 # the maximum width to 90. 767 1065 dlimit = DIST_LIMITS[dist] 768 sdtheta = Slider(axes_dtheta, 'dTheta', 0, 2*dlimit, valinit=dtheta) 769 sdphi = Slider(axes_dphi, 'dPhi', 0, 2*dlimit, valinit=dphi) 770 sdpsi = Slider(axes_dpsi, 'dPsi', 0, 2*dlimit, valinit=dpsi) 771 1066 sdtheta = Slider(axes_dtheta, u'ÎΞ', 0, 2*dlimit, valinit=0) 1067 sdphi = Slider(axes_dphi, u'ÎÏ', 0, 2*dlimit, valinit=0) 1068 sdpsi = Slider(axes_dpsi, u'ÎÏ', 0, 2*dlimit, valinit=0) 1069 1070 ## initial view and jitter 1071 theta, phi, psi = view 1072 stheta.set_val(theta) 1073 sphi.set_val(phi) 1074 spsi.set_val(psi) 1075 dtheta, dphi, dpsi = jitter 1076 sdtheta.set_val(dtheta) 1077 sdphi.set_val(dphi) 1078 sdpsi.set_val(dpsi) 772 1079 773 1080 ## callback to draw the new view 774 def update(val, axis=None): 1081 def _update(val, axis=None): 1082 # pylint: disable=unused-argument 775 1083 view = stheta.val, sphi.val, spsi.val 776 1084 jitter = sdtheta.val, sdphi.val, sdpsi.val 777 1085 # set small jitter as 0 if multiple pd dims 778 1086 dims = sum(v > 0 for v in jitter) 779 limit = [0, 0.5, 5 ][dims]1087 limit = [0, 0.5, 5, 5][dims] 780 1088 jitter = [0 if v < limit else v for v in jitter] 781 1089 axes.cla() 782 draw_beam(axes, (0, 0)) 783 draw_jitter(axes, view, jitter, dist=dist, size=size, draw_shape=draw_shape) 784 #draw_jitter(axes, view, (0,0,0)) 1090 1091 ## Visualize as person on globe 1092 #draw_sphere(axes, radius=0.5) 1093 #draw_person_on_sphere(axes, view, radius=0.5) 1094 1095 ## Move beam instead of shape 1096 #draw_beam(axes, -view[:2]) 1097 #draw_jitter(axes, (0,0,0), (0,0,0), views=3) 1098 1099 ## Move shape and draw scattering 1100 draw_beam(axes, (0, 0), alpha=1.) 1101 #draw_person_on_sphere(axes, view, radius=1.2, height=0.5) 1102 draw_jitter(axes, view, jitter, dist=dist, size=size, alpha=1., 1103 draw_shape=draw_shape, projection=projection, views=3) 785 1104 draw_mesh(axes, view, jitter, dist=dist, n=mesh, projection=projection) 786 1105 draw_scattering(calculator, axes, view, jitter, dist=dist) 1106 787 1107 plt.gcf().canvas.draw() 788 1108 789 1109 ## bind control widgets to view updater 790 stheta.on_changed(lambda v: update(v, 'theta'))791 sphi.on_changed(lambda v: update(v, 'phi'))792 spsi.on_changed(lambda v: update(v, 'psi'))793 sdtheta.on_changed(lambda v: update(v, 'dtheta'))794 sdphi.on_changed(lambda v: update(v, 'dphi'))795 sdpsi.on_changed(lambda v: update(v, 'dpsi'))1110 stheta.on_changed(lambda v: _update(v, 'theta')) 1111 sphi.on_changed(lambda v: _update(v, 'phi')) 1112 spsi.on_changed(lambda v: _update(v, 'psi')) 1113 sdtheta.on_changed(lambda v: _update(v, 'dtheta')) 1114 sdphi.on_changed(lambda v: _update(v, 'dphi')) 1115 sdpsi.on_changed(lambda v: _update(v, 'dpsi')) 796 1116 797 1117 ## initialize view 798 update(None, 'phi')1118 _update(None, 'phi') 799 1119 800 1120 ## go interactive 801 1121 plt.show() 802 1122 1123 1124 def map_colors(z, kw): 1125 """ 1126 Process matplotlib-style colour arguments. 1127 1128 Pulls 'cmap', 'alpha', 'vmin', and 'vmax' from th *kw* dictionary, setting 1129 the *kw['color']* to an RGB array. These are ignored if 'c' or 'color' are 1130 set inside *kw*. 1131 """ 1132 from matplotlib import cm 1133 1134 cmap = kw.pop('cmap', cm.coolwarm) 1135 alpha = kw.pop('alpha', None) 1136 vmin = kw.pop('vmin', z.min()) 1137 vmax = kw.pop('vmax', z.max()) 1138 c = kw.pop('c', None) 1139 color = kw.pop('color', c) 1140 if color is None: 1141 znorm = ((z - vmin) / (vmax - vmin)).clip(0, 1) 1142 color = cmap(znorm) 1143 elif isinstance(color, np.ndarray) and color.shape == z.shape: 1144 color = cmap(color) 1145 if alpha is None: 1146 if isinstance(color, np.ndarray): 1147 color = color[..., :3] 1148 else: 1149 color[..., 3] = alpha 1150 kw['color'] = color 1151 1152 def make_vec(*args): 1153 """Turn all elements of *args* into numpy arrays""" 1154 #return [np.asarray(v, 'd').flatten() for v in args] 1155 return [np.asarray(v, 'd') for v in args] 1156 1157 def make_image(z, kw): 1158 """Convert numpy array *z* into a *PIL* RGB image.""" 1159 import PIL.Image 1160 from matplotlib import cm 1161 1162 cmap = kw.pop('cmap', cm.coolwarm) 1163 1164 znorm = (z-z.min())/z.ptp() 1165 c = cmap(znorm) 1166 c = c[..., :3] 1167 rgb = np.asarray(c*255, 'u1') 1168 image = PIL.Image.fromarray(rgb, mode='RGB') 1169 return image 1170 1171 1172 _IPV_MARKERS = { 1173 'o': 'sphere', 1174 } 1175 _IPV_COLORS = { 1176 'w': 'white', 1177 'k': 'black', 1178 'c': 'cyan', 1179 'm': 'magenta', 1180 'y': 'yellow', 1181 'r': 'red', 1182 'g': 'green', 1183 'b': 'blue', 1184 } 1185 def _ipv_fix_color(kw): 1186 alpha = kw.pop('alpha', None) 1187 color = kw.get('color', None) 1188 if isinstance(color, str): 1189 color = _IPV_COLORS.get(color, color) 1190 kw['color'] = color 1191 if alpha is not None: 1192 color = kw['color'] 1193 #TODO: convert color to [r, g, b, a] if not already 1194 if isinstance(color, (tuple, list)): 1195 if len(color) == 3: 1196 color = (color[0], color[1], color[2], alpha) 1197 else: 1198 color = (color[0], color[1], color[2], alpha*color[3]) 1199 color = np.array(color) 1200 if isinstance(color, np.ndarray) and color.shape[-1] == 4: 1201 color[..., 3] = alpha 1202 kw['color'] = color 1203 1204 def _ipv_set_transparency(kw, obj): 1205 color = kw.get('color', None) 1206 if (isinstance(color, np.ndarray) 1207 and color.shape[-1] == 4 1208 and (color[..., 3] != 1.0).any()): 1209 obj.material.transparent = True 1210 obj.material.side = "FrontSide" 1211 1212 def ipv_axes(): 1213 """ 1214 Build a matplotlib style Axes interface for ipyvolume 1215 """ 1216 import ipyvolume as ipv 1217 1218 class Axes(object): 1219 """ 1220 Matplotlib Axes3D style interface to ipyvolume renderer. 1221 """ 1222 # pylint: disable=no-self-use,no-init 1223 # transparency can be achieved by setting the following: 1224 # mesh.color = [r, g, b, alpha] 1225 # mesh.material.transparent = True 1226 # mesh.material.side = "FrontSide" 1227 # smooth(ish) rotation can be achieved by setting: 1228 # slide.continuous_update = True 1229 # figure.animation = 0. 1230 # mesh.material.x = x 1231 # mesh.material.y = y 1232 # mesh.material.z = z 1233 # maybe need to synchronize update of x/y/z to avoid shimmy when moving 1234 def plot(self, x, y, z, **kw): 1235 """mpl style plot interface for ipyvolume""" 1236 _ipv_fix_color(kw) 1237 x, y, z = make_vec(x, y, z) 1238 ipv.plot(x, y, z, **kw) 1239 def plot_surface(self, x, y, z, **kw): 1240 """mpl style plot_surface interface for ipyvolume""" 1241 facecolors = kw.pop('facecolors', None) 1242 if facecolors is not None: 1243 kw['color'] = facecolors 1244 _ipv_fix_color(kw) 1245 x, y, z = make_vec(x, y, z) 1246 h = ipv.plot_surface(x, y, z, **kw) 1247 _ipv_set_transparency(kw, h) 1248 #h.material.side = "DoubleSide" 1249 return h 1250 def plot_trisurf(self, x, y, triangles=None, Z=None, **kw): 1251 """mpl style plot_trisurf interface for ipyvolume""" 1252 kw.pop('linewidth', None) 1253 _ipv_fix_color(kw) 1254 x, y, z = make_vec(x, y, Z) 1255 if triangles is not None: 1256 triangles = np.asarray(triangles) 1257 h = ipv.plot_trisurf(x, y, z, triangles=triangles, **kw) 1258 _ipv_set_transparency(kw, h) 1259 return h 1260 def scatter(self, x, y, z, **kw): 1261 """mpl style scatter interface for ipyvolume""" 1262 x, y, z = make_vec(x, y, z) 1263 map_colors(z, kw) 1264 marker = kw.get('marker', None) 1265 kw['marker'] = _IPV_MARKERS.get(marker, marker) 1266 h = ipv.scatter(x, y, z, **kw) 1267 _ipv_set_transparency(kw, h) 1268 return h 1269 def contourf(self, x, y, v, zdir='z', offset=0, levels=None, **kw): 1270 """mpl style contourf interface for ipyvolume""" 1271 # pylint: disable=unused-argument 1272 # Don't use contour for now (although we might want to later) 1273 self.pcolor(x, y, v, zdir='z', offset=offset, **kw) 1274 def pcolor(self, x, y, v, zdir='z', offset=0, **kw): 1275 """mpl style pcolor interface for ipyvolume""" 1276 # pylint: disable=unused-argument 1277 x, y, v = make_vec(x, y, v) 1278 image = make_image(v, kw) 1279 xmin, xmax = x.min(), x.max() 1280 ymin, ymax = y.min(), y.max() 1281 x = np.array([[xmin, xmax], [xmin, xmax]]) 1282 y = np.array([[ymin, ymin], [ymax, ymax]]) 1283 z = x*0 + offset 1284 u = np.array([[0., 1], [0, 1]]) 1285 v = np.array([[0., 0], [1, 1]]) 1286 h = ipv.plot_mesh(x, y, z, u=u, v=v, texture=image, wireframe=False) 1287 _ipv_set_transparency(kw, h) 1288 h.material.side = "DoubleSide" 1289 return h 1290 def text(self, *args, **kw): 1291 """mpl style text interface for ipyvolume""" 1292 pass 1293 def set_xlim(self, limits): 1294 """mpl style set_xlim interface for ipyvolume""" 1295 ipv.xlim(*limits) 1296 def set_ylim(self, limits): 1297 """mpl style set_ylim interface for ipyvolume""" 1298 ipv.ylim(*limits) 1299 def set_zlim(self, limits): 1300 """mpl style set_zlim interface for ipyvolume""" 1301 ipv.zlim(*limits) 1302 def set_axes_on(self): 1303 """mpl style set_axes_on interface for ipyvolume""" 1304 ipv.style.axis_on() 1305 def set_axis_off(self): 1306 """mpl style set_axes_off interface for ipyvolume""" 1307 ipv.style.axes_off() 1308 return Axes() 1309 1310 def _ipv_plot(calculator, draw_shape, size, view, jitter, dist, mesh, projection): 1311 from IPython.display import display 1312 import ipywidgets as widgets 1313 import ipyvolume as ipv 1314 1315 axes = ipv_axes() 1316 1317 def _draw(view, jitter): 1318 camera = ipv.gcf().camera 1319 #print(ipv.gcf().__dict__.keys()) 1320 #print(dir(ipv.gcf())) 1321 ipv.figure(animation=0.) # no animation when updating object mesh 1322 1323 # set small jitter as 0 if multiple pd dims 1324 dims = sum(v > 0 for v in jitter) 1325 limit = [0, 0.5, 5, 5][dims] 1326 jitter = [0 if v < limit else v for v in jitter] 1327 1328 ## Visualize as person on globe 1329 #draw_beam(axes, (0, 0)) 1330 #draw_sphere(axes, radius=0.5) 1331 #draw_person_on_sphere(axes, view, radius=0.5) 1332 1333 ## Move beam instead of shape 1334 #draw_beam(axes, view=(-view[0], -view[1])) 1335 #draw_jitter(axes, view=(0,0,0), jitter=(0,0,0)) 1336 1337 ## Move shape and draw scattering 1338 draw_beam(axes, (0, 0), steps=25) 1339 draw_jitter(axes, view, jitter, dist=dist, size=size, alpha=1.0, 1340 draw_shape=draw_shape, projection=projection) 1341 draw_mesh(axes, view, jitter, dist=dist, n=mesh, radius=0.95, 1342 projection=projection) 1343 draw_scattering(calculator, axes, view, jitter, dist=dist) 1344 1345 draw_axes(axes, origin=(-1, -1, -1.1)) 1346 ipv.style.box_off() 1347 ipv.style.axes_off() 1348 ipv.xyzlabel(" ", " ", " ") 1349 1350 ipv.gcf().camera = camera 1351 ipv.show() 1352 1353 1354 trange, prange = (-180., 180., 1.), (-180., 180., 1.) 1355 dtrange, dprange = (0., 180., 1.), (0., 180., 1.) 1356 1357 ## Super simple interfaca, but uses non-ascii variable namese 1358 # Ξ Ï Ï ÎΞ ÎÏ ÎÏ 1359 #def update(**kw): 1360 # view = kw['Ξ'], kw['Ï'], kw['Ï'] 1361 # jitter = kw['ÎΞ'], kw['ÎÏ'], kw['ÎÏ'] 1362 # draw(view, jitter) 1363 #widgets.interact(update, Ξ=trange, Ï=prange, Ï=prange, ÎΞ=dtrange, ÎÏ=dprange, ÎÏ=dprange) 1364 1365 def _update(theta, phi, psi, dtheta, dphi, dpsi): 1366 _draw(view=(theta, phi, psi), jitter=(dtheta, dphi, dpsi)) 1367 1368 def _slider(name, slice, init=0.): 1369 return widgets.FloatSlider( 1370 value=init, 1371 min=slice[0], 1372 max=slice[1], 1373 step=slice[2], 1374 description=name, 1375 disabled=False, 1376 #continuous_update=True, 1377 continuous_update=False, 1378 orientation='horizontal', 1379 readout=True, 1380 readout_format='.1f', 1381 ) 1382 theta = _slider(u'Ξ', trange, view[0]) 1383 phi = _slider(u'Ï', prange, view[1]) 1384 psi = _slider(u'Ï', prange, view[2]) 1385 dtheta = _slider(u'ÎΞ', dtrange, jitter[0]) 1386 dphi = _slider(u'ÎÏ', dprange, jitter[1]) 1387 dpsi = _slider(u'ÎÏ', dprange, jitter[2]) 1388 fields = { 1389 'theta': theta, 'phi': phi, 'psi': psi, 1390 'dtheta': dtheta, 'dphi': dphi, 'dpsi': dpsi, 1391 } 1392 ui = widgets.HBox([ 1393 widgets.VBox([theta, phi, psi]), 1394 widgets.VBox([dtheta, dphi, dpsi]) 1395 ]) 1396 1397 out = widgets.interactive_output(_update, fields) 1398 display(ui, out) 1399 1400 1401 _ENGINES = { 1402 "matplotlib": _mpl_plot, 1403 "mpl": _mpl_plot, 1404 #"plotly": _plotly_plot, 1405 "ipvolume": _ipv_plot, 1406 "ipv": _ipv_plot, 1407 } 1408 PLOT_ENGINE = _ENGINES["matplotlib"] 1409 def set_plotter(name): 1410 """ 1411 Setting the plotting engine to matplotlib/ipyvolume or equivalently mpl/ipv. 1412 """ 1413 global PLOT_ENGINE 1414 PLOT_ENGINE = _ENGINES[name] 1415 803 1416 def main(): 1417 """ 1418 Command line interface to the jitter viewer. 1419 """ 804 1420 parser = argparse.ArgumentParser( 805 1421 description="Display jitter", … … 811 1427 parser.add_argument('-s', '--size', type=str, default='10,40,100', 812 1428 help='a,b,c lengths') 1429 parser.add_argument('-v', '--view', type=str, default='0,0,0', 1430 help='initial view angles') 1431 parser.add_argument('-j', '--jitter', type=str, default='0,0,0', 1432 help='initial angular dispersion') 813 1433 parser.add_argument('-d', '--distribution', choices=DISTRIBUTIONS, 814 1434 default=DISTRIBUTIONS[0], … … 819 1439 help='oriented shape') 820 1440 opts = parser.parse_args() 821 size = tuple(int(v) for v in opts.size.split(',')) 822 run(opts.shape, size=size, 1441 size = tuple(float(v) for v in opts.size.split(',')) 1442 view = tuple(float(v) for v in opts.view.split(',')) 1443 jitter = tuple(float(v) for v in opts.jitter.split(',')) 1444 run(opts.shape, size=size, view=view, jitter=jitter, 823 1445 mesh=opts.mesh, dist=opts.distribution, 824 1446 projection=opts.projection) -
sasmodels/kernel.py
rcd28947 ra34b811 23 23 # pylint: enable=unused-import 24 24 25 25 26 class KernelModel(object): 27 """ 28 Model definition for the compute engine. 29 """ 26 30 info = None # type: ModelInfo 27 31 dtype = None # type: np.dtype 28 32 def make_kernel(self, q_vectors): 29 33 # type: (List[np.ndarray]) -> "Kernel" 34 """ 35 Instantiate a kernel for evaluating the model at *q_vectors*. 36 """ 30 37 raise NotImplementedError("need to implement make_kernel") 31 38 32 39 def release(self): 33 40 # type: () -> None 41 """ 42 Free resources associated with the kernel. 43 """ 34 44 pass 35 45 46 36 47 class Kernel(object): 37 #: kernel dimension, either "1d" or "2d" 48 """ 49 Instantiated model for the compute engine, applied to a particular *q*. 50 51 Subclasses should define :meth:`_call_kernel` to evaluate the kernel over 52 its inputs. 53 """ 54 #: Kernel dimension, either "1d" or "2d". 38 55 dim = None # type: str 56 #: Model info. 39 57 info = None # type: ModelInfo 40 results = None # type: List[np.ndarray]58 #: Numerical precision for the computation. 41 59 dtype = None # type: np.dtype 60 #: q values at which the kernel is to be evaluated 61 q_input = None # type: Any 62 #: Place to hold result of :meth:`_call_kernel` for subclass. 63 result = None # type: np.ndarray 42 64 43 65 def Iq(self, call_details, values, cutoff, magnetic): … … 55 77 built into the model, and do not need an additional scale. 56 78 """ 57 _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, magnetic,58 effective_radius_type=0)79 _, F2, _, shell_volume, _ = self.Fq(call_details, values, cutoff, 80 magnetic, radius_effective_mode=0) 59 81 combined_scale = values[0]/shell_volume 60 82 background = values[1] … … 62 84 __call__ = Iq 63 85 64 def Fq(self, call_details, values, cutoff, magnetic, effective_radius_type=0): 86 def Fq(self, call_details, values, cutoff, magnetic, 87 radius_effective_mode=0): 65 88 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray 66 89 r""" … … 79 102 .. math:: 80 103 81 I(q) = \text{scale} *P (1 + <F>^2/<F^2> (S - 1)) + \text{background}104 I(q) = \text{scale} P (1 + <F>^2/<F^2> (S - 1)) + \text{background} 82 105 = \text{scale}/<V> (<F^2> + <F>^2 (S - 1)) + \text{background} 83 106 … … 88 111 .. math:: 89 112 90 P(q) =\frac{\sum w_k F^2(q, x_k) / \sum w_k}{\sum w_k V_k / \sum w_k}113 P(q)=\frac{\sum w_k F^2(q, x_k) / \sum w_k}{\sum w_k V_k / \sum w_k} 91 114 92 115 The form factor itself is scaled by volume and contrast to compute the … … 120 143 volume fraction of the particles. The model can have several 121 144 different ways to compute effective radius, with the 122 * effective_radius_type* parameter used to select amongst them. The145 *radius_effective_mode* parameter used to select amongst them. The 123 146 volume fraction of particles should be determined from the total 124 147 volume fraction of the form, not just the shell volume fraction. … … 129 152 hollow and solid shapes. 130 153 """ 131 self._call_kernel(call_details, values, cutoff, magnetic, effective_radius_type) 154 self._call_kernel(call_details, values, cutoff, magnetic, 155 radius_effective_mode) 132 156 #print("returned",self.q_input.q, self.result) 133 157 nout = 2 if self.info.have_Fq and self.dim == '1d' else 1 … … 141 165 form_volume = self.result[nout*self.q_input.nq + 1]/total_weight 142 166 shell_volume = self.result[nout*self.q_input.nq + 2]/total_weight 143 effective_radius= self.result[nout*self.q_input.nq + 3]/total_weight167 radius_effective = self.result[nout*self.q_input.nq + 3]/total_weight 144 168 if shell_volume == 0.: 145 169 shell_volume = 1. 146 F1 = self.result[1:nout*self.q_input.nq:nout]/total_weight if nout == 2 else None 170 F1 = (self.result[1:nout*self.q_input.nq:nout]/total_weight 171 if nout == 2 else None) 147 172 F2 = self.result[0:nout*self.q_input.nq:nout]/total_weight 148 return F1, F2, effective_radius, shell_volume, form_volume/shell_volume173 return F1, F2, radius_effective, shell_volume, form_volume/shell_volume 149 174 150 175 def release(self): 151 176 # type: () -> None 177 """ 178 Free resources associated with the kernel instance. 179 """ 152 180 pass 153 181 154 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 182 def _call_kernel(self, call_details, values, cutoff, magnetic, 183 radius_effective_mode): 155 184 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray 156 185 """ -
sasmodels/kernel_iq.c
r12f4c19 rde032da 27 27 // parameters in the parameter table. 28 28 // CALL_VOLUME(form, shell, table) : assign form and shell values 29 // CALL_ EFFECTIVE_RADIUS(type, table) : call the R_eff function29 // CALL_RADIUS_EFFECTIVE(mode, table) : call the R_eff function 30 30 // CALL_IQ(q, table) : call the Iq function for 1D calcs. 31 31 // CALL_IQ_A(q, table) : call the Iq function with |q| for 2D data. … … 85 85 static void set_spin_weights(double in_spin, double out_spin, double weight[6]) 86 86 { 87 88 double norm; 87 89 in_spin = clip(in_spin, 0.0, 1.0); 88 90 out_spin = clip(out_spin, 0.0, 1.0); … … 94 96 // However, since the weights are applied to the final intensity and 95 97 // are not interned inside the I(q) function, we want the full 96 // weight and not the square root. Any function using 97 // set_spin_weights as part of calculating an amplitude will need to 98 // manually take that square root, but there is currently no such 99 // function. 100 weight[0] = (1.0-in_spin) * (1.0-out_spin); // dd 101 weight[1] = (1.0-in_spin) * out_spin; // du 102 weight[2] = in_spin * (1.0-out_spin); // ud 103 weight[3] = in_spin * out_spin; // uu 98 // weight and not the square root. Anyway no function will ever use 99 // set_spin_weights as part of calculating an amplitude, as the weights are 100 // related to polarisation efficiency of the instrument. The weights serve to 101 // construct various magnet scattering cross sections, which are linear combinations 102 // of the spin-resolved cross sections. The polarisation efficiency e_in and e_out 103 // are parameters ranging from 0.5 (unpolarised) beam to 1 (perfect optics). 104 // For in_spin or out_spin <0.5 one assumes a CS, where the spin is reversed/flipped 105 // with respect to the initial supermirror polariser. The actual polarisation efficiency 106 // in this case is however e_in/out = 1-in/out_spin. 107 108 if (out_spin < 0.5){norm=1-out_spin;} 109 else{norm=out_spin;} 110 111 112 // The norm is needed to make sure that the scattering cross sections are 113 //correctly weighted, such that the sum of spin-resolved measurements adds up to 114 // the unpolarised or half-polarised scattering cross section. No intensity weighting 115 // needed on the incoming polariser side (assuming that a user), has normalised 116 // to the incoming flux with polariser in for SANSPOl and unpolarised beam, respectively. 117 118 119 weight[0] = (1.0-in_spin) * (1.0-out_spin) / norm; // dd 120 weight[1] = (1.0-in_spin) * out_spin / norm; // du 121 weight[2] = in_spin * (1.0-out_spin) / norm; // ud 122 weight[3] = in_spin * out_spin / norm; // uu 104 123 weight[4] = weight[1]; // du.imag 105 124 weight[5] = weight[2]; // ud.imag … … 119 138 switch (xs) { 120 139 default: // keep compiler happy; condition ensures xs in [0,1,2,3] 121 case 0: // uu=> sld - D M_perpx140 case 0: // dd => sld - D M_perpx 122 141 return sld - px*perp; 123 case 1: // ud.real => -D M_perpy142 case 1: // du.real => -D M_perpy 124 143 return py*perp; 125 case 2: // du.real => -D M_perpy144 case 2: // ud.real => -D M_perpy 126 145 return py*perp; 127 case 3: // dd=> sld + D M_perpx146 case 3: // uu => sld + D M_perpx 128 147 return sld + px*perp; 129 148 } … … 285 304 pglobal double *result, // nq+1 return values, again with padding 286 305 const double cutoff, // cutoff in the dispersity weight product 287 int32_t effective_radius_type // which effective radius to compute306 int32_t radius_effective_mode // which effective radius to compute 288 307 ) 289 308 { … … 703 722 weighted_form += weight * form; 704 723 weighted_shell += weight * shell; 705 if ( effective_radius_type != 0) {706 weighted_radius += weight * CALL_ EFFECTIVE_RADIUS(effective_radius_type, local_values.table);724 if (radius_effective_mode != 0) { 725 weighted_radius += weight * CALL_RADIUS_EFFECTIVE(radius_effective_mode, local_values.table); 707 726 } 708 727 BUILD_ROTATION(); -
sasmodels/kernelcl.py
r8064d5e ra34b811 53 53 from __future__ import print_function 54 54 55 import sys 55 56 import os 56 57 import warnings … … 61 62 from time import perf_counter as clock 62 63 except ImportError: # CRUFT: python < 3.3 63 import sys64 64 if sys.platform.count("darwin") > 0: 65 65 from time import time as clock … … 69 69 import numpy as np # type: ignore 70 70 71 # Attempt to setup opencl. This may fail if the pyopencl package is not71 # Attempt to setup OpenCL. This may fail if the pyopencl package is not 72 72 # installed or if it is installed but there are no devices available. 73 73 try: … … 75 75 from pyopencl import mem_flags as mf 76 76 from pyopencl.characterize import get_fast_inaccurate_build_options 77 # Ask OpenCL for the default context so that we know that one exists 77 # Ask OpenCL for the default context so that we know that one exists. 78 78 cl.create_some_context(interactive=False) 79 79 HAVE_OPENCL = True … … 96 96 # pylint: enable=unused-import 97 97 98 # CRUFT: pyopencl < 2017.1 (as of June 2016 needs quotes around include path) 98 99 # CRUFT: pyopencl < 2017.1 (as of June 2016 needs quotes around include path). 99 100 def quote_path(v): 101 # type: (str) -> str 100 102 """ 101 103 Quote the path if it is not already quoted. … … 107 109 return '"'+v+'"' if v and ' ' in v and not v[0] in "\"'-" else v 108 110 111 109 112 def fix_pyopencl_include(): 113 # type: (None) -> None 110 114 """ 111 115 Monkey patch pyopencl to allow spaces in include file path. 112 116 """ 113 import pyopencl as cl 114 if hasattr(cl, '_DEFAULT_INCLUDE_OPTIONS'): 115 cl._DEFAULT_INCLUDE_OPTIONS = [quote_path(v) for v in cl._DEFAULT_INCLUDE_OPTIONS] 117 # pylint: disable=protected-access 118 import pyopencl 119 if hasattr(pyopencl, '_DEFAULT_INCLUDE_OPTIONS'): 120 pyopencl._DEFAULT_INCLUDE_OPTIONS = [ 121 quote_path(v) for v in pyopencl._DEFAULT_INCLUDE_OPTIONS 122 ] 123 116 124 117 125 if HAVE_OPENCL: … … 126 134 MAX_LOOPS = 2048 127 135 128 129 136 # Pragmas for enable OpenCL features. Be sure to protect them so that they 130 137 # still compile even if OpenCL is not present. … … 141 148 """ 142 149 150 143 151 def use_opencl(): 152 # type: () -> bool 153 """Return True if OpenCL is the default computational engine""" 144 154 sas_opencl = os.environ.get("SAS_OPENCL", "OpenCL").lower() 145 155 return HAVE_OPENCL and sas_opencl != "none" and not sas_opencl.startswith("cuda") 146 156 157 147 158 ENV = None 148 159 def reset_environment(): 160 # type: () -> None 149 161 """ 150 162 Call to create a new OpenCL context, such as after a change to SAS_OPENCL. … … 152 164 global ENV 153 165 ENV = GpuEnvironment() if use_opencl() else None 166 154 167 155 168 def environment(): … … 169 182 return ENV 170 183 184 171 185 def has_type(device, dtype): 172 186 # type: (cl.Device, np.dtype) -> bool … … 179 193 return "cl_khr_fp64" in device.extensions 180 194 else: 181 # Not supporting F16 type since it isn't accurate enough 195 # Not supporting F16 type since it isn't accurate enough. 182 196 return False 197 183 198 184 199 def get_warp(kernel, queue): … … 190 205 cl.kernel_work_group_info.PREFERRED_WORK_GROUP_SIZE_MULTIPLE, 191 206 queue.device) 207 192 208 193 209 def compile_model(context, source, dtype, fast=False): … … 211 227 source_list.insert(0, _F64_PRAGMA) 212 228 213 # Note: USE_SINCOS makes the intel cpu slower under opencl229 # Note: USE_SINCOS makes the Intel CPU slower under OpenCL. 214 230 if context.devices[0].type == cl.device_type.GPU: 215 231 source_list.insert(0, "#define USE_SINCOS\n") … … 218 234 source = "\n".join(source_list) 219 235 program = cl.Program(context, source).build(options=options) 236 220 237 #print("done with "+program) 221 238 return program 222 239 223 240 224 # for now, this returns one device in the context225 # TODO: create a context that contains all devices on all platforms241 # For now, this returns one device in the context. 242 # TODO: Create a context that contains all devices on all platforms. 226 243 class GpuEnvironment(object): 227 244 """ 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. 245 GPU context for OpenCL, with possibly many devices and one queue per device. 242 246 """ 243 247 def __init__(self): 244 248 # type: () -> None 245 # find gpu context249 # Find gpu context. 246 250 context_list = _create_some_context() 247 251 … … 257 261 self.context[dtype] = None 258 262 259 # Build a queue for each context 263 # Build a queue for each context. 260 264 self.queue = {} 261 265 context = self.context[F32] … … 267 271 self.queue[F64] = cl.CommandQueue(context, context.devices[0]) 268 272 269 # Byte boundary for data alignment273 ## Byte boundary for data alignment. 270 274 #self.data_boundary = max(context.devices[0].min_data_type_align_size 271 275 # for context in self.context.values()) 272 276 273 # Cache for compiled programs, and for items in context 277 # Cache for compiled programs, and for items in context. 274 278 self.compiled = {} 275 self.cache = {}276 279 277 280 def has_type(self, dtype): … … 288 291 """ 289 292 # Note: PyOpenCL caches based on md5 hash of source, options and device 290 # so we don't really need to cache things for ourselves. I'll do so 291 # anyway just to save some data munging time. 293 # but I'll do so as well just to save some data munging time. 292 294 tag = generate.tag_source(source) 293 295 key = "%s-%s-%s%s"%(name, dtype, tag, ("-fast" if fast else "")) 294 # Check timestamp on program 296 # Check timestamp on program. 295 297 program, program_timestamp = self.compiled.get(key, (None, np.inf)) 296 298 if program_timestamp < timestamp: … … 305 307 return program 306 308 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 = 0319 def unique_id():320 global _CURRENT_ID321 _CURRENT_ID += 1322 return _CURRENT_ID323 309 324 310 def _create_some_context(): … … 333 319 which one (and not a CUDA device, or no GPU). 334 320 """ 335 # Assume we do not get here if SAS_OPENCL is None or CUDA 321 # Assume we do not get here if SAS_OPENCL is None or CUDA. 336 322 sas_opencl = os.environ.get('SAS_OPENCL', 'opencl') 337 323 if sas_opencl.lower() != 'opencl': 338 # Setting PYOPENCL_CTX as a SAS_OPENCL to create cl context 324 # Setting PYOPENCL_CTX as a SAS_OPENCL to create cl context. 339 325 os.environ["PYOPENCL_CTX"] = sas_opencl 340 326 … … 343 329 return [cl.create_some_context(interactive=False)] 344 330 except Exception as exc: 331 # TODO: Should warnings instead be put into logging.warn? 345 332 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") 333 warnings.warn( 334 "pyopencl.create_some_context() failed. The environment " 335 "variable 'SAS_OPENCL' or 'PYOPENCL_CTX' might not be set " 336 "correctly") 348 337 349 338 return _get_default_context() 339 350 340 351 341 def _get_default_context(): … … 360 350 # is running may increase throughput. 361 351 # 362 # Mac book pro, base install:352 # MacBook Pro, base install: 363 353 # {'Apple': [Intel CPU, NVIDIA GPU]} 364 # Mac book pro, base install:354 # MacBook Pro, base install: 365 355 # {'Apple': [Intel CPU, Intel GPU]} 366 # 2 x nvidia 295 with Intel and NVIDIA opencl drivers installed356 # 2 x NVIDIA 295 with Intel and NVIDIA opencl drivers install: 367 357 # {'Intel': [CPU], 'NVIDIA': [GPU, GPU, GPU, GPU]} 368 358 gpu, cpu = None, None … … 387 377 else: 388 378 # System has cl.device_type.ACCELERATOR or cl.device_type.CUSTOM 389 # Intel Phi for example registers as an accelerator 379 # Intel Phi for example registers as an accelerator. 390 380 # Since the user installed a custom device on their system 391 381 # and went through the pain of sorting out OpenCL drivers for … … 394 384 gpu = device 395 385 396 # order the devices by gpu then by cpu; when searching for an available386 # Order the devices by gpu then by cpu; when searching for an available 397 387 # device by data type they will be checked in this order, which means 398 388 # that if the gpu supports double then the cpu will never be used (though … … 421 411 that the compiler is allowed to take shortcuts. 422 412 """ 413 info = None # type: ModelInfo 414 source = "" # type: str 415 dtype = None # type: np.dtype 416 fast = False # type: bool 417 _program = None # type: cl.Program 418 _kernels = None # type: Dict[str, cl.Kernel] 419 423 420 def __init__(self, source, model_info, dtype=generate.F32, fast=False): 424 421 # type: (Dict[str,str], ModelInfo, np.dtype, bool) -> None … … 427 424 self.dtype = dtype 428 425 self.fast = fast 429 self.timestamp = generate.ocl_timestamp(self.info)430 self._cache_key = unique_id()431 426 432 427 def __getstate__(self): … … 437 432 # type: (Tuple[ModelInfo, str, np.dtype, bool]) -> None 438 433 self.info, self.source, self.dtype, self.fast = state 434 self._program = self._kernels = None 439 435 440 436 def make_kernel(self, q_vectors): … … 442 438 return GpuKernel(self, q_vectors) 443 439 444 @property 445 def Iq(self): 446 return self._fetch_kernel('Iq') 447 448 def fetch_kernel(self, name): 440 def get_function(self, name): 449 441 # type: (str) -> cl.Kernel 450 442 """ … … 452 444 does not already exist. 453 445 """ 454 gpu = environment() 455 key = self._cache_key 456 if key not in gpu.cache: 457 program = gpu.compile_program( 458 self.info.name, 459 self.source['opencl'], 460 self.dtype, 461 self.fast, 462 self.timestamp) 463 variants = ['Iq', 'Iqxy', 'Imagnetic'] 464 names = [generate.kernel_name(self.info, k) for k in variants] 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 470 else: 471 data = gpu.cache[key] 472 return data[name] 473 474 # TODO: check that we don't need a destructor for buffers which go out of scope 446 if self._program is None: 447 self._prepare_program() 448 return self._kernels[name] 449 450 def _prepare_program(self): 451 # type: (str) -> None 452 env = environment() 453 timestamp = generate.ocl_timestamp(self.info) 454 program = env.compile_program( 455 self.info.name, 456 self.source['opencl'], 457 self.dtype, 458 self.fast, 459 timestamp) 460 variants = ['Iq', 'Iqxy', 'Imagnetic'] 461 names = [generate.kernel_name(self.info, k) for k in variants] 462 functions = [getattr(program, k) for k in names] 463 self._kernels = {k: v for k, v in zip(variants, functions)} 464 # Keep a handle to program so GC doesn't collect. 465 self._program = program 466 467 468 # TODO: Check that we don't need a destructor for buffers which go out of scope. 475 469 class GpuInput(object): 476 470 """ … … 494 488 def __init__(self, q_vectors, dtype=generate.F32): 495 489 # type: (List[np.ndarray], np.dtype) -> None 496 # TODO: do we ever need double precision q?490 # TODO: Do we ever need double precision q? 497 491 self.nq = q_vectors[0].size 498 492 self.dtype = np.dtype(dtype) 499 493 self.is_2d = (len(q_vectors) == 2) 500 # TODO: stretch input based on get_warp()501 # not doing it now since warp depends on kernel, which is not known494 # TODO: Stretch input based on get_warp(). 495 # Not doing it now since warp depends on kernel, which is not known 502 496 # at this point, so instead using 32, which is good on the set of 503 497 # architectures tested so far. … … 512 506 self.q[:self.nq] = q_vectors[0] 513 507 self.global_size = [self.q.shape[0]] 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""" 508 #print("creating inputs of size", self.global_size) 509 510 # Transfer input value to GPU. 519 511 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] 512 context = env.context[self.dtype] 513 self.q_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 514 hostbuf=self.q) 528 515 529 516 def release(self): 530 517 # type: () -> None 531 518 """ 532 Free the buffer associated with the q value 533 """ 534 environment().free_buffer(id(self)) 519 Free the buffer associated with the q value. 520 """ 521 if self.q_b is not None: 522 self.q_b.release() 523 self.q_b = None 535 524 536 525 def __del__(self): … … 538 527 self.release() 539 528 529 540 530 class GpuKernel(Kernel): 541 531 """ … … 544 534 *model* is the GpuModel object to call 545 535 546 The following attributes are defined: 547 548 *info* is the module information 549 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 555 556 The resulting call method takes the *pars*, a list of values for 557 the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight) 558 vectors for the polydisperse parameters. *cutoff* determines the 559 integration limits: any points with combined weight less than *cutoff* 560 will not be calculated. 536 The kernel is derived from :class:`Kernel`, providing the 537 :meth:`call_kernel` method to evaluate the kernel for a given set of 538 parameters. Because of the need to move the q values to the GPU before 539 evaluation, the kernel is instantiated for a particular set of q vectors, 540 and can be called many times without transfering q each time. 561 541 562 542 Call :meth:`release` when done with the kernel instance. 563 543 """ 544 #: SAS model information structure. 545 info = None # type: ModelInfo 546 #: Kernel precision. 547 dtype = None # type: np.dtype 548 #: Kernel dimensions (1d or 2d). 549 dim = "" # type: str 550 #: Calculation results, updated after each call to :meth:`_call_kernel`. 551 result = None # type: np.ndarray 552 564 553 def __init__(self, model, q_vectors): 565 # type: ( cl.Kernel, np.dtype, ModelInfo, List[np.ndarray]) -> None554 # type: (GpuModel, List[np.ndarray]) -> None 566 555 dtype = model.dtype 567 556 self.q_input = GpuInput(q_vectors, dtype) 568 557 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 558 559 # Attributes accessed from the outside. 574 560 self.dim = '2d' if self.q_input.is_2d else '1d' 575 561 self.info = model.info 576 self.dtype = model.dtype 577 578 # holding place for the returned value 562 self.dtype = dtype 563 564 # Converter to translate input to target type. 565 self._as_dtype = np.float64 if dtype == generate.F64 else np.float32 566 567 # Holding place for the returned value. 579 568 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""" 569 extra_q = 4 # Total weight, form volume, shell volume and R_eff. 570 self.result = np.empty(self.q_input.nq*nout + extra_q, dtype) 571 572 # Allocate result value on GPU. 586 573 env = environment() 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): 596 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 574 context = env.context[self.dtype] 575 width = ((self.result.size+31)//32)*32 * self.dtype.itemsize 576 self._result_b = cl.Buffer(context, mf.READ_WRITE, width) 577 578 def _call_kernel(self, call_details, values, cutoff, magnetic, 579 radius_effective_mode): 580 # type: (CallDetails, np.ndarray, float, bool, int) -> np.ndarray 597 581 env = environment() 598 582 queue = env.queue[self._model.dtype] 599 583 context = queue.context 600 584 601 # Arrange data transfer to/from card 602 q_b = self.q_input.q_b 603 result_b = self._result_b 585 # Arrange data transfer to card. 604 586 details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR, 605 587 hostbuf=call_details.buffer) … … 607 589 hostbuf=values) 608 590 591 # Setup kernel function and arguments. 609 592 name = 'Iq' if self.dim == '1d' else 'Imagnetic' if magnetic else 'Iqxy' 610 kernel = self._model. fetch_kernel(name)593 kernel = self._model.get_function(name) 611 594 kernel_args = [ 612 np.uint32(self.q_input.nq), None, None, 613 details_b, values_b, q_b, result_b, 614 self._as_dtype(cutoff), 615 np.uint32(effective_radius_type), 595 np.uint32(self.q_input.nq), # Number of inputs. 596 None, # Placeholder for pd_start. 597 None, # Placeholder for pd_stop. 598 details_b, # Problem definition. 599 values_b, # Parameter values. 600 self.q_input.q_b, # Q values. 601 self._result_b, # Result storage. 602 self._as_dtype(cutoff), # Probability cutoff. 603 np.uint32(radius_effective_mode), # R_eff mode. 616 604 ] 605 606 # Call kernel and retrieve results. 617 607 #print("Calling OpenCL") 618 608 #call_details.show(values) 619 #Call kernel and retrieve results620 609 wait_for = None 621 610 last_nap = clock() … … 628 617 *kernel_args, wait_for=wait_for)] 629 618 if stop < call_details.num_eval: 630 # Allow other processes to run 619 # Allow other processes to run. 631 620 wait_for[0].wait() 632 621 current_time = clock() … … 634 623 time.sleep(0.001) 635 624 last_nap = current_time 636 cl.enqueue_copy(queue, self.result, result_b, wait_for=wait_for)625 cl.enqueue_copy(queue, self.result, self._result_b, wait_for=wait_for) 637 626 #print("result", self.result) 638 627 639 # Free buffers 640 for v in (details_b, values_b): 641 if v is not None: 642 v.release() 628 # Free buffers. 629 details_b.release() 630 values_b.release() 643 631 644 632 def release(self): … … 647 635 Release resources associated with the kernel. 648 636 """ 649 environment().free_buffer(id(self))650 637 self.q_input.release() 638 if self._result_b is not None: 639 self._result_b.release() 640 self._result_b = None 651 641 652 642 def __del__(self): -
sasmodels/kernelcuda.py
rf872fd1 ra34b811 59 59 60 60 import os 61 import warnings62 61 import logging 63 62 import time 64 63 import re 64 import atexit 65 65 66 66 import numpy as np # type: ignore 67 67 68 68 69 # Attempt to setup cuda. This may fail if the pycuda package is not69 # Attempt to setup CUDA. This may fail if the pycuda package is not 70 70 # installed or if it is installed but there are no devices available. 71 71 try: … … 107 107 MAX_LOOPS = 2048 108 108 109 109 110 def use_cuda(): 110 env = os.environ.get("SAS_OPENCL", "").lower() 111 return HAVE_CUDA and (env == "" or env.startswith("cuda")) 111 # type: None -> bool 112 """Returns True if CUDA is the default compute engine.""" 113 sas_opencl = os.environ.get("SAS_OPENCL", "CUDA").lower() 114 return HAVE_CUDA and sas_opencl.startswith("cuda") 115 112 116 113 117 ENV = None … … 122 126 ENV = GpuEnvironment() if use_cuda() else None 123 127 128 124 129 def environment(): 125 130 # type: () -> "GpuEnvironment" … … 132 137 if not HAVE_CUDA: 133 138 raise RuntimeError("CUDA startup failed with ***" 134 + CUDA_ERROR + "***; using C compiler instead")139 + CUDA_ERROR + "***; using C compiler instead") 135 140 reset_environment() 136 141 if ENV is None: … … 138 143 return ENV 139 144 145 146 # PyTest is not freeing ENV, so make sure it gets freed. 147 atexit.register(lambda: ENV.release() if ENV is not None else None) 148 149 140 150 def has_type(dtype): 141 151 # type: (np.dtype) -> bool … … 143 153 Return true if device supports the requested precision. 144 154 """ 145 # Assume the nvidiacard supports 32-bit and 64-bit floats.146 # TODO: check if pycuda support F16155 # Assume the NVIDIA card supports 32-bit and 64-bit floats. 156 # TODO: Check if pycuda support F16. 147 157 return dtype in (generate.F32, generate.F64) 148 158 149 159 150 160 FUNCTION_PATTERN = re.compile(r"""^ 151 (?P<space>\s*) # initial space152 (?P<qualifiers>^(?:\s*\b\w+\b\s*)+) # one or more qualifiers before function153 (?P<function>\s*\b\w+\b\s*[(]) # function name plus open parens161 (?P<space>\s*) # Initial space. 162 (?P<qualifiers>^(?:\s*\b\w+\b\s*)+) # One or more qualifiers before function. 163 (?P<function>\s*\b\w+\b\s*[(]) # Function name plus open parens. 154 164 """, re.VERBOSE|re.MULTILINE) 155 165 … … 158 168 """, re.VERBOSE|re.MULTILINE) 159 169 170 160 171 def _add_device_tag(match): 161 172 # type: (None) -> str 162 # Note: should be re.Match, but that isn't a simple type173 # Note: Should be re.Match, but that isn't a simple type. 163 174 """ 164 175 replace qualifiers with __device__ qualifiers if needed … … 173 184 return "".join((space, "__device__ ", qualifiers, function)) 174 185 186 175 187 def mark_device_functions(source): 176 188 # type: (str) -> str … … 180 192 return FUNCTION_PATTERN.sub(_add_device_tag, source) 181 193 194 182 195 def show_device_functions(source): 183 196 # type: (str) -> str … … 186 199 """ 187 200 for match in FUNCTION_PATTERN.finditer(source): 188 print(match.group('qualifiers').replace('\n',r'\n'), match.group('function'), '(') 201 print(match.group('qualifiers').replace('\n', r'\n'), 202 match.group('function'), '(') 189 203 return source 204 190 205 191 206 def compile_model(source, dtype, fast=False): … … 212 227 #options = ['--verbose', '-E'] 213 228 options = ['--use_fast_math'] if fast else None 214 program = SourceModule(source, no_extern_c=True, options=options) # include_dirs=[...]229 program = SourceModule(source, no_extern_c=True, options=options) #, include_dirs=[...]) 215 230 216 231 #print("done with "+program) … … 218 233 219 234 220 # for now, this returns one device in the context221 # TODO: create a context that contains all devices on all platforms235 # For now, this returns one device in the context. 236 # TODO: Create a context that contains all devices on all platforms. 222 237 class GpuEnvironment(object): 223 238 """ 224 GPU context , with possibly many devices, and one queue per device.239 GPU context for CUDA. 225 240 """ 226 241 context = None # type: cuda.Context 227 242 def __init__(self, devnum=None): 228 243 # type: (int) -> None 229 # Byte boundary for data alignment230 #self.data_boundary = max(d.min_data_type_align_size231 # for d in self.context.devices)232 self.compiled = {}233 244 env = os.environ.get("SAS_OPENCL", "").lower() 234 245 if devnum is None and env.startswith("cuda:"): 235 246 devnum = int(env[5:]) 247 236 248 # Set the global context to the particular device number if one is 237 249 # given, otherwise use the default context. Perhaps this will be set … … 242 254 self.context = make_default_context() 243 255 256 ## Byte boundary for data alignment. 257 #self.data_boundary = max(d.min_data_type_align_size 258 # for d in self.context.devices) 259 260 # Cache for compiled programs, and for items in context. 261 self.compiled = {} 262 244 263 def release(self): 264 """Free the CUDA device associated with this context.""" 245 265 if self.context is not None: 246 266 self.context.pop() … … 262 282 Compile the program for the device in the given context. 263 283 """ 264 # Note: PyOpenCL caches based on md5 hash of source, options and device 265 # so we don't really need to cache things for ourselves. I'll do so 266 # anyway just to save some data munging time. 284 # Note: PyCuda (probably) caches but I'll do so as well just to 285 # save some data munging time. 267 286 tag = generate.tag_source(source) 268 287 key = "%s-%s-%s%s"%(name, dtype, tag, ("-fast" if fast else "")) 269 # Check timestamp on program 288 # Check timestamp on program. 270 289 program, program_timestamp = self.compiled.get(key, (None, np.inf)) 271 290 if program_timestamp < timestamp: … … 277 296 return program 278 297 298 279 299 class GpuModel(KernelModel): 280 300 """ … … 292 312 that the compiler is allowed to take shortcuts. 293 313 """ 294 info = None # type: ModelInfo295 source = "" # type: str296 dtype = None # type: np.dtype297 fast = False # type: bool298 program = None# type: SourceModule299 _kernels = None # type: List[cuda.Function]314 info = None # type: ModelInfo 315 source = "" # type: str 316 dtype = None # type: np.dtype 317 fast = False # type: bool 318 _program = None # type: SourceModule 319 _kernels = None # type: Dict[str, cuda.Function] 300 320 301 321 def __init__(self, source, model_info, dtype=generate.F32, fast=False): … … 305 325 self.dtype = dtype 306 326 self.fast = fast 307 self.program = None # delay program creation308 self._kernels = None309 327 310 328 def __getstate__(self): … … 315 333 # type: (Tuple[ModelInfo, str, np.dtype, bool]) -> None 316 334 self.info, self.source, self.dtype, self.fast = state 317 self. program= None335 self._program = self._kernels = None 318 336 319 337 def make_kernel(self, q_vectors): 320 338 # type: (List[np.ndarray]) -> "GpuKernel" 321 if self.program is None: 322 compile_program = environment().compile_program 323 timestamp = generate.ocl_timestamp(self.info) 324 self.program = compile_program( 325 self.info.name, 326 self.source['opencl'], 327 self.dtype, 328 self.fast, 329 timestamp) 330 variants = ['Iq', 'Iqxy', 'Imagnetic'] 331 names = [generate.kernel_name(self.info, k) for k in variants] 332 kernels = [self.program.get_function(k) for k in names] 333 self._kernels = dict((k, v) for k, v in zip(variants, kernels)) 334 is_2d = len(q_vectors) == 2 335 if is_2d: 336 kernel = [self._kernels['Iqxy'], self._kernels['Imagnetic']] 337 else: 338 kernel = [self._kernels['Iq']]*2 339 return GpuKernel(kernel, self.dtype, self.info, q_vectors) 340 341 def release(self): 342 # type: () -> None 343 """ 344 Free the resources associated with the model. 345 """ 346 if self.program is not None: 347 self.program = None 348 349 def __del__(self): 350 # type: () -> None 351 self.release() 352 353 # TODO: check that we don't need a destructor for buffers which go out of scope 339 return GpuKernel(self, q_vectors) 340 341 def get_function(self, name): 342 # type: (str) -> cuda.Function 343 """ 344 Fetch the kernel from the environment by name, compiling it if it 345 does not already exist. 346 """ 347 if self._program is None: 348 self._prepare_program() 349 return self._kernels[name] 350 351 def _prepare_program(self): 352 # type: (str) -> None 353 env = environment() 354 timestamp = generate.ocl_timestamp(self.info) 355 program = env.compile_program( 356 self.info.name, 357 self.source['opencl'], 358 self.dtype, 359 self.fast, 360 timestamp) 361 variants = ['Iq', 'Iqxy', 'Imagnetic'] 362 names = [generate.kernel_name(self.info, k) for k in variants] 363 functions = [program.get_function(k) for k in names] 364 self._kernels = {k: v for k, v in zip(variants, functions)} 365 # Keep a handle to program so GC doesn't collect. 366 self._program = program 367 368 369 # TODO: Check that we don't need a destructor for buffers which go out of scope. 354 370 class GpuInput(object): 355 371 """ … … 373 389 def __init__(self, q_vectors, dtype=generate.F32): 374 390 # type: (List[np.ndarray], np.dtype) -> None 375 # TODO: do we ever need double precision q?391 # TODO: Do we ever need double precision q? 376 392 self.nq = q_vectors[0].size 377 393 self.dtype = np.dtype(dtype) 378 394 self.is_2d = (len(q_vectors) == 2) 379 # TODO: stretch input based on get_warp()380 # not doing it now since warp depends on kernel, which is not known395 # TODO: Stretch input based on get_warp(). 396 # Not doing it now since warp depends on kernel, which is not known 381 397 # at this point, so instead using 32, which is good on the set of 382 398 # architectures tested so far. 383 399 if self.is_2d: 384 # Note: 16 rather than 15 because result is 1 longer than input. 385 width = ((self.nq+16)//16)*16 400 width = ((self.nq+15)//16)*16 386 401 self.q = np.empty((width, 2), dtype=dtype) 387 402 self.q[:self.nq, 0] = q_vectors[0] 388 403 self.q[:self.nq, 1] = q_vectors[1] 389 404 else: 390 # Note: 32 rather than 31 because result is 1 longer than input. 391 width = ((self.nq+32)//32)*32 405 width = ((self.nq+31)//32)*32 392 406 self.q = np.empty(width, dtype=dtype) 393 407 self.q[:self.nq] = q_vectors[0] 394 408 self.global_size = [self.q.shape[0]] 395 409 #print("creating inputs of size", self.global_size) 410 411 # Transfer input value to GPU. 396 412 self.q_b = cuda.to_device(self.q) 397 413 … … 399 415 # type: () -> None 400 416 """ 401 Free the memory.417 Free the buffer associated with the q value. 402 418 """ 403 419 if self.q_b is not None: … … 409 425 self.release() 410 426 427 411 428 class GpuKernel(Kernel): 412 429 """ 413 430 Callable SAS kernel. 414 431 415 *kernel* is the GpuKernel object to call 416 417 *model_info* is the module information 418 419 *q_vectors* is the q vectors at which the kernel should be evaluated 420 421 *dtype* is the kernel precision 422 423 The resulting call method takes the *pars*, a list of values for 424 the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight) 425 vectors for the polydisperse parameters. *cutoff* determines the 426 integration limits: any points with combined weight less than *cutoff* 427 will not be calculated. 432 *model* is the GpuModel object to call 433 434 The kernel is derived from :class:`Kernel`, providing the 435 :meth:`call_kernel` method to evaluate the kernel for a given set of 436 parameters. Because of the need to move the q values to the GPU before 437 evaluation, the kernel is instantiated for a particular set of q vectors, 438 and can be called many times without transfering q each time. 428 439 429 440 Call :meth:`release` when done with the kernel instance. 430 441 """ 431 def __init__(self, kernel, dtype, model_info, q_vectors): 432 # type: (cl.Kernel, np.dtype, ModelInfo, List[np.ndarray]) -> None 442 #: SAS model information structure. 443 info = None # type: ModelInfo 444 #: Kernel precision. 445 dtype = None # type: np.dtype 446 #: Kernel dimensions (1d or 2d). 447 dim = "" # type: str 448 #: Calculation results, updated after each call to :meth:`_call_kernel`. 449 result = None # type: np.ndarray 450 451 def __init__(self, model, q_vectors): 452 # type: (GpuModel, List[np.ndarray]) -> None 453 dtype = model.dtype 433 454 self.q_input = GpuInput(q_vectors, dtype) 434 self.kernel = kernel 435 # F16 isn't sufficient, so don't support it 455 self._model = model 456 457 # Attributes accessed from the outside. 458 self.dim = '2d' if self.q_input.is_2d else '1d' 459 self.info = model.info 460 self.dtype = dtype 461 462 # Converter to translate input to target type. 436 463 self._as_dtype = np.float64 if dtype == generate.F64 else np.float32 437 464 438 # attributes accessed from the outside 439 self.dim = '2d' if self.q_input.is_2d else '1d' 440 self.info = model_info 441 self.dtype = dtype 442 443 # holding place for the returned value 465 # Holding place for the returned value. 444 466 nout = 2 if self.info.have_Fq and self.dim == '1d' else 1 445 extra_q = 4 # total weight, form volume, shell volume and R_eff 446 self.result = np.empty(self.q_input.nq*nout+extra_q, dtype) 447 448 # Inputs and outputs for each kernel call 449 # Note: res may be shorter than res_b if global_size != nq 467 extra_q = 4 # Total weight, form volume, shell volume and R_eff. 468 self.result = np.empty(self.q_input.nq*nout + extra_q, dtype) 469 470 # Allocate result value on GPU. 450 471 width = ((self.result.size+31)//32)*32 * self.dtype.itemsize 451 self.result_b = cuda.mem_alloc(width) 452 self._need_release = [self.result_b] 453 454 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 455 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 456 # Arrange data transfer to card 472 self._result_b = cuda.mem_alloc(width) 473 474 def _call_kernel(self, call_details, values, cutoff, magnetic, 475 radius_effective_mode): 476 # type: (CallDetails, np.ndarray, float, bool, int) -> np.ndarray 477 478 # Arrange data transfer to card. 457 479 details_b = cuda.to_device(call_details.buffer) 458 480 values_b = cuda.to_device(values) 459 481 460 kernel = self.kernel[1 if magnetic else 0] 461 args = [ 462 np.uint32(self.q_input.nq), None, None, 463 details_b, values_b, self.q_input.q_b, self.result_b, 464 self._as_dtype(cutoff), 465 np.uint32(effective_radius_type), 482 # Setup kernel function and arguments. 483 name = 'Iq' if self.dim == '1d' else 'Imagnetic' if magnetic else 'Iqxy' 484 kernel = self._model.get_function(name) 485 kernel_args = [ 486 np.uint32(self.q_input.nq), # Number of inputs. 487 None, # Placeholder for pd_start. 488 None, # Placeholder for pd_stop. 489 details_b, # Problem definition. 490 values_b, # Parameter values. 491 self.q_input.q_b, # Q values. 492 self._result_b, # Result storage. 493 self._as_dtype(cutoff), # Probability cutoff. 494 np.uint32(radius_effective_mode), # R_eff mode. 466 495 ] 467 496 grid = partition(self.q_input.nq) 468 #print("Calling OpenCL") 497 498 # Call kernel and retrieve results. 499 #print("Calling CUDA") 469 500 #call_details.show(values) 470 # Call kernel and retrieve results471 501 last_nap = time.clock() 472 502 step = 100000000//self.q_input.nq + 1 … … 475 505 stop = min(start + step, call_details.num_eval) 476 506 #print("queuing",start,stop) 477 args[1:3] = [np.int32(start), np.int32(stop)]478 kernel(* args, **grid)507 kernel_args[1:3] = [np.int32(start), np.int32(stop)] 508 kernel(*kernel_args, **grid) 479 509 if stop < call_details.num_eval: 480 510 sync() 481 # Allow other processes to run 511 # Allow other processes to run. 482 512 current_time = time.clock() 483 513 if current_time - last_nap > 0.5: … … 485 515 last_nap = current_time 486 516 sync() 487 cuda.memcpy_dtoh(self.result, self. result_b)517 cuda.memcpy_dtoh(self.result, self._result_b) 488 518 #print("result", self.result) 489 519 … … 496 526 Release resources associated with the kernel. 497 527 """ 498 for p in self._need_release: 499 p.free() 500 self._need_release = [] 528 self.q_input.release() 529 if self._result_b is not None: 530 self._result_b.free() 531 self._result_b = None 501 532 502 533 def __del__(self): … … 512 543 Note: Maybe context.synchronize() is sufficient. 513 544 """ 514 #return # The following works in C++; don't know what pycuda is doing 515 # Create an event with which to synchronize 545 # Create an event with which to synchronize. 516 546 done = cuda.Event() 517 547 … … 519 549 done.record() 520 550 521 # line added to not hog resources551 # Make sure we don't hog resource while waiting to sync. 522 552 while not done.query(): 523 553 time.sleep(0.01) … … 525 555 # Block until the GPU executes the kernel. 526 556 done.synchronize() 557 527 558 # Clean up the event; I don't think they can be reused. 528 559 del done -
sasmodels/kerneldll.py
re44432d ra34b811 100 100 # pylint: enable=unused-import 101 101 102 # Compiler output is a byte stream that needs to be decode in python 3 102 # Compiler output is a byte stream that needs to be decode in python 3. 103 103 decode = (lambda s: s) if sys.version_info[0] < 3 else (lambda s: s.decode('utf8')) 104 104 … … 115 115 COMPILER = "tinycc" 116 116 elif "VCINSTALLDIR" in os.environ: 117 # If vcvarsall.bat has been called, then VCINSTALLDIR is in the environment 118 # and we can use the MSVC compiler. Otherwise, if tinycc is available 119 # the use it. Otherwise, hope that mingw is available. 117 # If vcvarsall.bat has been called, then VCINSTALLDIR is in the 118 # environment and we can use the MSVC compiler. Otherwise, if 119 # tinycc is available then use it. Otherwise, hope that mingw 120 # is available. 120 121 COMPILER = "msvc" 121 122 else: … … 124 125 COMPILER = "unix" 125 126 126 ARCH = "" if ct.sizeof(ct.c_void_p) > 4 else "x86" # 4 byte pointers on x86 127 ARCH = "" if ct.sizeof(ct.c_void_p) > 4 else "x86" # 4 byte pointers on x86. 127 128 if COMPILER == "unix": 128 # Generic unix compile 129 # On mac users will need the X code command line tools installed129 # Generic unix compile. 130 # On Mac users will need the X code command line tools installed. 130 131 #COMPILE = "gcc-mp-4.7 -shared -fPIC -std=c99 -fopenmp -O2 -Wall %s -o %s -lm -lgomp" 131 132 CC = "cc -shared -fPIC -std=c99 -O2 -Wall".split() 132 # add openmp support if not running on a mac133 # Add OpenMP support if not running on a Mac. 133 134 if sys.platform != "darwin": 134 # OpenMP seems to be broken on gcc 5.4.0 (ubuntu 16.04.9) 135 # OpenMP seems to be broken on gcc 5.4.0 (ubuntu 16.04.9). 135 136 # Shut it off for all unix until we can investigate. 136 137 #CC.append("-fopenmp") … … 144 145 # vcomp90.dll on the path. One may be found here: 145 146 # C:/Windows/winsxs/x86_microsoft.vc90.openmp*/vcomp90.dll 146 # Copy this to the python directory and uncomment the OpenMP COMPILE 147 # TODO: remove intermediate OBJ file created in the directory148 # TODO: maybe don't use randomized name for the c file149 # TODO: maybe ask distutils to find MSVC147 # Copy this to the python directory and uncomment the OpenMP COMPILE. 148 # TODO: Remove intermediate OBJ file created in the directory. 149 # TODO: Maybe don't use randomized name for the c file. 150 # TODO: Maybe ask distutils to find MSVC. 150 151 CC = "cl /nologo /Ox /MD /W3 /GS- /DNDEBUG".split() 151 152 if "SAS_OPENMP" in os.environ: … … 172 173 ALLOW_SINGLE_PRECISION_DLLS = True 173 174 174 def compile(source, output): 175 176 def compile_model(source, output): 175 177 # type: (str, str) -> None 176 178 """ … … 183 185 logging.info(command_str) 184 186 try: 185 # need shell=True on windows to keep console box from popping up187 # Need shell=True on windows to keep console box from popping up. 186 188 shell = (os.name == 'nt') 187 189 subprocess.check_output(command, shell=shell, stderr=subprocess.STDOUT) … … 192 194 raise RuntimeError("compile failed. File is in %r"%source) 193 195 196 194 197 def dll_name(model_info, dtype): 195 198 # type: (ModelInfo, np.dtype) -> str … … 202 205 basename += ARCH + ".so" 203 206 204 # Hack to find precompiled dlls 207 # Hack to find precompiled dlls. 205 208 path = joinpath(generate.DATA_PATH, '..', 'compiled_models', basename) 206 209 if os.path.exists(path): … … 242 245 raise ValueError("16 bit floats not supported") 243 246 if dtype == F32 and not ALLOW_SINGLE_PRECISION_DLLS: 244 dtype = F64 # Force 64-bit dll 245 # Note: dtype may be F128 for long double precision 247 dtype = F64 # Force 64-bit dll. 248 # Note: dtype may be F128 for long double precision. 246 249 247 250 dll = dll_path(model_info, dtype) … … 254 257 need_recompile = dll_time < newest_source 255 258 if need_recompile: 256 # Make sure the DLL path exists 259 # Make sure the DLL path exists. 257 260 if not os.path.exists(SAS_DLL_PATH): 258 261 os.makedirs(SAS_DLL_PATH) … … 262 265 with os.fdopen(system_fd, "w") as file_handle: 263 266 file_handle.write(source) 264 compile (source=filename, output=dll)265 # comment the following to keep the generated c file266 # Note: if there is a syntax error then compile raises an error267 compile_model(source=filename, output=dll) 268 # Comment the following to keep the generated C file. 269 # Note: If there is a syntax error then compile raises an error 267 270 # and the source file will not be deleted. 268 271 os.unlink(filename) … … 303 306 self.dllpath = dllpath 304 307 self._dll = None # type: ct.CDLL 305 self._kernels = None # type: List[Callable, Callable]308 self._kernels = None # type: List[Callable, Callable] 306 309 self.dtype = np.dtype(dtype) 307 310 … … 338 341 # type: (List[np.ndarray]) -> DllKernel 339 342 q_input = PyInput(q_vectors, self.dtype) 340 # Note: pickle not supported for DllKernel343 # Note: DLL is lazy loaded. 341 344 if self._dll is None: 342 345 self._load_dll() … … 358 361 self._dll = None 359 362 363 360 364 class DllKernel(Kernel): 361 365 """ … … 379 383 def __init__(self, kernel, model_info, q_input): 380 384 # type: (Callable[[], np.ndarray], ModelInfo, PyInput) -> None 381 #,model_info,q_input) 385 dtype = q_input.dtype 386 self.q_input = q_input 382 387 self.kernel = kernel 388 389 # Attributes accessed from the outside. 390 self.dim = '2d' if q_input.is_2d else '1d' 383 391 self.info = model_info 384 self.q_input = q_input 385 self.dtype = q_input.dtype 386 self.dim = '2d' if q_input.is_2d else '1d' 387 # leave room for f1/f2 results in case we need to compute beta for 1d models 392 self.dtype = dtype 393 394 # Converter to translate input to target type. 395 self._as_dtype = (np.float32 if dtype == generate.F32 396 else np.float64 if dtype == generate.F64 397 else np.float128) 398 399 # Holding place for the returned value. 388 400 nout = 2 if self.info.have_Fq else 1 389 # +4 for total weight, shell volume, effective radius, form volume390 self.result = np.empty( q_input.nq*nout + 4, self.dtype)391 self.real = (np.float32 if self.q_input.dtype == generate.F32 392 else np.float64 if self.q_input.dtype == generate.F64393 else np.float128)394 395 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 396 # type: (CallDetails, np.ndarray, np.ndarray, float, bool, int) -> np.ndarray401 extra_q = 4 # Total weight, form volume, shell volume and R_eff. 402 self.result = np.empty(self.q_input.nq*nout + extra_q, dtype) 403 404 def _call_kernel(self, call_details, values, cutoff, magnetic, 405 radius_effective_mode): 406 # type: (CallDetails, np.ndarray, float, bool, int) -> np.ndarray 407 408 # Setup kernel function and arguments. 397 409 kernel = self.kernel[1 if magnetic else 0] 398 args = [399 self.q_input.nq, # nq400 None, # pd_start401 None, # pd_stop pd_stride[MAX_PD]402 call_details.buffer.ctypes.data, # problem403 values.ctypes.data, # pars404 self.q_input.q.ctypes.data, # q405 self.result.ctypes.data, # results406 self. real(cutoff), # cutoff407 effective_radius_type, # cutoff410 kernel_args = [ 411 self.q_input.nq, # Number of inputs. 412 None, # Placeholder for pd_start. 413 None, # Placeholder for pd_stop. 414 call_details.buffer.ctypes.data, # Problem definition. 415 values.ctypes.data, # Parameter values. 416 self.q_input.q.ctypes.data, # Q values. 417 self.result.ctypes.data, # Result storage. 418 self._as_dtype(cutoff), # Probability cutoff. 419 radius_effective_mode, # R_eff mode. 408 420 ] 421 422 # Call kernel and retrieve results. 409 423 #print("Calling DLL") 410 424 #call_details.show(values) 411 425 step = 100 426 # TODO: Do we need the explicit sleep like the OpenCL and CUDA loops? 412 427 for start in range(0, call_details.num_eval, step): 413 428 stop = min(start + step, call_details.num_eval) 414 args[1:3] = [start, stop]415 kernel(* args) # type: ignore429 kernel_args[1:3] = [start, stop] 430 kernel(*kernel_args) # type: ignore 416 431 417 432 def release(self): 418 433 # type: () -> None 419 434 """ 420 Release anyresources associated with the kernel.435 Release resources associated with the kernel. 421 436 """ 422 self.q_input.release() 437 # TODO: OpenCL/CUDA allocate q_input in __init__ and free it in release. 438 # Should we be doing the same for DLL? 439 #self.q_input.release() 440 pass 441 442 def __del__(self): 443 # type: () -> None 444 self.release() -
sasmodels/kernelpy.py
raa8c6e0 ra34b811 16 16 from numpy import cbrt 17 17 except ImportError: 18 def cbrt(x): return x ** (1.0/3.0) 18 def cbrt(x): 19 """Return cubed root of x.""" 20 return x ** (1.0/3.0) 19 21 20 22 from .generate import F64 … … 33 35 logger = logging.getLogger(__name__) 34 36 37 35 38 class PyModel(KernelModel): 36 39 """ … … 38 41 """ 39 42 def __init__(self, model_info): 40 # Make sure Iq is available and vectorized 43 # Make sure Iq is available and vectorized. 41 44 _create_default_functions(model_info) 42 45 self.info = model_info 43 46 self.dtype = np.dtype('d') 44 logger.info("make python model " +self.info.name)47 logger.info("make python model %s", self.info.name) 45 48 46 49 def make_kernel(self, q_vectors): 50 """Instantiate the python kernel with input *q_vectors*""" 47 51 q_input = PyInput(q_vectors, dtype=F64) 48 52 return PyKernel(self.info, q_input) … … 53 57 """ 54 58 pass 59 55 60 56 61 class PyInput(object): … … 91 96 self.q = None 92 97 98 93 99 class PyKernel(Kernel): 94 100 """ … … 131 137 parameter_vector = np.empty(len(partable.call_parameters)-2, 'd') 132 138 133 # Create views into the array to hold the arguments 139 # Create views into the array to hold the arguments. 134 140 offset = 0 135 141 kernel_args, volume_args = [], [] … … 166 172 volume = model_info.form_volume 167 173 shell = model_info.shell_volume 168 radius = model_info. effective_radius174 radius = model_info.radius_effective 169 175 self._volume = ((lambda: (shell(*volume_args), volume(*volume_args))) if shell and volume 170 176 else (lambda: [volume(*volume_args)]*2) if volume … … 174 180 else (lambda mode: 1.0)) 175 181 176 177 178 def _call_kernel(self, call_details, values, cutoff, magnetic, effective_radius_type): 182 def _call_kernel(self, call_details, values, cutoff, magnetic, radius_effective_mode): 179 183 # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray 180 184 if magnetic: … … 182 186 #print("Calling python kernel") 183 187 #call_details.show(values) 184 radius = ((lambda: 0.0) if effective_radius_type == 0185 else (lambda: self._radius( effective_radius_type)))188 radius = ((lambda: 0.0) if radius_effective_mode == 0 189 else (lambda: self._radius(radius_effective_mode))) 186 190 self.result = _loops( 187 191 self._parameter_vector, self._form, self._volume, radius, … … 195 199 self.q_input.release() 196 200 self.q_input = None 201 197 202 198 203 def _loops(parameters, # type: np.ndarray … … 254 259 total = np.zeros(nq, 'd') 255 260 for loop_index in range(call_details.num_eval): 256 # update polydispersity parameter values261 # Update polydispersity parameter values. 257 262 if p0_index == p0_length: 258 263 pd_index = (loop_index//pd_stride)%pd_length … … 265 270 p0_index += 1 266 271 if weight > cutoff: 267 # Call the scattering function 272 # Call the scattering function. 268 273 # Assume that NaNs are only generated if the parameters are bad; 269 274 # exclude all q for that NaN. Even better would be to have an … … 273 278 continue 274 279 275 # update value and norm280 # Update value and norm. 276 281 total += weight * Iq 277 282 weight_norm += weight … … 293 298 any functions that are not already marked as vectorized. 294 299 """ 295 # Note: must call create_vector_Iq before create_vector_Iqxy300 # Note: Must call create_vector_Iq before create_vector_Iqxy. 296 301 _create_vector_Iq(model_info) 297 302 _create_vector_Iqxy(model_info) -
sasmodels/list_pars.py
r2d81cfe rb297ba9 16 16 from .compare import columnize 17 17 18 def find_pars( type=None):18 def find_pars(kind=None): 19 19 """ 20 20 Find all parameters in all models. … … 26 26 model_info = load_model_info(name) 27 27 for p in model_info.parameters.kernel_parameters: 28 if type is None or p.type == type:28 if kind is None or p.type == kind: 29 29 partable.setdefault(p.name, []) 30 30 partable[p.name].append(name) 31 31 return partable 32 32 33 def list_pars(names_only=True, type=None):33 def list_pars(names_only=True, kind=None): 34 34 """ 35 35 Print all parameters in all models. … … 38 38 occurs in. 39 39 """ 40 partable = find_pars( type)40 partable = find_pars(kind) 41 41 if names_only: 42 42 print(columnize(list(sorted(partable.keys())))) … … 57 57 help="list models which use this argument") 58 58 parser.add_argument( 59 ' type', default="any", nargs='?',59 'kind', default="any", nargs='?', 60 60 metavar="volume|orientation|sld|none|any", 61 61 choices=['volume', 'orientation', 'sld', None, 'any'], 62 type=lambda v: None if v == 'any' else ''if v == 'none' else v,63 help="only list arguments of the given type")62 type=lambda v: None if v == 'any' else None if v == 'none' else v, 63 help="only list arguments of the given kind") 64 64 args = parser.parse_args() 65 65 66 list_pars(names_only=not args.verbose, type=args.type)66 list_pars(names_only=not args.verbose, kind=args.kind) 67 67 68 68 if __name__ == "__main__": -
sasmodels/mixture.py
r39a06c9 rb297ba9 120 120 121 121 def random(): 122 """Random set of model parameters for mixture model""" 122 123 combined_pars = {} 123 124 for k, part in enumerate(parts): … … 149 150 150 151 class MixtureModel(KernelModel): 152 """ 153 Model definition for mixture of models. 154 """ 151 155 def __init__(self, model_info, parts): 152 156 # type: (ModelInfo, List[KernelModel]) -> None … … 165 169 kernels = [part.make_kernel(q_vectors) for part in self.parts] 166 170 return MixtureKernel(self.info, kernels) 171 make_kernel.__doc__ = KernelModel.make_kernel.__doc__ 167 172 168 173 def release(self): 169 174 # type: () -> None 170 """ 171 Free resources associated with the model. 172 """ 175 """Free resources associated with the model.""" 173 176 for part in self.parts: 174 177 part.release() 178 release.__doc__ = KernelModel.release.__doc__ 175 179 176 180 177 181 class MixtureKernel(Kernel): 182 """ 183 Instantiated kernel for mixture of models. 184 """ 178 185 def __init__(self, model_info, kernels): 179 186 # type: (ModelInfo, List[Kernel]) -> None … … 185 192 self.results = [] # type: List[np.ndarray] 186 193 187 def __call__(self, call_details, values, cutoff, magnetic):194 def Iq(self, call_details, values, cutoff, magnetic): 188 195 # type: (CallDetails, np.ndarray, np.ndarry, float, bool) -> np.ndarray 189 196 scale, background = values[0:2] … … 191 198 # remember the parts for plotting later 192 199 self.results = [] # type: List[np.ndarray] 193 parts = MixtureParts(self.info, self.kernels, call_details, values)200 parts = _MixtureParts(self.info, self.kernels, call_details, values) 194 201 for kernel, kernel_details, kernel_values in parts: 195 202 #print("calling kernel", kernel.info.name) … … 208 215 return scale*total + background 209 216 217 Iq.__doc__ = Kernel.Iq.__doc__ 218 __call__ = Iq 219 210 220 def release(self): 211 221 # type: () -> None 222 """Free resources associated with the kernel.""" 212 223 for k in self.kernels: 213 224 k.release() 214 225 215 226 216 class MixtureParts(object): 227 # Note: _MixtureParts doesn't implement iteration correctly, and only allows 228 # a single iterator to be active at once. It doesn't matter in this case 229 # since _MixtureParts is only used in one place, but it is not clean style. 230 class _MixtureParts(object): 231 """ 232 Mixture component iterator. 233 """ 217 234 def __init__(self, model_info, kernels, call_details, values): 218 235 # type: (ModelInfo, List[Kernel], CallDetails, np.ndarray) -> None … … 223 240 self.values = values 224 241 self.spin_index = model_info.parameters.npars + 2 242 # The following are redefined by __iter__, but set them here so that 243 # lint complains a little less. 244 self.part_num = -1 245 self.par_index = -1 246 self.mag_index = -1 225 247 #call_details.show(values) 226 248 -
sasmodels/model_test.py
r5024a56 r8795b6f 5 5 Usage:: 6 6 7 python -m sasmodels.model_test [opencl|cuda|dll] model1 model2 ... 8 9 if model1 is 'all', then all except the remaining models will be tested 7 python -m sasmodels.model_test [opencl|cuda|dll|all] model1 model2 ... 8 9 If model1 is 'all', then all except the remaining models will be tested. 10 Subgroups are also possible, such as 'py', 'single' or '1d'. See 11 :func:`core.list_models` for details. 10 12 11 13 Each model is tested using the default parameters at q=0.1, (qx, qy)=(0.1, 0.1), … … 45 47 from __future__ import print_function 46 48 49 import argparse 47 50 import sys 48 51 import unittest … … 58 61 import numpy as np # type: ignore 59 62 60 from . import core61 63 from .core import list_models, load_model_info, build_model 62 64 from .direct_model import call_kernel, call_Fq … … 89 91 suite = unittest.TestSuite() 90 92 91 if models[0] in core.KINDS: 93 try: 94 # See if the first model parses as a model group 95 group = list_models(models[0]) 92 96 skip = models[1:] 93 models = list_models(models[0])94 e lse:97 models = group 98 except Exception: 95 99 skip = [] 96 100 for model_name in models: … … 132 136 test_method_name = "test_%s_python" % model_info.id 133 137 test = ModelTestCase(test_name, model_info, 134 135 136 137 138 test_method_name, 139 platform="dll", # so that 140 dtype="double", 141 stash=stash) 138 142 suite.addTest(test) 139 143 else: # kernel implemented in C … … 144 148 test_method_name = "test_%s_dll" % model_info.id 145 149 test = ModelTestCase(test_name, model_info, 146 147 148 149 150 test_method_name, 151 platform="dll", 152 dtype="double", 153 stash=stash) 150 154 suite.addTest(test) 151 155 … … 159 163 # presence of *single=False* in the model file. 160 164 test = ModelTestCase(test_name, model_info, 161 162 163 165 test_method_name, 166 platform="ocl", dtype=None, 167 stash=stash) 164 168 #print("defining", test_name) 165 169 suite.addTest(test) … … 167 171 # test using cuda if desired and available 168 172 if 'cuda' in loaders and use_cuda(): 169 test_name = "%s-cuda" %model_name173 test_name = "%s-cuda" % model_info.id 170 174 test_method_name = "test_%s_cuda" % model_info.id 171 175 # Using dtype=None so that the models that are only … … 174 178 # presence of *single=False* in the model file. 175 179 test = ModelTestCase(test_name, model_info, 176 177 178 180 test_method_name, 181 platform="cuda", dtype=None, 182 stash=stash) 179 183 #print("defining", test_name) 180 184 suite.addTest(test) … … 236 240 s_name = pars.pop('@S') 237 241 ps_test = [pars] + list(test[1:]) 242 #print("PS TEST PARAMS!!!",ps_test) 238 243 # build the P@S model 239 244 s_info = load_model_info(s_name) … … 242 247 platform=self.platform) 243 248 # run the tests 249 #self.info = ps_model.info 250 #print("SELF.INFO PARAMS!!!",[p.id for p in self.info.parameters.call_parameters]) 251 #print("PS MODEL PARAMETERS:",[p.id for p in ps_model.info.parameters.call_parameters]) 244 252 results.append(self.run_one(ps_model, ps_test)) 245 253 … … 299 307 """Run a single test case.""" 300 308 user_pars, x, y = test[:3] 301 pars = expand_pars(self.info.parameters, user_pars) 302 invalid = invalid_pars(self.info.parameters, pars) 309 #print("PS MODEL PARAMETERS:",[p.id for p in model.info.parameters.call_parameters]) 310 pars = expand_pars(model.info.parameters, user_pars) 311 invalid = invalid_pars(model.info.parameters, pars) 303 312 if invalid: 304 313 raise ValueError("Unknown parameters in test: " + ", ".join(invalid)) … … 324 333 else: 325 334 y1 = y 326 y2 = test[3] if notisinstance(test[3], list) else [test[3]]327 F 1, F2, R_eff, volume, volume_ratio = call_Fq(kernel, pars)328 if F 1 is not None: # F1is none for models with Iq instead of Fq329 self._check_vectors(x, y1, F 1, 'F')330 self._check_vectors(x, y2, F 2, 'F^2')335 y2 = test[3] if isinstance(test[3], list) else [test[3]] 336 F, Fsq, R_eff, volume, volume_ratio = call_Fq(kernel, pars) 337 if F is not None: # F is none for models with Iq instead of Fq 338 self._check_vectors(x, y1, F, 'F') 339 self._check_vectors(x, y2, Fsq, 'F^2') 331 340 self._check_scalar(test[4], R_eff, 'R_eff') 332 341 self._check_scalar(test[5], volume, 'volume') 333 342 self._check_scalar(test[6], volume_ratio, 'form:shell ratio') 334 return F 2343 return Fsq 335 344 336 345 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)) 346 self.assertTrue(is_near(target, actual, 5), 347 '%s: expected:%s; actual:%s' 348 % (name, target, actual)) 352 349 353 350 def _check_vectors(self, x, target, actual, name='I'): … … 359 356 '%s(...) returned wrong length'%name) 360 357 for xi, yi, actual_yi in zip(x, target, actual): 361 if yi is None: 362 # smoke test --- make sure it runs and produces a value 363 self.assertTrue(not np.isnan(actual_yi), 364 'invalid %s(%s): %s' % (name, xi, actual_yi)) 365 elif np.isnan(yi): 366 # make sure nans match 367 self.assertTrue(np.isnan(actual_yi), 368 '%s(%s): expected:%s; actual:%s' 369 % (name, xi, yi, actual_yi)) 370 else: 371 # is_near does not work for infinite values, so also test 372 # for exact values. 373 self.assertTrue(yi == actual_yi or is_near(yi, actual_yi, 5), 374 '%s(%s); expected:%s; actual:%s' 375 % (name, xi, yi, actual_yi)) 358 self.assertTrue(is_near(yi, actual_yi, 5), 359 '%s(%s): expected:%s; actual:%s' 360 % (name, xi, target, actual)) 376 361 377 362 return ModelTestCase … … 385 370 invalid = [] 386 371 for par in sorted(pars.keys()): 387 # special handling of R_eff mode, which is not a usual parameter 372 # Ignore the R_eff mode parameter when checking for valid parameters. 373 # It is an allowed parameter for a model even though it does not exist 374 # in the parameter table. The call_Fq() function pops it from the 375 # parameter list and sends it directly to kernel.Fq(). 388 376 if par == product.RADIUS_MODE_ID: 389 377 continue … … 401 389 """ 402 390 Returns true if *actual* is within *digits* significant digits of *target*. 403 """ 404 import math 405 shift = 10**math.ceil(math.log10(abs(target))) 406 return abs(target-actual)/shift < 1.5*10**-digits 391 392 *taget* zero and inf should match *actual* zero and inf. If you want to 393 accept eps for zero, choose a value such as 1e-10, which must match up to 394 +/- 1e-15 when *digits* is the default value of 5. 395 396 If *target* is None, then just make sure that *actual* is not NaN. 397 398 If *target* is NaN, make sure *actual* is NaN. 399 """ 400 if target is None: 401 # target is None => actual cannot be NaN 402 return not np.isnan(actual) 403 elif target == 0.: 404 # target is 0. => actual must be 0. 405 # Note: if small values are allowed, then use maybe test zero against eps instead? 406 return actual == 0. 407 elif np.isfinite(target): 408 shift = np.ceil(np.log10(abs(target))) 409 return abs(target-actual) < 1.5*10**(shift-digits) 410 elif target == actual: 411 # target is inf => actual must be inf of same sign 412 return True 413 else: 414 # target is NaN => actual must be NaN 415 return np.isnan(target) == np.isnan(actual) 407 416 408 417 # CRUFT: old interface; should be deprecated and removed 409 418 def run_one(model_name): 419 # type: (str) -> str 420 """ 421 [Deprecated] Run the tests associated with *model_name*. 422 423 Use the following instead:: 424 425 succss, output = check_model(load_model_info(model_name)) 426 """ 410 427 # msg = "use check_model(model_info) rather than run_one(model_name)" 411 428 # warnings.warn(msg, category=DeprecationWarning, stacklevel=2) … … 416 433 return output 417 434 418 success, output = check_model(model_info)435 _, output = check_model(model_info) 419 436 return output 420 437 421 438 def check_model(model_info): 422 # type: (ModelInfo) -> str439 # type: (ModelInfo) -> Tuple[bool, str] 423 440 """ 424 441 Run the tests for a single model, capturing the output. … … 474 491 475 492 476 def main(*models):477 # type: (*str) -> int478 """479 Run tests given is models.480 481 Returns 0 if success or 1 if any tests fail.482 """483 try:484 from xmlrunner import XMLTestRunner as TestRunner485 test_args = {'output': 'logs'}486 except ImportError:487 from unittest import TextTestRunner as TestRunner488 test_args = {}489 490 if models and models[0] == '-v':491 verbosity = 2492 models = models[1:]493 else:494 verbosity = 1495 if models and models[0] == 'opencl':496 if not use_opencl():497 print("opencl is not available")498 return 1499 loaders = ['opencl']500 models = models[1:]501 elif models and models[0] == 'cuda':502 if not use_cuda():503 print("cuda is not available")504 return 1505 loaders = ['cuda']506 models = models[1:]507 elif models and models[0] == 'dll':508 # TODO: test if compiler is available?509 loaders = ['dll']510 models = models[1:]511 else:512 loaders = ['dll']513 if use_opencl():514 loaders.append('opencl')515 if use_cuda():516 loaders.append('cuda')517 if not models:518 print("""\519 usage:520 python -m sasmodels.model_test [-v] [opencl|cuda|dll] model1 model2 ...521 522 If -v is included on the command line, then use verbose output.523 524 If no platform is specified, then models will be tested with dll, and525 if available, OpenCL and CUDA; the compute target is ignored for pure python models.526 527 If model1 is 'all', then all except the remaining models will be tested.528 529 """)530 531 return 1532 533 runner = TestRunner(verbosity=verbosity, **test_args)534 result = runner.run(make_suite(loaders, models))535 return 1 if result.failures or result.errors else 0536 537 538 493 def model_tests(): 539 494 # type: () -> Iterator[Callable[[], None]] … … 549 504 loaders.append('cuda') 550 505 tests = make_suite(loaders, ['all']) 551 def build_test(test):506 def _build_test(test): 552 507 # In order for nosetest to show the test name, wrap the test.run_all 553 508 # instance in function that takes the test name as a parameter which … … 567 522 568 523 for test in tests: 569 yield build_test(test) 524 yield _build_test(test) 525 526 527 def main(): 528 # type: (*str) -> int 529 """ 530 Run tests given is models. 531 532 Returns 0 if success or 1 if any tests fail. 533 """ 534 try: 535 from xmlrunner import XMLTestRunner as TestRunner 536 test_args = {'output': 'logs'} 537 except ImportError: 538 from unittest import TextTestRunner as TestRunner 539 test_args = {} 540 541 parser = argparse.ArgumentParser(description="Test SasModels Models") 542 parser.add_argument("-v", "--verbose", action="store_const", 543 default=1, const=2, help="Use verbose output") 544 parser.add_argument("-e", "--engine", default="all", 545 help="Engines on which to run the test. " 546 "Valid values are opencl, cuda, dll, and all. " 547 "Defaults to all if no value is given") 548 parser.add_argument("models", nargs="*", 549 help="The names of the models to be tested. " 550 "If the first model is 'all', then all but the listed " 551 "models will be tested. See core.list_models() for " 552 "names of other groups, such as 'py' or 'single'.") 553 opts = parser.parse_args() 554 555 if opts.engine == "opencl": 556 if not use_opencl(): 557 print("opencl is not available") 558 return 1 559 loaders = ['opencl'] 560 elif opts.engine == "dll": 561 loaders = ["dll"] 562 elif opts.engine == "cuda": 563 if not use_cuda(): 564 print("cuda is not available") 565 return 1 566 loaders = ['cuda'] 567 elif opts.engine == "all": 568 loaders = ['dll'] 569 if use_opencl(): 570 loaders.append('opencl') 571 if use_cuda(): 572 loaders.append('cuda') 573 else: 574 print("unknown engine " + opts.engine) 575 return 1 576 577 runner = TestRunner(verbosity=opts.verbose, **test_args) 578 result = runner.run(make_suite(loaders, opts.models)) 579 return 1 if result.failures or result.errors else 0 570 580 571 581 572 582 if __name__ == "__main__": 573 sys.exit(main( *sys.argv[1:]))583 sys.exit(main()) -
sasmodels/modelinfo.py
rc1799d3 ra34b811 265 265 other sld parameters. The volume parameters are used for calls 266 266 to form_volume within the kernel (required for volume normalization), 267 to shell_volume (for hollow shapes), and to effective_radius(for267 to shell_volume (for hollow shapes), and to radius_effective (for 268 268 structure factor interactions) respectively. 269 269 … … 290 290 example, might be used to set the value of a shape parameter. 291 291 292 These values are set by :func:`make_parameter_table` and 292 Control parameters are used for variant models such as :ref:`rpa` which 293 have different cases with different parameters, as well as models 294 like :ref:`spherical_sld` with its user defined number of shells. 295 The control parameter should appear in the parameter table along with the 296 parameters it is is controlling. For variant models, use *[CASES]* in 297 place of the parameter limits Within the parameter definition table, 298 with case names such as:: 299 300 CASES = ["diblock copolymer", "triblock copolymer", ...] 301 302 This should give *limits=[[case1, case2, ...]]*, but the model loader 303 translates it to *limits=[0, len(CASES)-1]*, and adds *choices=CASES* to 304 the :class:`Parameter` definition. Note that models can use a list of 305 cases as a parameter without it being a control parameter. Either way, 306 the parameter is sent to the model evaluator as *float(choice_num)*, 307 where choices are numbered from 0. :meth:`ModelInfo.get_hidden_parameters` 308 will determine which parameers to display. 309 310 The class contructor should not be called directly, but instead the 311 parameter table is built using :func:`make_parameter_table` and 293 312 :func:`parse_parameter` therein. 294 313 """ … … 822 841 info.structure_factor = getattr(kernel_module, 'structure_factor', False) 823 842 # TODO: find Fq by inspection 824 info. effective_radius_type = getattr(kernel_module, 'effective_radius_type', None)843 info.radius_effective_modes = getattr(kernel_module, 'radius_effective_modes', None) 825 844 info.have_Fq = getattr(kernel_module, 'have_Fq', False) 826 845 info.profile_axes = getattr(kernel_module, 'profile_axes', ['x', 'y']) … … 829 848 info.source = getattr(kernel_module, 'source', []) 830 849 info.c_code = getattr(kernel_module, 'c_code', None) 831 info. effective_radius = getattr(kernel_module, 'effective_radius', None)850 info.radius_effective = getattr(kernel_module, 'radius_effective', None) 832 851 # TODO: check the structure of the tests 833 852 info.tests = getattr(kernel_module, 'tests', []) … … 845 864 info.single = getattr(kernel_module, 'single', not callable(info.Iq)) 846 865 info.random = getattr(kernel_module, 'random', None) 847 848 # multiplicity info849 control_pars = [p.id for p in parameters.kernel_parameters if p.is_control]850 default_control = control_pars[0] if control_pars else None851 info.control = getattr(kernel_module, 'control', default_control)852 866 info.hidden = getattr(kernel_module, 'hidden', None) # type: ignore 867 868 # Set control flag for explicitly set parameters, e.g., in the RPA model. 869 control = getattr(kernel_module, 'control', None) 870 if control is not None: 871 parameters[control].is_control = True 853 872 854 873 if callable(info.Iq) and parameters.has_2d: … … 903 922 #: *sphere*hardsphere* or *cylinder+sphere*. 904 923 composition = None # type: Optional[Tuple[str, List[ModelInfo]]] 905 #: Name of the control parameter for a variant model such as :ref:`rpa`.906 #: The *control* parameter should appear in the parameter table, with907 #: limits defined as *[CASES]*, for case names such as908 #: *CASES = ["diblock copolymer", "triblock copolymer", ...]*.909 #: This should give *limits=[[case1, case2, ...]]*, but the910 #: model loader translates this to *limits=[0, len(CASES)-1]*, and adds911 #: *choices=CASES* to the :class:`Parameter` definition. Note that912 #: models can use a list of cases as a parameter without it being a913 #: control parameter. Either way, the parameter is sent to the model914 #: evaluator as *float(choice_num)*, where choices are numbered from 0.915 #: See also :attr:`hidden`.916 control = None # type: str917 924 #: Different variants require different parameters. In order to show 918 925 #: just the parameters needed for the variant selected by :attr:`control`, … … 954 961 #: List of options for computing the effective radius of the shape, 955 962 #: or None if the model is not usable as a form factor model. 956 effective_radius_type= None # type: List[str]963 radius_effective_modes = None # type: List[str] 957 964 #: List of C source files used to define the model. The source files 958 965 #: should define the *Iq* function, and possibly *Iqac* or *Iqabc* if the … … 978 985 #: C code, either defined as a string, or in the sources. 979 986 shell_volume = None # type: Union[None, str, Callable[[np.ndarray], float]] 987 #: Computes the effective radius of the shape given the volume parameters. 988 #: Only needed for models defined in python that can be used for 989 #: monodisperse approximation for non-dilute solutions, P@S. The first 990 #: argument is the integer effective radius mode, with default 0. 991 radius_effective = None # type: Union[None, Callable[[int, np.ndarray], float]] 980 992 #: Returns *I(q, a, b, ...)* for parameters *a*, *b*, etc. defined 981 993 #: by the parameter table. *Iq* can be defined as a python function, or … … 989 1001 #: will return *I(q, a, b, ...)*. Multiplicity parameters are sent as 990 1002 #: pointers to doubles. Constants in floating point expressions should 991 #: include the decimal point. See :mod:`generate` for more details. 992 Iq = None # type: Union[None, str, Callable[[np.ndarray], np.ndarray]] 1003 #: include the decimal point. See :mod:`generate` for more details. If 1004 #: *have_Fq* is True, then Iq should return an interleaved array of 1005 #: $[\sum F(q_1), \sum F^2(q_1), \ldots, \sum F(q_n), \sum F^2(q_n)]$. 1006 Iq = None # type: Union[None, str, Callable[[...], np.ndarray]] 1007 #: Returns *I(qx, qy, a, b, ...)*. The interface follows :attr:`Iq`. 1008 Iqxy = None # type: Union[None, str, Callable[[...], np.ndarray]] 993 1009 #: Returns *I(qab, qc, a, b, ...)*. The interface follows :attr:`Iq`. 994 Iqac = None # type: Union[None, str, Callable[[ np.ndarray], np.ndarray]]1010 Iqac = None # type: Union[None, str, Callable[[...], np.ndarray]] 995 1011 #: Returns *I(qa, qb, qc, a, b, ...)*. The interface follows :attr:`Iq`. 996 Iqabc = None # type: Union[None, str, Callable[[ np.ndarray], np.ndarray]]1012 Iqabc = None # type: Union[None, str, Callable[[...], np.ndarray]] 997 1013 #: Returns *I(qx, qy, a, b, ...)*. The interface follows :attr:`Iq`. 998 Imagnetic = None # type: Union[None, str, Callable[[ np.ndarray], np.ndarray]]1014 Imagnetic = None # type: Union[None, str, Callable[[...], np.ndarray]] 999 1015 #: Returns a model profile curve *x, y*. If *profile* is defined, this 1000 1016 #: curve will appear in response to the *Show* button in SasView. Use -
sasmodels/models/_spherepy.py
r304c775 ra34b811 33 33 to the output of the software provided by the NIST (Kline, 2006). 34 34 35 36 35 References 37 36 ---------- … … 40 39 John Wiley and Sons, New York, (1955) 41 40 41 Source 42 ------ 43 44 `_spherepy.py <https://github.com/SasView/sasmodels/blob/master/sasmodels/models/_spherepy.py>`_ 45 `sphere.c <https://github.com/SasView/sasmodels/blob/master/sasmodels/models/sphere.c>`_ 46 47 Authorship and Verification 48 ---------------------------- 49 50 * **Author: P Kienzle** 51 * **Last Modified by:** 42 52 * **Last Reviewed by:** S King and P Parker **Date:** 2013/09/09 and 2014/01/06 53 * **Source added by :** Steve King **Date:** March 25, 2019 43 54 """ 44 55 … … 69 80 70 81 def form_volume(radius): 82 """Calculate volume for sphere""" 71 83 return 1.333333333333333 * pi * radius ** 3 72 84 73 def effective_radius(mode, radius): 74 return radius 85 def radius_effective(mode, radius): 86 """Calculate R_eff for sphere""" 87 return radius if mode else 0. 75 88 76 89 def Iq(q, sld, sld_solvent, radius): 90 """Calculate I(q) for sphere""" 77 91 #print "q",q 78 92 #print "sld,r",sld,sld_solvent,radius -
sasmodels/models/adsorbed_layer.py
r2d81cfe r0507e09 49 49 Layers*, *Macromol. Symp.*, 190 (2002) 33-42. 50 50 51 Source 52 ------ 53 54 `adsorbed_layer.py <https://github.com/SasView/sasmodels/blob/master/sasmodels/models/adsorbed_layer.py>`_ 55 51 56 Authorship and Verification 52 57 ---------------------------- … … 55 60 * **Last Modified by:** Paul Kienzle **Date:** April 14, 2016 56 61 * **Last Reviewed by:** Steve King **Date:** March 18, 2016 62 * **Source added by :** Steve King **Date:** March 25, 2019 57 63 """ 58 64 … … 87 93 def Iq(q, second_moment, adsorbed_amount, density_shell, radius, 88 94 volfraction, sld_shell, sld_solvent): 95 """Return I(q) for adsorbed layer model.""" 89 96 with errstate(divide='ignore'): 90 97 aa = ((sld_shell - sld_solvent)/density_shell * adsorbed_amount) / q … …