source: sasview/fittingview/src/sans/perspectives/fitting/fitting.py @ f7f6886

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 f7f6886 was f7f6886, checked in by Gervaise Alina <gervyh@…>, 13 years ago

working on batch fit

  • Property mode set to 100644
File size: 64.6 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 string
20import time
21from copy import deepcopy
22import models
23import fitpage
24
25
26from sans.dataloader.loader import Loader
27from sans.guiframe.dataFitting import Data2D
28from sans.guiframe.dataFitting import Data1D
29from sans.guiframe.dataFitting import check_data_validity
30from sans.guiframe.events import NewPlotEvent
31from sans.guiframe.events import StatusEvent 
32from sans.guiframe.events import EVT_SLICER_PANEL
33from sans.guiframe.events import EVT_SLICER_PARS_UPDATE
34from sans.guiframe.gui_style import GUIFRAME_ID
35from sans.guiframe.plugin_base import PluginBase
36from sans.fit.Fitting import Fit
37from .console import ConsoleUpdate
38from .fitproblem import FitProblemDictionary
39from .fitpanel import FitPanel
40from .fit_thread import FitThread
41from .pagestate import Reader
42from .fitpage import Chi2UpdateEvent
43
44MAX_NBR_DATA = 4
45SANS_F_TOL = 5e-05
46
47(PageInfoEvent, EVT_PAGE_INFO)   = wx.lib.newevent.NewEvent()
48
49if sys.platform.count("darwin")==0:
50    ON_MAC = False
51else:
52    ON_MAC = True   
53
54class Plugin(PluginBase):
55    """
56    Fitting plugin is used to perform fit
57    """
58    def __init__(self, standalone=False):
59        PluginBase.__init__(self, name="Fitting", standalone=standalone)
60       
61        #list of panel to send to guiframe
62        self.mypanels = []
63        # reference to the current running thread
64        self.calc_2D = None
65        self.calc_1D = None
66       
67        self.color_dict = {}
68       
69        self.fit_thread_list = {}
70        self.residuals = None
71        self.weight = None
72        self.fit_panel = None
73        # Start with a good default
74        self.elapsed = 0.022
75        # the type of optimizer selected, park or scipy
76        self.fitter  = None
77        self.fit_panel = None
78        #let fit ready
79        self.fitproblem_count = None
80        #Flag to let the plug-in know that it is running stand alone
81        self.standalone = True
82        ## dictionary of page closed and id
83        self.closed_page_dict = {}
84        ## Fit engine
85        self._fit_engine = 'scipy'
86        ## Relative error desired in the sum of squares (float); scipy only
87        self.ftol = SANS_F_TOL
88        #List of selected data
89        self.selected_data_list = []
90        ## list of slicer panel created to display slicer parameters and results
91        self.slicer_panels = []
92        # model 2D view
93        self.model2D_id = None
94        #keep reference of the simultaneous fit page
95        self.sim_page = None
96        self.index_model = 0
97        self.test_model_color = None
98        #Create a reader for fit page's state
99        self.state_reader = None 
100        self._extensions = '.fitv'
101        self.scipy_id = wx.NewId()
102        self.park_id = wx.NewId()
103       
104        self.temp_state = []
105        self.state_index = 0
106        self.sfile_ext = None
107        # take care of saving  data, model and page associated with each other
108        self.page_finder = {}
109        # Log startup
110        logging.info("Fitting plug-in started") 
111   
112    def create_fit_problem(self, page_id):
113        """
114        Given an ID create a fitproblem container
115        """
116        self.page_finder[page_id] = FitProblemDictionary()
117       
118    def delete_fit_problem(self, page_id):
119        """
120        Given an ID create a fitproblem container
121        """
122        if page_id in self.page_finder.iterkeys():
123            del self.page_finder[page_id]
124       
125    def add_color(self, color, id):
126        """
127        adds a color as a key with a plot id as its value to a dictionary
128        """
129        self.color_dict[id] = color
130       
131    def on_batch_selection(self, flag):
132        """
133        switch the the notebook of batch mode or not
134        """
135        self.batch_on = flag
136        if self.fit_panel is not None:
137            self.fit_panel.batch_on = self.batch_on
138       
139    def populate_menu(self, owner):
140        """
141        Create a menu for the Fitting plug-in
142       
143        :param id: id to create a menu
144        :param owner: owner of menu
145       
146        :return: list of information to populate the main menu
147       
148        """
149        #Menu for fitting
150        self.menu1 = wx.Menu()
151        id1 = wx.NewId()
152        simul_help = "Add new fit panel"
153        self.menu1.Append(id1, '&New Fit Page',simul_help)
154        wx.EVT_MENU(owner, id1, self.on_add_new_page)
155        self.menu1.AppendSeparator()
156        id1 = wx.NewId()
157        simul_help = "Simultaneous Fit"
158        self.menu1.Append(id1, '&Simultaneous Fit',simul_help)
159        wx.EVT_MENU(owner, id1, self.on_add_sim_page)
160        self.menu1.AppendSeparator()
161        #Set park engine
162       
163        scipy_help= "Scipy Engine: Perform Simple fit. More in Help window...."
164        self.menu1.AppendCheckItem(self.scipy_id, "Simple FitEngine [LeastSq]",
165                                   scipy_help) 
166        wx.EVT_MENU(owner, self.scipy_id,  self._onset_engine_scipy)
167       
168        park_help = "Park Engine: Perform Complex fit. More in Help window...."
169        self.menu1.AppendCheckItem(self.park_id, "Complex FitEngine [ParkMC]",
170                                   park_help) 
171        wx.EVT_MENU(owner, self.park_id,  self._onset_engine_park)
172       
173        self.menu1.FindItemById(self.scipy_id).Check(True)
174        self.menu1.FindItemById(self.park_id).Check(False)
175        self.menu1.AppendSeparator()
176        self.id_tol = wx.NewId()
177        ftol_help = "Change the current FTolerance (=%s) " % str(self.ftol)
178        ftol_help += "of Simple FitEngine..." 
179        self.menu1.Append(self.id_tol, "Change FTolerance [LeastSq Only]", 
180                                   ftol_help) 
181        wx.EVT_MENU(owner, self.id_tol,  self.show_ftol_dialog)
182       
183       
184        #create  menubar items
185        return [(self.menu1, self.sub_menu)]
186               
187    def on_add_sim_page(self, event):
188        """
189        Create a page to access simultaneous fit option
190        """
191        if self.sim_page != None:
192            msg= "Simultaneous Fit page already opened"
193            wx.PostEvent(self.parent, StatusEvent(status= msg))
194            return 
195       
196        self.sim_page= self.fit_panel.add_sim_page()
197       
198    def help(self, evt):
199        """
200        Show a general help dialog.
201        """
202        from help_panel import  HelpWindow
203        frame = HelpWindow(None, -1, 'HelpWindow')   
204        frame.Show(True)
205       
206    def get_context_menu(self, plotpanel=None):
207        """
208        Get the context menu items available for P(r).them allow fitting option
209        for Data2D and Data1D only.
210       
211        :param graph: the Graph object to which we attach the context menu
212       
213        :return: a list of menu items with call-back function
214       
215        :note: if Data1D was generated from Theory1D 
216                the fitting option is not allowed
217               
218        """
219        graph = plotpanel.graph
220        fit_option = "Select data for fitting"
221        fit_hint =  "Dialog with fitting parameters "
222       
223        if graph.selected_plottable not in plotpanel.plots:
224            return []
225        item = plotpanel.plots[graph.selected_plottable]
226        if item.__class__.__name__ is "Data2D": 
227            if hasattr(item,"is_data"):
228                if item.is_data:
229                    return [[fit_option, fit_hint, self._onSelect]]
230                else:
231                    return [] 
232            return [[fit_option, fit_hint, self._onSelect]]
233        else:
234           
235            # if is_data is true , this in an actual data loaded
236            #else it is a data created from a theory model
237            if hasattr(item,"is_data"):
238                if item.is_data:
239                    return [[fit_option, fit_hint,
240                              self._onSelect]]
241                else:
242                    return [] 
243        return []   
244
245
246    def get_panels(self, parent):
247        """
248        Create and return a list of panel objects
249        """
250        self.parent = parent
251        #self.parent.Bind(EVT_FITSTATE_UPDATE, self.on_set_state_helper)
252        # Creation of the fit panel
253        self.fit_panel = FitPanel(parent=self.parent, manager=self)
254        self.on_add_new_page(event=None)
255        #Set the manager for the main panel
256        self.fit_panel.set_manager(self)
257        # List of windows used for the perspective
258        self.perspective = []
259        self.perspective.append(self.fit_panel.window_name)
260       
261        #index number to create random model name
262        self.index_model = 0
263        self.index_theory= 0
264        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
265        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
266        self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)   
267        #Create reader when fitting panel are created
268        self.state_reader = Reader(self.set_state)   
269        #append that reader to list of available reader
270        loader = Loader()
271        loader.associate_file_reader(".fitv", self.state_reader)
272        #loader.associate_file_reader(".svs", self.state_reader)
273        #from sans.perspectives.calculator.sld_panel import SldPanel
274        #Send the fitting panel to guiframe
275        self.mypanels.append(self.fit_panel) 
276        #self.mypanels.append(SldPanel(parent=self.parent, base=self.parent))
277        return self.mypanels
278   
279    def clear_panel(self):
280        """
281        """
282        self.fit_panel.clear_panel()
283       
284    def set_default_perspective(self):
285        """
286        Call back method that True to notify the parent that the current plug-in
287        can be set as default  perspective.
288        when returning False, the plug-in is not candidate for an automatic
289        default perspective setting
290        """
291        return True
292   
293    def delete_data(self, data):
294        """
295        delete  the given data from panel
296        """
297        self.fit_panel.delete_data(data)
298       
299    def set_data(self, data_list=None):
300        """
301        receive a list of data to fit
302        """
303        if data_list is None:
304            data_list = []
305        selected_data_list = []
306        if self.batch_on:
307            page = self.add_fit_page(data=data_list)
308        else:
309            if len(data_list) > MAX_NBR_DATA:
310                from fitting_widgets import DataDialog
311                dlg = DataDialog(data_list=data_list, nb_data=MAX_NBR_DATA)
312                if dlg.ShowModal() == wx.ID_OK:
313                    selected_data_list = dlg.get_data()
314                dlg.Destroy()
315               
316            else:
317                selected_data_list = data_list
318            try:
319                for data in selected_data_list:
320                    page = self.add_fit_page(data=[data])
321            except:
322                msg = "Fitting Set_data: " + str(sys.exc_value)
323                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
324   
325    def set_top_panel(self):
326        """
327        Close default (welcome) panel
328        """
329        if 'default' in self.parent.panels:
330            self.parent.on_close_welcome_panel()
331
332             
333    def set_theory(self,  theory_list=None):
334        """
335        """
336        #set the model state for a given theory_state:
337        for item in theory_list:
338            try:
339                _, theory_state = item
340                self.fit_panel.set_model_state(theory_state)
341            except:
342                msg = "Fitting: cannot deal with the theory received"
343                logging.error("set_theory " + msg + "\n" + str(sys.exc_value))
344                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
345           
346    def set_state(self, state=None, datainfo=None, format=None):
347        """
348        Call-back method for the fit page state reader.
349        This method is called when a .fitv/.svs file is loaded.
350       
351        : param state: PageState object
352        : param datainfo: data
353        """
354        #state = self.state_reader.get_state()
355        if state != None:
356            state = state.clone()
357            # store fitting state in temp_state
358            self.temp_state.append(state) 
359        else:
360            self.temp_state = []
361        # index to start with for a new set_state
362        self.state_index = 0
363        # state file format
364        self.sfile_ext = format
365       
366        self.on_set_state_helper(event=None)
367
368    def  on_set_state_helper(self,event=None):
369        """
370        Set_state_helper. This actually sets state
371        after plotting data from state file.
372       
373        : event: FitStateUpdateEvent called
374            by dataloader.plot_data from guiframe
375        """
376        if len(self.temp_state) == 0:
377            if self.state_index==0 and len(self.mypanels) <= 0 \
378            and self.sfile_ext =='.svs':
379                self.fit_panel.add_default_pages()
380                self.temp_state = []
381                self.state_index = 0
382            return
383       
384        try:
385            # Load fitting state
386            state = self.temp_state[self.state_index]
387            #panel state should have model selection to set_state
388            if state.formfactorcombobox != None:
389                #set state
390                data = self.parent.create_gui_data(state.data)
391                data.group_id = state.data.group_id
392                self.parent.add_data(data_list={data.id:data})
393                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
394                                        title=data.title))
395                #need to be fix later make sure we are sendind guiframe.data
396                #to panel
397                state.data = data
398                page = self.fit_panel.set_state(state)   
399            else:
400                #just set data because set_state won't work
401                data = self.parent.create_gui_data(state.data)
402                data.group_id = state.data.group_id
403                self.parent.add_data(data_list={data.id:data})
404                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
405                                        title=data.title))
406                page = self.add_fit_page([data])
407                caption = page.window_caption
408                self.store_data(uid=page.uid, data_list=page.get_data_list(), 
409                        caption=caption)
410                self.mypanels.append(page) 
411               
412            # get ready for the next set_state
413            self.state_index += 1
414
415            #reset state variables to default when all set_state is finished.
416            if len(self.temp_state) == self.state_index:
417               
418                self.temp_state = []
419                #self.state_index = 0
420                # Make sure the user sees the fitting panel after loading
421                #self.parent.set_perspective(self.perspective)
422                self.on_perspective(event=None)
423        except:
424            self.state_index==0
425            self.temp_state = []
426            raise
427                 
428    def save_fit_state(self, filepath, fitstate): 
429        """
430        save fit page state into file
431        """
432        self.state_reader.write(filename=filepath, fitstate=fitstate)
433
434    def set_fit_weight(self, uid, flag, is2d=False, fid=None):
435        """
436        Set the fit weights of a given page for all
437        its data by default. If fid is provide then set the range
438        only for the data with fid as id
439        :param uid: id corresponding to a fit page
440        :param fid: id corresponding to a fit problem (data, model)
441        :param weight: current dy data
442        """
443        if uid in self.page_finder.keys():
444            self.page_finder[uid].set_weight(flag=flag, is2d=is2d)
445                   
446    def set_fit_range(self, uid, qmin, qmax, fid=None):
447        """
448        Set the fitting range of a given page for all
449        its data by default. If fid is provide then set the range
450        only for the data with fid as id
451        :param uid: id corresponding to a fit page
452        :param fid: id corresponding to a fit problem (data, model)
453        :param qmin: minimum  value of the fit range
454        :param qmax: maximum  value of the fit range
455        """
456        if uid in self.page_finder.keys():
457            self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
458                   
459    def schedule_for_fit(self, value=0, uid=None): 
460        """
461        Set the fit problem field to 0 or 1 to schedule that problem to fit.
462        Schedule the specified fitproblem or get the fit problem related to
463        the current page and set value.
464        :param value: integer 0 or 1
465        :param uid: the id related to a page contaning fitting information
466        """
467        if uid in self.page_finder.keys(): 
468            self.page_finder[uid].schedule_tofit(value)
469         
470    def get_page_finder(self):
471        """
472        return self.page_finder used also by simfitpage.py
473        """ 
474        return self.page_finder
475   
476    def set_page_finder(self,modelname,names,values):
477        """
478        Used by simfitpage.py to reset a parameter given the string constrainst.
479         
480        :param modelname: the name ot the model for with the parameter
481                            has to reset
482        :param value: can be a string in this case.
483        :param names: the paramter name
484         
485        :note: expecting park used for fit.
486         
487        """ 
488        sim_page_id = self.sim_page.uid
489        for uid, value in self.page_finder.iteritems():
490            if uid != sim_page_id:
491                list = value.get_model()
492                model = list[0]
493                if model.name == modelname:
494                    value.set_model_param(names, values)
495                    break
496         
497    def split_string(self,item): 
498        """
499        receive a word containing dot and split it. used to split parameterset
500        name into model name and parameter name example: ::
501       
502            paramaterset (item) = M1.A
503            Will return model_name = M1 , parameter name = A
504           
505        """
506        if string.find(item,".")!=-1:
507            param_names= re.split("\.",item)
508            model_name=param_names[0]           
509            ##Assume max len is 3; eg., M0.radius.width
510            if len(param_names) == 3:
511                param_name=param_names[1]+"."+param_names[2]
512            else:
513                param_name=param_names[1]                   
514            return model_name,param_name
515   
516    def set_ftol(self, ftol=None):
517        """
518        Set ftol: Relative error desired in the sum of chi squares. 
519        """
520        # check if it is flaot
521        try:
522            f_tol = float(ftol)
523        except:
524            # default
525            f_tol = SANS_F_TOL
526           
527        self.ftol = f_tol
528        # update ftol menu help strings
529        ftol_help = "Change the current FTolerance (=%s) " % str(self.ftol)
530        ftol_help += "of Simple FitEngine..." 
531        self.menu1.SetHelpString(self.id_tol, ftol_help)
532       
533    def show_ftol_dialog(self, event=None):
534        """
535        Dialog to select ftol for Scipy
536        """
537        #if event != None:
538        #    event.Skip()
539        from ftol_dialog import ChangeFtol
540        panel = ChangeFtol(self.parent, self)
541        panel.ShowModal()
542                 
543    def stop_fit(self, uid):
544        """
545        Stop the fit engine
546        """
547        if uid in self.fit_thread_list.keys():
548            calc_fit = self.fit_thread_list[uid]
549            if calc_fit is not  None and calc_fit.isrunning():
550                calc_fit.stop()
551                msg = "Fit stop!"
552                wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
553            del self.fit_thread_list[uid]
554        #set the fit button label of page when fit stop is trigger from
555        #simultaneous fit pane
556        if  self.sim_page is not None and uid == self.sim_page.uid:
557            for uid, value in self.page_finder.iteritems():
558                if value.get_scheduled() == 1:
559                    if uid in self.fit_panel.opened_pages.keys():
560                        panel = self.fit_panel.opened_pages[uid]
561                        panel. _on_fit_complete()
562 
563    def set_smearer(self, uid, smearer, fid, qmin=None, qmax=None, draw=True,
564                    enable_smearer=False):
565        """
566        Get a smear object and store it to a fit problem of fid as id. If proper
567        flag is enable , will plot the theory with smearing information.
568       
569        :param smearer: smear object to allow smearing data of id fid
570        :param enable_smearer: Define whether or not all (data, model) contained
571            in the structure of id uid will be smeared before fitting.
572        :param qmin: the maximum value of the theory plotting range
573        :param qmax: the maximum value of the theory plotting range
574        :param draw: Determine if the theory needs to be plot
575        """   
576        if uid not in self.page_finder.keys():
577            return
578        self.page_finder[uid].enable_smearing(flag=enable_smearer)
579        self.page_finder[uid].set_smearer(smearer, fid=fid)
580        if draw:
581            ## draw model 1D with smeared data
582            data =  self.page_finder[uid].get_fit_data(fid=fid)
583            if data is None:
584                msg = "set_mearer requires at least data.\n"
585                msg += "Got data = %s .\n" % str(data)
586                raise ValueError, msg
587            model = self.page_finder[uid].get_model(fid=fid)
588            if model is None:
589                return
590            enable1D = issubclass(data.__class__, Data1D)
591            enable2D = issubclass(data.__class__, Data2D)
592            ## if user has already selected a model to plot
593            ## redraw the model with data smeared
594            smear = self.page_finder[uid].get_smearer(fid=fid)
595            self.draw_model(model=model, data=data, page_id=uid, smearer=smear,
596                enable1D=enable1D, enable2D=enable2D,
597                qmin=qmin, qmax=qmax, weight=None)
598            self._mac_sleep(0.2)
599           
600    def _mac_sleep(self, sec=0.2):
601        """
602        Give sleep to MAC
603        """
604        if ON_MAC:
605           time.sleep(sec)
606       
607    def draw_model(self, model, page_id, data=None, smearer=None,
608                   enable1D=True, enable2D=False,
609                   state=None,
610                   toggle_mode_on=False,
611                   qmin=None, qmax=None, 
612                   update_chisqr=True, weight=None):
613        """
614        Draw model.
615       
616        :param model: the model to draw
617        :param name: the name of the model to draw
618        :param data: the data on which the model is based to be drawn
619        :param description: model's description
620        :param enable1D: if true enable drawing model 1D
621        :param enable2D: if true enable drawing model 2D
622        :param qmin:  Range's minimum value to draw model
623        :param qmax:  Range's maximum value to draw model
624        :param qstep: number of step to divide the x and y-axis
625        :param update_chisqr: update chisqr [bool]
626             
627        """
628        self.weight = weight
629        if issubclass(data.__class__, Data1D) or not enable2D:   
630            ## draw model 1D with no loaded data
631            self._draw_model1D(model=model, 
632                               data=data,
633                               page_id=page_id,
634                               enable1D=enable1D, 
635                               smearer=smearer,
636                               qmin=qmin,
637                               qmax=qmax, 
638                               toggle_mode_on=toggle_mode_on,
639                               state=state,
640                               update_chisqr=update_chisqr)
641        else:     
642            ## draw model 2D with no initial data
643            self._draw_model2D(model=model,
644                                page_id=page_id,
645                                data=data,
646                                enable2D=enable2D,
647                                smearer=smearer,
648                                qmin=qmin,
649                                qmax=qmax,
650                                state=state,
651                                toggle_mode_on=toggle_mode_on,
652                                update_chisqr=update_chisqr)
653           
654    def onFit(self, uid):
655        """
656        Get series of data, model, associates parameters and range and send then
657        to  series of fit engines. Fit data and model, display result to
658        corresponding panels.
659        :param uid: id related to the panel currently calling this fit function.
660        """
661        ##  count the number of fitproblem schedule to fit
662        fitproblem_count = 0
663        for value in self.page_finder.values():
664            if value.get_scheduled() == 1:
665                fitproblem_count += 1
666        ## if simultaneous fit change automatically the engine to park
667        if fitproblem_count > 1:
668            self._on_change_engine(engine='park')
669        self.fitproblem_count = fitproblem_count 
670        if self._fit_engine == "park":
671            engineType = "Simultaneous Fit"
672        else:
673            engineType = "Single Fit"
674        fitter_list = []       
675        sim_fitter = None     
676        if self.sim_page is not None and self.sim_page.uid == uid:
677            #simulatanous fit only one engine need to be created   
678            sim_fitter = Fit(self._fit_engine) 
679            fitter_list.append(sim_fitter) 
680       
681        self.current_pg = None
682        list_page_id = []
683        fit_id = 0
684        for page_id, value in self.page_finder.iteritems():
685            # For simulfit (uid give with None), do for-loop
686            # if uid is specified (singlefit), do it only on the page.
687            if engineType == "Single Fit":
688                if page_id != uid:
689                    continue
690            try:
691                if value.get_scheduled() == 1:
692                    #Get list of parameters name to fit
693                    pars = []
694                    templist = []
695                    page = self.fit_panel.get_page_by_id(page_id)
696                    templist = page.get_param_list()
697                    for element in templist:
698                        name = str(element[1])
699                        pars.append(name)
700                    for fitproblem in  value.get_fit_problem():
701                        if sim_fitter is None:
702                            fitter = Fit(self._fit_engine) 
703                            self._fit_helper(fitproblem, pars, fitter, fit_id)
704                            fitter_list.append(fitter) 
705                        else:
706                            fitter = sim_fitter
707                            self._fit_helper(fitproblem, pars, fitter, fit_id)
708                        fit_id += 1
709                    list_page_id.append(page_id)
710                    current_page_id = page_id
711                    value.clear_model_param()
712            except:
713                msg= "%s error: %s" % (engineType, sys.exc_value)
714                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
715                                                      type="stop"))
716                return 
717        ## If a thread is already started, stop it
718        #if self.calc_fit!= None and self.calc_fit.isrunning():
719        #    self.calc_fit.stop()
720        msg = "Fitting is in progress..."
721        wx.PostEvent( self.parent, StatusEvent(status=msg, type="progress" ))
722       
723        #Handler used for park engine displayed message
724        handler = ConsoleUpdate(parent=self.parent,
725                                manager=self,
726                                improvement_delta=0.1)
727        self._mac_sleep(0.2)
728        ## perform single fit
729        if fitproblem_count == 1:
730            calc_fit = FitThread(handler = handler,
731                                    fn=fitter_list,
732                                    pars=pars,
733                                    page_id=list_page_id,
734                                    completefn=self._single_fit_completed,
735                                    ftol=self.ftol)
736        else:
737            current_page_id = self.sim_page.uid
738            ## Perform more than 1 fit at the time
739            calc_fit = FitThread(handler=handler,
740                                    fn=fitter_list,
741                                    page_id=list_page_id,
742                                    updatefn=handler.update_fit,
743                                    completefn=self._simul_fit_completed,
744                                    ftol=self.ftol)
745        self.fit_thread_list[current_page_id] = calc_fit
746        calc_fit.queue()
747        msg = "Fitting is in progress..."
748        wx.PostEvent( self.parent, StatusEvent(status=msg, type="progress" ))
749       
750        self.ready_fit(calc_fit=calc_fit)
751       
752    def ready_fit(self, calc_fit):
753        """
754        Ready for another fit
755        """
756        if self.fitproblem_count != None and self.fitproblem_count > 1:
757            calc_fit.ready(2.5)
758        else:
759            time.sleep(0.4)
760           
761    def remove_plot(self, uid, fid=None, theory=False):
762        """
763        remove model plot when a fit page is closed
764        :param uid: the id related to the fitpage to close
765        :param fid: the id of the fitproblem(data, model, range,etc)
766        """
767        if uid not in self.page_finder.keys():
768            return
769        fitproblemList = self.page_finder[uid].get_fit_problem(fid)
770        for fitproblem in fitproblemList:
771            data = fitproblem.get_fit_data()
772            model = fitproblem.get_model()
773            plot_id = None
774            if model is not None:
775                plot_id = data.id + name
776            if theory:
777                plot_id = data.id
778            group_id = data.group_id
779            wx.PostEvent(self.parent, NewPlotEvent(id=plot_id,
780                                                       group_id=group_id,
781                                                       action='remove'))
782           
783    def store_data(self, uid, data_list=None, caption=None):
784        """
785        Recieve a list of data and store them ans well as a caption of
786        the fit page where they come from.
787        :param uid: if related to a fit page
788        :param data_list: list of data to fit
789        :param caption: caption of the window related to these data
790        """
791        if data_list is None:
792            data_list = []
793       
794        self.page_finder[uid].set_fit_data(data=data_list)
795        if caption is not None:
796            self.page_finder[uid].set_fit_tab_caption(caption=caption)
797           
798    def on_add_new_page(self, event=None):
799        """
800        ask fit panel to create a new empty page
801        """
802        try:
803            page = self.fit_panel.add_empty_page()
804            page_caption = page.window_caption
805            # add data associated to the page created
806            if page != None: 
807                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
808                                               info="info"))
809            else:
810                msg = "Page was already Created"
811                wx.PostEvent(self.parent, StatusEvent(status=msg,
812                                                       info="warning"))
813            self.set_top_panel()
814        except:
815            msg = "Creating Fit page: %s"%sys.exc_value
816            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
817       
818    def add_fit_page(self, data):
819        """
820        given a data, ask to the fitting panel to create a new fitting page,
821        get this page and store it into the page_finder of this plug-in
822        :param data: is a list of data
823        """
824        page = self.fit_panel.set_data(data)
825        page_caption = page.window_caption
826        #append Data1D to the panel containing its theory
827        #if theory already plotted
828        if page.uid in self.page_finder:
829            data = page.get_data()
830            theory_data = self.page_finder[page.uid].get_theory_data(data.id)
831            if issubclass(data.__class__, Data2D):
832                data.group_id = wx.NewId()
833                if theory_data is not None:
834                    group_id = str(page.uid) + " Model1D"
835                    wx.PostEvent(self.parent, 
836                             NewPlotEvent(group_id=group_id,
837                                               action="delete"))
838                    self.parent.update_data(prev_data=theory_data,
839                                             new_data=data)     
840            else:
841                if theory_data is not None:
842                    group_id = str(page.uid) + " Model2D"
843                    data.group_id = theory_data.group_id
844                    wx.PostEvent(self.parent, 
845                             NewPlotEvent(group_id=group_id,
846                                               action="delete"))
847                    self.parent.update_data(prev_data=theory_data,
848                                             new_data=data)   
849        self.store_data(uid=page.uid, data_list=page.get_data_list(), 
850                        caption=page.window_caption)
851        if self.sim_page is not None:
852            self.sim_page.draw_page()
853        return page
854           
855    def _onEVT_SLICER_PANEL(self, event):
856        """
857        receive and event telling to update a panel with a name starting with
858        event.panel_name. this method update slicer panel
859        for a given interactor.
860       
861        :param event: contains type of slicer , paramaters for updating
862            the panel and panel_name to find the slicer 's panel concerned.
863        """
864        for item in self.parent.panels:
865            name = event.panel_name
866            if self.parent.panels[item].window_caption.startswith(name):
867                self.parent.panels[item].set_slicer(event.type, event.params)
868               
869        self.parent._mgr.Update()
870   
871    def _closed_fitpage(self, event):   
872        """
873        request fitpanel to close a given page when its unique data is removed
874        from the plot. close fitpage only when the a loaded data is removed
875        """   
876        if event is None or event.data is None:
877            return
878        if hasattr(event.data,"is_data"):
879            if not event.data.is_data or \
880                event.data.__class__.__name__ == "Data1D":
881                self.fit_panel.close_page_with_data(event.data) 
882 
883    def _reset_schedule_problem(self, value=0, uid=None):
884        """
885        unschedule or schedule all fitproblem to be fit
886        """
887        # case that uid is not specified
888        if uid == None:
889            for page_id in self.page_finder.keys():
890                self.page_finder[page_id].schedule_tofit(value)
891        # when uid is given
892        else:
893            if uid in self.page_finder.keys():
894                self.page_finder[uid].schedule_tofit(value)
895               
896    def _fit_helper(self, fitproblem, pars, fitter, fit_id):
897        """
898        Create and set fit engine with series of data and model
899        :param pars: list of fittable parameters
900        :param fitter_list: list of fit engine
901        :param value:  structure storing data mapped to their model, range etc..
902        """
903        data = fitproblem.get_fit_data()
904        model = fitproblem.get_model()
905        smearer = fitproblem.get_smearer()
906        qmin, qmax = fitproblem.get_range()
907        #Extra list of parameters and their constraints
908        listOfConstraint = []
909        param = fitproblem.get_model_param()
910        if len(param) > 0:
911            for item in param:
912                ## check if constraint
913                if item[0] != None and item[1] != None:
914                    listOfConstraint.append((item[0],item[1]))
915        fitter.set_model(model, fit_id, pars, constraints=listOfConstraint)
916        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin, 
917                        qmax=qmax)
918        fitter.select_problem_for_fit(id=fit_id, value=1)
919       
920       
921    def _onSelect(self,event):
922        """
923        when Select data to fit a new page is created .Its reference is
924        added to self.page_finder
925        """
926        self.panel = event.GetEventObject()
927        Plugin.on_perspective(self, event=event)
928        for plottable in self.panel.graph.plottables:
929            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
930                data_id = self.panel.graph.selected_plottable
931                if plottable == self.panel.plots[data_id]:
932                    data = plottable
933                    self.add_fit_page(data=[data])
934                    return
935            else:
936                data = plottable
937                self.add_fit_page(data=[data])
938        self.set_top_panel()
939           
940    def update_fit(self, result=None, msg=""):
941        """
942        """
943        print "update_fit result", result
944       
945    def _batch_single_fit_complete_helper(self, result, pars, page_id, 
946                                          elapsed=None):
947        """
948        Display fit result in batch
949        :param result: list of objects received fromt fit engines
950        :param pars: list of  fitted parameters names
951        :param page_id: list of page ids which called fit function
952        :param elapsed: time spent at the fitting level
953        """
954        self._update_fit_button(page_id)
955        msg = "Single Fitting complete "
956        wx.PostEvent(self.parent, StatusEvent(status=msg, info="info",
957                                                      type="stop"))
958        if self.batch_on:
959            batch_result = {"Chi2":[]}
960            for index  in range(len(pars)):
961                batch_result[pars[index]] = []
962                batch_result["error on %s" % pars[index]] = []
963            for res in result:
964                if res is None:
965                    null_value = numpy.nan
966                    batch_result["Chi2"].append(null_value)
967                    for index  in range(len(pars)):
968                        batch_result[pars[index]].append(null_value)
969                        item = null_value
970                        batch_result["error on %s" % pars[index]].append(item)
971                else:
972                    batch_result["Chi2"].append(res.fitness)
973                    for index  in range(len(pars)):
974                        batch_result[pars[index]].append(res.pvec[index])
975                        item = res.stderr[index]
976                        batch_result["error on %s" % pars[index]].append(item)
977            pid = page_id[0]
978            self.page_finder[pid].set_result(result=batch_result)     
979            self.parent.on_set_batch_result(data=batch_result, 
980                                            name=self.sub_menu)
981            for uid in page_id:
982                cpage = self.fit_panel.get_page_by_id(uid)
983                cpage._on_fit_complete()
984           
985    def _single_fit_completed(self, result, pars, page_id, elapsed=None):
986        """
987         Display fit result on one page of the notebook.
988        :param result: list of object generated when fit ends
989        :param pars: list of names of parameters fitted
990        :param page_id: list of page ids which called fit function
991        :param elapsed: time spent at the fitting level
992        """ 
993        self._mac_sleep(0.2)
994        if page_id[0] in self.fit_thread_list.keys():
995            del self.fit_thread_list[page_id[0]] 
996        if self.batch_on:
997            wx.CallAfter(self._batch_single_fit_complete_helper,
998                          result, pars, page_id, elapsed=None)
999            return 
1000        else: 
1001            try:
1002                result = result[0]
1003                if result == None:
1004                    self._update_fit_button(page_id)
1005                    msg= "Single Fitting did not converge!!!"
1006                    wx.PostEvent(self.parent, 
1007                                 StatusEvent(status=msg, 
1008                                             info="warning",
1009                                             type="stop"))
1010                    return
1011                if not numpy.isfinite(result.fitness) or \
1012                        numpy.any(result.pvec == None) or \
1013                        not numpy.all(numpy.isfinite(result.pvec)):
1014                    msg = "Single Fitting did not converge!!!"
1015                    wx.PostEvent(self.parent, 
1016                                 StatusEvent(status=msg, 
1017                                             info="warning",
1018                                             type="stop"))
1019                    self._update_fit_button(page_id)
1020                    return
1021               
1022                for uid in page_id:
1023                    cpage = self.fit_panel.get_page_by_id(uid)
1024                    # Make sure we got all results
1025                    #(CallAfter is important to MAC)
1026                    wx.CallAfter(cpage.onsetValues, result.fitness, pars, 
1027                                 result.pvec, result.stderr)
1028                    cpage._on_fit_complete()
1029                if result.stderr == None:
1030                    msg = "Fit Abort: "
1031                else:
1032                    msg = "Fitting: "
1033                msg += "Completed!!!"
1034                wx.PostEvent(self.parent, StatusEvent(status=msg))
1035            except ValueError:
1036                self._update_fit_button(page_id)
1037                msg = "Single Fitting did not converge!!!"
1038                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1039                                                      type="stop"))
1040            except:
1041                self._update_fit_button(page_id)
1042                msg = "Single Fit completed but Following"
1043                msg += " error occurred:%s" % sys.exc_value
1044                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1045                                                      type="stop"))
1046                raise
1047               
1048    def _simul_fit_completed(self, result, page_id, pars=None, elapsed=None):
1049        """
1050        Display result of the fit on related panel(s).
1051        :param result: list of object generated when fit ends
1052        :param pars: list of names of parameters fitted
1053        :param page_id: list of page ids which called fit function
1054        :param elapsed: time spent at the fitting level
1055        """
1056        result = result[0]
1057        self.fit_thread_list = {}
1058        if page_id is None:
1059            page_id = []
1060        ## fit more than 1 model at the same time
1061        self._mac_sleep(0.2) 
1062        try:
1063            msg = "" 
1064            if result == None:
1065                self._update_fit_button(page_id)
1066                msg= "Complex Fitting did not converge!!!"
1067                wx.PostEvent(self.parent, StatusEvent(status=msg,
1068                                                      type="stop"))
1069                return
1070            if not numpy.isfinite(result.fitness) or \
1071                numpy.any(result.pvec == None) or not \
1072                numpy.all(numpy.isfinite(result.pvec)):
1073                self._update_fit_button(page_id)
1074                msg= "Simultaneous Fitting did not converge!!!"
1075                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
1076                return
1077             
1078            for uid in page_id:   
1079                fpdict = self.page_finder[uid]
1080                for value in fpdict.itervalues():
1081                    model = value.get_model()
1082                    data =  value.get_fit_data()
1083                    small_param_name = []
1084                    small_out = []
1085                    small_cov = []
1086                    #Separate result in to data corresponding to each page
1087                    for p in result.parameters:
1088                        model_name, param_name = self.split_string(p.name) 
1089                        if model.name == model_name:
1090                            p_name= model.name+"."+param_name
1091                            if p.name == p_name:     
1092                                if p.value != None and numpy.isfinite(p.value):
1093                                    small_out.append(p.value)
1094                                    small_param_name.append(param_name)
1095                                    small_cov.append(p.stderr)
1096                # Display result on each page
1097                cpage = self.fit_panel.get_page_by_id(uid)
1098                wx.CallAfter(cpage.onsetValues, 
1099                                    result.fitness,
1100                                  small_param_name,
1101                                  small_out,small_cov)
1102                cpage._on_fit_complete()
1103                msg = "Fit completed!"
1104                wx.PostEvent(self.parent, StatusEvent(status=msg))
1105        except Exception:
1106            raise
1107            self._update_fit_button(page_id)
1108            msg = "Complex Fitting did not converge!!!"
1109            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1110                                                  type="stop"))
1111            return
1112        except:
1113            self._update_fit_button(page_id)
1114            msg = "Simultaneous Fit completed"
1115            msg += " but Following error occurred:%s" % sys.exc_value
1116            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1117   
1118    def _update_fit_button(self, page_id):
1119        """
1120        Update Fit button when fit stopped
1121       
1122        : parameter page_id: fitpage where the button is
1123        """
1124        if page_id.__class__.__name__ != 'list':
1125            page_id = [page_id]
1126        for uid in page_id: 
1127            page = self.fit_panel.get_page_by_id(uid)
1128            page._on_fit_complete()
1129       
1130    def _on_show_panel(self, event):
1131        """
1132        """
1133        pass
1134       
1135    def _onset_engine_park(self,event):
1136        """
1137        set engine to park
1138        """
1139        self._on_change_engine('park')
1140       
1141    def _onset_engine_scipy(self,event):
1142        """
1143        set engine to scipy
1144        """
1145        self._on_change_engine('scipy')
1146       
1147    def _on_slicer_event(self, event):
1148        """
1149        Receive a panel as event and send it to guiframe
1150       
1151        :param event: event containing a panel
1152       
1153        """
1154        if event.panel is not None:
1155            new_panel = event.panel
1156            self.slicer_panels.append(event.panel)
1157            # Set group ID if available
1158            event_id = self.parent.popup_panel(new_panel)
1159            new_panel.uid = event_id
1160            self.mypanels.append(new_panel) 
1161       
1162    def _onclearslicer(self, event):
1163        """
1164        Clear the boxslicer when close the panel associate with this slicer
1165        """
1166        name =event.GetPane().caption
1167   
1168        for panel in self.slicer_panels:
1169            if panel.window_caption==name:
1170               
1171                for item in self.parent.panels:
1172                    if hasattr(self.parent.panels[item], "uid"):
1173                        if self.parent.panels[item].uid ==panel.base.uid:
1174                            self.parent.panels[item].onClearSlicer(event)
1175                            self.parent._mgr.Update()
1176                            break 
1177                break
1178   
1179    def _return_engine_type(self):
1180        """
1181        return the current type of engine
1182        """
1183        return self._fit_engine
1184     
1185     
1186    def _on_change_engine(self, engine='park'):
1187        """
1188        Allow to select the type of engine to perform fit
1189       
1190        :param engine: the key work of the engine
1191       
1192        """
1193        ## saving fit engine name
1194        self._fit_engine = engine
1195        ## change menu item state
1196        if engine == "park":
1197            self.menu1.FindItemById(self.park_id).Check(True)
1198            self.menu1.FindItemById(self.scipy_id).Check(False)
1199        else:
1200            self.menu1.FindItemById(self.park_id).Check(False)
1201            self.menu1.FindItemById(self.scipy_id).Check(True)
1202        ## post a message to status bar
1203        msg = "Engine set to: %s" % self._fit_engine
1204        wx.PostEvent(self.parent, 
1205                     StatusEvent(status=msg))
1206        ## send the current engine type to fitpanel
1207        self.fit_panel._on_engine_change(name=self._fit_engine)
1208
1209       
1210    def _on_model_panel(self, evt):
1211        """
1212        react to model selection on any combo box or model menu.plot the model 
1213       
1214        :param evt: wx.combobox event
1215       
1216        """
1217        model = evt.model
1218        uid = evt.uid
1219        qmin = evt.qmin
1220        qmax = evt.qmax
1221        smearer = evt.smearer
1222        caption = evt.caption
1223        enable_smearer = evt.enable_smearer
1224        if model == None:
1225            return
1226        if uid not in self.page_finder.keys():
1227            return
1228        # save the name containing the data name with the appropriate model
1229        self.page_finder[uid].set_model(model)
1230        self.page_finder[uid].enable_smearing(enable_smearer)
1231        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1232        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1233        if self.sim_page is not None:
1234            self.sim_page.draw_page()
1235       
1236    def _update1D(self, x, output):
1237        """
1238        Update the output of plotting model 1D
1239        """
1240        msg = "Plot updating ... "
1241        wx.PostEvent(self.parent, StatusEvent(status=msg,type="update"))
1242       
1243    def _complete1D(self, x, y, page_id, elapsed, index, model,
1244                    toggle_mode_on=False, state=None, 
1245                    data=None, update_chisqr=True):
1246        """
1247        Complete plotting 1D data
1248        """ 
1249        try:
1250            new_plot = Data1D(x=x, y=y)
1251            new_plot.is_data = False
1252            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1253            _yaxis, _yunit = data.get_yaxis() 
1254            _xaxis, _xunit = data.get_xaxis() 
1255            new_plot.title = data.name
1256            new_plot.group_id = data.group_id
1257            new_plot.id =  str(page_id) + "model"
1258            if new_plot.id in self.color_dict:
1259                new_plot.custom_color = self.color_dict[new_plot.id] 
1260            #find if this theory was already plotted and replace that plot given
1261            #the same id
1262            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1263            new_plot.name = model.name + " ["+ str(model.__class__.__name__)+"]"
1264            new_plot.xaxis(_xaxis, _xunit)
1265            new_plot.yaxis(_yaxis, _yunit)
1266            self.page_finder[page_id].set_theory_data(data=new_plot, 
1267                                                      fid=data.id)
1268            self.parent.update_theory(data_id=data.id, theory=new_plot,
1269                                       state=state)   
1270            current_pg = self.fit_panel.get_page_by_id(page_id)
1271            title = new_plot.title
1272            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1273                                            title=str(title)))
1274            caption = current_pg.window_caption
1275            self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1276            self.page_finder[page_id].set_theory_data(data=new_plot, 
1277                                                      fid=data.id)
1278            if toggle_mode_on:
1279                wx.PostEvent(self.parent, 
1280                             NewPlotEvent(group_id=str(page_id) + " Model2D",
1281                                               action="Hide"))
1282            else:
1283                if update_chisqr:
1284                    wx.PostEvent(current_pg,
1285                                 Chi2UpdateEvent(output=self._cal_chisqr(
1286                                                                data=data,
1287                                                            page_id=page_id,
1288                                                            index=index)))
1289                else:
1290                    self._plot_residuals(page_id, data, index)
1291
1292            msg = "Computation  completed!"
1293            wx.PostEvent( self.parent, StatusEvent(status=msg, type="stop" ))
1294        except:
1295            raise
1296            #msg = " Error occurred when drawing %s Model 1D: " % new_plot.name
1297            #msg += " %s"  % sys.exc_value
1298            #wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1299   
1300    def _update2D(self, output,time=None):
1301        """
1302        Update the output of plotting model
1303        """
1304        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1305        #updating ... ", type="update"))
1306        #self.ready_fit()
1307 
1308    def _complete2D(self, image, data, model, page_id,  elapsed, index, qmin,
1309                     qmax, toggle_mode_on=False, state=None, 
1310                     update_chisqr=True):
1311        """
1312        Complete get the result of modelthread and create model 2D
1313        that can be plot.
1314        """
1315        new_plot= Data2D(image=image, err_image=data.err_data)
1316        new_plot.name = model.name
1317        new_plot.title = "Analytical model 2D "
1318        new_plot.id = str(page_id) + "model"
1319        new_plot.group_id = str(page_id) + " Model2D"
1320        new_plot.detector = data.detector
1321        new_plot.source = data.source
1322        new_plot.is_data = False 
1323        new_plot.qx_data = data.qx_data
1324        new_plot.qy_data = data.qy_data
1325        new_plot.q_data = data.q_data
1326        new_plot.mask = data.mask
1327        ## plot boundaries
1328        new_plot.ymin = data.ymin
1329        new_plot.ymax = data.ymax
1330        new_plot.xmin = data.xmin
1331        new_plot.xmax = data.xmax
1332        title = data.title
1333        if len(title) > 1:
1334            new_plot.title = "Model2D for " + data.name
1335        new_plot.is_data = False
1336        new_plot.name = model.name + " [" + \
1337                                    str(model.__class__.__name__) + "-2D]"
1338        theory_data = deepcopy(new_plot)
1339        theory_data.name = "Unknown"
1340       
1341        self.page_finder[page_id].set_theory_data(data=theory_data, fid=data.id)
1342        self.parent.update_theory(data_id=data.id, 
1343                                       theory=new_plot,
1344                                       state=state) 
1345        current_pg = self.fit_panel.get_page_by_id(page_id)
1346        title = new_plot.title
1347        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1348                                               title=title))
1349        self.page_finder[page_id].set_theory_data(data=new_plot, fid=data.id)
1350        if toggle_mode_on:
1351            wx.PostEvent(self.parent, 
1352                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1353                                               action="Hide"))
1354        else:
1355            # Chisqr in fitpage
1356            if update_chisqr:
1357                wx.PostEvent(current_pg,
1358                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1359                                                         page_id=page_id,
1360                                                         index=index)))
1361            else:
1362                self._plot_residuals(page_id, data, index)
1363        msg = "Computation  completed!"
1364        wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1365   
1366    def _draw_model2D(self, model, page_id, qmin,
1367                      qmax,
1368                      data=None, smearer=None,
1369                      description=None, enable2D=False,
1370                      state=None,
1371                      toggle_mode_on=False,
1372                       update_chisqr=True):
1373        """
1374        draw model in 2D
1375       
1376        :param model: instance of the model to draw
1377        :param description: the description of the model
1378        :param enable2D: when True allows to draw model 2D
1379        :param qmin: the minimum value to  draw model 2D
1380        :param qmax: the maximum value to draw model 2D
1381        :param qstep: the number of division of Qx and Qy of the model to draw
1382           
1383        """
1384        if not enable2D:
1385            return None
1386        try:
1387            from model_thread import Calc2D
1388            ## If a thread is already started, stop it
1389            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1390                self.calc_2D.stop()
1391            self.calc_2D = Calc2D(model=model, 
1392                                    data=data,
1393                                    page_id=page_id,
1394                                    smearer=smearer,
1395                                    qmin=qmin,
1396                                    qmax=qmax,
1397                                    toggle_mode_on=toggle_mode_on,
1398                                    state=state,
1399                                    completefn=self._complete2D,
1400                                    update_chisqr=update_chisqr)
1401            self.calc_2D.queue()
1402
1403        except:
1404            raise
1405            #msg = " Error occurred when drawing %s Model 2D: " % model.name
1406            #msg += " %s" % sys.exc_value
1407            #wx.PostEvent(self.parent, StatusEvent(status=msg))
1408
1409    def _draw_model1D(self, model, page_id, data, 
1410                      qmin, qmax, smearer=None,
1411                state=None,
1412                toggle_mode_on=False, update_chisqr=True, 
1413                enable1D=True):
1414        """
1415        Draw model 1D from loaded data1D
1416       
1417        :param data: loaded data
1418        :param model: the model to plot
1419       
1420        """
1421        if not enable1D:
1422            return 
1423        try:
1424            from model_thread import Calc1D
1425            ## If a thread is already started, stop it
1426            if (self.calc_1D is not None) and self.calc_1D.isrunning():
1427                self.calc_1D.stop()
1428            self.calc_1D = Calc1D(data=data,
1429                                  model=model,
1430                                  page_id=page_id, 
1431                                  qmin=qmin,
1432                                  qmax=qmax,
1433                                  smearer=smearer,
1434                                  state=state,
1435                                  toggle_mode_on=toggle_mode_on,
1436                                  completefn=self._complete1D,
1437                                  #updatefn = self._update1D,
1438                                  update_chisqr=update_chisqr)
1439            self.calc_1D.queue()
1440        except:
1441            msg = " Error occurred when drawing %s Model 1D: " % model.name
1442            msg += " %s" % sys.exc_value
1443            wx.PostEvent(self.parent, StatusEvent(status=msg))
1444   
1445 
1446   
1447    def _cal_chisqr(self, page_id, data, index=None): 
1448        """
1449        Get handy Chisqr using the output from draw1D and 2D,
1450        instead of calling expansive CalcChisqr in guithread
1451        """
1452        data = deepcopy(data)
1453       
1454        # default chisqr
1455        chisqr = None
1456        #to compute chisq make sure data has valid data
1457        # return None if data == None
1458        if not check_data_validity(data):
1459            return chisqr
1460       
1461        # Get data: data I, theory I, and data dI in order
1462        if data.__class__.__name__ == "Data2D":
1463            if index == None: 
1464                index = numpy.ones(len(data.data),ntype=bool)
1465            if self.weight != None:
1466                data.err_data = self.weight
1467            # get rid of zero error points
1468            index = index & (data.err_data != 0) 
1469            index = index & (numpy.isfinite(data.data)) 
1470            fn = data.data[index] 
1471            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1472            gn = theory_data.data[index]
1473            en = data.err_data[index]
1474        else:
1475            # 1 d theory from model_thread is only in the range of index
1476            if index == None: 
1477                index = numpy.ones(len(data.y), ntype=bool)
1478            if self.weight != None:
1479                data.dy = self.weight
1480            if data.dy == None or data.dy == []:
1481                dy = numpy.ones(len(data.y))
1482            else:
1483                ## Set consitently w/AbstractFitengine:
1484                # But this should be corrected later.
1485                dy = deepcopy(data.dy)
1486                dy[dy==0] = 1 
1487            fn = data.y[index] 
1488            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1489            gn = theory_data.y
1490            en = dy[index]
1491        # residual
1492        res = (fn - gn) / en
1493        residuals = res[numpy.isfinite(res)]
1494        # get chisqr only w/finite
1495        chisqr = numpy.average(residuals * residuals)
1496        self._plot_residuals(page_id, data, index)
1497        #reset weight
1498        self.weight = None
1499        return chisqr
1500   
1501    def _plot_residuals(self, page_id, data=None, index=None): 
1502        """
1503        Plot the residuals
1504       
1505        :param data: data
1506        :param index: index array (bool)
1507        : Note: this is different from the residuals in cal_chisqr()
1508        """
1509        # Get data: data I, theory I, and data dI in order
1510        if data.__class__.__name__ == "Data2D":
1511            # build residuals
1512            residuals = Data2D()
1513            #residuals.copy_from_datainfo(data)
1514            # Not for trunk the line below, instead use the line above
1515            data.clone_without_data(len(data.data), residuals)
1516            residuals.data = None
1517            fn = data.data#[index]
1518            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1519            gn = theory_data.data#[index]
1520            en = data.err_data#[index]
1521            residuals.data = (fn - gn) / en
1522            residuals.qx_data = data.qx_data#[index]
1523            residuals.qy_data = data.qy_data #[index]
1524            residuals.q_data = data.q_data#[index]
1525            residuals.err_data = numpy.ones(len(residuals.data))#[index]
1526            residuals.xmin = min(residuals.qx_data)
1527            residuals.xmax = max(residuals.qx_data)
1528            residuals.ymin = min(residuals.qy_data)
1529            residuals.ymax = max(residuals.qy_data)
1530            residuals.q_data = data.q_data#[index]
1531            residuals.mask = data.mask
1532            residuals.scale = 'linear'
1533            # check the lengths
1534            if len(residuals.data) != len(residuals.q_data):
1535                return
1536        else:
1537            # 1 d theory from model_thread is only in the range of index
1538            if data.dy == None or data.dy == []:
1539                dy = numpy.ones(len(data.y))
1540            else:
1541                ## Set consitently w/AbstractFitengine:
1542                ## But this should be corrected later.
1543                dy = deepcopy(data.dy)
1544                dy[dy==0] = 1 
1545            fn = data.y[index] 
1546            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1547            gn = theory_data.y
1548            en = dy[index]
1549            # build residuals
1550            residuals = Data1D()
1551            residuals.y = (fn - gn) / en
1552            residuals.x = data.x[index]
1553            residuals.dy = numpy.ones(len(residuals.y))
1554            residuals.dx = None
1555            residuals.dxl = None
1556            residuals.dxw = None
1557            residuals.ytransform = 'y'
1558            # For latter scale changes
1559            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
1560            residuals.yaxis('\\rm{Residuals} ', 'normalized')
1561        new_plot = residuals
1562        new_plot.name = "Residuals for " + str(data.name)
1563        ## allow to highlight data when plotted
1564        new_plot.interactive = True
1565        ## when 2 data have the same id override the 1 st plotted
1566        new_plot.id = new_plot.name#name + " residuals"
1567        ##group_id specify on which panel to plot this data
1568        new_plot.group_id = new_plot.id
1569        #new_plot.is_data = True
1570        ##post data to plot
1571        title = new_plot.name
1572       
1573        # plot data
1574        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))   
1575       
1576#def profile(fn, *args, **kw):
1577#    import cProfile, pstats, os
1578#    global call_result
1579#    def call():
1580#        global call_result
1581#        call_result = fn(*args, **kw)
1582#    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1583#    stats = pstats.Stats('profile.out')
1584#    #stats.sort_stats('time')
1585#    stats.sort_stats('calls')
1586#    stats.print_stats()
1587#    os.unlink('profile.out')
1588#    return call_result
1589if __name__ == "__main__":
1590    i = Plugin()
1591   
1592   
1593   
1594   
Note: See TracBrowser for help on using the repository browser.