source: sasview/src/sas/pr/invertor.py @ 3350ad6

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 3350ad6 was 3350ad6, checked in by Doucet, Mathieu <doucetm@…>, 10 years ago

pylint fixes

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