source: sasview/prview/perspectives/pr/pr.py @ 4f63160

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 4f63160 was f3d51f6, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Initial import: gui for P(r) inversion

  • Property mode set to 100644
File size: 17.8 KB
RevLine 
[f3d51f6]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, 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   
304    def _completed(self, out, cov, pr, elapsed):
305        """
306            Method called with the results when the inversion
307            is done
308           
309            @param out: output coefficient for the base functions
310            @param cov: covariance matrix
311            @param pr: Invertor instance
312            @param elapsed: time spent computing
313        """
314        # Save useful info
315        self.elapsed = elapsed
316        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
317        wx.PostEvent(self.parent, StatusEvent(status=message))
318
319        # Show result on control panel
320        self.control_panel.chi2 = pr.chi2
321        self.control_panel.elapsed = elapsed
322        self.control_panel.oscillation = pr.oscillations(out)
323        #print "OSCILL", pr.oscillations(out)
324       
325        for i in range(len(out)):
326            try:
327                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
328            except: 
329                print "%d: %g +- ?" % (i, out[i])       
330       
331        # Make a plot of I(q) data
332        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
333        new_plot.name = "I_{obs}(q)"
334        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
335        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
336        #new_plot.group_id = "test group"
337        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
338               
339        # Show I(q) fit
340        self.show_iq(out, self.pr)
341       
342        # Show P(r) fit
343        x_values, x_range = self.show_pr(out, self.pr) 
344       
345        # Popup result panel
346        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
347       
348    def setup_plot_inversion(self, alpha, nfunc, d_max):
349        self.alpha = alpha
350        self.nfunc = nfunc
351        self.max_length = d_max
352       
353        self._create_plot_pr()
354        self.perform_inversion()
355
356    def estimate_plot_inversion(self, alpha, nfunc, d_max):
357        self.alpha = alpha
358        self.nfunc = nfunc
359        self.max_length = d_max
360       
361        self._create_plot_pr()
362        self.perform_estimate()
363
364    def _create_plot_pr(self):
365        """
366            Create and prepare invertor instance from
367            a plottable data set.
368            @param path: path of the file to read in
369        """
370        # Get the data from the chosen data set and perform inversion
371        pr = Invertor()
372        pr.d_max = self.max_length
373        pr.alpha = self.alpha
374        pr.x = self.current_plottable.x
375        pr.y = self.current_plottable.y
376       
377        # Fill in errors if none were provided
378        if self.current_plottable.dy == None:
379            print "no error", self.current_plottable.name
380            y = numpy.zeros(len(pr.y))
381            for i in range(len(pr.y)):
382                y[i] = math.sqrt(pr.y[i])
383            pr.err = y
384        else:
385            pr.err = self.current_plottable.dy
386           
387        self.pr = pr
388
389         
390    def setup_file_inversion(self, alpha, nfunc, d_max, path):
391        self.alpha = alpha
392        self.nfunc = nfunc
393        self.max_length = d_max
394       
395        self._create_file_pr(path)
396       
397        self.perform_inversion()
398         
399    def estimate_file_inversion(self, alpha, nfunc, d_max, path):
400        self.alpha = alpha
401        self.nfunc = nfunc
402        self.max_length = d_max
403       
404        if self._create_file_pr(path):
405            self.perform_estimate()
406         
407    def _create_file_pr(self, path):
408        """
409            Create and prepare invertor instance from
410            a file data set.
411            @param path: path of the file to read in
412        """
413        # Load data
414        if os.path.isfile(path):
415            x, y, err = self.load(path)
416           
417            # Get the data from the chosen data set and perform inversion
418            pr = Invertor()
419            pr.d_max = self.max_length
420            pr.alpha = self.alpha
421            pr.x = x
422            pr.y = y
423            pr.err = err
424           
425            self.pr = pr
426            return True
427        return False
428       
429    def perform_estimate(self):
430        print "ESTIMATE"
431        from pr_thread import EstimatePr
432        from copy import deepcopy
433       
434        # If a thread is already started, stop it
435        if self.estimation_thread != None and self.estimation_thread.isrunning():
436            self.estimation_thread.stop()
437               
438        pr = self.pr.clone()
439        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
440                                            completefn = self._estimate_completed, 
441                                            updatefn   = None)
442        self.estimation_thread.queue()
443        self.estimation_thread.ready(2.5)
444       
445    def perform_inversion(self):
446       
447        # Time estimate
448        #estimated = self.elapsed*self.nfunc**2
449        message = "Computation time may take up to %g seconds" % self.elapsed
450        wx.PostEvent(self.parent, StatusEvent(status=message))
451       
452        # Start inversion thread
453        self.start_thread()
454        return
455       
456        out, cov = self.pr.lstsq(self.nfunc)
457       
458        # Save useful info
459        self.elapsed = self.pr.elapsed
460       
461        for i in range(len(out)):
462            try:
463                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
464            except: 
465                print "%d: %g +- ?" % (i, out[i])       
466       
467       
468       
469        # Make a plot of I(q) data
470        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
471        new_plot.name = "I_{obs}(q)"
472        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
473        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
474        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
475               
476        # Show I(q) fit
477        self.show_iq(out, self.pr)
478       
479        # Show P(r) fit
480        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
481       
482       
483         
484         
485    def _on_context_inversion(self, event):
486        panel = event.GetEventObject()
487
488        from inversion_panel import InversionDlg
489       
490        # If we have more than one displayed plot, make the user choose
491        if len(panel.plots)>1:
492            dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
493            dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
494            if dialog.ShowModal() == wx.ID_OK:
495                dataset = dialog.get_content()
496                dialog.Destroy()
497            else:
498                dialog.Destroy()
499                return
500        elif len(panel.plots)==1:
501            dataset = panel.plots.keys()[0]
502        else:
503            print "Error: No data is available"
504            return
505       
506        # Store a reference to the current plottable
507        self.current_plottable = panel.plots[dataset]
508        self.control_panel.plotname = dataset
509        self.control_panel.nfunc = self.nfunc
510        self.control_panel.d_max = self.max_length
511        self.control_panel.alpha = self.alpha
512        self.parent.set_perspective(self.perspective)
513           
514    def get_panels(self, parent):
515        """
516            Create and return a list of panel objects
517        """
518        from inversion_panel import InversionControl
519       
520        self.parent = parent
521        self.control_panel = InversionControl(self.parent, -1, style=wx.RAISED_BORDER)
522        self.control_panel.set_manager(self)
523        self.control_panel.nfunc = self.nfunc
524        self.control_panel.d_max = self.max_length
525        self.control_panel.alpha = self.alpha
526       
527        self.perspective = []
528        self.perspective.append(self.control_panel.window_name)
529        return [self.control_panel]
530   
531    def get_perspective(self):
532        """
533            Get the list of panel names for this perspective
534        """
535        return self.perspective
536   
537    def on_perspective(self, event):
538        """
539            Call back function for the perspective menu item.
540            We notify the parent window that the perspective
541            has changed.
542        """
543        self.parent.set_perspective(self.perspective)
544   
545    def post_init(self):
546        """
547            Post initialization call back to close the loose ends
548            [Somehow openGL needs this call]
549        """
550        pass
551   
Note: See TracBrowser for help on using the repository browser.