- Timestamp:
- Mar 4, 2015 11:56:03 AM (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:
- 5f8fc78
- Parents:
- 091e71a2
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/pr/invertor.py
r79492222 r3350ad6 1 # pylint: disable=invalid-name 1 2 """ 2 3 Module to perform P(r) inversion. … … 11 12 import os 12 13 import re 14 import logging 13 15 from numpy.linalg import lstsq 14 16 from scipy import optimize … … 20 22 Future work: extend this function to allow topic selection 21 23 """ 22 info_txt 24 info_txt = "The inversion approach is based on Moore, J. Appl. Cryst. " 23 25 info_txt += "(1980) 13, 168-175.\n\n" 24 26 info_txt += "P(r) is set to be equal to an expansion of base functions " … … 47 49 info_txt += " - Maximum distance: the maximum distance between any " 48 50 info_txt += "two points in the system.\n" 49 51 50 52 return info_txt 51 53 52 54 53 55 class Invertor(Cinvertor): 54 56 """ 55 57 Invertor class to perform P(r) inversion 56 58 57 59 The problem is solved by posing the problem as Ax = b, 58 60 where x is the set of coefficients we are looking for. 59 61 60 62 Npts is the number of points. 61 63 62 64 In the following i refers to the ith base function coefficient. 63 65 The matrix has its entries j in its first Npts rows set to :: 64 66 65 67 A[j][i] = (Fourier transformed base function for point j) 66 68 67 69 We them choose a number of r-points, n_r, to evaluate the second 68 70 derivative of P(r) at. This is used as our regularization term. … … 71 73 A[j+Npts][i] = (2nd derivative of P(r), d**2(P(r))/d(r)**2, 72 74 evaluated at r[j]) 73 75 74 76 The vector b has its first Npts entries set to :: 75 77 76 78 b[j] = (I(q) observed for point j) 77 79 78 80 The following n_r entries are set to zero. 79 81 80 82 The result is found by using scipy.linalg.basic.lstsq to invert 81 83 the matrix and find the coefficients x. 82 84 83 85 Methods inherited from Cinvertor: 84 86 … … 89 91 """ 90 92 ## Chisqr of the last computation 91 chi2 93 chi2 = 0 92 94 ## Time elapsed for last computation 93 95 elapsed = 0 94 96 ## Alpha to get the reg term the same size as the signal 95 97 suggested_alpha = 0 98 alpha = 0 96 99 ## Last number of base functions used 97 100 nfunc = 10 … … 104 107 ## Information dictionary for application use 105 108 info = {} 106 109 107 110 def __init__(self): 108 111 Cinvertor.__init__(self) 109 112 self.slit_height = None 113 self.slit_width = None 114 self.err = None 115 self.d_max = None 116 self.q_min = self.set_qmin(-1.0) 117 self.q_max = self.set_qmax(-1.0) 118 self.x = None 119 self.y = None 120 self.has_bck = False 121 110 122 def __setstate__(self, state): 111 123 """ … … 117 129 self.err, self.has_bck, 118 130 self.slit_height, self.slit_width) = state 119 131 120 132 def __reduce_ex__(self, proto): 121 133 """ … … 129 141 self.err, self.has_bck, 130 142 self.slit_height, self.slit_width, 131 143 ) 132 144 return (Invertor, tuple(), state, None, None) 133 145 134 146 def __setattr__(self, name, value): 135 147 """ … … 172 184 else: 173 185 raise ValueError, "Invertor: has_bck can only be True or False" 174 186 175 187 return Cinvertor.__setattr__(self, name, value) 176 188 177 189 def __getattr__(self, name): 178 190 """ … … 219 231 return self.__dict__[name] 220 232 return None 221 233 222 234 def clone(self): 223 235 """ … … 225 237 """ 226 238 #import copy 227 239 228 240 invertor = Invertor() 229 invertor.chi2 241 invertor.chi2 = self.chi2 230 242 invertor.elapsed = self.elapsed 231 invertor.nfunc 232 invertor.alpha 233 invertor.d_max 234 invertor.q_min 235 invertor.q_max 236 243 invertor.nfunc = self.nfunc 244 invertor.alpha = self.alpha 245 invertor.d_max = self.d_max 246 invertor.q_min = self.q_min 247 invertor.q_max = self.q_max 248 237 249 invertor.x = self.x 238 250 invertor.y = self.y … … 241 253 invertor.slit_height = self.slit_height 242 254 invertor.slit_width = self.slit_width 243 255 244 256 invertor.info = copy.deepcopy(self.info) 245 257 246 258 return invertor 247 259 248 260 def invert(self, nfunc=10, nr=20): 249 261 """ 250 262 Perform inversion to P(r) 251 263 252 264 The problem is solved by posing the problem as Ax = b, 253 265 where x is the set of coefficients we are looking for. 254 266 255 267 Npts is the number of points. 256 268 257 269 In the following i refers to the ith base function coefficient. 258 270 The matrix has its entries j in its first Npts rows set to :: 259 271 260 272 A[i][j] = (Fourier transformed base function for point j) 261 273 262 274 We them choose a number of r-points, n_r, to evaluate the second 263 275 derivative of P(r) at. This is used as our regularization term. … … 265 277 266 278 A[i+Npts][j] = (2nd derivative of P(r), d**2(P(r))/d(r)**2, evaluated at r[j]) 267 279 268 280 The vector b has its first Npts entries set to :: 269 281 270 282 b[j] = (I(q) observed for point j) 271 283 272 284 The following n_r entries are set to zero. 273 285 274 286 The result is found by using scipy.linalg.basic.lstsq to invert 275 287 the matrix and find the coefficients x. 276 288 277 289 :param nfunc: number of base functions to use. 278 290 :param nr: number of r points to evaluate the 2nd derivative at for the reg. term. … … 282 294 self.background = 0.0 283 295 return self.lstsq(nfunc, nr=nr) 284 296 285 297 def iq(self, out, q): 286 298 """ 287 299 Function to call to evaluate the scattering intensity 288 300 289 301 :param args: c-parameters, and q 290 302 :return: I(q) 291 303 292 304 """ 293 305 return Cinvertor.iq(self, out, q) + self.background 294 306 295 307 def invert_optimize(self, nfunc=10, nr=20): 296 308 """ 297 309 Slower version of the P(r) inversion that uses scipy.optimize.leastsq. 298 310 299 311 This probably produce more reliable results, but is much slower. 300 312 The minimization function is set to … … 303 315 the square of the first derivative 304 316 of P(r), d(P(r))/dr, integrated over the full range of r. 305 317 306 318 :param nfunc: number of base functions to use. 307 319 :param nr: number of r points to evaluate the 2nd derivative at 308 320 for the reg. term. 309 321 310 322 :return: c_out, c_cov - the coefficients with covariance matrix 311 323 312 324 """ 313 325 self.nfunc = nfunc … … 316 328 msg = "Invertor.invert: Data array are of different length" 317 329 raise RuntimeError, msg 318 330 319 331 p = numpy.ones(nfunc) 320 332 t_0 = time.time() 321 out, cov_x, _, _, _ = optimize.leastsq(self.residuals, 322 p, full_output=1) 323 333 out, cov_x, _, _, _ = optimize.leastsq(self.residuals, p, full_output=1) 334 324 335 # Compute chi^2 325 336 res = self.residuals(out) … … 327 338 for i in range(len(res)): 328 339 chisqr += res[i] 329 340 330 341 self.chi2 = chisqr 331 342 332 343 # Store computation time 333 344 self.elapsed = time.time() - t_0 334 345 335 346 if cov_x is None: 336 347 cov_x = numpy.ones([nfunc, nfunc]) 337 348 cov_x *= math.fabs(chisqr) 338 349 return out, cov_x 339 350 340 351 def pr_fit(self, nfunc=5): 341 352 """ … … 343 354 is set to some P(r) distribution that we are trying to reproduce 344 355 with a set of base functions. 345 356 346 357 This method is provided as a test. 347 358 """ … … 350 361 msg = "Invertor.invert: Data arrays are of different length" 351 362 raise RuntimeError, msg 352 363 353 364 p = numpy.ones(nfunc) 354 365 t_0 = time.time() 355 out, cov_x, _, _, _ = optimize.leastsq(self.pr_residuals, p, 356 full_output=1) 357 366 out, cov_x, _, _, _ = optimize.leastsq(self.pr_residuals, p, full_output=1) 367 358 368 # Compute chi^2 359 369 res = self.pr_residuals(out) … … 361 371 for i in range(len(res)): 362 372 chisqr += res[i] 363 373 364 374 self.chisqr = chisqr 365 375 366 376 # Store computation time 367 377 self.elapsed = time.time() - t_0 368 378 369 379 return out, cov_x 370 380 371 381 def pr_err(self, c, c_cov, r): 372 382 """ 373 383 Returns the value of P(r) for a given r, and base function 374 384 coefficients, with error. 375 385 376 386 :param c: base function coefficients 377 387 :param c_cov: covariance matrice of the base function coefficients 378 388 :param r: r-value to evaluate P(r) at 379 389 380 390 :return: P(r) 381 391 382 392 """ 383 393 return self.get_pr_err(c, c_cov, r) 384 394 385 395 def _accept_q(self, q): 386 396 """ … … 392 402 return False 393 403 return True 394 404 395 405 def lstsq(self, nfunc=5, nr=20): 396 406 """ 397 407 The problem is solved by posing the problem as Ax = b, 398 408 where x is the set of coefficients we are looking for. 399 409 400 410 Npts is the number of points. 401 411 402 412 In the following i refers to the ith base function coefficient. 403 413 The matrix has its entries j in its first Npts rows set to :: 404 414 405 415 A[i][j] = (Fourier transformed base function for point j) 406 416 407 417 We them choose a number of r-points, n_r, to evaluate the second 408 418 derivative of P(r) at. This is used as our regularization term. … … 411 421 A[i+Npts][j] = (2nd derivative of P(r), d**2(P(r))/d(r)**2, 412 422 evaluated at r[j]) 413 423 414 424 The vector b has its first Npts entries set to :: 415 425 416 426 b[j] = (I(q) observed for point j) 417 427 418 428 The following n_r entries are set to zero. 419 429 420 430 The result is found by using scipy.linalg.basic.lstsq to invert 421 431 the matrix and find the coefficients x. 422 432 423 433 :param nfunc: number of base functions to use. 424 434 :param nr: number of r points to evaluate the 2nd derivative at for the reg. term. … … 431 441 # blah = numpy.ascontiguousarray(blah_original) 432 442 # ... before passing it to C 433 443 434 444 if self.is_valid() < 0: 435 445 msg = "Invertor: invalid data; incompatible data lengths." 436 446 raise RuntimeError, msg 437 447 438 448 self.nfunc = nfunc 439 449 # a -- An M x N matrix. 440 450 # b -- An M x nrhs matrix or M vector. 441 451 npts = len(self.x) 442 nq 452 nq = nr 443 453 sqrt_alpha = math.sqrt(math.fabs(self.alpha)) 444 454 if sqrt_alpha < 0.0: … … 453 463 b = numpy.zeros(npts + nq) 454 464 err = numpy.zeros([nfunc, nfunc]) 455 465 456 466 # Construct the a matrix and b vector that represent the problem 457 467 t_0 = time.time() … … 460 470 except: 461 471 raise RuntimeError, "Invertor: could not invert I(Q)\n %s" % sys.exc_value 462 472 463 473 # Perform the inversion (least square fit) 464 474 c, chi2, _, _ = lstsq(a, b) … … 469 479 chi2 = -1.0 470 480 self.chi2 = chi2 471 481 472 482 inv_cov = numpy.zeros([nfunc, nfunc]) 473 483 # Get the covariance matrix, defined as inv_cov = a_transposed * a 474 484 self._get_invcov_matrix(nfunc, nr, a, inv_cov) 475 485 476 486 # Compute the reg term size for the output 477 487 sum_sig, sum_reg = self._get_reg_size(nfunc, nr, a) 478 488 479 489 if math.fabs(self.alpha) > 0: 480 490 new_alpha = sum_sig / (sum_reg / self.alpha) … … 482 492 new_alpha = 0.0 483 493 self.suggested_alpha = new_alpha 484 494 485 495 try: 486 496 cov = numpy.linalg.pinv(inv_cov) … … 489 499 # We were not able to estimate the errors 490 500 # Return an empty error matrix 491 pass492 501 logging.error(sys.exc_value) 502 493 503 # Keep a copy of the last output 494 504 if self.has_bck == False: … … 498 508 else: 499 509 self.background = c[0] 500 510 501 511 err_0 = numpy.zeros([nfunc, nfunc]) 502 512 c_0 = numpy.zeros(nfunc) 503 513 504 514 for i in range(nfunc_0): 505 c_0[i] = c[i +1]515 c_0[i] = c[i + 1] 506 516 for j in range(nfunc_0): 507 err_0[i][j] = err[i +1][j+1]508 517 err_0[i][j] = err[i + 1][j + 1] 518 509 519 self.out = c_0 510 520 self.cov = err_0 511 521 512 522 # Store computation time 513 523 self.elapsed = time.time() - t_0 514 524 515 525 return self.out, self.cov 516 526 517 527 def estimate_numterms(self, isquit_func=None): 518 528 """ 519 529 Returns a reasonable guess for the 520 530 number of terms 521 531 522 532 :param isquit_func: 523 533 reference to thread function to call to check whether the computation needs to 524 534 be stopped. 525 535 526 536 :return: number of terms, alpha, message 527 537 528 538 """ 529 539 from num_term import Num_terms … … 536 546 best_alpha, _, _ = self.estimate_alpha(self.nfunc) 537 547 return self.nfunc, best_alpha, "Could not estimate number of terms" 538 548 539 549 def estimate_alpha(self, nfunc): 540 550 """ 541 551 Returns a reasonable guess for the 542 552 regularization constant alpha 543 553 544 554 :param nfunc: number of terms to use in the expansion. 545 555 546 556 :return: alpha, message, elapsed 547 557 548 558 where alpha is the estimate for alpha, 549 559 message is a message for the user, … … 553 563 try: 554 564 pr = self.clone() 555 565 556 566 # T_0 for computation time 557 567 starttime = time.time() 558 568 elapsed = 0 559 569 560 570 # If the current alpha is zero, try 561 571 # another value 562 572 if pr.alpha <= 0: 563 573 pr.alpha = 0.0001 564 574 565 575 # Perform inversion to find the largest alpha 566 576 out, _ = pr.invert(nfunc) … … 568 578 initial_alpha = pr.alpha 569 579 initial_peaks = pr.get_peaks(out) 570 580 571 581 # Try the inversion with the estimated alpha 572 582 pr.alpha = pr.suggested_alpha 573 583 out, _ = pr.invert(nfunc) 574 584 575 585 npeaks = pr.get_peaks(out) 576 586 # if more than one peak to start with … … 582 592 return pr.suggested_alpha, message, elapsed 583 593 else: 584 594 585 595 # Look at smaller values 586 596 # We assume that for the suggested alpha, we have 1 peak … … 590 600 found = False 591 601 for i in range(10): 592 pr.alpha = (0.33) **(i+1) * alpha602 pr.alpha = (0.33) ** (i + 1) * alpha 593 603 out, _ = pr.invert(nfunc) 594 604 595 605 peaks = pr.get_peaks(out) 596 606 if peaks > 1: … … 598 608 break 599 609 best_alpha = pr.alpha 600 610 601 611 # If we didn't find a turning point for alpha and 602 612 # the initial alpha already had only one peak, … … 605 615 initial_alpha < best_alpha: 606 616 best_alpha = initial_alpha 607 617 608 618 # Check whether the size makes sense 609 619 message = '' 610 620 611 621 if not found: 612 622 message = None … … 614 624 # best alpha is too big, return a 615 625 # reasonable value 616 message 626 message = "The estimated alpha for your system is too " 617 627 message += "large. " 618 628 message += "Try increasing your maximum distance." 619 629 620 630 return best_alpha, message, elapsed 621 631 622 632 except: 623 633 message = "Invertor.estimate_alpha: %s" % sys.exc_value 624 634 return 0, message, elapsed 625 635 626 636 def to_file(self, path, npts=100): 627 637 """ 628 638 Save the state to a file that will be readable 629 639 by SliceView. 630 640 631 641 :param path: path of the file to write 632 642 :param npts: number of P(r) points to be written 633 643 634 644 """ 635 645 file = open(path, 'w') … … 653 663 for i in range(len(self.out)): 654 664 file.write("#C_%i=%s+-%s\n" % (i, str(self.out[i]), 655 665 str(self.cov[i][i]))) 656 666 file.write("<r> <Pr> <dPr>\n") 657 r = numpy.arange(0.0, self.d_max, self.d_max /npts)658 667 r = numpy.arange(0.0, self.d_max, self.d_max / npts) 668 659 669 for r_i in r: 660 670 (value, err) = self.pr_err(self.out, self.cov, r_i) 661 671 file.write("%g %g %g\n" % (r_i, value, err)) 662 672 663 673 file.close() 664 674 665 675 def from_file(self, path): 666 676 """ … … 668 678 to be able to generate P(r) from a set of 669 679 parameters. 670 680 671 681 :param path: path of the file to load 672 682 673 683 """ 674 684 #import os … … 677 687 try: 678 688 fd = open(path, 'r') 679 689 680 690 buff = fd.read() 681 691 lines = buff.split('\n') … … 728 738 else: 729 739 self.has_bck = False 730 740 731 741 # Now read in the parameters 732 742 elif line.startswith('#C_'): … … 737 747 i = int(m.group(1)) 738 748 self.out[i] = float(toks2[0]) 739 749 740 750 self.cov[i][i] = float(toks2[1]) 741 751 742 752 except: 743 753 msg = "Invertor.from_file: corrupted file\n%s" % sys.exc_value
Note: See TracChangeset
for help on using the changeset viewer.