- Timestamp:
- Mar 4, 2015 12:30:43 PM (10 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:
- ab13d33
- Parents:
- 5f8fc78
- Location:
- src/sas
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/invariant/invariant.py
r79492222 rc76bdc3 1 # pylint: disable=invalid-name 1 2 ##################################################################### 2 3 #This software was developed by the University of Tennessee as part of the 3 4 #Distributed Data Analysis of Neutron Scattering Experiments (DANSE) 4 #project funded by the US National Science Foundation. 5 #project funded by the US National Science Foundation. 5 6 #See the license text in license.txt 6 7 #copyright 2010, University of Tennessee … … 15 16 16 17 """ 17 import math 18 import math 18 19 import numpy 19 20 … … 21 22 22 23 # The minimum q-value to be used when extrapolating 23 Q_MINIMUM 24 Q_MINIMUM = 1e-5 24 25 25 26 # The maximum q-value to be used when extrapolating 26 Q_MAXIMUM 27 Q_MAXIMUM = 10 27 28 28 29 # Number of steps in the extrapolation … … 32 33 """ 33 34 Define interface that need to compute a function or an inverse 34 function given some x, y 35 function given some x, y 35 36 """ 36 37 37 38 def linearize_data(self, data): 38 39 """ 39 Linearize data so that a linear fit can be performed. 40 Linearize data so that a linear fit can be performed. 40 41 Filter out the data that can't be transformed. 41 42 42 43 :param data: LoadData1D instance 43 44 44 45 """ 45 46 # Check that the vector lengths are equal 46 assert (len(data.x)==len(data.y))47 assert len(data.x) == len(data.y) 47 48 if data.dy is not None: 48 assert (len(data.x)==len(data.dy))49 assert len(data.x) == len(data.dy) 49 50 dy = data.dy 50 51 else: 51 52 dy = numpy.ones(len(data.y)) 52 53 53 54 # Transform the data 54 55 data_points = zip(data.x, data.y, dy) … … 56 57 output_points = [(self.linearize_q_value(p[0]), 57 58 math.log(p[1]), 58 p[2] /p[1]) for p in data_points if p[0]>0 and \59 p[1] >0 and p[2]>0]60 59 p[2] / p[1]) for p in data_points if p[0] > 0 and \ 60 p[1] > 0 and p[2] > 0] 61 61 62 x_out, y_out, dy_out = zip(*output_points) 62 63 63 64 # Create Data1D object 64 65 x_out = numpy.asarray(x_out) … … 66 67 dy_out = numpy.asarray(dy_out) 67 68 linear_data = LoaderData1D(x=x_out, y=y_out, dy=dy_out) 68 69 69 70 return linear_data 70 71 71 72 def get_allowed_bins(self, data): 72 73 """ 73 74 Goes through the data points and returns a list of boolean values 74 75 to indicate whether each points is allowed by the model or not. 75 76 76 77 :param data: Data1D object 77 78 """ 78 return [p[0] >0 and p[1]>0 and p[2]>0 for p in zip(data.x, data.y,79 data.dy)]80 79 return [p[0] > 0 and p[1] > 0 and p[2] > 0 for p in zip(data.x, data.y, 80 data.dy)] 81 81 82 def linearize_q_value(self, value): 82 83 """ … … 90 91 """ 91 92 return NotImplemented 92 93 93 94 def evaluate_model(self, x): 94 95 """ … … 96 97 """ 97 98 return NotImplemented 98 99 99 100 def evaluate_model_errors(self, x): 100 101 """ … … 102 103 """ 103 104 return NotImplemented 104 105 105 106 class Guinier(Transform): 106 107 """ 107 class of type Transform that performs operations related to guinier 108 class of type Transform that performs operations related to guinier 108 109 function 109 110 """ … … 113 114 self.radius = radius 114 115 ## Uncertainty of scale parameter 115 self.dscale 116 self.dscale = 0 116 117 ## Unvertainty of radius parameter 117 118 self.dradius = 0 118 119 119 120 def linearize_q_value(self, value): 120 121 """ 121 122 Transform the input q-value for linearization 122 123 123 124 :param value: q-value 124 125 125 126 :return: q*q 126 127 """ 127 128 return value * value 128 129 129 130 def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0): 130 131 """ … … 136 137 self.radius = math.sqrt(-3 * slope) 137 138 # Errors 138 self.dscale = math.exp(constant) *dconstant139 self.dscale = math.exp(constant) * dconstant 139 140 if slope == 0.0: 140 141 n_zero = -1.0e-24 141 self.dradius = -3.0 /2.0/math.sqrt(-3 * n_zero)*dslope142 self.dradius = -3.0 / 2.0 / math.sqrt(-3 * n_zero) * dslope 142 143 else: 143 self.dradius = -3.0 /2.0/math.sqrt(-3 * slope)*dslope144 144 self.dradius = -3.0 / 2.0 / math.sqrt(-3 * slope) * dslope 145 145 146 return [self.radius, self.scale], [self.dradius, self.dscale] 146 147 147 148 def evaluate_model(self, x): 148 149 """ … … 150 151 """ 151 152 return self._guinier(x) 152 153 153 154 def evaluate_model_errors(self, x): 154 155 """ 155 156 Returns the error on I(q) for the given array of q-values 156 157 157 158 :param x: array of q-values 158 159 """ 159 p1 = numpy.array([self.dscale * math.exp(-((self.radius * q) **2/3)) \160 p1 = numpy.array([self.dscale * math.exp(-((self.radius * q) ** 2 / 3)) \ 160 161 for q in x]) 161 p2 = numpy.array([self.scale * math.exp(-((self.radius * q) **2/3))\162 * (-(q **2/3)) * 2 * self.radius * self.dradius for q in x])163 diq2 = p1 *p1 + p2*p2164 return numpy.array( [math.sqrt(err) for err in diq2])165 162 p2 = numpy.array([self.scale * math.exp(-((self.radius * q) ** 2 / 3))\ 163 * (-(q ** 2 / 3)) * 2 * self.radius * self.dradius for q in x]) 164 diq2 = p1 * p1 + p2 * p2 165 return numpy.array([math.sqrt(err) for err in diq2]) 166 166 167 def _guinier(self, x): 167 168 """ … … 169 170 to x 170 171 Compute a F(x) = scale* e-((radius*x)**2/3). 171 172 :param x: a vector of q values 172 173 :param x: a vector of q values 173 174 :param scale: the scale value 174 175 :param radius: the guinier radius value 175 176 176 177 :return: F(x) 177 """ 178 # transform the radius of coming from the inverse guinier function to a 178 """ 179 # transform the radius of coming from the inverse guinier function to a 179 180 # a radius of a guinier function 180 181 if self.radius <= 0: 181 msg = "Rg expected positive value, but got %s" %self.radius182 raise ValueError(msg) 183 value = numpy.array([math.exp(-((self.radius * i) **2/3)) for i in x ])182 msg = "Rg expected positive value, but got %s" % self.radius 183 raise ValueError(msg) 184 value = numpy.array([math.exp(-((self.radius * i) ** 2 / 3)) for i in x]) 184 185 return self.scale * value 185 186 186 187 class PowerLaw(Transform): 187 188 """ 188 class of type transform that perform operation related to power_law 189 class of type transform that perform operation related to power_law 189 190 function 190 191 """ … … 193 194 self.scale = scale 194 195 self.power = power 195 196 self.dscale = 0.0 197 self.dpower = 0.0 198 196 199 def linearize_q_value(self, value): 197 200 """ 198 201 Transform the input q-value for linearization 199 202 200 203 :param value: q-value 201 204 202 205 :return: log(q) 203 206 """ 204 207 return math.log(value) 205 208 206 209 def extract_model_parameters(self, constant, slope, dconstant=0, dslope=0): 207 210 """ 208 Assign new value to the scale and the power 211 Assign new value to the scale and the power 209 212 """ 210 213 self.power = -slope 211 214 self.scale = math.exp(constant) 212 215 213 216 # Errors 214 self.dscale = math.exp(constant) *dconstant217 self.dscale = math.exp(constant) * dconstant 215 218 self.dpower = -dslope 216 219 217 220 return [self.power, self.scale], [self.dpower, self.dscale] 218 221 219 222 def evaluate_model(self, x): 220 223 """ … … 223 226 """ 224 227 return self._power_law(x) 225 228 226 229 def evaluate_model_errors(self, x): 227 230 """ … … 230 233 """ 231 234 p1 = numpy.array([self.dscale * math.pow(q, -self.power) for q in x]) 232 p2 = numpy.array([self.scale * self.power * math.pow(q, -self.power -1)\235 p2 = numpy.array([self.scale * self.power * math.pow(q, -self.power - 1)\ 233 236 * self.dpower for q in x]) 234 diq2 = p1 *p1 + p2*p2235 return numpy.array( [math.sqrt(err) for err in diq2])236 237 diq2 = p1 * p1 + p2 * p2 238 return numpy.array([math.sqrt(err) for err in diq2]) 239 237 240 def _power_law(self, x): 238 241 """ 239 242 F(x) = scale* (x)^(-power) 240 when power= 4. the model is porod 243 when power= 4. the model is porod 241 244 else power_law 242 245 The model has three parameters: :: … … 244 247 2. power: power of the function 245 248 3. scale : scale factor value 246 249 247 250 :param x: array 248 251 :return: F(x) … … 250 253 if self.power <= 0: 251 254 msg = "Power_law function expected positive power," 252 msg += " but got %s" %self.power255 msg += " but got %s" % self.power 253 256 raise ValueError(msg) 254 257 if self.scale <= 0: 255 msg = "scale expected positive value, but got %s" %self.scale256 raise ValueError(msg) 257 258 value = numpy.array([ math.pow(i, -self.power) for i in x ])258 msg = "scale expected positive value, but got %s" % self.scale 259 raise ValueError(msg) 260 261 value = numpy.array([math.pow(i, -self.power) for i in x]) 259 262 return self.scale * value 260 263 261 class Extrapolator :264 class Extrapolator(object): 262 265 """ 263 266 Extrapolate I(q) distribution using a given model … … 266 269 """ 267 270 Determine a and b given a linear equation y = ax + b 268 269 If a model is given, it will be used to linearize the data before 271 272 If a model is given, it will be used to linearize the data before 270 273 the extrapolation is performed. If None, 271 274 a simple linear fit will be done. 272 273 :param data: data containing x and y such as y = ax + b 274 :param model: optional Transform object 275 """ 276 self.data 275 276 :param data: data containing x and y such as y = ax + b 277 :param model: optional Transform object 278 """ 279 self.data = data 277 280 self.model = model 278 281 279 282 # Set qmin as the lowest non-zero value 280 283 self.qmin = Q_MINIMUM 281 284 for q_value in self.data.x: 282 if q_value > 0: 285 if q_value > 0: 283 286 self.qmin = q_value 284 287 break 285 288 self.qmax = max(self.data.x) 286 289 287 290 def fit(self, power=None, qmin=None, qmax=None): 288 291 """ 289 292 Fit data for y = ax + b return a and b 290 293 291 294 :param power: a fixed, otherwise None 292 295 :param qmin: Minimum Q-value … … 297 300 if qmax is None: 298 301 qmax = self.qmax 299 302 300 303 # Identify the bin range for the fit 301 304 idx = (self.data.x >= qmin) & (self.data.x <= qmax) 302 305 303 306 fx = numpy.zeros(len(self.data.x)) 304 307 305 308 # Uncertainty 306 if type(self.data.dy) ==numpy.ndarray and \307 len(self.data.dy) ==len(self.data.x) and \308 numpy.all(self.data.dy >0):309 if type(self.data.dy) == numpy.ndarray and \ 310 len(self.data.dy) == len(self.data.x) and \ 311 numpy.all(self.data.dy > 0): 309 312 sigma = self.data.dy 310 313 else: … … 313 316 # Compute theory data f(x) 314 317 fx[idx] = self.data.y[idx] 315 318 316 319 # Linearize the data 317 320 if self.model is not None: 318 321 linearized_data = self.model.linearize_data(\ 319 322 LoaderData1D(self.data.x[idx], 320 321 dy =sigma[idx]))323 fx[idx], 324 dy=sigma[idx])) 322 325 else: 323 326 linearized_data = LoaderData1D(self.data.x[idx], 324 327 fx[idx], 325 dy =sigma[idx])326 327 ##power is given only for function = power_law 328 dy=sigma[idx]) 329 330 ##power is given only for function = power_law 328 331 if power != None: 329 332 sigma2 = linearized_data.dy * linearized_data.dy 330 333 a = -(power) 331 b = (numpy.sum(linearized_data.y /sigma2) \332 - a *numpy.sum(linearized_data.x/sigma2))/numpy.sum(1.0/sigma2)333 334 335 deltas = linearized_data.x *a + \336 numpy.ones(len(linearized_data.x)) *b-linearized_data.y337 residuals = numpy.sum(deltas *deltas/sigma2)338 339 err = math.fabs(residuals) / numpy.sum(1.0 /sigma2)334 b = (numpy.sum(linearized_data.y / sigma2) \ 335 - a * numpy.sum(linearized_data.x / sigma2)) / numpy.sum(1.0 / sigma2) 336 337 338 deltas = linearized_data.x * a + \ 339 numpy.ones(len(linearized_data.x)) * b - linearized_data.y 340 residuals = numpy.sum(deltas * deltas / sigma2) 341 342 err = math.fabs(residuals) / numpy.sum(1.0 / sigma2) 340 343 return [a, b], [0, math.sqrt(err)] 341 344 else: 342 A = numpy.vstack([ linearized_data.x/linearized_data.dy, 343 1.0/linearized_data.dy]).T 344 (p, residuals, rank, s) = numpy.linalg.lstsq(A, 345 linearized_data.y/linearized_data.dy) 346 345 A = numpy.vstack([linearized_data.x / linearized_data.dy, 1.0 / linearized_data.dy]).T 346 (p, residuals, _, _) = numpy.linalg.lstsq(A, linearized_data.y / linearized_data.dy) 347 347 348 # Get the covariance matrix, defined as inv_cov = a_transposed * a 348 349 err = numpy.zeros(2) … … 354 355 except: 355 356 err = [-1.0, -1.0] 356 357 357 358 return p, err 358 359 359 360 360 361 class InvariantCalculator(object): … … 363 364 Can provide volume fraction and surface area if the user provides 364 365 Porod constant and contrast values. 365 366 366 367 :precondition: the user must send a data of type DataLoader.Data1D 367 368 the user provide background and scale values. 368 369 :note: Some computations depends on each others. 369 370 :note: Some computations depends on each others. 370 371 """ 371 def __init__(self, data, background=0, scale=1 372 def __init__(self, data, background=0, scale=1): 372 373 """ 373 374 Initialize variables. 374 375 375 376 :param data: data must be of type DataLoader.Data1D 376 :param background: Background value. The data will be corrected 377 :param background: Background value. The data will be corrected 377 378 before processing 378 379 :param scale: Scaling factor for I(q). The data will be corrected … … 388 389 self._data = self._get_data(data) 389 390 # get the dxl if the data is smeared: This is done only once on init. 390 if self._data.dxl != None and self._data.dxl.all() > 0:391 if self._data.dxl != None and self._data.dxl.all() > 0: 391 392 # assumes constant dxl 392 393 self._smeared = self._data.dxl[0] 393 394 394 395 # Since there are multiple variants of Q*, you should force the 395 396 # user to use the get method and keep Q* a private data member 396 397 self._qstar = None 397 398 398 399 # You should keep the error on Q* so you can reuse it without 399 400 # recomputing the whole thing. 400 401 self._qstar_err = 0 401 402 402 403 # Extrapolation parameters 403 404 self._low_extrapolation_npts = 4 … … 405 406 self._low_extrapolation_power = None 406 407 self._low_extrapolation_power_fitted = None 407 408 408 409 self._high_extrapolation_npts = 4 409 410 self._high_extrapolation_function = PowerLaw() 410 411 self._high_extrapolation_power = None 411 412 self._high_extrapolation_power_fitted = None 412 413 413 414 # Extrapolation range 414 415 self._low_q_limit = Q_MINIMUM 415 416 416 417 def _get_data(self, data): 417 418 """ 418 419 :note: this function must be call before computing any type 419 420 of invariant 420 421 421 422 :return: new data = self._scale *data - self._background 422 423 """ 423 424 if not issubclass(data.__class__, LoaderData1D): 424 425 #Process only data that inherited from DataLoader.Data_info.Data1D 425 raise ValueError, "Data must be of type DataLoader.Data1D"426 raise ValueError, "Data must be of type DataLoader.Data1D" 426 427 #from copy import deepcopy 427 new_data = (self._scale * data) - self._background 428 428 new_data = (self._scale * data) - self._background 429 429 430 # Check that the vector lengths are equal 430 assert (len(new_data.x)==len(new_data.y))431 431 assert len(new_data.x) == len(new_data.y) 432 432 433 # Verify that the errors are set correctly 433 434 if new_data.dy is None or len(new_data.x) != len(new_data.dy) or \ 434 (min(new_data.dy) ==0 and max(new_data.dy)==0):435 new_data.dy = numpy.ones(len(new_data.x)) 435 (min(new_data.dy) == 0 and max(new_data.dy) == 0): 436 new_data.dy = numpy.ones(len(new_data.x)) 436 437 return new_data 437 438 438 439 def _fit(self, model, qmin=Q_MINIMUM, qmax=Q_MAXIMUM, power=None): 439 440 """ 440 fit data with function using 441 fit data with function using 441 442 data = self._get_data() 442 443 fx = Functor(data , function) 443 444 y = data.y 444 445 slope, constant = linalg.lstsq(y,fx) 445 446 446 447 :param qmin: data first q value to consider during the fit 447 448 :param qmax: data last q value to consider during the fit 448 :param power : power value to consider for power-law 449 :param power : power value to consider for power-law 449 450 :param function: the function to use during the fit 450 451 451 452 :return a: the scale of the function 452 453 :return b: the other parameter of the function for guinier will be radius … … 454 455 """ 455 456 extrapolator = Extrapolator(data=self._data, model=model) 456 p, dp = extrapolator.fit(power=power, qmin=qmin, qmax=qmax) 457 457 p, dp = extrapolator.fit(power=power, qmin=qmin, qmax=qmax) 458 458 459 return model.extract_model_parameters(constant=p[1], slope=p[0], 459 460 dconstant=dp[1], dslope=dp[0]) 460 461 461 462 def _get_qstar(self, data): 462 463 """ 463 464 Compute invariant for pinhole data. 464 465 This invariant is given by: :: 465 466 q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1 466 467 q_star = x0**2 *y0 *dx0 +x1**2 *y1 *dx1 467 468 + ..+ xn**2 *yn *dxn for non smeared data 468 469 q_star = dxl0 *x0 *y0 *dx0 +dxl1 *x1 *y1 *dx1 469 470 q_star = dxl0 *x0 *y0 *dx0 +dxl1 *x1 *y1 *dx1 470 471 + ..+ dlxn *xn *yn *dxn for smeared data 471 472 472 473 where n >= len(data.x)-1 473 474 dxl = slit height dQl … … 475 476 dx0 = (x1 - x0)/2 476 477 dxn = (xn - xn-1)/2 477 478 478 479 :param data: the data to use to compute invariant. 479 480 480 481 :return q_star: invariant value for pinhole data. q_star > 0 481 482 """ 482 if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x)!= len(data.y): 483 msg = "Length x and y must be equal" 484 msg += " and greater than 1; got x=%s, y=%s"%(len(data.x), 485 len(data.y)) 483 if len(data.x) <= 1 or len(data.y) <= 1 or len(data.x) != len(data.y): 484 msg = "Length x and y must be equal" 485 msg += " and greater than 1; got x=%s, y=%s" % (len(data.x), len(data.y)) 486 486 raise ValueError, msg 487 487 else: … … 490 490 gx = data.x * data.x 491 491 # assumes that len(x) == len(dxl). 492 else: 493 gx = data.dxl * data.x 494 495 n = len(data.x) - 1492 else: 493 gx = data.dxl * data.x 494 495 n = len(data.x) - 1 496 496 #compute the first delta q 497 dx0 = (data.x[1] - data.x[0]) /2497 dx0 = (data.x[1] - data.x[0]) / 2 498 498 #compute the last delta q 499 dxn = (data.x[n] - data.x[n -1])/2500 sum= 0501 sum+= gx[0] * data.y[0] * dx0502 sum+= gx[n] * data.y[n] * dxn503 499 dxn = (data.x[n] - data.x[n - 1]) / 2 500 total = 0 501 total += gx[0] * data.y[0] * dx0 502 total += gx[n] * data.y[n] * dxn 503 504 504 if len(data.x) == 2: 505 return sum505 return total 506 506 else: 507 507 #iterate between for element different 508 508 #from the first and the last 509 for i in xrange(1, n -1):510 dxi = (data.x[i +1] - data.x[i-1])/2511 sum+= gx[i] * data.y[i] * dxi512 return sum513 509 for i in xrange(1, n - 1): 510 dxi = (data.x[i + 1] - data.x[i - 1]) / 2 511 total += gx[i] * data.y[i] * dxi 512 return total 513 514 514 def _get_qstar_uncertainty(self, data): 515 515 """ 516 516 Compute invariant uncertainty with with pinhole data. 517 517 This uncertainty is given as follow: :: 518 518 519 519 dq_star = math.sqrt[(x0**2*(dy0)*dx0)**2 + 520 520 (x1**2 *(dy1)*dx1)**2 + ..+ (xn**2 *(dyn)*dxn)**2 ] … … 524 524 dxn = (xn - xn-1)/2 525 525 dyn: error on dy 526 526 527 527 :param data: 528 528 :note: if data doesn't contain dy assume dy= math.sqrt(data.y) 529 """ 529 """ 530 530 if len(data.x) <= 1 or len(data.y) <= 1 or \ 531 531 len(data.x) != len(data.y) or \ 532 532 (data.dy is not None and (len(data.dy) != len(data.y))): 533 533 msg = "Length of data.x and data.y must be equal" 534 msg += " and greater than 1; got x=%s, y=%s"%(len(data.x), 535 len(data.y)) 534 msg += " and greater than 1; got x=%s, y=%s" % (len(data.x), len(data.y)) 536 535 raise ValueError, msg 537 536 else: 538 537 #Create error for data without dy error 539 538 if data.dy is None: 540 dy = math.sqrt(data.y) 539 dy = math.sqrt(data.y) 541 540 else: 542 541 dy = data.dy … … 547 546 else: 548 547 gx = data.dxl * data.x 549 548 550 549 n = len(data.x) - 1 551 550 #compute the first delta 552 dx0 = (data.x[1] - data.x[0]) /2551 dx0 = (data.x[1] - data.x[0]) / 2 553 552 #compute the last delta 554 dxn = (data.x[n] - data.x[n-1])/2555 sum= 0556 sum += (gx[0] * dy[0] * dx0)**2557 sum += (gx[n] * dy[n] * dxn)**2553 dxn = (data.x[n] - data.x[n - 1]) / 2 554 total = 0 555 total += (gx[0] * dy[0] * dx0) ** 2 556 total += (gx[n] * dy[n] * dxn) ** 2 558 557 if len(data.x) == 2: 559 return math.sqrt( sum)558 return math.sqrt(total) 560 559 else: 561 560 #iterate between for element different 562 561 #from the first and the last 563 for i in xrange(1, n -1):564 dxi = (data.x[i +1] - data.x[i-1])/2565 sum += (gx[i] * dy[i] * dxi)**2566 return math.sqrt( sum)567 562 for i in xrange(1, n - 1): 563 dxi = (data.x[i + 1] - data.x[i - 1]) / 2 564 total += (gx[i] * dy[i] * dxi) ** 2 565 return math.sqrt(total) 566 568 567 def _get_extrapolated_data(self, model, npts=INTEGRATION_NSTEPS, 569 q_start=Q_MINIMUM, q_end=Q_MAXIMUM):568 q_start=Q_MINIMUM, q_end=Q_MAXIMUM): 570 569 """ 571 570 :return: extrapolate data create from data … … 578 577 iq = model.evaluate_model(q) 579 578 diq = model.evaluate_model_errors(q) 580 579 581 580 result_data = LoaderData1D(x=q, y=iq, dy=diq) 582 581 if self._smeared != None: 583 582 result_data.dxl = self._smeared * numpy.ones(len(q)) 584 583 return result_data 585 584 586 585 def get_data(self): 587 586 """ … … 589 588 """ 590 589 return self._data 591 590 592 591 def get_extrapolation_power(self, range='high'): 593 592 """ … … 598 597 return self._low_extrapolation_power_fitted 599 598 return self._high_extrapolation_power_fitted 600 599 601 600 def get_qstar_low(self): 602 601 """ 603 602 Compute the invariant for extrapolated data at low q range. 604 603 605 604 Implementation: 606 605 data = self._get_extra_data_low() 607 606 return self._get_qstar() 608 607 609 608 :return q_star: the invariant for data extrapolated at low q. 610 609 """ … … 612 611 qmin = self._data.x[0] 613 612 qmax = self._data.x[self._low_extrapolation_npts - 1] 614 613 615 614 # Extrapolate the low-Q data 616 p, dp= self._fit(model=self._low_extrapolation_function,617 618 619 615 p, _ = self._fit(model=self._low_extrapolation_function, 616 qmin=qmin, 617 qmax=qmax, 618 power=self._low_extrapolation_power) 620 619 self._low_extrapolation_power_fitted = p[0] 621 620 622 621 # Distribution starting point 623 622 self._low_q_limit = Q_MINIMUM 624 623 if Q_MINIMUM >= qmin: 625 self._low_q_limit = qmin /10626 624 self._low_q_limit = qmin / 10 625 627 626 data = self._get_extrapolated_data(\ 628 627 model=self._low_extrapolation_function, 629 630 631 628 npts=INTEGRATION_NSTEPS, 629 q_start=self._low_q_limit, q_end=qmin) 630 632 631 # Systematic error 633 632 # If we have smearing, the shape of the I(q) distribution at low Q will 634 # may not be a Guinier or simple power law. The following is 633 # may not be a Guinier or simple power law. The following is 635 634 # a conservative estimation for the systematic error. 636 err = qmin *qmin*math.fabs((qmin-self._low_q_limit)*\637 (data.y[0] - data.y[INTEGRATION_NSTEPS -1]))638 return self._get_qstar(data), self._get_qstar_uncertainty(data) +err639 635 err = qmin * qmin * math.fabs((qmin - self._low_q_limit) * \ 636 (data.y[0] - data.y[INTEGRATION_NSTEPS - 1])) 637 return self._get_qstar(data), self._get_qstar_uncertainty(data) + err 638 640 639 def get_qstar_high(self): 641 640 """ 642 641 Compute the invariant for extrapolated data at high q range. 643 642 644 643 Implementation: 645 644 data = self._get_extra_data_high() 646 645 return self._get_qstar() 647 646 648 647 :return q_star: the invariant for data extrapolated at high q. 649 648 """ … … 652 651 qmin = self._data.x[x_len - (self._high_extrapolation_npts - 1)] 653 652 qmax = self._data.x[x_len] 654 653 655 654 # fit the data with a model to get the appropriate parameters 656 p, dp= self._fit(model=self._high_extrapolation_function,657 658 659 655 p, _ = self._fit(model=self._high_extrapolation_function, 656 qmin=qmin, 657 qmax=qmax, 658 power=self._high_extrapolation_power) 660 659 self._high_extrapolation_power_fitted = p[0] 661 660 662 661 #create new Data1D to compute the invariant 663 662 data = self._get_extrapolated_data(\ 664 663 model=self._high_extrapolation_function, 665 666 q_start=qmax, q_end=Q_MAXIMUM)667 664 npts=INTEGRATION_NSTEPS, 665 q_start=qmax, q_end=Q_MAXIMUM) 666 668 667 return self._get_qstar(data), self._get_qstar_uncertainty(data) 669 668 670 669 def get_extra_data_low(self, npts_in=None, q_start=None, npts=20): 671 670 """ 672 671 Returns the extrapolated data used for the loew-Q invariant calculation. 673 By default, the distribution will cover the data points used for the 672 By default, the distribution will cover the data points used for the 674 673 extrapolation. The number of overlap points is a parameter (npts_in). 675 By default, the maximum q-value of the distribution will be 676 the minimum q-value used when extrapolating for the purpose of the 677 invariant calculation. 678 674 By default, the maximum q-value of the distribution will be 675 the minimum q-value used when extrapolating for the purpose of the 676 invariant calculation. 677 679 678 :param npts_in: number of data points for which 680 679 the extrapolated data overlap 681 680 :param q_start: is the minimum value to uses for extrapolated data 682 681 :param npts: the number of points in the extrapolated distribution 683 682 684 683 """ 685 684 # Get extrapolation range 686 685 if q_start is None: 687 686 q_start = self._low_q_limit 688 687 689 688 if npts_in is None: 690 689 npts_in = self._low_extrapolation_npts 691 q_end = self._data.x[max(0, npts_in -1)]692 690 q_end = self._data.x[max(0, npts_in - 1)] 691 693 692 if q_start >= q_end: 694 693 return numpy.zeros(0), numpy.zeros(0) … … 696 695 return self._get_extrapolated_data(\ 697 696 model=self._low_extrapolation_function, 698 699 700 697 npts=npts, 698 q_start=q_start, q_end=q_end) 699 701 700 def get_extra_data_high(self, npts_in=None, q_end=Q_MAXIMUM, npts=20): 702 701 """ 703 702 Returns the extrapolated data used for the high-Q invariant calculation. 704 By default, the distribution will cover the data points used for the 703 By default, the distribution will cover the data points used for the 705 704 extrapolation. The number of overlap points is a parameter (npts_in). 706 By default, the maximum q-value of the distribution will be Q_MAXIMUM, 707 the maximum q-value used when extrapolating for the purpose of the 708 invariant calculation. 709 705 By default, the maximum q-value of the distribution will be Q_MAXIMUM, 706 the maximum q-value used when extrapolating for the purpose of the 707 invariant calculation. 708 710 709 :param npts_in: number of data points for which the 711 710 extrapolated data overlap … … 717 716 npts_in = self._high_extrapolation_npts 718 717 _npts = len(self._data.x) 719 q_start = self._data.x[min(_npts, _npts -npts_in)]720 718 q_start = self._data.x[min(_npts, _npts - npts_in)] 719 721 720 if q_start >= q_end: 722 721 return numpy.zeros(0), numpy.zeros(0) 723 722 724 723 return self._get_extrapolated_data(\ 725 724 model=self._high_extrapolation_function, 726 727 728 725 npts=npts, 726 q_start=q_start, q_end=q_end) 727 729 728 def set_extrapolation(self, range, npts=4, function=None, power=None): 730 729 """ 731 730 Set the extrapolation parameters for the high or low Q-range. 732 731 Note that this does not turn extrapolation on or off. 733 732 734 733 :param range: a keyword set the type of extrapolation . type string 735 734 :param npts: the numbers of q points of data to consider … … 739 738 of type string. 740 739 :param power: an power to apply power_low function 741 740 742 741 """ 743 742 range = range.lower() … … 748 747 msg = "Extrapolation function should be 'guinier' or 'power_law'" 749 748 raise ValueError, msg 750 749 751 750 if range == 'high': 752 751 if function != 'power_law': 753 752 msg = "Extrapolation only allows a power law at high Q" 754 753 raise ValueError, msg 755 self._high_extrapolation_npts 754 self._high_extrapolation_npts = npts 756 755 self._high_extrapolation_power = power 757 756 self._high_extrapolation_power_fitted = power … … 761 760 else: 762 761 self._low_extrapolation_function = Guinier() 763 self._low_extrapolation_npts 762 self._low_extrapolation_npts = npts 764 763 self._low_extrapolation_power = power 765 764 self._low_extrapolation_power_fitted = power 766 765 767 766 def get_qstar(self, extrapolation=None): 768 767 """ 769 768 Compute the invariant of the local copy of data. 770 771 :param extrapolation: string to apply optional extrapolation 772 769 770 :param extrapolation: string to apply optional extrapolation 771 773 772 :return q_star: invariant of the data within data's q range 774 773 775 774 :warning: When using setting data to Data1D , 776 775 the user is responsible of 777 776 checking that the scale and the background are 778 777 properly apply to the data 779 778 780 779 """ 781 780 self._qstar = self._get_qstar(self._data) 782 781 self._qstar_err = self._get_qstar_uncertainty(self._data) 783 782 784 783 if extrapolation is None: 785 784 return self._qstar 786 785 787 786 # Compute invariant plus invariant of extrapolated data 788 extrapolation = extrapolation.lower() 787 extrapolation = extrapolation.lower() 789 788 if extrapolation == "low": 790 789 qs_low, dqs_low = self.get_qstar_low() 791 qs_hi, dqs_hi 792 790 qs_hi, dqs_hi = 0, 0 791 793 792 elif extrapolation == "high": 794 793 qs_low, dqs_low = 0, 0 795 qs_hi, dqs_hi 796 794 qs_hi, dqs_hi = self.get_qstar_high() 795 797 796 elif extrapolation == "both": 798 797 qs_low, dqs_low = self.get_qstar_low() 799 qs_hi, dqs_hi 800 801 self._qstar 802 self._qstar_err = math.sqrt(self._qstar_err *self._qstar_err \803 + dqs_low *dqs_low + dqs_hi*dqs_hi)804 798 qs_hi, dqs_hi = self.get_qstar_high() 799 800 self._qstar += qs_low + qs_hi 801 self._qstar_err = math.sqrt(self._qstar_err * self._qstar_err \ 802 + dqs_low * dqs_low + dqs_hi * dqs_hi) 803 805 804 return self._qstar 806 805 807 806 def get_surface(self, contrast, porod_const, extrapolation=None): 808 807 """ 809 808 Compute the specific surface from the data. 810 809 811 810 Implementation:: 812 811 813 812 V = self.get_volume_fraction(contrast, extrapolation) 814 813 815 814 Compute the surface given by: 816 815 surface = (2*pi *V(1- V)*porod_const)/ q_star 817 816 818 817 :param contrast: contrast value to compute the volume 819 :param porod_const: Porod constant to compute the surface 818 :param porod_const: Porod constant to compute the surface 820 819 :param extrapolation: string to apply optional extrapolation 821 822 :return: specific surface 820 821 :return: specific surface 823 822 """ 824 823 # Compute the volume 825 824 volume = self.get_volume_fraction(contrast, extrapolation) 826 return 2 * math.pi * volume * (1 - volume) * \827 float(porod_const) /self._qstar828 825 return 2 * math.pi * volume * (1 - volume) * \ 826 float(porod_const) / self._qstar 827 829 828 def get_volume_fraction(self, contrast, extrapolation=None): 830 829 """ 831 830 Compute volume fraction is deduced as follow: :: 832 831 833 832 q_star = 2*(pi*contrast)**2* volume( 1- volume) 834 833 for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2) … … 837 836 volume1 = (1- sqrt(1- 4*k))/2 838 837 volume2 = (1+ sqrt(1- 4*k))/2 839 838 840 839 q_star: the invariant value included extrapolation is applied 841 840 unit 1/A^(3)*1/cm 842 841 q_star = self.get_qstar() 843 842 844 843 the result returned will be 0 <= volume <= 1 845 844 846 845 :param contrast: contrast value provides by the user of type float. 847 846 contrast unit is 1/A^(2)= 10^(16)cm^(2) 848 847 :param extrapolation: string to apply optional extrapolation 849 848 850 849 :return: volume fraction 851 850 852 851 :note: volume fraction must have no unit 853 852 """ 854 853 if contrast <= 0: 855 raise ValueError, "The contrast parameter must be greater than zero" 856 854 raise ValueError, "The contrast parameter must be greater than zero" 855 857 856 # Make sure Q star is up to date 858 857 self.get_qstar(extrapolation) 859 858 860 859 if self._qstar <= 0: 861 860 msg = "Invalid invariant: Invariant Q* must be greater than zero" 862 861 raise RuntimeError, msg 863 862 864 863 # Compute intermediate constant 865 k = 1.e-8 * self._qstar/(2 * (math.pi * math.fabs(float(contrast)))**2)864 k = 1.e-8 * self._qstar / (2 * (math.pi * math.fabs(float(contrast))) ** 2) 866 865 # Check discriminant value 867 866 discrim = 1 - 4 * k 868 867 869 868 # Compute volume fraction 870 869 if discrim < 0: … … 872 871 raise RuntimeError, msg 873 872 elif discrim == 0: 874 return 1 /2873 return 1 / 2 875 874 else: 876 875 volume1 = 0.5 * (1 - math.sqrt(discrim)) 877 876 volume2 = 0.5 * (1 + math.sqrt(discrim)) 878 877 879 878 if 0 <= volume1 and volume1 <= 1: 880 879 return volume1 881 elif 0 <= volume2 and volume2 <= 1: 882 return volume2 880 elif 0 <= volume2 and volume2 <= 1: 881 return volume2 883 882 msg = "Could not compute the volume fraction: inconsistent results" 884 883 raise RuntimeError, msg 885 884 886 885 def get_qstar_with_error(self, extrapolation=None): 887 886 """ … … 889 888 This uncertainty computation depends on whether or not the data is 890 889 smeared. 891 890 892 891 :param extrapolation: string to apply optional extrapolation 893 892 894 893 :return: invariant, the invariant uncertainty 895 """ 894 """ 896 895 self.get_qstar(extrapolation) 897 896 return self._qstar, self._qstar_err 898 897 899 898 def get_volume_fraction_with_error(self, contrast, extrapolation=None): 900 899 """ 901 900 Compute uncertainty on volume value as well as the volume fraction 902 901 This uncertainty is given by the following equation: :: 903 902 904 903 dV = 0.5 * (4*k* dq_star) /(2* math.sqrt(1-k* q_star)) 905 904 906 905 for k = 10^(-8)*q_star/(2*(pi*|contrast|)**2) 907 906 908 907 q_star: the invariant value including extrapolated value if existing 909 908 dq_star: the invariant uncertainty 910 909 dV: the volume uncertainty 911 910 912 911 The uncertainty will be set to -1 if it can't be computed. 913 914 :param contrast: contrast value 912 913 :param contrast: contrast value 915 914 :param extrapolation: string to apply optional extrapolation 916 915 917 916 :return: V, dV = volume fraction, error on volume fraction 918 917 """ 919 918 volume = self.get_volume_fraction(contrast, extrapolation) 920 919 921 920 # Compute error 922 k = 1.e-8 * self._qstar /(2 * (math.pi* math.fabs(float(contrast)))**2)921 k = 1.e-8 * self._qstar / (2 * (math.pi * math.fabs(float(contrast))) ** 2) 923 922 # Check value inside the sqrt function 924 923 value = 1 - k * self._qstar … … 927 926 # Compute uncertainty 928 927 uncertainty = math.fabs((0.5 * 4 * k * \ 929 self._qstar_err) /(2 * math.sqrt(1 - k * self._qstar)))930 928 self._qstar_err) / (2 * math.sqrt(1 - k * self._qstar))) 929 931 930 return volume, uncertainty 932 931 933 932 def get_surface_with_error(self, contrast, porod_const, extrapolation=None): 934 933 """ 935 934 Compute uncertainty of the surface value as well as the surface value. 936 935 The uncertainty is given as follow: :: 937 936 938 937 dS = porod_const *2*pi[( dV -2*V*dV)/q_star 939 938 + dq_star(v-v**2) 940 939 941 940 q_star: the invariant value 942 941 dq_star: the invariant uncertainty 943 942 V: the volume fraction value 944 943 dV: the volume uncertainty 945 944 946 945 :param contrast: contrast value 947 :param porod_const: porod constant value 946 :param porod_const: porod constant value 948 947 :param extrapolation: string to apply optional extrapolation 949 948 950 949 :return S, dS: the surface, with its uncertainty 951 950 """ … … 956 955 v, dv = self.get_volume_fraction_with_error(contrast, extrapolation) 957 956 958 s = self.get_surface(contrast=contrast, porod_const=porod_const, 957 s = self.get_surface(contrast=contrast, porod_const=porod_const, 959 958 extrapolation=extrapolation) 960 ds = porod_const * 2 * math.pi * (( dv - 2 * v * dv)/ self._qstar\961 + self._qstar_err * ( v - v**2))959 ds = porod_const * 2 * math.pi * ((dv - 2 * v * dv) / self._qstar\ 960 + self._qstar_err * (v - v ** 2)) 962 961 963 962 return s, ds -
src/sas/pr/invertor.py
r5f8fc78 rc76bdc3 110 110 slit_height = None 111 111 slit_width = None 112 ## Error 113 err = 0 114 ## Data to invert 115 x = None 116 y = None 112 117 113 118 def __init__(self): 114 119 Cinvertor.__init__(self) 115 self.err = None 116 self.d_max = None 120 self.d_max = 200.0 117 121 self.q_min = self.set_qmin(-1.0) 118 122 self.q_max = self.set_qmax(-1.0) 119 self.x = None120 self.y = None121 123 self.has_bck = False 122 124
Note: See TracChangeset
for help on using the changeset viewer.