source: sasview/sansview/perspectives/fitting/fitting.py @ 189be4e

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 189be4e was d0d7704, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on switch plot from theory to data1d

  • Property mode set to 100644
File size: 53.3 KB
Line 
1
2
3################################################################################
4#This software was developed by the University of Tennessee as part of the
5#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
6#project funded by the US National Science Foundation.
7#
8#See the license text in license.txt
9#
10#copyright 2009, University of Tennessee
11################################################################################
12
13
14import  re
15import sys
16import wx
17import logging
18import numpy
19import math
20import string
21import time
22import thread
23from copy import deepcopy
24
25import models
26import fitpage
27
28from DataLoader.loader import Loader
29
30from sans.guiframe.dataFitting import Data2D
31from sans.guiframe.dataFitting import Data1D
32from sans.guiframe.dataFitting import Theory1D
33
34from sans.guicomm.events import NewPlotEvent, StatusEvent 
35from sans.guicomm.events import EVT_SLICER_PANEL,ERR_DATA,EVT_REMOVE_DATA
36from sans.guicomm.events import EVT_SLICER_PARS_UPDATE
37
38from sans.fit.AbstractFitEngine import Model
39from sans.fit.AbstractFitEngine import FitAbort
40from console import ConsoleUpdate
41
42from fitproblem import FitProblem
43from fitpanel import FitPanel
44from fit_thread import FitThread
45from pagestate import Reader
46
47DEFAULT_BEAM = 0.005
48DEFAULT_QMIN = 0.001
49DEFAULT_QMAX = 0.13
50DEFAULT_NPTS = 50
51
52(PageInfoEvent, EVT_PAGE_INFO)   = wx.lib.newevent.NewEvent()
53
54from fitpage import Chi2UpdateEvent
55
56
57class PlotInfo:
58    """
59    store some plotting field
60    """
61    _xunit = 'A^{-1}'
62    _xaxis= "\\rm{Q}"
63    _yunit = "cm^{-1}"
64    _yaxis= "\\rm{Intensity} "
65    id = "Model"
66    group_id = "Model"
67    title= None
68    info= None
69   
70   
71class Plugin:
72    """
73    Fitting plugin is used to perform fit
74    """
75    def __init__(self):
76        """
77        """
78        ## Plug-in name
79        self.sub_menu = "Fitting"
80       
81        ## Reference to the parent window
82        self.parent = None
83        #Provide list of models existing in the application
84        self.menu_mng = models.ModelManager()
85        ## List of panels for the simulation perspective (names)
86        self.perspective = []
87        #list of panel to send to guiframe
88        self.mypanels = []
89        # reference to the current running thread
90        self.calc_2D = None
91        self.calc_1D = None
92        self.calc_fit = None
93       
94        # Start with a good default
95        self.elapsed = 0.022
96        # the type of optimizer selected, park or scipy
97        self.fitter  = None
98        #let fit ready
99        self.fitproblem_count = None
100        #Flag to let the plug-in know that it is running stand alone
101        self.standalone = True
102        ## dictionary of page closed and id
103        self.closed_page_dict = {}
104        ## Fit engine
105        self._fit_engine = 'scipy'
106        #List of selected data
107        self.selected_data_list = []
108        ## list of slicer panel created to display slicer parameters and results
109        self.slicer_panels = []
110        # model 2D view
111        self.model2D_id = None
112        #keep reference of the simultaneous fit page
113        self.sim_page = None
114        #dictionary containing data name and error on dy of that data
115        self.err_dy = {}
116        self.theory_data = None 
117        #Create a reader for fit page's state
118        self.state_reader = None 
119        # Log startup
120        logging.info("Fitting plug-in started") 
121       
122    def populate_menu(self, id, owner):
123        """
124        Create a menu for the Fitting plug-in
125       
126        :param id: id to create a menu
127        :param owner: owner of menu
128       
129        :return: list of information to populate the main menu
130       
131        """
132        #Menu for fitting
133        self.menu1 = wx.Menu()
134       
135        #Set park engine
136        id3 = wx.NewId()
137        scipy_help= "Scipy Engine: Perform Simple fit. More in Help window...."
138        self.menu1.AppendCheckItem(id3, "Simple Fit  [Scipy]",scipy_help) 
139        wx.EVT_MENU(owner, id3,  self._onset_engine_scipy)
140       
141        id3 = wx.NewId()
142        park_help = "Park Engine: Perform Complex fit. More in Help window...."
143        self.menu1.AppendCheckItem(id3, "Complex Fit  [Park]",park_help) 
144        wx.EVT_MENU(owner, id3,  self._onset_engine_park)
145       
146        self.menu1.FindItemByPosition(0).Check(True)
147        self.menu1.FindItemByPosition(1).Check(False)
148           
149        self.menu1.AppendSeparator()
150        id1 = wx.NewId()
151        simul_help = "Allow to edit fit engine with multiple model and data"
152        self.menu1.Append(id1, '&Simultaneous Page',simul_help)
153        wx.EVT_MENU(owner, id1, self.on_add_sim_page)
154       
155        #menu for model
156        menu2 = wx.Menu()
157        self.menu_mng.populate_menu(menu2, owner)
158        id2 = wx.NewId()
159        owner.Bind(models.EVT_MODEL,self._on_model_menu)
160     
161        self.fit_panel.set_owner(owner)
162        self.fit_panel.set_model_list(self.menu_mng.get_model_list())
163        owner.Bind(fitpage.EVT_MODEL_BOX,self._on_model_panel)
164
165        #create  menubar items
166        return [(id, self.menu1, "Fitting")]
167               
168    def on_add_sim_page(self, event):
169        """
170        Create a page to access simultaneous fit option
171        """
172        Plugin.on_perspective(self,event=event)
173        if self.sim_page !=None:
174            msg= "Simultaneous Fit page already opened"
175            wx.PostEvent(self.parent, StatusEvent(status= msg))
176            return 
177       
178        self.sim_page= self.fit_panel.add_sim_page()
179       
180    def help(self, evt):
181        """
182        Show a general help dialog.
183        """
184       
185        from help_panel import  HelpWindow
186        frame = HelpWindow(None, -1, 'HelpWindow')   
187        frame.Show(True)
188       
189    def get_context_menu(self, graph=None):
190        """
191        Get the context menu items available for P(r).them allow fitting option
192        for Data2D and Data1D only.
193       
194        :param graph: the Graph object to which we attach the context menu
195       
196        :return: a list of menu items with call-back function
197       
198        :note: if Data1D was generated from Theory1D 
199                the fitting option is not allowed
200               
201        """
202        self.graph = graph
203        fit_option = "Select data for fitting"
204        fit_hint =  "Dialog with fitting parameters "
205       
206        for item in graph.plottables:
207            if item.__class__.__name__ is "Data2D":
208               
209                if hasattr(item,"is_data"):
210                    if item.is_data:
211                        return [[fit_option, fit_hint, self._onSelect]]
212                    else:
213                        return [] 
214                return [[fit_option, fit_hint, self._onSelect]]
215            else:
216                if item.name == graph.selected_plottable :
217                    if item.name in ["$I_{obs}(q)$","$I_{fit}(q)$","$P_{fit}(r)$"]:
218                        return [] 
219                    if hasattr(item, "group_id"):
220                        # if is_data is true , this in an actual data loaded
221                        #else it is a data created from a theory model
222                        if hasattr(item,"is_data"):
223                            if item.is_data:
224                                return [[fit_option, fit_hint,
225                                          self._onSelect]]
226                            else:
227                                return [] 
228                        else:
229                           return [[fit_option, fit_hint, self._onSelect]]
230        return []   
231
232
233    def get_panels(self, parent):
234        """
235        Create and return a list of panel objects
236        """
237        self.parent = parent
238        # Creation of the fit panel
239        self.fit_panel = FitPanel(self.parent, -1)
240        #Set the manager for the main panel
241        self.fit_panel.set_manager(self)
242        # List of windows used for the perspective
243        self.perspective = []
244        self.perspective.append(self.fit_panel.window_name)
245        # take care of saving  data, model and page associated with each other
246        self.page_finder = {}
247        #index number to create random model name
248        self.index_model = 0
249        self.index_theory= 0
250        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
251        self.parent.Bind(ERR_DATA, self._on_data_error)
252        self.parent.Bind(EVT_REMOVE_DATA, self._closed_fitpage)
253        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
254        self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)   
255        #Create reader when fitting panel are created
256        self.state_reader = Reader(self.set_state)   
257        #append that reader to list of available reader
258        loader = Loader()
259        loader.associate_file_reader(".fitv", self.state_reader)
260        #Send the fitting panel to guiframe
261        self.mypanels.append(self.fit_panel)
262        return self.mypanels
263   
264    def get_perspective(self):
265        """
266        Get the list of panel names for this perspective
267        """
268        return self.perspective
269   
270    def on_perspective(self, event):
271        """
272        Call back function for the perspective menu item.
273        We notify the parent window that the perspective
274        has changed.
275        """
276        self.parent.set_perspective(self.perspective)
277   
278    def set_default_perspective(self):
279        """
280        Call back method that True to notify the parent that the current plug-in
281        can be set as default  perspective.
282        when returning False, the plug-in is not candidate for an automatic
283        default perspective setting
284        """
285        return True
286   
287    def post_init(self):
288        """
289        Post initialization call back to close the loose ends
290        """
291        pass
292   
293    def set_state(self, state, datainfo=None):
294        """
295        Call-back method for the fit page state reader.
296        This method is called when a .fitv file is loaded.
297       
298        :param state: PageState object
299       
300        """
301        #working on reading state
302        return 
303        try: 
304            # Load fitting state
305            page = self.fit_panel.set_state(state)   
306            # Make sure the user sees the fitting panel after loading
307            self.parent.set_perspective(self.perspective)   
308                   
309        except:
310            raise
311       
312    def save_fit_state(self, filepath, fitstate): 
313        """
314        save fit page state into file
315        """
316        self.state_reader.write(filename=filepath, fitstate=fitstate)
317       
318    def copy_data(self, item, dy=None):
319        """
320        receive a data 1D and the list of errors on dy
321        and create a new data1D data
322   
323        """
324        id = None
325        if hasattr(item,"id"):
326            id = item.id
327
328        data= Data1D(x=item.x, y=item.y,dx=None, dy=None)
329        data.copy_from_datainfo(item)
330        item.clone_without_data(clone=data)   
331        data.dy = deepcopy(dy)
332        data.name = item.name
333        ## allow to highlight data when plotted
334        data.interactive = deepcopy(item.interactive)
335        ## when 2 data have the same id override the 1 st plotted
336        data.id = id
337        data.group_id = item.group_id
338        return data
339   
340    def set_fit_range(self, page, qmin, qmax):
341        """
342        Set the fitting range of a given page
343        """
344        if page in self.page_finder.iterkeys():
345            fitproblem= self.page_finder[page]
346            fitproblem.set_range(qmin= qmin, qmax= qmax)
347                   
348    def schedule_for_fit(self,value=0,page=None,fitproblem =None): 
349        """
350        Set the fit problem field to 0 or 1 to schedule that problem to fit.
351        Schedule the specified fitproblem or get the fit problem related to
352        the current page and set value.
353       
354        :param value: integer 0 or 1
355        :param fitproblem: fitproblem to schedule or not to fit
356       
357        """   
358        if fitproblem !=None:
359            fitproblem.schedule_tofit(value)
360        else:
361            if page in self.page_finder.iterkeys():
362                fitproblem= self.page_finder[page]
363                fitproblem.schedule_tofit(value)
364         
365    def get_page_finder(self):
366        """
367        return self.page_finder used also by simfitpage.py
368        """ 
369        return self.page_finder
370   
371    def set_page_finder(self,modelname,names,values):
372        """
373        Used by simfitpage.py to reset a parameter given the string constrainst.
374         
375        :param modelname: the name ot the model for with the parameter has to reset
376        :param value: can be a string in this case.
377        :param names: the paramter name
378         
379        :note: expecting park used for fit.
380         
381        """ 
382        sim_page= self.sim_page
383        for page, value in self.page_finder.iteritems():
384            if page != sim_page:
385                list=value.get_model()
386                model = list[0]
387                if model.name== modelname:
388                    value.set_model_param(names,values)
389                    break
390         
391    def split_string(self,item): 
392        """
393        receive a word containing dot and split it. used to split parameterset
394        name into model name and parameter name example: ::
395       
396            paramaterset (item) = M1.A
397            Will return model_name = M1 , parameter name = A
398           
399        """
400        if string.find(item,".")!=-1:
401            param_names= re.split("\.",item)
402            model_name=param_names[0]           
403            ##Assume max len is 3; eg., M0.radius.width
404            if len(param_names) == 3:
405                param_name=param_names[1]+"."+param_names[2]
406            else:
407                param_name=param_names[1]                   
408            return model_name,param_name
409       
410    def stop_fit(self):
411        """
412        Stop the fit engine
413        """
414        if self.calc_fit!= None and self.calc_fit.isrunning():
415            self.calc_fit.stop()
416            wx.PostEvent(self.parent, StatusEvent(status="Fitting  \
417                is cancelled" , type="stop"))
418           
419    def set_smearer_nodraw(self,smearer, qmin=None, qmax=None):
420        """
421        Get a smear object and store it to a fit problem
422       
423        :param smearer: smear object to allow smearing data
424       
425        """ 
426        self.current_pg=self.fit_panel.get_current_page()
427        self.page_finder[self.current_pg].set_smearer(smearer)
428        ## draw model 1D with smeared data
429        data =  self.page_finder[self.current_pg].get_fit_data()
430        model = self.page_finder[self.current_pg].get_model()
431        ## if user has already selected a model to plot
432        ## redraw the model with data smeared
433        smear =self.page_finder[self.current_pg].get_smearer()   
434           
435    def set_smearer(self,smearer, qmin=None, qmax=None):
436        """
437        Get a smear object and store it to a fit problem
438       
439        :param smearer: smear object to allow smearing data
440       
441        """   
442        self.current_pg=self.fit_panel.get_current_page()
443        self.page_finder[self.current_pg].set_smearer(smearer)
444        ## draw model 1D with smeared data
445        data =  self.page_finder[self.current_pg].get_fit_data()
446        model = self.page_finder[self.current_pg].get_model()
447        ## if user has already selected a model to plot
448        ## redraw the model with data smeared
449
450        smear =self.page_finder[self.current_pg].get_smearer()
451        if model!= None:
452            self.draw_model( model=model, data= data, smearer= smear,
453                qmin= qmin, qmax= qmax)
454
455    def draw_model(self, model, data= None,smearer= None,
456                   enable1D= True, enable2D= False,
457                   qmin= DEFAULT_QMIN, qmax= DEFAULT_QMAX, qstep= DEFAULT_NPTS):
458        """
459        Draw model.
460       
461        :param model: the model to draw
462        :param name: the name of the model to draw
463        :param data: the data on which the model is based to be drawn
464        :param description: model's description
465        :param enable1D: if true enable drawing model 1D
466        :param enable2D: if true enable drawing model 2D
467        :param qmin:  Range's minimum value to draw model
468        :param qmax:  Range's maximum value to draw model
469        :param qstep: number of step to divide the x and y-axis
470             
471        """
472        if data.__class__.__name__ !="Data2D":   
473            ## draw model 1D with no loaded data
474            self._draw_model1D( model= model, data= data,
475                                                    enable1D=enable1D, 
476                                                    smearer= smearer,
477                                                    qmin= qmin, qmax= qmax, qstep= qstep )
478        else:     
479            ## draw model 2D with no initial data
480             self._draw_model2D(model=model,
481                                      data = data,
482                                      enable2D= enable2D,
483                                      smearer= smearer,
484                                      qmin=qmin,
485                                      qmax=qmax,
486                                      qstep=qstep)
487           
488    def onFit(self):
489        """
490        perform fit
491        """
492        ##  count the number of fitproblem schedule to fit
493        fitproblem_count= 0
494        for value in self.page_finder.itervalues():
495            if value.get_scheduled()==1:
496                fitproblem_count += 1
497               
498        ## if simultaneous fit change automatically the engine to park
499        if fitproblem_count >1:
500            self._on_change_engine(engine='park')
501           
502        self.fitproblem_count = fitproblem_count 
503         
504        from sans.fit.Fitting import Fit
505        self.fitter= Fit(self._fit_engine)
506       
507        if self._fit_engine=="park":
508            engineType="Simultaneous Fit"
509        else:
510            engineType="Single Fit"
511           
512        fproblemId = 0
513        self.current_pg=None
514        for page, value in self.page_finder.iteritems():
515            try:
516                if value.get_scheduled()==1:
517                    #Get list of parameters name to fit
518                    pars = []
519                    templist = []
520                    templist = page.get_param_list()
521                    for element in templist:
522                        name = str(element[1])
523                        pars.append(name)
524                    #Set Engine  (model , data) related to the page on
525                    self._fit_helper( value=value,pars=pars,
526                                      id=fproblemId, title= engineType ) 
527                    fproblemId += 1 
528                    self.current_pg= page
529            except:
530                msg= "%s error: %s" % (engineType,sys.exc_value)
531                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
532                                                      type="stop"))
533                return 
534        ## If a thread is already started, stop it
535        #if self.calc_fit!= None and self.calc_fit.isrunning():
536        #    self.calc_fit.stop()
537         #Handler used for park engine displayed message
538        handler = ConsoleUpdate(parent=self.parent,improvement_delta=0.1)
539        ## perform single fit
540        if fitproblem_count == 1:
541            calc_fit=FitThread(parent =self.parent,
542                                    handler = handler,
543                                    fn= self.fitter,
544                                   cpage=self.current_pg,
545                                   pars= pars,
546                                   updatefn=handler.update_fit,
547                                   completefn= self._single_fit_completed)
548        else:
549            ## Perform more than 1 fit at the time
550            calc_fit=FitThread(parent=self.parent,
551                                handler=handler,
552                                    fn= self.fitter,
553                                   completefn= self._simul_fit_completed,
554                                  updatefn=handler.update_fit)
555       
556        calc_fit.queue()
557        self.ready_fit(calc_fit=calc_fit)
558     
559    def ready_fit(self, calc_fit):
560        """
561        Ready for another fit
562        """
563        if self.fitproblem_count != None and self.fitproblem_count > 1:
564            calc_fit.ready(2.5)
565           
566        else:
567            time.sleep(0.4)
568           
569    def remove_plot(self, page, theory=False):
570        """
571        remove model plot when a fit page is closed
572        """
573        fitproblem = self.page_finder[page]
574        data = fitproblem.get_fit_data()
575        model = fitproblem.get_model()
576        if model is not None:
577            name = model.name
578            new_plot = Theory1D(x=[], y=[], dy=None)
579            new_plot.name = name
580            new_plot.xaxis(data._xaxis, data._xunit)
581            new_plot.yaxis(data._yaxis, data._yunit)
582            new_plot.group_id = data.group_id
583            new_plot.id = data.id + name
584            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=data.name))
585        if theory:
586            new_plot_data = Data1D(x=[], y=[], dx=None, dy=None)
587            new_plot_data.name = data.name
588            new_plot_data.xaxis(data._xaxis, data._xunit)
589            new_plot_data.yaxis(data._yaxis, data._yunit)
590            new_plot_data.group_id = data.group_id
591            new_plot_data.id = data.id
592            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot_data,
593                                                    title=data.name))
594    def create_fittable_data2D(self, data):
595        """
596        check if the current data is a data 2d and add dy to that data
597       
598        :return: Data2D
599       
600        """
601        if data.__class__.__name__ != "Data2D":
602            raise ValueError, " create_fittable_data2D expects a Data2D"
603        ## Data2D case
604        new_data = deepcopy(data)
605        if not hasattr(data, "is_data"):
606            new_data.group_id += "data2D"
607            new_data.id +="data2D"
608            new_data.is_data = False
609            title = new_data.name
610            title += " Fit"
611            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
612                                                    title=str(title)))
613        else:
614            new_data.is_data = True
615        return new_data
616       
617    def create_fittable_data1D(self, data):
618        """
619        check if the current data is a theory 1d and add dy to that data
620       
621        :return: Data1D
622       
623        """
624        class_name = data.__class__.__name__
625        if not class_name in ["Data1D", "Theory1D"]:
626            raise ValueError, "create_fittable_data1D expects Data1D"
627     
628        #get the appropriate dy
629        dy = deepcopy(data.dy)
630        if len(self.err_dy) > 0:
631            if data.name in  self.err_dy.iterkeys():
632                dy = self.err_dy[data.name]   
633        if data.__class__.__name__ == "Theory1D":
634            new_data = self.copy_data(data, dy)
635            if new_data.dy is None:
636                new_data.dy = numpy.zeros(len(new_data.y))
637            new_data.group_id = str(new_data.group_id)+"data1D"
638            new_data.id = str(new_data.id)+"data1D"
639            new_data.is_data = False
640            title = new_data.name
641            title = 'Data created from Theory'
642            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
643                                                    title=str(title),
644                                                   reset=True))
645        else:
646            new_data = self.copy_data(data, dy) 
647            new_data.id = data.id
648            new_data.is_data = True
649        return new_data
650           
651    def store_page(self, page, data):
652        """
653        Helper to save page reference into the plug-in
654       
655        :param page: page to store
656       
657        """
658        page.set_data(data) 
659        #create a fitproblem storing all link to data,model,page creation
660        if not page in self.page_finder.keys():
661            self.page_finder[page] = FitProblem()
662        self.page_finder[page].add_fit_data(data)
663       
664    def add_fit_page(self, data):
665        """
666        given a data, ask to the fitting panel to create a new fitting page,
667        get this page and store it into the page_finder of this plug-in
668        """
669        try:
670            page = self.fit_panel.add_fit_page(data)
671            # add data associated to the page created
672            if page != None: 
673                self.store_page(page=page, data=data)
674                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
675                                               info="info"))
676            else:
677                msg = "Page was already Created"
678                wx.PostEvent(self.parent, StatusEvent(status=msg, info="warning"))
679        except:
680            msg = "Creating Fit page: %s"%sys.exc_value
681            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
682       
683    def _onEVT_SLICER_PANEL(self, event):
684        """
685        receive and event telling to update a panel with a name starting with
686        event.panel_name. this method update slicer panel for a given interactor.
687       
688        :param event: contains type of slicer , paramaters for updating the panel
689            and panel_name to find the slicer 's panel concerned.
690        """
691        for item in self.parent.panels:
692            if self.parent.panels[item].window_caption.startswith(event.panel_name):
693                self.parent.panels[item].set_slicer(event.type, event.params)
694               
695        self.parent._mgr.Update()
696   
697    def _closed_fitpage(self, event):   
698        """
699        request fitpanel to close a given page when its unique data is removed
700        from the plot. close fitpage only when the a loaded data is removed
701        """   
702        if event is None or event.data is None:
703            return
704       
705        if hasattr(event.data,"is_data"):
706            if not event.data.is_data or event.data.__class__.__name__=="Data1D":
707                self.fit_panel.close_page_with_data(event.data) 
708       
709    def _add_page_onmenu(self, name,fitproblem=None):
710        """
711        Add name of a closed page of fitpanel in a menu
712        """
713        list = self.menu1.GetMenuItems()
714        for item in list:
715            if name == item.GetItemLabel():
716                self.closed_page_dict[name][1] = fitproblem
717               
718        if not name in self.closed_page_dict.keys():   
719            # Post paramters
720            event_id = wx.NewId()
721            self.menu1.Append(event_id, name, "Show %s fit panel" % name)
722            self.closed_page_dict[name]= [event_id, fitproblem]
723            wx.EVT_MENU(self.parent,event_id,  self._open_closed_page)
724       
725    def _open_closed_page(self, event):   
726        """
727        reopen a closed page
728        """
729        for name, value in self.closed_page_dict.iteritems():
730            if event.GetId() in value:
731                id,fitproblem = value
732                if name !="Model":
733                    data= fitproblem.get_fit_data()
734                    page = self.fit_panel.add_fit_page(data= data,reset=True)
735                    if fitproblem != None:
736                        self.page_finder[page]=fitproblem
737                        if self.sim_page != None:
738                            self.sim_page.draw_page()
739                           
740                else:
741                    model = fitproblem
742                    self.fit_panel.add_model_page(model=model, topmenu=True,
743                                                  reset= True)
744                    break
745   
746    def _reset_schedule_problem(self, value=0):
747        """
748        unschedule or schedule all fitproblem to be fit
749        """
750        for page, fitproblem in self.page_finder.iteritems():
751            fitproblem.schedule_tofit(value)
752           
753    def _fit_helper(self,pars,value, id, title="Single Fit " ):
754        """
755        helper for fitting
756        """
757        metadata = value.get_fit_data()
758        model = value.get_model()
759        smearer = value.get_smearer()
760        qmin , qmax = value.get_range()
761        self.fit_id =id
762        #Create list of parameters for fitting used
763        templist=[]
764       
765        try:
766            #Extra list of parameters and their constraints
767            listOfConstraint= []
768           
769            param = value.get_model_param()
770            if len(param)>0:
771                for item in param:
772                    ## check if constraint
773                    if item[0] !=None and item[1] != None:
774                        listOfConstraint.append((item[0],item[1]))
775                   
776            #Do the single fit
777            self.fitter.set_model(model, self.fit_id,
778                                   pars,constraints = listOfConstraint)
779           
780            self.fitter.set_data(data=metadata,Uid=self.fit_id,
781                                 smearer=smearer,qmin= qmin,qmax=qmax )
782           
783            self.fitter.select_problem_for_fit(Uid= self.fit_id,
784                                               value= value.get_scheduled())
785            value.clear_model_param()
786        except:
787            msg= title +" error: %s" % sys.exc_value
788            wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
789            return
790       
791    def _onSelect(self,event):
792        """
793        when Select data to fit a new page is created .Its reference is
794        added to self.page_finder
795        """
796        self.panel = event.GetEventObject()
797        Plugin.on_perspective(self,event=event)
798        for plottable in self.panel.graph.plottables:
799            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
800                if plottable.name == self.panel.graph.selected_plottable:
801                    data = self.create_fittable_data1D(data=plottable)
802                    self.add_fit_page(data=data)
803                    return
804            else:
805                data = self.create_fittable_data2D(data=plottable)
806                self.add_fit_page(data=data)
807           
808    def _single_fit_completed(self,result,pars,cpage, elapsed=None):
809        """
810        Display fit result on one page of the notebook.
811       
812        :param result: result of fit
813        :param pars: list of names of parameters fitted
814        :param current_pg: the page where information will be displayed
815        :param qmin: the minimum value of x to replot the model
816        :param qmax: the maximum value of x to replot model
817         
818        """     
819        try:
820            if result ==None:
821                msg= "Simple Fitting Stop !!!"
822                wx.PostEvent(self.parent, StatusEvent(status=msg,info="warning",
823                                                      type="stop"))
824                return
825            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
826                msg= "Single Fitting did not converge!!!"
827                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
828                return
829            for page, value in self.page_finder.iteritems():
830                if page==cpage :
831                    model= value.get_model()
832                    break
833            param_name = []
834            i = 0
835            for name in pars:
836                param_name.append(name)
837
838            cpage.onsetValues(result.fitness,param_name, result.pvec,result.stderr)
839           
840        except:
841            msg= "Single Fit completed but Following error occurred:%s"% sys.exc_value
842            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
843                                                  type="stop"))
844            return
845       
846    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None):
847        """
848        Parameter estimation completed,
849        display the results to the user
850       
851        :param alpha: estimated best alpha
852        :param elapsed: computation time
853       
854        """
855        ## fit more than 1 model at the same time
856        try:
857            msg = "" 
858            if result ==None:
859                msg= "Complex Fitting Stop !!!"
860                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
861                return
862            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
863                msg= "Single Fitting did not converge!!!"
864                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
865                return
866             
867            for page, value in self.page_finder.iteritems():   
868                if value.get_scheduled()==1:
869                    model = value.get_model()
870                    data =  value.get_fit_data()
871                    small_param_name = []
872                    small_out = []
873                    small_cov = []
874                    i = 0
875                    #Separate result in to data corresponding to each page
876                    for p in result.parameters:
877                        model_name,param_name = self.split_string(p.name) 
878                        if model.name == model_name:
879                            p_name= model.name+"."+param_name
880                            if p.name == p_name:     
881                                if p.value != None and numpy.isfinite(p.value):
882                                    small_out.append(p.value )
883                                    small_param_name.append(param_name)
884                                    small_cov.append(p.stderr)
885
886                    # Display result on each page
887                    page.onsetValues(result.fitness, small_param_name,small_out,small_cov)
888        except:
889             msg= "Simultaneous Fit completed"
890             msg +=" but Following error occurred:%s"%sys.exc_value
891             wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
892             return 
893             
894    def _on_show_panel(self, event):
895        """
896        """
897        #print "_on_show_panel: fitting"
898        pass
899       
900    def _onset_engine_park(self,event):
901        """
902        set engine to park
903        """
904        Plugin.on_perspective(self,event=event)
905        self._on_change_engine('park')
906       
907    def _onset_engine_scipy(self,event):
908        """
909        set engine to scipy
910        """
911        self._on_change_engine('scipy')
912       
913    def _on_slicer_event(self, event):
914        """
915        Receive a panel as event and send it to guiframe
916       
917        :param event: event containing a panel
918       
919        """
920        if event.panel is not None:
921            new_panel = event.panel
922            self.slicer_panels.append(event.panel)
923            # Set group ID if available
924            event_id = self.parent.popup_panel(new_panel)
925            #self.menu3.Append(event_id, new_panel.window_caption,
926            #                 "Show %s plot panel" % new_panel.window_caption)
927            # Set UID to allow us to reference the panel later
928         
929            new_panel.uid = event_id
930            self.mypanels.append(new_panel) 
931       
932    def _onclearslicer(self, event):
933        """
934        Clear the boxslicer when close the panel associate with this slicer
935        """
936        name =event.GetPane().caption
937   
938        for panel in self.slicer_panels:
939            if panel.window_caption==name:
940               
941                for item in self.parent.panels:
942                    if hasattr(self.parent.panels[item],"uid"):
943                        if self.parent.panels[item].uid ==panel.base.uid:
944                            self.parent.panels[item].onClearSlicer(event)
945                            self.parent._mgr.Update()
946                            break 
947                break
948   
949    def _return_engine_type(self):
950        """
951        return the current type of engine
952        """
953        return self._fit_engine
954     
955     
956    def _on_change_engine(self, engine='park'):
957        """
958        Allow to select the type of engine to perform fit
959       
960        :param engine: the key work of the engine
961       
962        """
963        ## saving fit engine name
964        self._fit_engine = engine
965        ## change menu item state
966        if engine=="park":
967            self.menu1.FindItemByPosition(0).Check(False)
968            self.menu1.FindItemByPosition(1).Check(True)
969        else:
970            self.menu1.FindItemByPosition(0).Check(True)
971            self.menu1.FindItemByPosition(1).Check(False)
972           
973        ## post a message to status bar
974        wx.PostEvent(self.parent, StatusEvent(status="Engine set to: %s" % self._fit_engine))
975   
976        ## Bind every open fit page with a newevent to know the current fitting engine
977        import fitpage
978        event= fitpage.FitterTypeEvent()
979        event.type = self._fit_engine
980        for key in self.page_finder.keys():
981            wx.PostEvent(key, event)
982       
983    def _on_model_panel(self, evt):
984        """
985        react to model selection on any combo box or model menu.plot the model 
986       
987        :param evt: wx.combobox event
988       
989        """
990        model = evt.model
991        if model == None:
992            return
993        smearer = None
994        qmin = None
995        qmax = None
996        if hasattr(evt, "qmin"):
997            qmin = evt.qmin
998        if hasattr(evt, "qmax"):
999            qmax = evt.qmax
1000        if hasattr(evt, "smearer"):
1001            smearer = evt.smearer
1002        model.origin_name = model.name
1003        self.current_pg = self.fit_panel.get_current_page() 
1004        ## make sure nothing is done on self.sim_page
1005        ## example trying to call set_panel on self.sim_page
1006        if self.current_pg != self.sim_page :
1007            if self.page_finder[self.current_pg].get_model()== None :
1008               
1009                model.name = "M"+str(self.index_model)
1010                self.index_model += 1 
1011            else:
1012                model.name= self.page_finder[self.current_pg].get_model().name
1013           
1014            data = self.page_finder[self.current_pg].get_fit_data()
1015           
1016            # save the name containing the data name with the appropriate model
1017            self.page_finder[self.current_pg].set_model(model)
1018            qmin, qmax= self.current_pg.get_range()
1019            self.page_finder[self.current_pg].set_range(qmin=qmin, qmax=qmax)
1020            smearer=  self.page_finder[self.current_pg].get_smearer()
1021            # save model name
1022            self.set_smearer(smearer=smearer, qmin=qmin, qmax=qmax)
1023           
1024            if self.sim_page!=None:
1025                self.sim_page.draw_page()
1026       
1027    def _on_model_menu(self, evt):
1028        """
1029        Plot a theory from a model selected from the menu
1030       
1031        :param evt: wx.menu event
1032       
1033        """
1034        model = evt.model
1035        Plugin.on_perspective(self,event=evt)
1036        # Create a model page. If a new page is created, the model
1037        # will be plotted automatically. If a page already exists,
1038        # the content will be updated and the plot refreshed
1039        self.fit_panel.add_model_page(model,topmenu=True)
1040
1041    def _update1D(self,x, output):
1042        """
1043        Update the output of plotting model 1D
1044        """
1045        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1046        #updating ... ",type="update"))
1047        self.ready_fit()
1048        #self.calc_thread.ready(0.01)
1049   
1050    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
1051        """
1052        fill Data2D with default value
1053       
1054        :param theory: Data2D to fill
1055       
1056        """
1057        from DataLoader.data_info import Detector, Source
1058       
1059        detector = Detector()
1060        theory.detector.append(detector)         
1061        theory.source= Source()
1062       
1063        ## Default values   
1064        theory.detector[0].distance= 8000   # mm       
1065        theory.source.wavelength= 6         # A     
1066        theory.detector[0].pixel_size.x= 5  # mm
1067        theory.detector[0].pixel_size.y= 5  # mm
1068       
1069        theory.detector[0].beam_center.x= qmax
1070        theory.detector[0].beam_center.y= qmax
1071   
1072        ## create x_bins and y_bins of the model 2D
1073        pixel_width_x = theory.detector[0].pixel_size.x
1074        pixel_width_y = theory.detector[0].pixel_size.y
1075        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1076        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1077
1078        # theory default: assume the beam center is located at the center of sqr detector
1079        xmax = qmax
1080        xmin = -qmax
1081        ymax = qmax
1082        ymin = -qmax
1083       
1084        x=  numpy.linspace(start= -1*qmax,
1085                               stop= qmax,
1086                               num= qstep,
1087                               endpoint=True ) 
1088        y = numpy.linspace(start= -1*qmax,
1089                               stop= qmax,
1090                               num= qstep,
1091                               endpoint=True )
1092         
1093        ## use data info instead
1094        new_x = numpy.tile(x, (len(y),1))
1095        new_y = numpy.tile(y, (len(x),1))
1096        new_y = new_y.swapaxes(0,1)
1097       
1098        # all data reuire now in 1d array
1099        qx_data = new_x.flatten()
1100        qy_data = new_y.flatten()
1101       
1102        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1103        # set all True (standing for unmasked) as default
1104        mask    = numpy.ones(len(qx_data), dtype = bool)
1105       
1106        # calculate the range of qx and qy: this way, it is a little more independent
1107        x_size = xmax- xmin
1108        y_size = ymax -ymin
1109       
1110        # store x and y bin centers in q space
1111        x_bins  = x
1112        y_bins  = y
1113        # bin size: x- & y-directions
1114        xstep = x_size/len(x_bins-1)
1115        ystep = y_size/len(y_bins-1)
1116       
1117        #theory.data = numpy.zeros(len(mask))
1118        theory.err_data = numpy.ones(len(mask))
1119        theory.qx_data = qx_data
1120        theory.qy_data = qy_data 
1121        theory.q_data = q_data
1122        theory.mask = mask           
1123        theory.x_bins = x_bins 
1124        theory.y_bins = y_bins   
1125       
1126        # max and min taking account of the bin sizes
1127        theory.xmin= xmin
1128        theory.xmax= xmax
1129        theory.ymin= ymin
1130        theory.ymax= ymax
1131        theory.group_id = "Model"
1132        theory.id = "Model"
1133       
1134    def _get_plotting_info(self, data=None):
1135        """
1136        get plotting info from data if data !=None else use some default
1137        """
1138        my_info = PlotInfo()
1139        if data !=None:
1140            if hasattr(data,"info"):
1141                x_name, x_units = data.get_xaxis() 
1142                y_name, y_units = data.get_yaxis() 
1143               
1144                my_info._xunit = x_units
1145                my_info._xaxis = x_name
1146                my_info._yunit = y_units
1147                my_info._yaxis = y_name
1148               
1149            my_info.title= data.name
1150            if hasattr(data, "info"):
1151                my_info.info= data.info
1152            if hasattr(data, "group_id"):
1153                my_info.group_id= data.group_id
1154        return my_info
1155               
1156    def _complete1D(self, x,y, elapsed,index,model,data=None):
1157        """
1158        Complete plotting 1D data
1159        """ 
1160        try:
1161            new_plot = Theory1D(x=x, y=y)
1162            my_info = self._get_plotting_info( data)
1163            new_plot.name = model.name
1164            new_plot.id = my_info.id
1165            new_plot.group_id = my_info.group_id
1166           
1167            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1168            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1169            if data!=None:
1170                if new_plot.id == data.id:
1171                    new_plot.id += "Model"
1172                new_plot.is_data =False 
1173           
1174            title= new_plot.name
1175            # x, y are only in range of index
1176            self.theory_data = new_plot
1177            #new_plot.perspective = self.get_perspective()
1178            # Pass the reset flag to let the plotting event handler
1179            # know that we are replacing the whole plot
1180            if title== None:
1181                title = "Analytical model 1D "
1182            if data ==None:
1183                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1184                             title=str(title), reset=True))
1185            else:
1186                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1187            # Chisqr in fitpage
1188            current_pg=self.fit_panel.get_current_page()
1189            wx.PostEvent(current_pg,Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1190            msg = "Plot 1D  complete !"
1191            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1192        except:
1193            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1194            msg+= " %s"%sys.exc_value
1195            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1196   
1197    def _update2D(self, output,time=None):
1198        """
1199        Update the output of plotting model
1200        """
1201        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1202        #updating ... ",type="update"))
1203        self.ready_fit()
1204        #self.calc_thread.ready(0.01)
1205   
1206    def _complete2D(self, image, data, model, elapsed, index, qmin,
1207                     qmax, qstep=DEFAULT_NPTS):
1208        """
1209        Complete get the result of modelthread and create model 2D
1210        that can be plot.
1211        """
1212        err_image = numpy.zeros(numpy.shape(image))
1213       
1214        theory= Data2D(image= image , err_image= err_image)
1215        theory.name= model.name
1216       
1217        if data ==None:
1218            self._fill_default_model2D(theory=theory, qmax=qmax, qstep=qstep,
1219                                        qmin= qmin)
1220       
1221        else:
1222            theory.id= data.id+"Model"
1223            theory.group_id= data.name+"Model"
1224            theory.x_bins= data.x_bins
1225            theory.y_bins= data.y_bins
1226            theory.detector= data.detector
1227            theory.source= data.source
1228            theory.is_data =False 
1229            theory.qx_data = data.qx_data
1230            theory.qy_data = data.qy_data
1231            theory.q_data = data.q_data
1232            theory.err_data = err_image#numpy.zeros(len(data.err_data))#data.err_data
1233            theory.mask = data.mask
1234            ## plot boundaries
1235            theory.ymin= data.ymin
1236            theory.ymax= data.ymax
1237            theory.xmin= data.xmin
1238            theory.xmax= data.xmax
1239           
1240        self.theory_data = theory
1241        ## plot
1242        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1243                         title="Analytical model 2D ", reset=True ))
1244        # Chisqr in fitpage
1245        current_pg = self.fit_panel.get_current_page()
1246        wx.PostEvent(current_pg,
1247            Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1248        msg = "Plot 2D complete !"
1249        wx.PostEvent( self.parent, StatusEvent(status=msg, type="stop" ))
1250     
1251    def _on_data_error(self, event):
1252        """
1253        receives and event from plotting plu-gins to store the data name and
1254        their errors of y coordinates for 1Data hide and show error
1255        """
1256        self.err_dy = event.err_dy
1257         
1258    def _draw_model2D(self,model,data=None, smearer= None,description=None, enable2D=False,
1259                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1260        """
1261        draw model in 2D
1262       
1263        :param model: instance of the model to draw
1264        :param description: the description of the model
1265        :param enable2D: when True allows to draw model 2D
1266        :param qmin: the minimum value to  draw model 2D
1267        :param qmax: the maximum value to draw model 2D
1268        :param qstep: the number of division of Qx and Qy of the model to draw
1269           
1270        """
1271        x=  numpy.linspace(start= -1*qmax,
1272                               stop= qmax,
1273                               num= qstep,
1274                               endpoint=True ) 
1275        y = numpy.linspace(start= -1*qmax,
1276                               stop= qmax,
1277                               num= qstep,
1278                               endpoint=True )
1279        ## use data info instead
1280        if data !=None:
1281            ## check if data2D to plot
1282            if hasattr(data, "x_bins"):
1283                enable2D = True
1284                x= data.x_bins
1285                y= data.y_bins
1286               
1287        if not enable2D:
1288            return None,None
1289        try:
1290            from model_thread import Calc2D
1291            ## If a thread is already started, stop it
1292            if self.calc_2D != None and self.calc_2D.isrunning():
1293                self.calc_2D.stop()
1294
1295            self.calc_2D = Calc2D(  x= x,
1296                                    y= y,
1297                                    model= model, 
1298                                    data = data,
1299                                    smearer = smearer,
1300                                    qmin= qmin,
1301                                    qmax= qmax,
1302                                    qstep= qstep,
1303                                    completefn= self._complete2D,
1304                                    updatefn= self._update2D )
1305            self.calc_2D.queue()
1306
1307        except:
1308            msg= " Error occurred when drawing %s Model 2D: "%model.name
1309            msg+= " %s"%sys.exc_value
1310            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1311
1312    def _draw_model1D(self, model, data=None, smearer= None,
1313                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1314        """
1315        Draw model 1D from loaded data1D
1316       
1317        :param data: loaded data
1318        :param model: the model to plot
1319       
1320        """
1321        x=  numpy.linspace(start= qmin,
1322                           stop= qmax,
1323                           num= qstep,
1324                           endpoint=True
1325                           )
1326        if data!=None:
1327            ## check for data2D
1328            if hasattr(data,"x_bins"):
1329                return
1330            x = data.x
1331            if qmin == DEFAULT_QMIN :
1332                qmin = min(data.x)
1333            if qmax == DEFAULT_QMAX:
1334                qmax = max(data.x) 
1335           
1336       
1337        if not enable1D:
1338            return 
1339   
1340        try:
1341            from model_thread import Calc1D
1342            ## If a thread is already started, stop it
1343            if self.calc_1D!= None and self.calc_1D.isrunning():
1344                self.calc_1D.stop()
1345            self.calc_1D= Calc1D( x= x,
1346                                  data = data,
1347                                  model= model, 
1348                                  qmin = qmin,
1349                                  qmax = qmax,
1350                                  smearer = smearer,
1351                                  completefn = self._complete1D,
1352                                  updatefn = self._update1D  )
1353            self.calc_1D.queue()
1354
1355        except:
1356            msg= " Error occurred when drawing %s Model 1D: "%model.name
1357            msg+= " %s"%sys.exc_value
1358            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1359
1360    def _cal_chisqr(self, data=None, index=None): 
1361        """
1362        Get handy Chisqr using the output from draw1D and 2D,
1363        instead of calling expansive CalcChisqr in guithread
1364        """
1365        # default chisqr
1366        chisqr = None
1367       
1368        # return None if data == None
1369        if data == None: return chisqr
1370       
1371        # Get data: data I, theory I, and data dI in order
1372        if data.__class__.__name__ =="Data2D":
1373            if index == None: index = numpy.ones(len(data.data),ntype=bool)
1374            index = index & (data.err_data !=0 )   # get rid of zero error points
1375            fn = data.data[index] 
1376            gn = self.theory_data.data[index]
1377            en = data.err_data[index]
1378        else:
1379            # 1 d theory from model_thread is only in the range of index
1380            if index == None: index = numpy.ones(len(data.y),ntype=bool)
1381            if data.dy== None or data.dy ==[]:
1382                dy = numpy.ones(len(data.y))
1383            else:
1384                ## Set consitently w/AbstractFitengine: But this should be corrected later.
1385                dy = data.dy
1386                dy[dy==0] = 1 
1387            fn = data.y[index] 
1388            gn = self.theory_data.y
1389            en = dy[index]
1390
1391        # residual
1392        res = (fn - gn)/en
1393        # get chisqr only w/finite
1394        chisqr = numpy.average(res[numpy.isfinite(res)]*res[numpy.isfinite(res)])
1395
1396        return chisqr
1397   
1398   
1399#def profile(fn, *args, **kw):
1400#    import cProfile, pstats, os
1401#    global call_result
1402#    def call():
1403#        global call_result
1404#        call_result = fn(*args, **kw)
1405#    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1406#    stats = pstats.Stats('profile.out')
1407#    #stats.sort_stats('time')
1408#    stats.sort_stats('calls')
1409#    stats.print_stats()
1410#    os.unlink('profile.out')
1411#    return call_result
1412if __name__ == "__main__":
1413    i = Plugin()
1414   
1415   
1416   
1417   
Note: See TracBrowser for help on using the repository browser.