source: sasview/prview/perspectives/pr/pr.py @ f71287f4

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 f71287f4 was 32dffae4, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Added alpha estimator and help

  • Property mode set to 100644
File size: 18.1 KB
Line 
1#TODO: Use simview to generate P(r) and I(q) pairs in sansview.
2# Make sure the option of saving each curve is available
3# Use the I(q) curve as input and compare the output to P(r)
4
5import os
6import wx
7from sans.guitools.plottables import Data1D, Theory1D
8from sans.guicomm.events import NewPlotEvent, StatusEvent   
9import math, numpy
10from sans.pr.invertor import Invertor
11
12class Plugin:
13   
14    def __init__(self):
15        ## Plug-in name
16        self.sub_menu = "Pr inversion"
17       
18        ## Reference to the parent window
19        self.parent = None
20       
21        ## Simulation window manager
22        self.simview = None
23       
24        ## List of panels for the simulation perspective (names)
25        self.perspective = []
26       
27        ## State data
28        self.alpha      = 0.0001
29        self.nfunc      = 10
30        self.max_length = 140.0
31        ## Remember last plottable processed
32        self.last_data  = "sphere_60_q0_2.txt"
33        ## Time elapsed for last computation [sec]
34        # Start with a good default
35        self.elapsed = 0.022
36       
37        ## Current invertor
38        self.invertor    = None
39        ## Calculation thread
40        self.calc_thread = None
41        ## Estimation thread
42        self.estimation_thread = None
43        ## Result panel
44        self.control_panel = None
45        ## Currently views plottable
46        self.current_plottable = None
47
48    def populate_menu(self, id, owner):
49        """
50            Create a menu for the plug-in
51        """
52        import wx
53        shapes = wx.Menu()
54
55        id = wx.NewId()
56        shapes.Append(id, '&Sphere test')
57        wx.EVT_MENU(owner, id, self._fit_pr)
58        return [(id, shapes, "P(r)")]
59   
60    def _fit_pr(self, evt):
61        from sans.pr.invertor import Invertor
62        import numpy
63        import pylab
64        import math
65        from sans.guicomm.events import NewPlotEvent           
66        from sans.guitools.plottables import Data1D, Theory1D
67       
68        # Generate P(r) for sphere
69        radius = 60.0
70        d_max  = 2*radius
71       
72       
73        r = pylab.arange(0.01, d_max, d_max/51.0)
74        M = len(r)
75        y = numpy.zeros(M)
76        pr_err = numpy.zeros(M)
77       
78        sum = 0.0
79        for j in range(M):
80            value = self.pr_theory(r[j], radius)
81            sum += value
82            y[j] = value
83            pr_err[j] = math.sqrt(y[j])
84
85           
86        y = y/sum*d_max/len(r)
87
88
89
90        # Perform fit
91        pr = Invertor()
92        pr.d_max = d_max
93        pr.alpha = 0
94        pr.x = r
95        pr.y = y
96        pr.err = pr_err
97        out, cov = pr.pr_fit()
98        for i in range(len(out)):
99            print "%g +- %g" % (out[i], math.sqrt(cov[i][i]))
100
101
102        # Show input P(r)
103        new_plot = Data1D(pr.x, pr.y, pr.err)
104        new_plot.name = "P_{obs}(r)"
105        new_plot.xaxis("\\rm{r}", 'A')
106        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
107        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Pr"))
108
109        # Show P(r) fit
110        self.show_pr(out, pr)
111       
112        # Show I(q) fit
113        q = pylab.arange(0.001, 0.1, 0.01/51.0)
114        self.show_iq(out, pr, q)
115       
116       
117    def show_shpere(self, x, radius=70.0, x_range=70.0):
118        import numpy
119        import pylab
120        import math
121        from sans.guicomm.events import NewPlotEvent           
122        from sans.guitools.plottables import Data1D, Theory1D
123        # Show P(r)
124        y_true = numpy.zeros(len(x))
125
126        sum_true = 0.0
127        for i in range(len(x)):
128            y_true[i] = self.pr_theory(x[i], radius)           
129            sum_true += y_true[i]
130           
131        y_true = y_true/sum_true*x_range/len(x)
132       
133        # Show the theory P(r)
134        new_plot = Theory1D(x, y_true)
135        new_plot.name = "P_{true}(r)"
136        new_plot.xaxis("\\rm{r}", 'A')
137        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
138       
139       
140        #Put this call in plottables/guitools   
141        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Sphere P(r)"))
142       
143    def show_iq(self, out, pr, q=None):
144        import numpy
145        import pylab
146        import math
147        from sans.guicomm.events import NewPlotEvent           
148        from sans.guitools.plottables import Data1D, Theory1D
149
150        qtemp = pr.x
151        if not q==None:
152            qtemp = q
153
154        # Make a plot
155        maxq = -1
156        for q_i in qtemp:
157            if q_i>maxq:
158                maxq=q_i
159               
160        x = pylab.arange(0.001, maxq, maxq/301.0)
161        y = numpy.zeros(len(x))
162        err = numpy.zeros(len(x))
163        for i in range(len(x)):
164            value = pr.iq(out, x[i])
165            y[i] = value
166            try:
167                err[i] = math.sqrt(math.fabs(value))
168            except:
169                err[i] = 1.0
170                print "Error getting error", value, x[i]
171               
172        new_plot = Theory1D(x, y)
173        new_plot.name = "I_{fit}(q)"
174        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
175        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
176        #new_plot.group_id = "test group"
177        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
178       
179       
180       
181       
182    def show_pr(self, out, pr, cov=None):
183        import numpy
184        import pylab
185        import math
186        from sans.guicomm.events import NewPlotEvent           
187        from sans.guitools.plottables import Data1D, Theory1D
188       
189        # Show P(r)
190        x = pylab.arange(0.0, pr.d_max, pr.d_max/51.0)
191   
192        y = numpy.zeros(len(x))
193        dy = numpy.zeros(len(x))
194        y_true = numpy.zeros(len(x))
195
196        sum = 0.0
197        for i in range(len(x)):
198            if cov==None:
199                value = pr.pr(out, x[i])
200            else:
201                (value, dy[i]) = pr.pr_err(out, cov, x[i])
202            sum += value
203            y[i] = value
204           
205        y = y/sum*pr.d_max/len(x)
206        dy = dy/sum*pr.d_max/len(x)
207       
208        if cov==None:
209            new_plot = Theory1D(x, y)
210        else:
211            new_plot = Data1D(x, y, dy=dy)
212        new_plot.name = "P_{fit}(r)"
213        new_plot.xaxis("\\rm{r}", 'A')
214        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
215           
216        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
217       
218        return x, pr.d_max
219       
220       
221    def choose_file(self):
222        """
223       
224        """
225        #TODO: this should be in a common module
226        return self.parent.choose_file()
227               
228    def load(self, path = "sphere_test_data.txt"):
229        import numpy, math, sys
230        # Read the data from the data file
231        data_x   = numpy.zeros(0)
232        data_y   = numpy.zeros(0)
233        data_err = numpy.zeros(0)
234        if not path == None:
235            input_f = open(path,'r')
236            buff    = input_f.read()
237            lines   = buff.split('\n')
238            for line in lines:
239                try:
240                    toks = line.split()
241                    x = float(toks[0])
242                    y = float(toks[1])
243                    data_x = numpy.append(data_x, x)
244                    data_y = numpy.append(data_y, y)
245                    try:
246                        scale = 0.05/math.sqrt(data_x[0])
247                    except:
248                        scale = 1.0
249                    #data_err = numpy.append(data_err, 10.0*math.sqrt(y)+1000.0)
250                    data_err = numpy.append(data_err, scale*math.sqrt(y))
251                except:
252                    print "Error reading line: ", line
253                    print sys.exc_value
254                   
255        print "Lines read:", len(data_x)
256        return data_x, data_y, data_err
257       
258    def pr_theory(self, r, R):
259        """
260           
261        """
262        if r<=2*R:
263            return 12.0* ((0.5*r/R)**2) * ((1.0-0.5*r/R)**2) * ( 2.0 + 0.5*r/R )
264        else:
265            return 0.0
266
267    def get_context_menu(self, plot_id=None):
268        """
269            Get the context menu items available for P(r)
270            @param plot_id: Unique ID of a plot, so that we can recognize those
271                            that we created
272            @return: a list of menu items with call-back function
273        """
274        return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]
275   
276
277    def start_thread(self):
278        from pr_thread import CalcPr
279        from copy import deepcopy
280       
281        # If a thread is already started, stop it
282        if self.calc_thread != None and self.calc_thread.isrunning():
283            self.calc_thread.stop()
284               
285        pr = self.pr.clone()
286        self.calc_thread = CalcPr(pr, self.nfunc, error_func=self._thread_error, completefn=self._completed, updatefn=None)
287        self.calc_thread.queue()
288        self.calc_thread.ready(2.5)
289   
290    def _thread_error(self, error):
291        wx.PostEvent(self.parent, StatusEvent(status=error))
292   
293    def _estimate_completed(self, alpha, message, elapsed):
294        """
295            Parameter estimation completed,
296            display the results to the user
297            @param alpha: estimated best alpha
298            @param elapsed: computation time
299        """
300        # Save useful info
301        self.elapsed = elapsed
302        self.control_panel.alpha_estimate = alpha
303        if not message==None:
304            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
305   
306    def _completed(self, out, cov, pr, elapsed):
307        """
308            Method called with the results when the inversion
309            is done
310           
311            @param out: output coefficient for the base functions
312            @param cov: covariance matrix
313            @param pr: Invertor instance
314            @param elapsed: time spent computing
315        """
316        # Save useful info
317        self.elapsed = elapsed
318        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
319        wx.PostEvent(self.parent, StatusEvent(status=message))
320
321        # Show result on control panel
322        self.control_panel.chi2 = pr.chi2
323        self.control_panel.elapsed = elapsed
324        self.control_panel.oscillation = pr.oscillations(out)
325        #print "OSCILL", pr.oscillations(out)
326        print "PEAKS:", pr.get_peaks(out)
327       
328        for i in range(len(out)):
329            try:
330                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
331            except: 
332                print "%d: %g +- ?" % (i, out[i])       
333       
334        # Make a plot of I(q) data
335        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
336        new_plot.name = "I_{obs}(q)"
337        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
338        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
339        #new_plot.group_id = "test group"
340        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
341               
342        # Show I(q) fit
343        self.show_iq(out, self.pr)
344       
345        # Show P(r) fit
346        x_values, x_range = self.show_pr(out, self.pr) 
347       
348        # Popup result panel
349        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
350       
351    def setup_plot_inversion(self, alpha, nfunc, d_max):
352        self.alpha = alpha
353        self.nfunc = nfunc
354        self.max_length = d_max
355       
356        self._create_plot_pr()
357        self.perform_inversion()
358
359    def estimate_plot_inversion(self, alpha, nfunc, d_max):
360        self.alpha = alpha
361        self.nfunc = nfunc
362        self.max_length = d_max
363       
364        self._create_plot_pr()
365        self.perform_estimate()
366
367    def _create_plot_pr(self):
368        """
369            Create and prepare invertor instance from
370            a plottable data set.
371            @param path: path of the file to read in
372        """
373        # Get the data from the chosen data set and perform inversion
374        pr = Invertor()
375        pr.d_max = self.max_length
376        pr.alpha = self.alpha
377        pr.x = self.current_plottable.x
378        pr.y = self.current_plottable.y
379       
380        # Fill in errors if none were provided
381        if self.current_plottable.dy == None:
382            print "no error", self.current_plottable.name
383            y = numpy.zeros(len(pr.y))
384            for i in range(len(pr.y)):
385                y[i] = math.sqrt(pr.y[i])
386            pr.err = y
387        else:
388            pr.err = self.current_plottable.dy
389           
390        self.pr = pr
391
392         
393    def setup_file_inversion(self, alpha, nfunc, d_max, path):
394        self.alpha = alpha
395        self.nfunc = nfunc
396        self.max_length = d_max
397       
398        self._create_file_pr(path)
399       
400        self.perform_inversion()
401         
402    def estimate_file_inversion(self, alpha, nfunc, d_max, path):
403        self.alpha = alpha
404        self.nfunc = nfunc
405        self.max_length = d_max
406       
407        if self._create_file_pr(path):
408            self.perform_estimate()
409         
410    def _create_file_pr(self, path):
411        """
412            Create and prepare invertor instance from
413            a file data set.
414            @param path: path of the file to read in
415        """
416        # Load data
417        if os.path.isfile(path):
418            x, y, err = self.load(path)
419           
420            # Get the data from the chosen data set and perform inversion
421            pr = Invertor()
422            pr.d_max = self.max_length
423            pr.alpha = self.alpha
424            pr.x = x
425            pr.y = y
426            pr.err = err
427           
428            self.pr = pr
429            return True
430        return False
431       
432    def perform_estimate(self):
433        from pr_thread import EstimatePr
434        from copy import deepcopy
435       
436        wx.PostEvent(self.parent, StatusEvent(status=''))
437        # If a thread is already started, stop it
438        if self.estimation_thread != None and self.estimation_thread.isrunning():
439            self.estimation_thread.stop()
440               
441        pr = self.pr.clone()
442        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
443                                            completefn = self._estimate_completed, 
444                                            updatefn   = None)
445        self.estimation_thread.queue()
446        self.estimation_thread.ready(2.5)
447       
448    def perform_inversion(self):
449       
450        # Time estimate
451        #estimated = self.elapsed*self.nfunc**2
452        message = "Computation time may take up to %g seconds" % self.elapsed
453        wx.PostEvent(self.parent, StatusEvent(status=message))
454       
455        # Start inversion thread
456        self.start_thread()
457        return
458       
459        out, cov = self.pr.lstsq(self.nfunc)
460       
461        # Save useful info
462        self.elapsed = self.pr.elapsed
463       
464        for i in range(len(out)):
465            try:
466                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
467            except: 
468                print "%d: %g +- ?" % (i, out[i])       
469       
470       
471       
472        # Make a plot of I(q) data
473        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
474        new_plot.name = "I_{obs}(q)"
475        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
476        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
477        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
478               
479        # Show I(q) fit
480        self.show_iq(out, self.pr)
481       
482        # Show P(r) fit
483        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
484       
485       
486         
487         
488    def _on_context_inversion(self, event):
489        panel = event.GetEventObject()
490
491        from inversion_panel import InversionDlg
492       
493        # If we have more than one displayed plot, make the user choose
494        if len(panel.plots)>1:
495            dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
496            dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
497            if dialog.ShowModal() == wx.ID_OK:
498                dataset = dialog.get_content()
499                dialog.Destroy()
500            else:
501                dialog.Destroy()
502                return
503        elif len(panel.plots)==1:
504            dataset = panel.plots.keys()[0]
505        else:
506            print "Error: No data is available"
507            return
508       
509        # Store a reference to the current plottable
510        self.current_plottable = panel.plots[dataset]
511        self.control_panel.plotname = dataset
512        self.control_panel.nfunc = self.nfunc
513        self.control_panel.d_max = self.max_length
514        self.control_panel.alpha = self.alpha
515        self.parent.set_perspective(self.perspective)
516           
517    def get_panels(self, parent):
518        """
519            Create and return a list of panel objects
520        """
521        from inversion_panel import InversionControl
522       
523        self.parent = parent
524        self.control_panel = InversionControl(self.parent, -1, style=wx.RAISED_BORDER)
525        self.control_panel.set_manager(self)
526        self.control_panel.nfunc = self.nfunc
527        self.control_panel.d_max = self.max_length
528        self.control_panel.alpha = self.alpha
529       
530        self.perspective = []
531        self.perspective.append(self.control_panel.window_name)
532        return [self.control_panel]
533   
534    def get_perspective(self):
535        """
536            Get the list of panel names for this perspective
537        """
538        return self.perspective
539   
540    def on_perspective(self, event):
541        """
542            Call back function for the perspective menu item.
543            We notify the parent window that the perspective
544            has changed.
545        """
546        self.parent.set_perspective(self.perspective)
547   
548    def post_init(self):
549        """
550            Post initialization call back to close the loose ends
551            [Somehow openGL needs this call]
552        """
553        self.parent.set_perspective(self.perspective)
554   
Note: See TracBrowser for help on using the repository browser.