source: sasview/src/sas/sascalc/pr/invertor.py @ 57a91fc

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 57a91fc was 57a91fc, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

cherrypicked changes from master to sascalc (except PR#190)

  • Property mode set to 100644
File size: 25.6 KB
RevLine 
[3350ad6]1# pylint: disable=invalid-name
[51f14603]2"""
3Module to perform P(r) inversion.
4The module contains the Invertor class.
[bc3e38c]5
6FIXME: The way the Invertor interacts with its C component should be cleaned up
[51f14603]7"""
[57a91fc]8from __future__ import division
[51f14603]9
[9a5097c]10import numpy as np
[51f14603]11import sys
12import math
13import time
14import copy
15import os
16import re
[3350ad6]17import logging
[51f14603]18from numpy.linalg import lstsq
19from scipy import optimize
[b699768]20from sas.sascalc.pr.core.pr_inversion import Cinvertor
[51f14603]21
[463e7ffc]22logger = logging.getLogger(__name__)
[c155a16]23
[51f14603]24def help():
25    """
26    Provide general online help text
27    Future work: extend this function to allow topic selection
28    """
[3350ad6]29    info_txt = "The inversion approach is based on Moore, J. Appl. Cryst. "
[51f14603]30    info_txt += "(1980) 13, 168-175.\n\n"
31    info_txt += "P(r) is set to be equal to an expansion of base functions "
32    info_txt += "of the type "
33    info_txt += "phi_n(r) = 2*r*sin(pi*n*r/D_max). The coefficient of each "
34    info_txt += "base functions "
35    info_txt += "in the expansion is found by performing a least square fit "
36    info_txt += "with the "
37    info_txt += "following fit function:\n\n"
38    info_txt += "chi**2 = sum_i[ I_meas(q_i) - I_th(q_i) ]**2/error**2 +"
39    info_txt += "Reg_term\n\n"
40    info_txt += "where I_meas(q) is the measured scattering intensity and "
41    info_txt += "I_th(q) is "
42    info_txt += "the prediction from the Fourier transform of the P(r) "
43    info_txt += "expansion. "
44    info_txt += "The Reg_term term is a regularization term set to the second"
45    info_txt += " derivative "
46    info_txt += "d**2P(r)/dr**2 integrated over r. It is used to produce "
47    info_txt += "a smooth P(r) output.\n\n"
48    info_txt += "The following are user inputs:\n\n"
49    info_txt += "   - Number of terms: the number of base functions in the P(r)"
50    info_txt += " expansion.\n\n"
51    info_txt += "   - Regularization constant: a multiplicative constant "
52    info_txt += "to set the size of "
53    info_txt += "the regularization term.\n\n"
54    info_txt += "   - Maximum distance: the maximum distance between any "
55    info_txt += "two points in the system.\n"
[3350ad6]56
[51f14603]57    return info_txt
[3350ad6]58
[51f14603]59
60class Invertor(Cinvertor):
61    """
62    Invertor class to perform P(r) inversion
[3350ad6]63
[51f14603]64    The problem is solved by posing the problem as  Ax = b,
65    where x is the set of coefficients we are looking for.
[3350ad6]66
[51f14603]67    Npts is the number of points.
[3350ad6]68
[51f14603]69    In the following i refers to the ith base function coefficient.
70    The matrix has its entries j in its first Npts rows set to ::
71
72        A[j][i] = (Fourier transformed base function for point j)
[3350ad6]73
[6701a0b]74    We then choose a number of r-points, n_r, to evaluate the second
[51f14603]75    derivative of P(r) at. This is used as our regularization term.
76    For a vector r of length n_r, the following n_r rows are set to ::
77
78        A[j+Npts][i] = (2nd derivative of P(r), d**2(P(r))/d(r)**2,
79        evaluated at r[j])
[3350ad6]80
[51f14603]81    The vector b has its first Npts entries set to ::
82
83        b[j] = (I(q) observed for point j)
[3350ad6]84
[51f14603]85    The following n_r entries are set to zero.
[3350ad6]86
[51f14603]87    The result is found by using scipy.linalg.basic.lstsq to invert
88    the matrix and find the coefficients x.
[3350ad6]89
[51f14603]90    Methods inherited from Cinvertor:
91
92    * ``get_peaks(pars)``: returns the number of P(r) peaks
93    * ``oscillations(pars)``: returns the oscillation parameters for the output P(r)
94    * ``get_positive(pars)``: returns the fraction of P(r) that is above zero
95    * ``get_pos_err(pars)``: returns the fraction of P(r) that is 1-sigma above zero
96    """
97    ## Chisqr of the last computation
[3350ad6]98    chi2 = 0
[51f14603]99    ## Time elapsed for last computation
100    elapsed = 0
101    ## Alpha to get the reg term the same size as the signal
102    suggested_alpha = 0
103    ## Last number of base functions used
104    nfunc = 10
105    ## Last output values
106    out = None
107    ## Last errors on output values
108    cov = None
109    ## Background value
110    background = 0
111    ## Information dictionary for application use
112    info = {}
[3350ad6]113
[51f14603]114    def __init__(self):
115        Cinvertor.__init__(self)
[3350ad6]116
[51f14603]117    def __setstate__(self, state):
118        """
119        restore the state of invertor for pickle
120        """
121        (self.__dict__, self.alpha, self.d_max,
122         self.q_min, self.q_max,
123         self.x, self.y,
[cb62bd5]124         self.err, self.est_bck,
[51f14603]125         self.slit_height, self.slit_width) = state
[3350ad6]126
[51f14603]127    def __reduce_ex__(self, proto):
128        """
129        Overwrite the __reduce_ex__
130        """
131
132        state = (self.__dict__,
133                 self.alpha, self.d_max,
134                 self.q_min, self.q_max,
135                 self.x, self.y,
[cb62bd5]136                 self.err, self.est_bck,
[51f14603]137                 self.slit_height, self.slit_width,
[3350ad6]138                )
[51f14603]139        return (Invertor, tuple(), state, None, None)
[3350ad6]140
[51f14603]141    def __setattr__(self, name, value):
142        """
143        Set the value of an attribute.
144        Access the parent class methods for
145        x, y, err, d_max, q_min, q_max and alpha
146        """
[fba204bf]147        if name == 'x':
[51f14603]148            if 0.0 in value:
149                msg = "Invertor: one of your q-values is zero. "
150                msg += "Delete that entry before proceeding"
[574adc7]151                raise ValueError(msg)
[51f14603]152            return self.set_x(value)
153        elif name == 'y':
154            return self.set_y(value)
155        elif name == 'err':
156            value2 = abs(value)
157            return self.set_err(value2)
158        elif name == 'd_max':
[2c60f304]159            if value <= 0.0:
160                msg = "Invertor: d_max must be greater than zero."
161                msg += "Correct that entry before proceeding"
[574adc7]162                raise ValueError(msg)
[51f14603]163            return self.set_dmax(value)
164        elif name == 'q_min':
[235f514]165            if value is None:
[51f14603]166                return self.set_qmin(-1.0)
167            return self.set_qmin(value)
168        elif name == 'q_max':
[235f514]169            if value is None:
[51f14603]170                return self.set_qmax(-1.0)
171            return self.set_qmax(value)
172        elif name == 'alpha':
173            return self.set_alpha(value)
174        elif name == 'slit_height':
175            return self.set_slit_height(value)
176        elif name == 'slit_width':
177            return self.set_slit_width(value)
[cb62bd5]178        elif name == 'est_bck':
[51f14603]179            if value == True:
[cb62bd5]180                return self.set_est_bck(1)
[51f14603]181            elif value == False:
[cb62bd5]182                return self.set_est_bck(0)
[51f14603]183            else:
[574adc7]184                raise ValueError("Invertor: est_bck can only be True or False")
[3350ad6]185
[51f14603]186        return Cinvertor.__setattr__(self, name, value)
[3350ad6]187
[51f14603]188    def __getattr__(self, name):
189        """
190        Return the value of an attribute
191        """
192        #import numpy
193        if name == 'x':
[9a5097c]194            out = np.ones(self.get_nx())
[51f14603]195            self.get_x(out)
196            return out
197        elif name == 'y':
[9a5097c]198            out = np.ones(self.get_ny())
[51f14603]199            self.get_y(out)
200            return out
201        elif name == 'err':
[9a5097c]202            out = np.ones(self.get_nerr())
[51f14603]203            self.get_err(out)
204            return out
205        elif name == 'd_max':
206            return self.get_dmax()
207        elif name == 'q_min':
208            qmin = self.get_qmin()
209            if qmin < 0:
210                return None
211            return qmin
212        elif name == 'q_max':
213            qmax = self.get_qmax()
214            if qmax < 0:
215                return None
216            return qmax
217        elif name == 'alpha':
218            return self.get_alpha()
219        elif name == 'slit_height':
220            return self.get_slit_height()
221        elif name == 'slit_width':
222            return self.get_slit_width()
[cb62bd5]223        elif name == 'est_bck':
224            value = self.get_est_bck()
[b8080e1]225            return value == 1
[51f14603]226        elif name in self.__dict__:
227            return self.__dict__[name]
228        return None
[3350ad6]229
[aed159f]230    def add_errors(self, sigma=0.05):
[fba204bf]231        """
[aed159f]232        Adds errors to data set is they are not available.
233        Uses  $\Delta y = \sigma | y |$.
[fba204bf]234        """
[aed159f]235        self.dy = sigma * np.fabs(self.y)
[fba204bf]236
[51f14603]237    def clone(self):
238        """
239        Return a clone of this instance
240        """
241        #import copy
[3350ad6]242
[51f14603]243        invertor = Invertor()
[3350ad6]244        invertor.chi2 = self.chi2
[51f14603]245        invertor.elapsed = self.elapsed
[3350ad6]246        invertor.nfunc = self.nfunc
247        invertor.alpha = self.alpha
248        invertor.d_max = self.d_max
249        invertor.q_min = self.q_min
250        invertor.q_max = self.q_max
251
[51f14603]252        invertor.x = self.x
253        invertor.y = self.y
[eeea6a3]254        invertor.err = self.err
[cb62bd5]255        invertor.est_bck = self.est_bck
256        invertor.background = self.background
[51f14603]257        invertor.slit_height = self.slit_height
258        invertor.slit_width = self.slit_width
[3350ad6]259
[51f14603]260        invertor.info = copy.deepcopy(self.info)
[3350ad6]261
[51f14603]262        return invertor
[3350ad6]263
[51f14603]264    def invert(self, nfunc=10, nr=20):
265        """
266        Perform inversion to P(r)
[3350ad6]267
[51f14603]268        The problem is solved by posing the problem as  Ax = b,
269        where x is the set of coefficients we are looking for.
[3350ad6]270
[51f14603]271        Npts is the number of points.
[3350ad6]272
[51f14603]273        In the following i refers to the ith base function coefficient.
274        The matrix has its entries j in its first Npts rows set to ::
275
276            A[i][j] = (Fourier transformed base function for point j)
[3350ad6]277
[6701a0b]278        We then choose a number of r-points, n_r, to evaluate the second
[51f14603]279        derivative of P(r) at. This is used as our regularization term.
280        For a vector r of length n_r, the following n_r rows are set to ::
281
282            A[i+Npts][j] = (2nd derivative of P(r), d**2(P(r))/d(r)**2, evaluated at r[j])
[3350ad6]283
[51f14603]284        The vector b has its first Npts entries set to ::
285
286            b[j] = (I(q) observed for point j)
[3350ad6]287
[51f14603]288        The following n_r entries are set to zero.
[3350ad6]289
[51f14603]290        The result is found by using scipy.linalg.basic.lstsq to invert
291        the matrix and find the coefficients x.
[3350ad6]292
[51f14603]293        :param nfunc: number of base functions to use.
294        :param nr: number of r points to evaluate the 2nd derivative at for the reg. term.
295        :return: c_out, c_cov - the coefficients with covariance matrix
296        """
297        # Reset the background value before proceeding
[cb62bd5]298        # self.background = 0.0
299        if not self.est_bck:
300            self.y -= self.background
301        out, cov = self.lstsq(nfunc, nr=nr)
302        if not self.est_bck:
303            self.y += self.background
304        return out, cov
[3350ad6]305
[51f14603]306    def iq(self, out, q):
307        """
308        Function to call to evaluate the scattering intensity
[3350ad6]309
[51f14603]310        :param args: c-parameters, and q
311        :return: I(q)
[3350ad6]312
[51f14603]313        """
314        return Cinvertor.iq(self, out, q) + self.background
[3350ad6]315
[51f14603]316    def invert_optimize(self, nfunc=10, nr=20):
317        """
318        Slower version of the P(r) inversion that uses scipy.optimize.leastsq.
[3350ad6]319
[51f14603]320        This probably produce more reliable results, but is much slower.
321        The minimization function is set to
322        sum_i[ (I_obs(q_i) - I_theo(q_i))/err**2 ] + alpha * reg_term,
323        where the reg_term is given by Svergun: it is the integral of
324        the square of the first derivative
325        of P(r), d(P(r))/dr, integrated over the full range of r.
[3350ad6]326
[51f14603]327        :param nfunc: number of base functions to use.
328        :param nr: number of r points to evaluate the 2nd derivative at
329            for the reg. term.
[3350ad6]330
[51f14603]331        :return: c_out, c_cov - the coefficients with covariance matrix
[3350ad6]332
[51f14603]333        """
334        self.nfunc = nfunc
335        # First, check that the current data is valid
336        if self.is_valid() <= 0:
337            msg = "Invertor.invert: Data array are of different length"
[574adc7]338            raise RuntimeError(msg)
[3350ad6]339
[9a5097c]340        p = np.ones(nfunc)
[51f14603]341        t_0 = time.time()
[3350ad6]342        out, cov_x, _, _, _ = optimize.leastsq(self.residuals, p, full_output=1)
343
[51f14603]344        # Compute chi^2
345        res = self.residuals(out)
346        chisqr = 0
347        for i in range(len(res)):
348            chisqr += res[i]
[3350ad6]349
[51f14603]350        self.chi2 = chisqr
351
352        # Store computation time
353        self.elapsed = time.time() - t_0
[3350ad6]354
[51f14603]355        if cov_x is None:
[9a5097c]356            cov_x = np.ones([nfunc, nfunc])
[51f14603]357            cov_x *= math.fabs(chisqr)
358        return out, cov_x
[3350ad6]359
[51f14603]360    def pr_fit(self, nfunc=5):
361        """
362        This is a direct fit to a given P(r). It assumes that the y data
363        is set to some P(r) distribution that we are trying to reproduce
364        with a set of base functions.
[3350ad6]365
[51f14603]366        This method is provided as a test.
367        """
368        # First, check that the current data is valid
369        if self.is_valid() <= 0:
370            msg = "Invertor.invert: Data arrays are of different length"
[574adc7]371            raise RuntimeError(msg)
[3350ad6]372
[9a5097c]373        p = np.ones(nfunc)
[51f14603]374        t_0 = time.time()
[3350ad6]375        out, cov_x, _, _, _ = optimize.leastsq(self.pr_residuals, p, full_output=1)
376
[51f14603]377        # Compute chi^2
378        res = self.pr_residuals(out)
379        chisqr = 0
380        for i in range(len(res)):
381            chisqr += res[i]
[3350ad6]382
[51f14603]383        self.chisqr = chisqr
[3350ad6]384
[51f14603]385        # Store computation time
386        self.elapsed = time.time() - t_0
387
388        return out, cov_x
[3350ad6]389
[51f14603]390    def pr_err(self, c, c_cov, r):
391        """
392        Returns the value of P(r) for a given r, and base function
393        coefficients, with error.
[3350ad6]394
[51f14603]395        :param c: base function coefficients
396        :param c_cov: covariance matrice of the base function coefficients
397        :param r: r-value to evaluate P(r) at
[3350ad6]398
[51f14603]399        :return: P(r)
[3350ad6]400
[51f14603]401        """
402        return self.get_pr_err(c, c_cov, r)
[3350ad6]403
[51f14603]404    def _accept_q(self, q):
405        """
406        Check q-value against user-defined range
407        """
[ac07a3a]408        if self.q_min is not None and q < self.q_min:
[51f14603]409            return False
[ac07a3a]410        if self.q_max is not None and q > self.q_max:
[51f14603]411            return False
412        return True
[3350ad6]413
[51f14603]414    def lstsq(self, nfunc=5, nr=20):
415        """
416        The problem is solved by posing the problem as  Ax = b,
417        where x is the set of coefficients we are looking for.
[3350ad6]418
[51f14603]419        Npts is the number of points.
[3350ad6]420
[51f14603]421        In the following i refers to the ith base function coefficient.
422        The matrix has its entries j in its first Npts rows set to ::
423
424            A[i][j] = (Fourier transformed base function for point j)
[3350ad6]425
[57a91fc]426        We then choose a number of r-points, n_r, to evaluate the second
[51f14603]427        derivative of P(r) at. This is used as our regularization term.
428        For a vector r of length n_r, the following n_r rows are set to ::
429
430            A[i+Npts][j] = (2nd derivative of P(r), d**2(P(r))/d(r)**2,
431            evaluated at r[j])
[3350ad6]432
[51f14603]433        The vector b has its first Npts entries set to ::
434
435            b[j] = (I(q) observed for point j)
[3350ad6]436
[51f14603]437        The following n_r entries are set to zero.
[3350ad6]438
[51f14603]439        The result is found by using scipy.linalg.basic.lstsq to invert
440        the matrix and find the coefficients x.
[3350ad6]441
[51f14603]442        :param nfunc: number of base functions to use.
443        :param nr: number of r points to evaluate the 2nd derivative at for the reg. term.
444
445        If the result does not allow us to compute the covariance matrix,
446        a matrix filled with zeros will be returned.
447
448        """
449        # Note: To make sure an array is contiguous:
[9a5097c]450        # blah = np.ascontiguousarray(blah_original)
[51f14603]451        # ... before passing it to C
[3350ad6]452
[51f14603]453        if self.is_valid() < 0:
454            msg = "Invertor: invalid data; incompatible data lengths."
[574adc7]455            raise RuntimeError(msg)
[3350ad6]456
[51f14603]457        self.nfunc = nfunc
458        # a -- An M x N matrix.
459        # b -- An M x nrhs matrix or M vector.
460        npts = len(self.x)
[3350ad6]461        nq = nr
[51f14603]462        sqrt_alpha = math.sqrt(math.fabs(self.alpha))
463        if sqrt_alpha < 0.0:
464            nq = 0
465
466        # If we need to fit the background, add a term
[b8080e1]467        if self.est_bck:
[51f14603]468            nfunc_0 = nfunc
469            nfunc += 1
470
[9a5097c]471        a = np.zeros([npts + nq, nfunc])
472        b = np.zeros(npts + nq)
473        err = np.zeros([nfunc, nfunc])
[3350ad6]474
[51f14603]475        # Construct the a matrix and b vector that represent the problem
476        t_0 = time.time()
477        try:
478            self._get_matrix(nfunc, nq, a, b)
[d04ac05]479        except Exception as exc:
480            raise RuntimeError("Invertor: could not invert I(Q)\n  %s" % str(exc))
[3350ad6]481
[51f14603]482        # Perform the inversion (least square fit)
[57a91fc]483        c, chi2, _, _ = lstsq(a, b, rcond=-1)
[51f14603]484        # Sanity check
485        try:
486            float(chi2)
487        except:
[6da860a]488            chi2 = -1.0
[51f14603]489        self.chi2 = chi2
[3350ad6]490
[9a5097c]491        inv_cov = np.zeros([nfunc, nfunc])
[51f14603]492        # Get the covariance matrix, defined as inv_cov = a_transposed * a
493        self._get_invcov_matrix(nfunc, nr, a, inv_cov)
[3350ad6]494
[51f14603]495        # Compute the reg term size for the output
496        sum_sig, sum_reg = self._get_reg_size(nfunc, nr, a)
[3350ad6]497
[51f14603]498        if math.fabs(self.alpha) > 0:
499            new_alpha = sum_sig / (sum_reg / self.alpha)
500        else:
501            new_alpha = 0.0
502        self.suggested_alpha = new_alpha
[3350ad6]503
[51f14603]504        try:
[9a5097c]505            cov = np.linalg.pinv(inv_cov)
[57a91fc]506            err = math.fabs(chi2 / (npts - nfunc)) * cov
507        except Exception as exc:
[51f14603]508            # We were not able to estimate the errors
509            # Return an empty error matrix
[aed159f]510            logger.error(sys.exc_info()[1])
[3350ad6]511
[51f14603]512        # Keep a copy of the last output
[b8080e1]513        if not self.est_bck:
[51f14603]514            self.out = c
515            self.cov = err
516        else:
517            self.background = c[0]
[3350ad6]518
[9a5097c]519            err_0 = np.zeros([nfunc, nfunc])
520            c_0 = np.zeros(nfunc)
[3350ad6]521
[51f14603]522            for i in range(nfunc_0):
[3350ad6]523                c_0[i] = c[i + 1]
[51f14603]524                for j in range(nfunc_0):
[3350ad6]525                    err_0[i][j] = err[i + 1][j + 1]
526
[51f14603]527            self.out = c_0
528            self.cov = err_0
[3350ad6]529
[51f14603]530        # Store computation time
531        self.elapsed = time.time() - t_0
[3350ad6]532
[51f14603]533        return self.out, self.cov
[3350ad6]534
[51f14603]535    def estimate_numterms(self, isquit_func=None):
536        """
537        Returns a reasonable guess for the
538        number of terms
[3350ad6]539
[51f14603]540        :param isquit_func:
541          reference to thread function to call to check whether the computation needs to
542          be stopped.
[3350ad6]543
[51f14603]544        :return: number of terms, alpha, message
[3350ad6]545
[51f14603]546        """
[b8080e1]547        from .num_term import NTermEstimator
[5f8fc78]548        estimator = NTermEstimator(self.clone())
[51f14603]549        try:
550            return estimator.num_terms(isquit_func)
[57a91fc]551        except Exception as exc:
[51f14603]552            # If we fail, estimate alpha and return the default
553            # number of terms
554            best_alpha, _, _ = self.estimate_alpha(self.nfunc)
[aed159f]555            logger.warning("Invertor.estimate_numterms: %s" % sys.exc_info()[1])
[51f14603]556            return self.nfunc, best_alpha, "Could not estimate number of terms"
[3350ad6]557
[51f14603]558    def estimate_alpha(self, nfunc):
559        """
560        Returns a reasonable guess for the
561        regularization constant alpha
[3350ad6]562
[51f14603]563        :param nfunc: number of terms to use in the expansion.
[3350ad6]564
[51f14603]565        :return: alpha, message, elapsed
[3350ad6]566
[51f14603]567        where alpha is the estimate for alpha,
568        message is a message for the user,
569        elapsed is the computation time
570        """
571        #import time
572        try:
573            pr = self.clone()
[3350ad6]574
[51f14603]575            # T_0 for computation time
576            starttime = time.time()
577            elapsed = 0
[3350ad6]578
[51f14603]579            # If the current alpha is zero, try
580            # another value
581            if pr.alpha <= 0:
582                pr.alpha = 0.0001
[3350ad6]583
[51f14603]584            # Perform inversion to find the largest alpha
585            out, _ = pr.invert(nfunc)
586            elapsed = time.time() - starttime
587            initial_alpha = pr.alpha
588            initial_peaks = pr.get_peaks(out)
[3350ad6]589
[51f14603]590            # Try the inversion with the estimated alpha
591            pr.alpha = pr.suggested_alpha
592            out, _ = pr.invert(nfunc)
[3350ad6]593
[51f14603]594            npeaks = pr.get_peaks(out)
595            # if more than one peak to start with
596            # just return the estimate
597            if npeaks > 1:
598                #message = "Your P(r) is not smooth,
599                #please check your inversion parameters"
600                message = None
601                return pr.suggested_alpha, message, elapsed
602            else:
[3350ad6]603
[51f14603]604                # Look at smaller values
605                # We assume that for the suggested alpha, we have 1 peak
606                # if not, send a message to change parameters
607                alpha = pr.suggested_alpha
608                best_alpha = pr.suggested_alpha
609                found = False
610                for i in range(10):
[3350ad6]611                    pr.alpha = (0.33) ** (i + 1) * alpha
[51f14603]612                    out, _ = pr.invert(nfunc)
[3350ad6]613
[51f14603]614                    peaks = pr.get_peaks(out)
615                    if peaks > 1:
616                        found = True
617                        break
618                    best_alpha = pr.alpha
[3350ad6]619
[51f14603]620                # If we didn't find a turning point for alpha and
621                # the initial alpha already had only one peak,
622                # just return that
623                if not found and initial_peaks == 1 and \
624                    initial_alpha < best_alpha:
625                    best_alpha = initial_alpha
[3350ad6]626
[51f14603]627                # Check whether the size makes sense
628                message = ''
[3350ad6]629
[51f14603]630                if not found:
631                    message = None
632                elif best_alpha >= 0.5 * pr.suggested_alpha:
633                    # best alpha is too big, return a
634                    # reasonable value
[3350ad6]635                    message = "The estimated alpha for your system is too "
[51f14603]636                    message += "large. "
637                    message += "Try increasing your maximum distance."
[3350ad6]638
[51f14603]639                return best_alpha, message, elapsed
[3350ad6]640
[b8080e1]641        except:
[aed159f]642            message = "Invertor.estimate_alpha: %s" % sys.exc_info()[1]
[51f14603]643            return 0, message, elapsed
[3350ad6]644
[51f14603]645    def to_file(self, path, npts=100):
646        """
647        Save the state to a file that will be readable
648        by SliceView.
[3350ad6]649
[51f14603]650        :param path: path of the file to write
651        :param npts: number of P(r) points to be written
[3350ad6]652
[51f14603]653        """
654        file = open(path, 'w')
655        file.write("#d_max=%g\n" % self.d_max)
656        file.write("#nfunc=%g\n" % self.nfunc)
657        file.write("#alpha=%g\n" % self.alpha)
658        file.write("#chi2=%g\n" % self.chi2)
659        file.write("#elapsed=%g\n" % self.elapsed)
660        file.write("#qmin=%s\n" % str(self.q_min))
661        file.write("#qmax=%s\n" % str(self.q_max))
662        file.write("#slit_height=%g\n" % self.slit_height)
663        file.write("#slit_width=%g\n" % self.slit_width)
664        file.write("#background=%g\n" % self.background)
[b8080e1]665        if self.est_bck:
[51f14603]666            file.write("#has_bck=1\n")
667        else:
668            file.write("#has_bck=0\n")
669        file.write("#alpha_estimate=%g\n" % self.suggested_alpha)
[45dffa69]670        if self.out is not None:
[51f14603]671            if len(self.out) == len(self.cov):
672                for i in range(len(self.out)):
673                    file.write("#C_%i=%s+-%s\n" % (i, str(self.out[i]),
[3350ad6]674                                                   str(self.cov[i][i])))
[51f14603]675        file.write("<r>  <Pr>  <dPr>\n")
[9a5097c]676        r = np.arange(0.0, self.d_max, self.d_max / npts)
[3350ad6]677
[51f14603]678        for r_i in r:
679            (value, err) = self.pr_err(self.out, self.cov, r_i)
680            file.write("%g  %g  %g\n" % (r_i, value, err))
[3350ad6]681
[51f14603]682        file.close()
[3350ad6]683
[51f14603]684    def from_file(self, path):
685        """
686        Load the state of the Invertor from a file,
687        to be able to generate P(r) from a set of
688        parameters.
[3350ad6]689
[51f14603]690        :param path: path of the file to load
[3350ad6]691
[51f14603]692        """
693        #import os
694        #import re
695        if os.path.isfile(path):
696            try:
697                fd = open(path, 'r')
[3350ad6]698
[51f14603]699                buff = fd.read()
700                lines = buff.split('\n')
701                for line in lines:
702                    if line.startswith('#d_max='):
703                        toks = line.split('=')
704                        self.d_max = float(toks[1])
705                    elif line.startswith('#nfunc='):
706                        toks = line.split('=')
707                        self.nfunc = int(toks[1])
[9a5097c]708                        self.out = np.zeros(self.nfunc)
709                        self.cov = np.zeros([self.nfunc, self.nfunc])
[51f14603]710                    elif line.startswith('#alpha='):
711                        toks = line.split('=')
712                        self.alpha = float(toks[1])
713                    elif line.startswith('#chi2='):
714                        toks = line.split('=')
715                        self.chi2 = float(toks[1])
716                    elif line.startswith('#elapsed='):
717                        toks = line.split('=')
718                        self.elapsed = float(toks[1])
719                    elif line.startswith('#alpha_estimate='):
720                        toks = line.split('=')
721                        self.suggested_alpha = float(toks[1])
722                    elif line.startswith('#qmin='):
723                        toks = line.split('=')
724                        try:
725                            self.q_min = float(toks[1])
726                        except:
727                            self.q_min = None
728                    elif line.startswith('#qmax='):
729                        toks = line.split('=')
730                        try:
731                            self.q_max = float(toks[1])
732                        except:
733                            self.q_max = None
734                    elif line.startswith('#slit_height='):
735                        toks = line.split('=')
736                        self.slit_height = float(toks[1])
737                    elif line.startswith('#slit_width='):
738                        toks = line.split('=')
739                        self.slit_width = float(toks[1])
740                    elif line.startswith('#background='):
741                        toks = line.split('=')
742                        self.background = float(toks[1])
743                    elif line.startswith('#has_bck='):
744                        toks = line.split('=')
[b8080e1]745                        self.est_bck = int(toks[1]) == 1
[3350ad6]746
[51f14603]747                    # Now read in the parameters
748                    elif line.startswith('#C_'):
749                        toks = line.split('=')
750                        p = re.compile('#C_([0-9]+)')
751                        m = p.search(toks[0])
752                        toks2 = toks[1].split('+-')
753                        i = int(m.group(1))
754                        self.out[i] = float(toks2[0])
[3350ad6]755
[51f14603]756                        self.cov[i][i] = float(toks2[1])
[3350ad6]757
[b8080e1]758            except:
[aed159f]759                msg = "Invertor.from_file: corrupted file\n%s" % sys.exc_info()[1]
[574adc7]760                raise RuntimeError(msg)
[51f14603]761        else:
762            msg = "Invertor.from_file: '%s' is not a file" % str(path)
[574adc7]763            raise RuntimeError(msg)
Note: See TracBrowser for help on using the repository browser.