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

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

Add a couple of figures of merit (GUI)

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