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

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

Added help and updated requirements

  • Property mode set to 100644
File size: 19.1 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
[634f1cf]31        self.q_min      = None
32        self.q_max      = None
[f3d51f6]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)
[119a11d]60       
[f3d51f6]61        return [(id, shapes, "P(r)")]
62   
[119a11d]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   
[f3d51f6]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               
[634f1cf]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)
[f3d51f6]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        for i in range(len(x)):
221            if cov==None:
222                value = pr.pr(out, x[i])
223            else:
224                (value, dy[i]) = pr.pr_err(out, cov, x[i])
225            sum += value
226            y[i] = value
227           
228        y = y/sum*pr.d_max/len(x)
229        dy = dy/sum*pr.d_max/len(x)
230       
231        if cov==None:
232            new_plot = Theory1D(x, y)
233        else:
234            new_plot = Data1D(x, y, dy=dy)
235        new_plot.name = "P_{fit}(r)"
236        new_plot.xaxis("\\rm{r}", 'A')
237        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
238           
239        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
240       
241        return x, pr.d_max
242       
243       
244    def choose_file(self):
245        """
246       
247        """
248        #TODO: this should be in a common module
249        return self.parent.choose_file()
250               
251    def load(self, path = "sphere_test_data.txt"):
252        import numpy, math, sys
253        # Read the data from the data file
254        data_x   = numpy.zeros(0)
255        data_y   = numpy.zeros(0)
256        data_err = numpy.zeros(0)
257        if not path == None:
258            input_f = open(path,'r')
259            buff    = input_f.read()
260            lines   = buff.split('\n')
261            for line in lines:
262                try:
263                    toks = line.split()
264                    x = float(toks[0])
265                    y = float(toks[1])
266                    data_x = numpy.append(data_x, x)
267                    data_y = numpy.append(data_y, y)
268                    try:
269                        scale = 0.05/math.sqrt(data_x[0])
270                    except:
271                        scale = 1.0
272                    #data_err = numpy.append(data_err, 10.0*math.sqrt(y)+1000.0)
273                    data_err = numpy.append(data_err, scale*math.sqrt(y))
274                except:
275                    print "Error reading line: ", line
276                    print sys.exc_value
277                   
278        print "Lines read:", len(data_x)
279        return data_x, data_y, data_err
280       
281    def pr_theory(self, r, R):
282        """
283           
284        """
285        if r<=2*R:
286            return 12.0* ((0.5*r/R)**2) * ((1.0-0.5*r/R)**2) * ( 2.0 + 0.5*r/R )
287        else:
288            return 0.0
289
290    def get_context_menu(self, plot_id=None):
291        """
292            Get the context menu items available for P(r)
293            @param plot_id: Unique ID of a plot, so that we can recognize those
294                            that we created
295            @return: a list of menu items with call-back function
296        """
297        return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]
298   
299
300    def start_thread(self):
301        from pr_thread import CalcPr
302        from copy import deepcopy
303       
304        # If a thread is already started, stop it
305        if self.calc_thread != None and self.calc_thread.isrunning():
306            self.calc_thread.stop()
307               
308        pr = self.pr.clone()
309        self.calc_thread = CalcPr(pr, self.nfunc, error_func=self._thread_error, completefn=self._completed, updatefn=None)
310        self.calc_thread.queue()
311        self.calc_thread.ready(2.5)
312   
313    def _thread_error(self, error):
314        wx.PostEvent(self.parent, StatusEvent(status=error))
315   
[32dffae4]316    def _estimate_completed(self, alpha, message, elapsed):
[f3d51f6]317        """
318            Parameter estimation completed,
319            display the results to the user
320            @param alpha: estimated best alpha
321            @param elapsed: computation time
322        """
323        # Save useful info
324        self.elapsed = elapsed
325        self.control_panel.alpha_estimate = alpha
[32dffae4]326        if not message==None:
327            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
[f3d51f6]328   
329    def _completed(self, out, cov, pr, elapsed):
330        """
331            Method called with the results when the inversion
332            is done
333           
334            @param out: output coefficient for the base functions
335            @param cov: covariance matrix
336            @param pr: Invertor instance
337            @param elapsed: time spent computing
338        """
339        # Save useful info
340        self.elapsed = elapsed
341        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
342        wx.PostEvent(self.parent, StatusEvent(status=message))
343
344        # Show result on control panel
345        self.control_panel.chi2 = pr.chi2
346        self.control_panel.elapsed = elapsed
347        self.control_panel.oscillation = pr.oscillations(out)
348        #print "OSCILL", pr.oscillations(out)
[32dffae4]349        print "PEAKS:", pr.get_peaks(out)
[f3d51f6]350       
351        for i in range(len(out)):
352            try:
353                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
354            except: 
355                print "%d: %g +- ?" % (i, out[i])       
356       
357        # Make a plot of I(q) data
358        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
359        new_plot.name = "I_{obs}(q)"
360        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
361        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
362        #new_plot.group_id = "test group"
363        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
364               
365        # Show I(q) fit
366        self.show_iq(out, self.pr)
367       
368        # Show P(r) fit
369        x_values, x_range = self.show_pr(out, self.pr) 
370       
371        # Popup result panel
372        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
373       
[634f1cf]374    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
[f3d51f6]375        self.alpha = alpha
376        self.nfunc = nfunc
377        self.max_length = d_max
[634f1cf]378        self.q_min = q_min
379        self.q_max = q_max
[f3d51f6]380       
381        self._create_plot_pr()
382        self.perform_inversion()
383
[634f1cf]384    def estimate_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
[f3d51f6]385        self.alpha = alpha
386        self.nfunc = nfunc
387        self.max_length = d_max
[634f1cf]388        self.q_min = q_min
389        self.q_max = q_max
[f3d51f6]390       
391        self._create_plot_pr()
392        self.perform_estimate()
393
394    def _create_plot_pr(self):
395        """
396            Create and prepare invertor instance from
397            a plottable data set.
398            @param path: path of the file to read in
399        """
400        # Get the data from the chosen data set and perform inversion
401        pr = Invertor()
402        pr.d_max = self.max_length
403        pr.alpha = self.alpha
[634f1cf]404        pr.q_min = self.q_min
405        pr.q_max = self.q_max
[f3d51f6]406        pr.x = self.current_plottable.x
407        pr.y = self.current_plottable.y
408       
409        # Fill in errors if none were provided
410        if self.current_plottable.dy == None:
411            print "no error", self.current_plottable.name
412            y = numpy.zeros(len(pr.y))
413            for i in range(len(pr.y)):
414                y[i] = math.sqrt(pr.y[i])
415            pr.err = y
416        else:
417            pr.err = self.current_plottable.dy
418           
419        self.pr = pr
420
421         
[634f1cf]422    def setup_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
[f3d51f6]423        self.alpha = alpha
424        self.nfunc = nfunc
425        self.max_length = d_max
[634f1cf]426        self.q_min = q_min
427        self.q_max = q_max
[f3d51f6]428       
429        self._create_file_pr(path)
430       
431        self.perform_inversion()
432         
[634f1cf]433    def estimate_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
[f3d51f6]434        self.alpha = alpha
435        self.nfunc = nfunc
436        self.max_length = d_max
[634f1cf]437        self.q_min = q_min
438        self.q_max = q_max
[f3d51f6]439       
440        if self._create_file_pr(path):
441            self.perform_estimate()
442         
443    def _create_file_pr(self, path):
444        """
445            Create and prepare invertor instance from
446            a file data set.
447            @param path: path of the file to read in
448        """
449        # Load data
450        if os.path.isfile(path):
451            x, y, err = self.load(path)
452           
453            # Get the data from the chosen data set and perform inversion
454            pr = Invertor()
455            pr.d_max = self.max_length
456            pr.alpha = self.alpha
[634f1cf]457            pr.q_min = self.q_min
458            pr.q_max = self.q_max
[f3d51f6]459            pr.x = x
460            pr.y = y
461            pr.err = err
462           
463            self.pr = pr
464            return True
465        return False
466       
467    def perform_estimate(self):
468        from pr_thread import EstimatePr
469        from copy import deepcopy
470       
[32dffae4]471        wx.PostEvent(self.parent, StatusEvent(status=''))
[f3d51f6]472        # If a thread is already started, stop it
473        if self.estimation_thread != None and self.estimation_thread.isrunning():
474            self.estimation_thread.stop()
475               
476        pr = self.pr.clone()
477        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
478                                            completefn = self._estimate_completed, 
479                                            updatefn   = None)
480        self.estimation_thread.queue()
481        self.estimation_thread.ready(2.5)
482       
483    def perform_inversion(self):
484       
485        # Time estimate
486        #estimated = self.elapsed*self.nfunc**2
487        message = "Computation time may take up to %g seconds" % self.elapsed
488        wx.PostEvent(self.parent, StatusEvent(status=message))
489       
490        # Start inversion thread
491        self.start_thread()
492        return
493       
494        out, cov = self.pr.lstsq(self.nfunc)
495       
496        # Save useful info
497        self.elapsed = self.pr.elapsed
498       
499        for i in range(len(out)):
500            try:
501                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
502            except: 
503                print "%d: %g +- ?" % (i, out[i])       
504       
505       
506       
507        # Make a plot of I(q) data
508        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
509        new_plot.name = "I_{obs}(q)"
510        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
511        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
512        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
513               
514        # Show I(q) fit
515        self.show_iq(out, self.pr)
516       
517        # Show P(r) fit
518        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
519       
520       
521         
522         
523    def _on_context_inversion(self, event):
524        panel = event.GetEventObject()
525
526        from inversion_panel import InversionDlg
527       
528        # If we have more than one displayed plot, make the user choose
529        if len(panel.plots)>1:
530            dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
531            dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
532            if dialog.ShowModal() == wx.ID_OK:
533                dataset = dialog.get_content()
534                dialog.Destroy()
535            else:
536                dialog.Destroy()
537                return
538        elif len(panel.plots)==1:
539            dataset = panel.plots.keys()[0]
540        else:
541            print "Error: No data is available"
542            return
543       
544        # Store a reference to the current plottable
545        self.current_plottable = panel.plots[dataset]
546        self.control_panel.plotname = dataset
547        self.control_panel.nfunc = self.nfunc
548        self.control_panel.d_max = self.max_length
549        self.control_panel.alpha = self.alpha
550        self.parent.set_perspective(self.perspective)
551           
552    def get_panels(self, parent):
553        """
554            Create and return a list of panel objects
555        """
556        from inversion_panel import InversionControl
557       
558        self.parent = parent
559        self.control_panel = InversionControl(self.parent, -1, style=wx.RAISED_BORDER)
560        self.control_panel.set_manager(self)
561        self.control_panel.nfunc = self.nfunc
562        self.control_panel.d_max = self.max_length
563        self.control_panel.alpha = self.alpha
564       
565        self.perspective = []
566        self.perspective.append(self.control_panel.window_name)
567        return [self.control_panel]
568   
569    def get_perspective(self):
570        """
571            Get the list of panel names for this perspective
572        """
573        return self.perspective
574   
575    def on_perspective(self, event):
576        """
577            Call back function for the perspective menu item.
578            We notify the parent window that the perspective
579            has changed.
580        """
581        self.parent.set_perspective(self.perspective)
582   
583    def post_init(self):
584        """
585            Post initialization call back to close the loose ends
586            [Somehow openGL needs this call]
587        """
[32dffae4]588        self.parent.set_perspective(self.perspective)
[f3d51f6]589   
Note: See TracBrowser for help on using the repository browser.