Changeset 02098e3 in sasview for src/sas/models
- Timestamp:
- Dec 8, 2015 11:09:13 AM (9 years ago)
- Branches:
- master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
- Children:
- 50a77df
- Parents:
- 12e5cc8 (diff), 1c2bf90 (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. - Location:
- src/sas/models
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/models/PeakGaussModel.py
rac7be54 rd430ee8 3 3 PeakGaussModel function as a BaseComponent model 4 4 """ 5 from __future__ import division 5 6 6 7 from sas.models.BaseComponent import BaseComponent -
src/sas/models/resolution.py
rbe0c318 r80ba1a2 11 11 MINIMUM_RESOLUTION = 1e-8 12 12 13 class Resolution1D(object): 13 14 # When extrapolating to -q, what is the minimum positive q relative to q_min 15 # that we wish to calculate? 16 MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION = 0.01 17 18 class Resolution(object): 14 19 """ 15 20 Abstract base class defining a 1D resolution function. … … 32 37 33 38 34 class Perfect1D(Resolution 1D):39 class Perfect1D(Resolution): 35 40 """ 36 41 Resolution function to use when there is no actual resolution smearing … … 45 50 46 51 47 class Pinhole1D(Resolution 1D):52 class Pinhole1D(Resolution): 48 53 r""" 49 54 Pinhole aperture with q-dependent gaussian resolution. … … 77 82 78 83 79 class Slit1D(Resolution 1D):84 class Slit1D(Resolution): 80 85 """ 81 86 Slit aperture with a complicated resolution function. … … 92 97 The *weight_matrix* is computed by :func:`slit1d_resolution` 93 98 """ 94 def __init__(self, q, width, height, q_calc=None): 95 # TODO: maybe issue warnings rather than raising errors 96 if not np.isscalar(width): 97 if np.any(np.diff(width) > 0.0): 98 raise ValueError("Slit resolution requires fixed width slits") 99 width = width[0] 100 if not np.isscalar(height): 101 if np.any(np.diff(height) > 0.0): 102 raise ValueError("Slit resolution requires fixed height slits") 103 height = height[0] 104 99 def __init__(self, q, width, height=0., q_calc=None): 105 100 # Remember what width/height was used even though we won't need them 106 101 # after the weight matrix is constructed 107 102 self.width, self.height = width, height 103 104 # Allow independent resolution on each point even though it is not 105 # needed in practice. 106 if np.isscalar(width): 107 width = np.ones(len(q))*width 108 else: 109 width = np.asarray(width) 110 if np.isscalar(height): 111 height = np.ones(len(q))*height 112 else: 113 height = np.asarray(height) 108 114 109 115 self.q = q.flatten() … … 147 153 148 154 149 def slit_resolution(q_calc, q, width, height ):155 def slit_resolution(q_calc, q, width, height, n_height=30): 150 156 r""" 151 157 Build a weight matrix to compute *I_s(q)* from *I(q_calc)*, given 152 $q_v$ = *width* and $q_h$ = *height*. 153 154 *width* and *height* are scalars since current instruments use the 155 same slit settings for all measurement points. 158 $q_\perp$ = *width* and $q_\parallel$ = *height*. *n_height* is 159 is the number of steps to use in the integration over $q_\parallel$ 160 when both $q_\perp$ and $q_\parallel$ are non-zero. 161 162 Each $q$ can have an independent width and height value even though 163 current instruments use the same slit setting for all measured points. 156 164 157 165 If slit height is large relative to width, use: … … 159 167 .. math:: 160 168 161 I_s(q_ o) = \frac{1}{\Delta q_v}162 \int_0^{\Delta q_ v} I(\sqrt{q_o^2 + u^2} du169 I_s(q_i) = \frac{1}{\Delta q_\perp} 170 \int_0^{\Delta q_\perp} I(\sqrt{q_i^2 + q_\perp^2} dq_\perp 163 171 164 172 If slit width is large relative to height, use: … … 166 174 .. math:: 167 175 168 I_s(q_o) = \frac{1}{2 \Delta q_v} 169 \int_{-\Delta q_v}^{\Delta q_v} I(u) du 170 """ 171 if width == 0.0 and height == 0.0: 172 #print "condition zero" 173 return 1 174 176 I_s(q_i) = \frac{1}{2 \Delta q_\parallel} 177 \int_{-\Delta q_\parallel}^{\Delta q_\parallel} 178 I(|q_i + q_\parallel|) dq_\parallel 179 180 For a mixture of slit width and height use: 181 182 .. math:: 183 184 I_s(q_i) = \frac{1}{2 \Delta q_\parallel \Delta q_\perp} 185 \int_{-\Delta q_\parallel)^{\Delta q_parallel} 186 \int_0^[\Delta q_\perp} 187 I(\sqrt{(q_i + q_\parallel)^2 + q_\perp^2}) 188 dq_\perp dq_\parallel 189 190 191 Algorithm 192 --------- 193 194 We are using the mid-point integration rule to assign weights to each 195 element of a weight matrix $W$ so that 196 197 .. math:: 198 199 I_s(q) = W I(q_\text{calc}) 200 201 If *q_calc* is at the mid-point, we can infer the bin edges from the 202 pairwise averages of *q_calc*, adding the missing edges before 203 *q_calc[0]* and after *q_calc[-1]*. 204 205 For $q_\parallel = 0$, the smeared value can be computed numerically 206 using the $u$ substitution 207 208 .. math:: 209 210 u_j = \sqrt{q_j^2 - q^2} 211 212 This gives 213 214 .. math:: 215 216 I_s(q) \approx \sum_j I(u_j) \Delta u_j 217 218 where $I(u_j)$ is the value at the mid-point, and $\Delta u_j$ is the 219 difference between consecutive edges which have been first converted 220 to $u$. Only $u_j \in [0, \Delta q_\perp]$ are used, which corresponds 221 to $q_j \in [q, \sqrt{q^2 + \Delta q_\perp}]$, so 222 223 .. math:: 224 225 W_{ij} = \frac{1}{\Delta q_\perp} \Delta u_j 226 = \frac{1}{\Delta q_\perp} 227 \sqrt{q_{j+1}^2 - q_i^2} - \sqrt{q_j^2 - q_i^2} 228 \text{if} q_j \in [q_i, \sqrt{q_i^2 + q_\perp^2}] 229 230 where $I_s(q_i)$ is the theory function being computed and $q_j$ are the 231 mid-points between the calculated values in *q_calc*. We tweak the 232 edges of the initial and final intervals so that they lie on integration 233 limits. 234 235 (To be precise, the transformed midpoint $u(q_j)$ is not necessarily the 236 midpoint of the edges $u((q_{j-1}+q_j)/2)$ and $u((q_j + q_{j+1})/2)$, 237 but it is at least in the interval, so the approximation is going to be 238 a little better than the left or right Riemann sum, and should be 239 good enough for our purposes.) 240 241 For $q_\perp = 0$, the $u$ substitution is simpler: 242 243 .. math:: 244 245 u_j = |q_j - q| 246 247 so 248 249 .. math:: 250 251 W_ij = \frac{1}{2 \Delta q_\parallel} \Delta u_j 252 = \frac{1}{2 \Delta q_\parallel} (q_{j+1} - q_j) 253 \text{if} q_j \in [q-\Delta q_\parallel, q+\Delta q_\parallel] 254 255 However, we need to support cases were $u_j < 0$, which means using 256 $2 (q_{j+1} - q_j)$ when $q_j \in [0, q_\parallel-q_i]$. This is not 257 an issue for $q_i > q_\parallel$. 258 259 For bot $q_\perp > 0$ and $q_\parallel > 0$ we perform a 2 dimensional 260 integration with 261 262 .. math:: 263 264 u_jk = \sqrt{q_j^2 - (q + (k\Delta q_\parallel/L))^2} 265 \text{for} k = -L \ldots L 266 267 for $L$ = *n_height*. This gives 268 269 .. math:: 270 271 W_{ij} = \frac{1}{2 \Delta q_\perp q_\parallel} 272 \sum_{k=-L}^L \Delta u_jk (\frac{\Delta q_\parallel}{2 L + 1} 273 274 275 """ 276 #np.set_printoptions(precision=6, linewidth=10000) 277 278 # The current algorithm is a midpoint rectangle rule. 175 279 q_edges = bin_edges(q_calc) # Note: requires q > 0 176 280 q_edges[q_edges<0.0] = 0.0 # clip edges below zero 177 178 #np.set_printoptions(linewidth=10000) 179 if width <= 100.0 * height or height == 0: 180 # The current algorithm is a midpoint rectangle rule. In the test case, 181 # neither trapezoid nor Simpson's rule improved the accuracy. 182 #print "condition h", q_edges.shape, q.shape, q_calc.shape 183 weights = np.zeros((len(q), len(q_calc)), 'd') 184 for i, qi in enumerate(q): 185 weights[i, :] = np.diff(q_to_u(q_edges, qi)) 186 weights /= width 187 weights = weights.T 188 else: 189 #print "condition w" 190 # Make q_calc into a row vector, and q into a column vector 191 q, q_calc = q[None,:], q_calc[:,None] 192 in_x = (q_calc >= q-width) * (q_calc <= q+width) 193 weights = np.diff(q_edges)[:,None] * in_x 194 281 weights = np.zeros((len(q), len(q_calc)), 'd') 282 283 #print q_calc 284 for i, (qi, w, h) in enumerate(zip(q, width, height)): 285 if w == 0. and h == 0.: 286 # Perfect resolution, so return the theory value directly. 287 # Note: assumes that q is a subset of q_calc. If qi need not be 288 # in q_calc, then we can do a weighted interpolation by looking 289 # up qi in q_calc, then weighting the result by the relative 290 # distance to the neighbouring points. 291 weights[i, :] = (q_calc == qi) 292 elif h == 0: 293 weights[i, :] = _q_perp_weights(q_edges, qi, w) 294 elif w == 0: 295 in_x = 1.0 * ((q_calc >= qi-h) & (q_calc <= qi+h)) 296 abs_x = 1.0*(q_calc < abs(qi - h)) if qi < h else 0. 297 #print qi - h, qi + h 298 #print in_x + abs_x 299 weights[i,:] = (in_x + abs_x) * np.diff(q_edges) / (2*h) 300 else: 301 L = n_height 302 for k in range(-L, L+1): 303 weights[i,:] += _q_perp_weights(q_edges, qi+k*h/L, w) 304 weights[i,:] /= 2*L + 1 305 306 return weights.T 307 308 309 def _q_perp_weights(q_edges, qi, w): 310 # Convert bin edges from q to u 311 u_limit = np.sqrt(qi**2 + w**2) 312 u_edges = q_edges**2 - qi**2 313 u_edges[q_edges < abs(qi)] = 0. 314 u_edges[q_edges > u_limit] = u_limit**2 - qi**2 315 weights = np.diff(np.sqrt(u_edges))/w 316 #print "i, qi",i,qi,qi+width 317 #print q_calc 318 #print weights 195 319 return weights 196 320 … … 212 336 function. 213 337 """ 214 height # keep lint happy215 q_min, q_max = np.min(q), np.max(np.sqrt(q**2 + width**2)) 338 q_min, q_max = np.min(q-height), np.max(np.sqrt((q+height)**2 + width**2)) 339 216 340 return geometric_extrapolation(q, q_min, q_max) 217 341 … … 233 357 ]) 234 358 return edges 235 236 237 def q_to_u(q, q0):238 """239 Convert *q* values to *u* values for the integral computed at *q0*.240 """241 # array([value])**2 - value**2 is not always zero242 qpsq = q**2 - q0**2243 qpsq[qpsq<0] = 0244 return sqrt(qpsq)245 359 246 360 … … 275 389 q = np.sort(q) 276 390 if q_min < q[0]: 277 if q_min <= 0: q_min = q [0]/10391 if q_min <= 0: q_min = q_min*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION 278 392 n_low = np.ceil((q[0]-q_min) / (q[1]-q[0])) if q[1]>q[0] else 15 279 393 q_low = np.linspace(q_min, q[0], n_low+1)[:-1] … … 297 411 *points_per_decade* sets the ratio between consecutive steps such 298 412 that there will be $n$ points used for every factor of 10 increase 299 in $q$.413 in *q*. 300 414 301 415 If *points_per_decade* is not given, it will be estimated as follows. … … 316 430 Substituting: 317 431 318 .. math::319 320 432 n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n) 321 / (\log q_n - \log q_1)433 / (\log q_n - log q_1) 322 434 """ 323 435 q = np.sort(q) … … 327 439 log_delta_q = log(10.) / points_per_decade 328 440 if q_min < q[0]: 329 if q_min < 0: q_min = q[0] /10441 if q_min < 0: q_min = q[0]*MIN_Q_SCALE_FOR_NEGATIVE_Q_EXTRAPOLATION 330 442 n_low = log_delta_q * (log(q[0])-log(q_min)) 331 443 q_low = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1] … … 359 471 360 472 361 def romberg_slit_1d(q, delta_qv, form, pars):473 def romberg_slit_1d(q, width, height, form, pars): 362 474 """ 363 475 Romberg integration for slit resolution. … … 366 478 that make it slow to evaluate but give it good accuracy. 367 479 """ 368 from scipy.integrate import romberg 480 from scipy.integrate import romberg, dblquad 369 481 370 482 if any(k not in form.info['defaults'] for k in pars.keys()): … … 374 486 (", ".join(sorted(extra)), ", ".join(sorted(keys)))) 375 487 376 _fn = lambda u, q0: eval_form(sqrt(q0**2 + u**2), form, pars) 377 r = [romberg(_fn, 0, delta_qv[0], args=(qi,), 378 divmax=100, vec_func=True, tol=0, rtol=1e-8) 379 for qi in q] 488 if np.isscalar(width): 489 width = [width]*len(q) 490 if np.isscalar(height): 491 height = [height]*len(q) 492 _int_w = lambda w, qi: eval_form(sqrt(qi**2 + w**2), form, pars) 493 _int_h = lambda h, qi: eval_form(abs(qi+h), form, pars) 494 # If both width and height are defined, then it is too slow to use dblquad. 495 # Instead use trapz on a fixed grid, interpolated into the I(Q) for 496 # the extended Q range. 497 #_int_wh = lambda w, h, qi: eval_form(sqrt((qi+h)**2 + w**2), form, pars) 498 q_calc = slit_extend_q(q, np.asarray(width), np.asarray(height)) 499 Iq = eval_form(q_calc, form, pars) 500 result = np.empty(len(q)) 501 for i, (qi, w, h) in enumerate(zip(q, width, height)): 502 if h == 0.: 503 r = romberg(_int_w, 0, w, args=(qi,), 504 divmax=100, vec_func=True, tol=0, rtol=1e-8) 505 result[i] = r/w 506 elif w == 0.: 507 r = romberg(_int_h, -h, h, args=(qi,), 508 divmax=100, vec_func=True, tol=0, rtol=1e-8) 509 result[i] = r/(2*h) 510 else: 511 w_grid = np.linspace(0, w, 21)[None,:] 512 h_grid = np.linspace(-h, h, 23)[:,None] 513 u = sqrt((qi+h_grid)**2 + w_grid**2) 514 Iu = np.interp(u, q_calc, Iq) 515 #print np.trapz(Iu, w_grid, axis=1) 516 Is = np.trapz(np.trapz(Iu, w_grid, axis=1), h_grid[:,0]) 517 result[i] = Is / (2*h*w) 518 """ 519 r, err = dblquad(_int_wh, -h, h, lambda h: 0., lambda h: w, 520 args=(qi,)) 521 result[i] = r/(w*2*h) 522 """ 523 380 524 # r should be [float, ...], but it is [array([float]), array([float]),...] 381 return np.asarray(r).flatten()/delta_qv[0]525 return result 382 526 383 527 … … 520 664 521 665 def compare(self, q, output, answer, tolerance): 522 err = (output - answer)/answer523 idx = abs(err) >= tolerance524 problem = zip(q[idx], output[idx], answer[idx], err[idx])525 print "\n".join(str(v) for v in problem)666 #err = (output - answer)/answer 667 #idx = abs(err) >= tolerance 668 #problem = zip(q[idx], output[idx], answer[idx], err[idx]) 669 #print "\n".join(str(v) for v in problem) 526 670 np.testing.assert_allclose(output, answer, rtol=tolerance) 527 671 … … 609 753 data = np.loadtxt(data_string.split('\n')).T 610 754 q, delta_qv, _, answer = data 611 answer = romberg_slit_1d(q, delta_qv, self.model, pars)755 answer = romberg_slit_1d(q, delta_qv, 0., self.model, pars) 612 756 q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20), 613 delta_qv[0], delta_qv[0])757 delta_qv[0], 0.) 614 758 resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc) 615 759 output = self.Iq_sphere(pars, resolution) … … 629 773 form = load_model('ellipsoid', dtype='double') 630 774 q = np.logspace(log10(4e-5),log10(2.5e-2), 68) 631 delta_qv = [0.117]632 resolution = Slit1D(q, width= delta_qv, height=0)633 answer = romberg_slit_1d(q, delta_qv, form, pars)775 width, height = 0.117, 0. 776 resolution = Slit1D(q, width=width, height=height) 777 answer = romberg_slit_1d(q, width, height, form, pars) 634 778 output = resolution.apply(eval_form(resolution.q_calc, form, pars)) 635 779 # TODO: 10% is too much error; use better algorithm 780 #print np.max(abs(answer-output)/answer) 636 781 self.compare(q, output, answer, 0.1) 637 782 … … 860 1005 861 1006 def _eval_demo_1d(resolution, title): 1007 import sys 862 1008 from sasmodels import core 863 from sasmodels.models import cylinder 864 ## or alternatively: 865 # cylinder = core.load_model_definition('cylinder') 866 model = core.load_model(cylinder) 1009 name = sys.argv[1] if len(sys.argv) > 1 else 'cylinder' 1010 1011 if name == 'cylinder': 1012 pars = {'length':210, 'radius':500} 1013 elif name == 'teubner_strey': 1014 pars = {'a2':0.003, 'c1':-1e4, 'c2':1e10, 'background':0.312643} 1015 elif name == 'sphere' or name == 'spherepy': 1016 pars = TEST_PARS_SLIT_SPHERE 1017 elif name == 'ellipsoid': 1018 pars = { 1019 'scale':0.05, 1020 'rpolar':500, 'requatorial':15000, 1021 'sld':6, 'solvent_sld': 1, 1022 } 1023 else: 1024 pars = {} 1025 defn = core.load_model_definition(name) 1026 model = core.load_model(defn) 867 1027 868 1028 kernel = core.make_kernel(model, [resolution.q_calc]) 869 theory = core.call_kernel(kernel, {'length':210, 'radius':500})1029 theory = core.call_kernel(kernel, pars) 870 1030 Iq = resolution.apply(theory) 1031 1032 if isinstance(resolution, Slit1D): 1033 width, height = resolution.width, resolution.height 1034 Iq_romb = romberg_slit_1d(resolution.q, width, height, model, pars) 1035 else: 1036 dq = resolution.q_width 1037 Iq_romb = romberg_pinhole_1d(resolution.q, dq, model, pars) 871 1038 872 1039 import matplotlib.pyplot as plt 873 1040 plt.loglog(resolution.q_calc, theory, label='unsmeared') 874 1041 plt.loglog(resolution.q, Iq, label='smeared', hold=True) 1042 plt.loglog(resolution.q, Iq_romb, label='romberg smeared', hold=True) 875 1043 plt.legend() 876 1044 plt.title(title) … … 879 1047 880 1048 def demo_pinhole_1d(): 881 q = np.logspace(- 3, -1, 400)1049 q = np.logspace(-4, np.log10(0.2), 400) 882 1050 q_width = 0.1*q 883 1051 resolution = Pinhole1D(q, q_width) … … 885 1053 886 1054 def demo_slit_1d(): 887 q = np.logspace(-3, -1, 400) 888 qx_width = 0.005 889 qy_width = 0.0 890 resolution = Slit1D(q, qx_width, qy_width) 891 _eval_demo_1d(resolution, title="0.005 Qx Slit Resolution") 1055 q = np.logspace(-4, np.log10(0.2), 100) 1056 w = h = 0. 1057 #w = 0.000000277790 1058 w = 0.0277790 1059 #h = 0.00277790 1060 #h = 0.0277790 1061 resolution = Slit1D(q, w, h) 1062 _eval_demo_1d(resolution, title="(%g,%g) Slit Resolution"%(w,h)) 892 1063 893 1064 def demo(): … … 895 1066 plt.subplot(121) 896 1067 demo_pinhole_1d() 1068 #plt.yscale('linear') 897 1069 plt.subplot(122) 898 1070 demo_slit_1d() 1071 #plt.yscale('linear') 899 1072 plt.show() 900 1073 901 1074 902 1075 if __name__ == "__main__": 903 #demo()904 main()1076 demo() 1077 #main() -
src/sas/models/dispersion_models.py
rfd5ac0d r0e4e554 154 154 c_models.set_dispersion_weights(self.cdisp, values, weights) 155 155 156 models = {"gaussian":GaussianDispersion, "rectangula ":RectangleDispersion,156 models = {"gaussian":GaussianDispersion, "rectangular":RectangleDispersion, 157 157 "array":ArrayDispersion, "schulz":SchulzDispersion, 158 158 "lognormal":LogNormalDispersion}
Note: See TracChangeset
for help on using the changeset viewer.