Changeset 7954f5c in sasmodels
- Timestamp:
- Mar 23, 2015 7:58:11 AM (10 years ago)
- Branches:
- master, core_shell_microgels, costrafo411, magnetic_model, release_v0.94, release_v0.95, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
- Children:
- bcd3aa3
- Parents:
- 33c8d73
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/resolution.py
r33c8d73 r7954f5c 4 4 This defines classes for 1D and 2D resolution calculations. 5 5 """ 6 from __future__ import division 6 7 from scipy.special import erf 7 from numpy import sqrt 8 from numpy import sqrt, log, log10 8 9 import numpy as np 9 10 10 SLIT_SMEAR_POINTS = 50011 11 MINIMUM_RESOLUTION = 1e-8 12 # TODO: Q_EXTEND_STEPS is much bigger than necessary13 Q_EXTEND_STEPS = 30 # number of extra q points above and below14 15 12 16 13 class Resolution1D(object): … … 34 31 raise NotImplementedError("Subclass does not define the apply function") 35 32 33 36 34 class Perfect1D(Resolution1D): 37 35 """ … … 46 44 return theory 47 45 46 48 47 class Pinhole1D(Resolution1D): 49 48 r""" … … 55 54 56 55 *q_calc* is the list of points to calculate, or None if this should 57 be autogenerated from the *q,q_width*.56 be estimated from the *q* and *q_width*. 58 57 """ 59 58 def __init__(self, q, q_width, q_calc=None): 60 #TODO: maybe add min_step=np.inf61 59 #*min_step* is the minimum point spacing to use when computing the 62 60 #underlying model. It should be on the order of … … 70 68 # default to Perfect1D if the pinhole geometry is not defined. 71 69 self.q, self.q_width = q, q_width 72 self.q_calc = pinhole_extend_q(q, q_width) if q_calc is None else q_calc 70 self.q_calc = pinhole_extend_q(q, q_width) \ 71 if q_calc is None else np.sort(q_calc) 73 72 self.weight_matrix = pinhole_resolution(self.q_calc, 74 73 self.q, np.maximum(q_width, MINIMUM_RESOLUTION)) … … 77 76 return apply_resolution_matrix(self.weight_matrix, theory) 78 77 78 79 79 class Slit1D(Resolution1D): 80 80 """ … … 86 86 87 87 *qy_height* slit height 88 """ 89 def __init__(self, q, qx_width, qy_width): 90 if np.isscalar(qx_width): 91 qx_width = qx_width*np.ones(len(q)) 92 if np.isscalar(qy_width): 93 qy_width = qy_width*np.ones(len(q)) 94 self.q, self.qx_width, self.qy_width = [ 95 v.flatten() for v in (q, qx_width, qy_width)] 96 self.q_calc = slit_extend_q(q, qx_width, qy_width) 88 89 *q_calc* is the list of points to calculate, or None if this should 90 be estimated from the *q* and *q_width*. 91 92 The *weight_matrix* is computed by :func:`slit1d_resolution` 93 """ 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 105 # Remember what width/height was used even though we won't need them 106 # after the weight matrix is constructed 107 self.width, self.height = width, height 108 109 self.q = q.flatten() 110 self.q_calc = slit_extend_q(q, width, height) \ 111 if q_calc is None else np.sort(q_calc) 97 112 self.weight_matrix = \ 98 slit_resolution(self.q_calc, self.q, self.qx_width, self.qy_width)113 slit_resolution(self.q_calc, self.q, width, height) 99 114 100 115 def apply(self, theory): … … 106 121 Apply the resolution weight matrix to the computed theory function. 107 122 """ 108 #print "apply shapes", theory.shape, self.weight_matrix.shape123 #print "apply shapes", theory.shape, weight_matrix.shape 109 124 Iq = np.dot(theory[None,:], weight_matrix) 110 125 #print "result shape",Iq.shape 111 126 return Iq.flatten() 112 127 128 113 129 def pinhole_resolution(q_calc, q, q_width): 114 130 """ … … 119 135 *W*, the resolution smearing can be computed using *dot(W,q)*. 120 136 121 *q_calc* must be increasing. 122 """ 137 *q_calc* must be increasing. *q_width* must be greater than zero. 138 """ 139 # The current algorithm is a midpoint rectangle rule. In the test case, 140 # neither trapezoid nor Simpson's rule improved the accuracy. 123 141 edges = bin_edges(q_calc) 124 142 edges[edges<0.0] = 0.0 # clip edges below zero 125 index = (q_width > 0.0) # treat perfect resolution differently126 143 G = erf( (edges[:,None] - q[None,:]) / (sqrt(2.0)*q_width)[None,:] ) 127 144 weights = G[1:] - G[:-1] 128 # w = np.sum(weights, axis=0); print "w",w.shape, w129 145 weights /= np.sum(weights, axis=0)[None,:] 130 146 return weights 131 147 132 148 133 def pinhole_extend_q(q, q_width): 149 def slit_resolution(q_calc, q, width, height): 150 r""" 151 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. 156 157 If slit height is large relative to width, use: 158 159 .. math:: 160 161 I_s(q_o) = \frac{1}{\Delta q_v} 162 \int_0^{\Delta q_v} I(\sqrt{q_o^2 + u^2} du 163 164 If slit width is large relative to height, use: 165 166 .. math:: 167 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 175 q_edges = bin_edges(q_calc) # Note: requires q > 0 176 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 195 return weights 196 197 198 def pinhole_extend_q(q, q_width, nsigma=3): 134 199 """ 135 200 Given *q* and *q_width*, find a set of sampling points *q_calc* so … … 137 202 function. 138 203 """ 139 # If using min_step, you could do something like: 140 # q_calc = np.arange(min, max, min_step) 141 # index = np.searchsorted(q_calc, q) 142 # q_calc[index] = q 143 # A refinement would be to assign q to the nearest neighbour. A further 144 # refinement would be to guard multiple q points between q_calc points, 145 # either by removing duplicates or by only moving the values nearest the 146 # edges. For really sparse samples, you will want to remove all remaining 147 # points that are not within three standard deviations of a measured q. 148 q_min, q_max = np.min(q), np.max(q) 149 extended_min, extended_max = np.min(q - 3*q_width), np.max(q + 3*q_width) 150 nbins_low, nbins_high = Q_EXTEND_STEPS, Q_EXTEND_STEPS 151 if extended_min < q_min: 152 q_low = np.linspace(extended_min, q_min, nbins_low+1)[:-1] 153 q_low = q_low[q_low>0.0] 154 else: 155 q_low = [] 156 if extended_max > q_max: 157 q_high = np.linspace(q_max, extended_max, nbins_high+1)[:-1] 158 else: 159 q_high = [] 160 return np.concatenate([q_low, q, q_high]) 161 162 163 def slit_resolution(q_calc, q, qx_width, qy_width): 164 edges = bin_edges(q_calc) # Note: requires q > 0 165 edges[edges<0.0] = 0.0 # clip edges below zero 166 qy_min, qy_max = 0.0, edges[-1] 167 168 # Make q_calc into a row vector, and q, qx_width, qy_width into columns 169 # Make weights match [ q_calc X q ] 170 weights = np.zeros((len(q),len(q_calc)), 'd') 171 q_calc = q_calc[None,:] 172 q, qx_width, qy_width, edges = [ 173 v[:,None] for v in (q, qx_width, qy_width, edges)] 174 175 # Loop for width (height is analytical). 176 # Condition: height >>> width, otherwise, below is not accurate enough. 177 # Smear weight numerical iteration for width>0 when height>0. 178 # When width = 0, the numerical iteration will be skipped. 179 # The resolution calculation for the height is done by direct integration, 180 # assuming the I(q'=sqrt(q_j^2-(q+shift_w)^2)) is constant within 181 # a q' bin, [q_high, q_low]. 182 # In general, this weight numerical iteration for width>0 might be a rough 183 # approximation, but it must be good enough when height >>> width. 184 E_sq = edges**2 185 y_points = SLIT_SMEAR_POINTS if np.any(qy_width>0) else 1 186 qy_step = 0 if y_points == 1 else qy_width/(y_points-1) 187 for k in range(-y_points+1,y_points): 188 qy = np.clip(q + qy_step*k, qy_min, qy_max) 189 qx_low = qy 190 qx_high = sqrt(qx_low**2 + qx_width**2) 191 in_x = (q_calc>=qx_low)*(q_calc<=qx_high) 192 qy_sq = qy**2 193 weights += (sqrt(E_sq[1:]-qy_sq) - sqrt(qy_sq - E_sq[:-1]))*in_x 194 #w = np.sum(weights, axis=1); print "w",w.shape, w 195 weights /= np.sum(weights, axis=1)[:,None] 196 return weights 197 198 199 def slit_extend_q(q, qx_width, qy_width): 200 return q 204 q_min, q_max = np.min(q - nsigma*q_width), np.max(q + nsigma*q_width) 205 return geometric_extrapolation(q, q_min, q_max) 206 207 208 def slit_extend_q(q, width, height): 209 """ 210 Given *q*, *width* and *height*, find a set of sampling points *q_calc* so 211 that each point I(q) has sufficient support from the underlying 212 function. 213 """ 214 height # keep lint happy 215 q_min, q_max = np.min(q), np.max(np.sqrt(q**2 + width**2)) 216 return geometric_extrapolation(q, q_min, q_max) 201 217 202 218 … … 218 234 return edges 219 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 zero 242 qpsq = q**2 - q0**2 243 qpsq[qpsq<0] = 0 244 return sqrt(qpsq) 245 246 247 def interpolate(q, max_step): 248 """ 249 Returns *q_calc* with points spaced at most max_step apart. 250 """ 251 step = np.diff(q) 252 index = step>max_step 253 if np.any(index): 254 inserts = [] 255 for q_i,step_i in zip(q[:-1][index],step[index]): 256 n = np.ceil(step_i/max_step) 257 inserts.extend(q_i + np.arange(1,n)*(step_i/n)) 258 # Extend a couple of fringes beyond the end of the data 259 inserts.extend(q[-1] + np.arange(1,8)*max_step) 260 q_calc = np.sort(np.hstack((q,inserts))) 261 else: 262 q_calc = q 263 return q_calc 264 265 266 def linear_extrapolation(q, q_min, q_max): 267 """ 268 Extrapolate *q* out to [*q_min*, *q_max*] using the step size in *q* as 269 a guide. Extrapolation below uses steps the same size as the first 270 interval. Extrapolation above uses steps the same size as the final 271 interval. 272 273 if *q_min* is zero or less then *q[0]/10* is used instead. 274 """ 275 q = np.sort(q) 276 if q_min < q[0]: 277 if q_min <= 0: q_min = q[0]/10 278 delta = q[1] - q[0] 279 q_low = np.arange(q_min, q[0], delta) 280 else: 281 q_low = [] 282 if q_max > q[-1]: 283 delta = q[-1] - q[-2] 284 q_high = np.arange(q[-1]+delta, q_max, delta) 285 else: 286 q_high = [] 287 return np.concatenate([q_low, q, q_high]) 288 289 290 def geometric_extrapolation(q, q_min, q_max): 291 r""" 292 Extrapolate *q* to [*q_min*, *q_max*] using geometric steps, with the 293 average geometric step size in *q* as the step size. 294 295 if *q_min* is zero or less then *q[0]/10* is used instead. 296 297 Starting at $q_1$ and stepping geometrically by $\Delta q$ 298 to $q_n$ in $n$ points gives a geometric average of: 299 300 .. math:: 301 302 \log \Delta q = (\log q_n - log q_1) / (n - 1) 303 304 From this we can compute the number of steps required to extend $q$ 305 from $q_n$ to $q_\text{max}$ by $\Delta q$ as: 306 307 .. math:: 308 309 n_\text{extend} = (\log q_\text{max} - \log q_n) / \log \Delta q 310 311 Substituting: 312 313 n_\text{extend} = (n-1) (\log q_\text{max} - \log q_n) 314 / (\log q_n - log q_1) 315 """ 316 q = np.sort(q) 317 delta_q = (len(q)-1)/(log(q[-1]) - log(q[0])) 318 if q_min < q[0]: 319 if q_min < 0: q_min = q[0]/10 320 n_low = delta_q * (log(q[0])-log(q_min)) 321 q_low = np.logspace(log10(q_min), log10(q[0]), np.ceil(n_low)+1)[:-1] 322 else: 323 q_low = [] 324 if q_max > q[-1]: 325 n_high = delta_q * (log(q_max)-log(q[-1])) 326 q_high = np.logspace(log10(q[-1]), log10(q_max), np.ceil(n_high)+1)[1:] 327 else: 328 q_high = [] 329 return np.concatenate([q_low, q, q_high]) 330 331 220 332 ############################################################################ 221 333 # unit tests … … 223 335 import unittest 224 336 337 338 def eval_form(q, form, pars): 339 from sasmodels import core 340 kernel = core.make_kernel(form, [q]) 341 theory = core.call_kernel(kernel, pars) 342 kernel.release() 343 return theory 344 345 346 def gaussian(q, q0, dq): 347 from numpy import exp, pi 348 return exp(-0.5*((q-q0)/dq)**2)/(sqrt(2*pi)*dq) 349 350 351 def romberg_slit_1d(q, delta_qv, form, pars): 352 """ 353 Romberg integration for slit resolution. 354 355 This is an adaptive integration technique. It is called with settings 356 that make it slow to evaluate but give it good accuracy. 357 """ 358 from scipy.integrate import romberg 359 _fn = lambda u, q0: eval_form(sqrt(q0**2 + u**2), form, pars) 360 r = [romberg(_fn, 0, delta_qv[0], args=(qi,), 361 divmax=100, vec_func=True, tol=0, rtol=1e-8) 362 for qi in q] 363 # r should be [float, ...], but it is [array([float]), array([float]),...] 364 return np.asarray(r).flatten()/delta_qv[0] 365 366 367 def romberg_pinhole_1d(q, q_width, form, pars): 368 """ 369 Romberg integration for pinhole resolution. 370 371 This is an adaptive integration technique. It is called with settings 372 that make it slow to evaluate but give it good accuracy. 373 """ 374 from scipy.integrate import romberg 375 376 _fn = lambda q, q0, dq: eval_form(q, form, pars)*gaussian(q, q0, dq) 377 r = [romberg(_fn, max(qi-5*dqi,0.01*q[0]), qi+5*dqi, args=(qi, dqi), 378 divmax=100, vec_func=True, tol=0, rtol=1e-8) 379 for qi,dqi in zip(q,q_width)] 380 return np.asarray(r).flatten() 381 382 225 383 class ResolutionTest(unittest.TestCase): 226 384 … … 246 404 Slit smearing with perfect resolution. 247 405 """ 248 resolution = Slit1D(self.x, 0., 0.)406 resolution = Slit1D(self.x, width=0, height=0, q_calc=self.x) 249 407 theory = self.Iq(resolution.q_calc) 250 408 output = resolution.apply(theory) 251 409 np.testing.assert_equal(output, self.y) 252 410 253 @unittest.skip(" slit smearing is still broken")254 def test_slit (self):411 @unittest.skip("not yet supported") 412 def test_slit_high(self): 255 413 """ 256 414 Slit smearing with height 0.005 257 415 """ 258 resolution = Slit1D(self.x, 0., 0.005)416 resolution = Slit1D(self.x, width=0, height=0.005, q_calc=self.x) 259 417 theory = self.Iq(resolution.q_calc) 260 418 output = resolution.apply(theory) 261 answer = [ 9.0618, 8.6401, 8.1186, 7.1391, 6.1528, 262 5.5555, 4.5584, 3.5606, 2.5623, 2. ] 419 answer = [ 9.0618, 8.6402, 8.1187, 7.1392, 6.1528, 420 5.5555, 4.5584, 3.5606, 2.5623, 2.0000 ] 421 np.testing.assert_allclose(output, answer, atol=1e-4) 422 423 @unittest.skip("not yet supported") 424 def test_slit_both_high(self): 425 """ 426 Slit smearing with width < 100*height. 427 """ 428 q = np.logspace(-4, -1, 10) 429 resolution = Slit1D(q, width=0.2, height=np.inf) 430 theory = 1000*self.Iq(resolution.q_calc**4) 431 output = resolution.apply(theory) 432 answer = [ 8.85785, 8.43012, 7.92687, 6.94566, 6.03660, 433 5.40363, 4.40655, 3.40880, 2.41058, 2.00000 ] 434 np.testing.assert_allclose(output, answer, atol=1e-4) 435 436 @unittest.skip("not yet supported") 437 def test_slit_wide(self): 438 """ 439 Slit smearing with width 0.0002 440 """ 441 resolution = Slit1D(self.x, width=0.0002, height=0, q_calc=self.x) 442 theory = self.Iq(resolution.q_calc) 443 output = resolution.apply(theory) 444 answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ] 445 np.testing.assert_allclose(output, answer, atol=1e-4) 446 447 @unittest.skip("not yet supported") 448 def test_slit_both_wide(self): 449 """ 450 Slit smearing with width > 100*height. 451 """ 452 resolution = Slit1D(self.x, width=0.0002, height=0.000001, 453 q_calc=self.x) 454 theory = self.Iq(resolution.q_calc) 455 output = resolution.apply(theory) 456 answer = [ 11.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0 ] 263 457 np.testing.assert_allclose(output, answer, atol=1e-4) 264 458 … … 285 479 np.testing.assert_allclose(output, answer, atol=1e-8) 286 480 287 # Q, dQ, I(Q) for Igor default sphere model. 288 # combines CMSphere5.txt and CMSphere5smearsphere.txt from sasview/tests 289 # TODO: move test data into its own file? 290 SPHERE_RESOLUTION_TEST_DATA = """\ 481 482 class IgorComparisonTest(unittest.TestCase): 483 484 def setUp(self): 485 self.pars = TEST_PARS_PINHOLE_SPHERE 486 from sasmodels import core 487 from sasmodels.models import sphere 488 self.model = core.load_model(sphere, dtype='double') 489 490 def Iq_sphere(self, pars, resolution): 491 from sasmodels import core 492 kernel = core.make_kernel(self.model, [resolution.q_calc]) 493 theory = core.call_kernel(kernel, pars) 494 result = resolution.apply(theory) 495 kernel.release() 496 return result 497 498 def compare(self, q, output, answer, tolerance): 499 err = (output - answer)/answer 500 idx = abs(err) >= tolerance 501 problem = zip(q[idx], output[idx], answer[idx], err[idx]) 502 print "\n".join(str(v) for v in problem) 503 np.testing.assert_allclose(output, answer, rtol=tolerance) 504 505 def test_perfect(self): 506 """ 507 Compare sphere model with NIST Igor SANS, no resolution smearing. 508 """ 509 pars = TEST_PARS_SLIT_SPHERE 510 data_string = TEST_DATA_SLIT_SPHERE 511 512 data = np.loadtxt(data_string.split('\n')).T 513 q, width, answer, _ = data 514 resolution = Perfect1D(q) 515 output = self.Iq_sphere(pars, resolution) 516 self.compare(q, output, answer, 1e-6) 517 518 def test_pinhole(self): 519 """ 520 Compare pinhole resolution smearing with NIST Igor SANS 521 """ 522 pars = TEST_PARS_PINHOLE_SPHERE 523 data_string = TEST_DATA_PINHOLE_SPHERE 524 525 data = np.loadtxt(data_string.split('\n')).T 526 q, q_width, answer = data 527 resolution = Pinhole1D(q, q_width) 528 output = self.Iq_sphere(pars, resolution) 529 # TODO: relative error should be lower 530 self.compare(q, output, answer, 3e-4) 531 532 def test_pinhole_romberg(self): 533 """ 534 Compare pinhole resolution smearing with romberg integration result. 535 """ 536 pars = TEST_PARS_PINHOLE_SPHERE 537 data_string = TEST_DATA_PINHOLE_SPHERE 538 pars['radius'] *= 5 539 radius = pars['radius'] 540 541 data = np.loadtxt(data_string.split('\n')).T 542 q, q_width, answer = data 543 answer = romberg_pinhole_1d(q, q_width, self.model, pars) 544 ## Getting 0.1% requires 5 sigma and 200 points per fringe 545 #q_calc = interpolate(pinhole_extend_q(q, q_width, nsigma=5), 546 # 2*np.pi/radius/200) 547 #tol = 0.001 548 ## The default 3 sigma and no extra points gets 1% 549 q_calc, tol = None, 0.01 550 resolution = Pinhole1D(q, q_width, q_calc=q_calc) 551 output = self.Iq_sphere(pars, resolution) 552 if 0: # debug plot 553 import matplotlib.pyplot as plt 554 resolution = Perfect1D(q) 555 source = self.Iq_sphere(pars, resolution) 556 plt.loglog(q, source, '.') 557 plt.loglog(q, answer, '-', hold=True) 558 plt.loglog(q, output, '-', hold=True) 559 plt.show() 560 self.compare(q, output, answer, tol) 561 562 def test_slit(self): 563 """ 564 Compare slit resolution smearing with NIST Igor SANS 565 """ 566 pars = TEST_PARS_SLIT_SPHERE 567 data_string = TEST_DATA_SLIT_SPHERE 568 569 data = np.loadtxt(data_string.split('\n')).T 570 q, delta_qv, _, answer = data 571 resolution = Slit1D(q, width=delta_qv, height=0) 572 output = self.Iq_sphere(pars, resolution) 573 # TODO: eliminate Igor test since it is too inaccurate to be useful. 574 # This means we can eliminate the test data as well, and instead 575 # use a generated q vector. 576 self.compare(q, output, answer, 0.5) 577 578 def test_slit_romberg(self): 579 """ 580 Compare slit resolution smearing with romberg integration result. 581 """ 582 pars = TEST_PARS_SLIT_SPHERE 583 data_string = TEST_DATA_SLIT_SPHERE 584 radius = pars['radius'] 585 586 data = np.loadtxt(data_string.split('\n')).T 587 q, delta_qv, _, answer = data 588 answer = romberg_slit_1d(q, delta_qv, self.model, pars) 589 q_calc = slit_extend_q(interpolate(q, 2*np.pi/radius/20), 590 delta_qv[0], delta_qv[0]) 591 resolution = Slit1D(q, width=delta_qv, height=0, q_calc=q_calc) 592 output = self.Iq_sphere(pars, resolution) 593 # TODO: relative error should be lower 594 self.compare(q, output, answer, 0.025) 595 596 #TODO: can sas q spacing be too sparse for the resolution calculation? 597 @unittest.skip("suppress sparse data test; not supported by current code") 598 def test_pinhole_sparse(self): 599 """ 600 Compare pinhole resolution smearing with NIST Igor SANS on sparse data 601 """ 602 pars = TEST_PARS_PINHOLE_SPHERE 603 data_string = TEST_DATA_PINHOLE_SPHERE 604 605 data = np.loadtxt(data_string.split('\n')).T 606 q, q_width, answer = data[:, ::20] # Take every nth point 607 resolution = Pinhole1D(q, q_width) 608 output = self.Iq_sphere(pars, resolution) 609 self.compare(q, output, answer, 1e-6) 610 611 612 613 # pinhole sphere parameters 614 TEST_PARS_PINHOLE_SPHERE = { 615 'scale': 1.0, 'background': 0.01, 616 'radius': 60.0, 'sld': 1, 'solvent_sld': 6.3, 617 } 618 # Q, dQ, I(Q) calculated by NIST Igor SANS package 619 TEST_DATA_PINHOLE_SPHERE = """\ 291 620 0.001278 0.0002847 2538.41176383 292 621 0.001562 0.0002905 2536.91820405 … … 391 720 """ 392 721 393 class Pinhole1DTest(unittest.TestCase): 394 395 def setUp(self): 396 # sample q, q_width, I(q) calculated by NIST Igor SANS package 397 self.data = np.loadtxt(SPHERE_RESOLUTION_TEST_DATA.split('\n')).T 398 self.pars = dict(scale=1.0, background=0.01, radius=60.0, 399 solvent_sld=6.3, sld=1) 400 401 def Iq(self, q, q_width): 402 from sasmodels import core 403 from sasmodels.models import sphere 404 405 model = core.load_model(sphere) 406 resolution = Pinhole1D(q, q_width) 407 kernel = core.make_kernel(model, [resolution.q_calc]) 408 theory = core.call_kernel(kernel, self.pars) 409 result = resolution.apply(theory) 410 return result 411 412 def test_sphere(self): 413 """ 414 Compare pinhole resolution smearing with NIST Igor SANS 415 """ 416 q, q_width, answer = self.data 417 output = self.Iq(q, q_width) 418 np.testing.assert_allclose(output, answer, rtol=0.006) 419 420 #TODO: is all sas data sampled densely enough to support resolution calcs? 421 @unittest.skip("suppress sparse data test; not supported by current code") 422 def test_sphere_sparse(self): 423 """ 424 Compare pinhole resolution smearing with NIST Igor SANS on sparse data 425 """ 426 q, q_width, answer = self.data[:, ::20] # Take every nth point 427 output = self.Iq(q, q_width) 428 np.testing.assert_allclose(output, answer, rtol=0.006) 429 722 # Slit sphere parameters 723 TEST_PARS_SLIT_SPHERE = { 724 'scale': 0.01, 'background': 0.01, 725 'radius': 60000, 'sld': 1, 'solvent_sld': 4, 726 } 727 # Q dQ I(Q) I_smeared(Q) 728 TEST_DATA_SLIT_SPHERE = """\ 729 2.26097e-05 0.117 5.5781372896e+09 1.4626077708e+06 730 2.53847e-05 0.117 5.0363141458e+09 1.3117318023e+06 731 2.81597e-05 0.117 4.4850108103e+09 1.1594863713e+06 732 3.09347e-05 0.117 3.9364658459e+09 1.0094881630e+06 733 3.37097e-05 0.117 3.4019975074e+09 8.6518941303e+05 734 3.92597e-05 0.117 2.4139519814e+09 6.0232158311e+05 735 4.48097e-05 0.117 1.5816877820e+09 3.8739994090e+05 736 5.03597e-05 0.117 9.3715407224e+08 2.2745304775e+05 737 5.59097e-05 0.117 4.8387917428e+08 1.2101295768e+05 738 6.14597e-05 0.117 2.0193586928e+08 6.0055107771e+04 739 6.70097e-05 0.117 5.5886110911e+07 3.2749521065e+04 740 7.25597e-05 0.117 3.7782348010e+06 2.6350963616e+04 741 7.81097e-05 0.117 5.3407817904e+06 2.9624963314e+04 742 8.36597e-05 0.117 2.7975485523e+07 3.4403962254e+04 743 8.92097e-05 0.117 4.9845448282e+07 3.6130017903e+04 744 9.47597e-05 0.117 6.0092588905e+07 3.3495107849e+04 745 1.00310e-04 0.117 5.6823430831e+07 2.7475726279e+04 746 1.05860e-04 0.117 4.3857024036e+07 2.0144282226e+04 747 1.11410e-04 0.117 2.7277144760e+07 1.3647403260e+04 748 1.22510e-04 0.117 3.3119334113e+06 6.6519711526e+03 749 1.33610e-04 0.117 1.4412859402e+06 6.9726212813e+03 750 1.44710e-04 0.117 8.5620162463e+06 8.1441335775e+03 751 1.55810e-04 0.117 9.6957429033e+06 6.4559996521e+03 752 1.66910e-04 0.117 4.3818341914e+06 3.6252154156e+03 753 1.78010e-04 0.117 2.7448997387e+05 2.4006505342e+03 754 1.89110e-04 0.117 8.0472009936e+05 2.8187789089e+03 755 2.00210e-04 0.117 2.8149552834e+06 3.0915662855e+03 756 2.11310e-04 0.117 2.7510907861e+06 2.3722530293e+03 757 2.22410e-04 0.117 1.0053133293e+06 1.4473468311e+03 758 2.33510e-04 0.117 5.8428305052e+03 1.2048540556e+03 759 2.44610e-04 0.117 5.1699305004e+05 1.4625670042e+03 760 2.55710e-04 0.117 1.2120227268e+06 1.5010705973e+03 761 2.66810e-04 0.117 9.7896842846e+05 1.1336343426e+03 762 2.77910e-04 0.117 2.5507264791e+05 8.1848818080e+02 763 3.05660e-04 0.117 5.2403101181e+05 7.4913374357e+02 764 3.33410e-04 0.117 5.8699343809e+04 4.4669964560e+02 765 3.61160e-04 0.117 3.0844327150e+05 4.6774007542e+02 766 3.88910e-04 0.117 8.3360142970e+03 2.7169550220e+02 767 4.16660e-04 0.117 1.8630080583e+05 3.0710983679e+02 768 4.44410e-04 0.117 3.1616804732e-01 1.7959006831e+02 769 4.72160e-04 0.117 1.1299016314e+05 2.0763952339e+02 770 4.99910e-04 0.117 2.9952522747e+03 1.2536542765e+02 771 5.27660e-04 0.117 6.7625695649e+04 1.4013969777e+02 772 5.55410e-04 0.117 7.6927460089e+03 8.2145593180e+01 773 6.10910e-04 0.117 1.1229057779e+04 8.4519745643e+01 774 6.66410e-04 0.117 1.3035567943e+04 8.1554625609e+01 775 7.21910e-04 0.117 1.3309931343e+04 7.4437319172e+01 776 7.77410e-04 0.117 1.2462626212e+04 6.4697088261e+01 777 8.32910e-04 0.117 1.0912927143e+04 5.3773301044e+01 778 8.88410e-04 0.117 9.0172597469e+03 4.2843375753e+01 779 9.43910e-04 0.117 7.0496495917e+03 3.2771032724e+01 780 9.99410e-04 0.117 5.2030483682e+03 2.4113557144e+01 781 1.05491e-03 0.117 3.5988976711e+03 1.7160773658e+01 782 1.11041e-03 0.117 2.2996060652e+03 1.2016626459e+01 783 1.22141e-03 0.117 6.4766590598e+02 6.0373017740e+00 784 1.33241e-03 0.117 4.1963483264e+01 4.5215452974e+00 785 1.44341e-03 0.117 6.3370708246e+01 5.1054681903e+00 786 1.55441e-03 0.117 3.0736750577e+02 5.9176165298e+00 787 1.66541e-03 0.117 5.0327682399e+02 5.9815000189e+00 788 1.77641e-03 0.117 5.4084331454e+02 5.1634639625e+00 789 1.88741e-03 0.117 4.3488671756e+02 3.8535158148e+00 790 1.99841e-03 0.117 2.6322287860e+02 2.5824997753e+00 791 2.10941e-03 0.117 1.0793633150e+02 1.7315517194e+00 792 2.22041e-03 0.117 1.8474448850e+01 1.4077213604e+00 793 2.33141e-03 0.117 1.5864062279e+00 1.4771560682e+00 794 2.44241e-03 0.117 3.2267213848e+01 1.6916253448e+00 795 2.55341e-03 0.117 7.4289116207e+01 1.8274751193e+00 796 2.66441e-03 0.117 9.9000521929e+01 1.7706812289e+00 797 """ 430 798 431 799 def main(): … … 494 862 #demo() 495 863 main() 496 497
Note: See TracChangeset
for help on using the changeset viewer.