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

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 a2da783 was a2da783, checked in by Jae Cho <jhjcho@…>, 13 years ago

resid id is now str id

  • Property mode set to 100644
File size: 67.4 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        batch_inputs = {}
685        batch_outputs = {}
686        for page_id, value in self.page_finder.iteritems():
687            # For simulfit (uid give with None), do for-loop
688            # if uid is specified (singlefit), do it only on the page.
689            if engineType == "Single Fit":
690                if page_id != uid:
691                    continue
692            try:
693                if value.get_scheduled() == 1:
694                    #Get list of parameters name to fit
695                    pars = []
696                    templist = []
697                    page = self.fit_panel.get_page_by_id(page_id)
698                    templist = page.get_param_list()
699                    for element in templist:
700                        name = str(element[1])
701                        pars.append(name)
702                    for fitproblem in  value.get_fit_problem():
703                        if sim_fitter is None:
704                            fitter = Fit(self._fit_engine) 
705                            self._fit_helper(fitproblem=fitproblem, 
706                                                pars=pars, 
707                                                fitter=fitter,
708                                              fit_id=fit_id, 
709                                              batch_inputs=batch_inputs,
710                                              batch_outputs=batch_outputs)
711                            fitter_list.append(fitter) 
712                        else:
713                            fitter = sim_fitter
714                            self._fit_helper(fitproblem=fitproblem, 
715                                                pars=pars, 
716                                                fitter=fitter,
717                                              fit_id=fit_id, 
718                                              batch_inputs=batch_inputs,
719                                              batch_outputs=batch_outputs)
720                        fit_id += 1
721                    list_page_id.append(page_id)
722                    current_page_id = page_id
723                    value.clear_model_param()
724            except:
725                raise
726                msg= "%s error: %s" % (engineType, sys.exc_value)
727                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
728                                                      type="stop"))
729                return 
730        ## If a thread is already started, stop it
731        #if self.calc_fit!= None and self.calc_fit.isrunning():
732        #    self.calc_fit.stop()
733        msg = "Fitting is in progress..."
734        wx.PostEvent( self.parent, StatusEvent(status=msg, type="progress" ))
735       
736        #Handler used for park engine displayed message
737        handler = ConsoleUpdate(parent=self.parent,
738                                manager=self,
739                                improvement_delta=0.1)
740        self._mac_sleep(0.2)
741        ## perform single fit
742        if fitproblem_count == 1:
743            calc_fit = FitThread(handler = handler,
744                                    fn=fitter_list,
745                                    pars=pars,
746                                    batch_inputs=batch_inputs,
747                                    batch_outputs=batch_outputs,
748                                    page_id=list_page_id,
749                                    completefn=self._single_fit_completed,
750                                    ftol=self.ftol)
751        else:
752            current_page_id = self.sim_page.uid
753            ## Perform more than 1 fit at the time
754            calc_fit = FitThread(handler=handler,
755                                    fn=fitter_list,
756                                    batch_inputs=batch_inputs,
757                                    batch_outputs=batch_outputs,
758                                    page_id=list_page_id,
759                                    updatefn=handler.update_fit,
760                                    completefn=self._simul_fit_completed,
761                                    ftol=self.ftol)
762        self.fit_thread_list[current_page_id] = calc_fit
763        calc_fit.queue()
764        msg = "Fitting is in progress..."
765        wx.PostEvent( self.parent, StatusEvent(status=msg, type="progress" ))
766       
767        self.ready_fit(calc_fit=calc_fit)
768       
769    def ready_fit(self, calc_fit):
770        """
771        Ready for another fit
772        """
773        if self.fitproblem_count != None and self.fitproblem_count > 1:
774            calc_fit.ready(2.5)
775        else:
776            time.sleep(0.4)
777           
778    def remove_plot(self, uid, fid=None, theory=False):
779        """
780        remove model plot when a fit page is closed
781        :param uid: the id related to the fitpage to close
782        :param fid: the id of the fitproblem(data, model, range,etc)
783        """
784        if uid not in self.page_finder.keys():
785            return
786        fitproblemList = self.page_finder[uid].get_fit_problem(fid)
787        for fitproblem in fitproblemList:
788            data = fitproblem.get_fit_data()
789            model = fitproblem.get_model()
790            plot_id = None
791            if model is not None:
792                plot_id = data.id + name
793            if theory:
794                plot_id = data.id
795            group_id = data.group_id
796            wx.PostEvent(self.parent, NewPlotEvent(id=plot_id,
797                                                       group_id=group_id,
798                                                       action='remove'))
799           
800    def store_data(self, uid, data_list=None, caption=None):
801        """
802        Recieve a list of data and store them ans well as a caption of
803        the fit page where they come from.
804        :param uid: if related to a fit page
805        :param data_list: list of data to fit
806        :param caption: caption of the window related to these data
807        """
808        if data_list is None:
809            data_list = []
810       
811        self.page_finder[uid].set_fit_data(data=data_list)
812        if caption is not None:
813            self.page_finder[uid].set_fit_tab_caption(caption=caption)
814           
815    def on_add_new_page(self, event=None):
816        """
817        ask fit panel to create a new empty page
818        """
819        try:
820            page = self.fit_panel.add_empty_page()
821            page_caption = page.window_caption
822            # add data associated to the page created
823            if page != None: 
824                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
825                                               info="info"))
826            else:
827                msg = "Page was already Created"
828                wx.PostEvent(self.parent, StatusEvent(status=msg,
829                                                       info="warning"))
830            self.set_top_panel()
831        except:
832            msg = "Creating Fit page: %s"%sys.exc_value
833            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
834       
835    def add_fit_page(self, data):
836        """
837        given a data, ask to the fitting panel to create a new fitting page,
838        get this page and store it into the page_finder of this plug-in
839        :param data: is a list of data
840        """
841        page = self.fit_panel.set_data(data)
842        # page could be None when loading state files
843        if page == None:
844            return page
845        page_caption = page.window_caption
846        #append Data1D to the panel containing its theory
847        #if theory already plotted
848        if page.uid in self.page_finder:
849            data = page.get_data()
850            theory_data = self.page_finder[page.uid].get_theory_data(data.id)
851            if issubclass(data.__class__, Data2D):
852                data.group_id = wx.NewId()
853                if theory_data is not None:
854                    group_id = str(page.uid) + " Model1D"
855                    wx.PostEvent(self.parent, 
856                             NewPlotEvent(group_id=group_id,
857                                               action="delete"))
858                    self.parent.update_data(prev_data=theory_data,
859                                             new_data=data)     
860            else:
861                if theory_data is not None:
862                    group_id = str(page.uid) + " Model2D"
863                    data.group_id = theory_data.group_id
864                    wx.PostEvent(self.parent, 
865                             NewPlotEvent(group_id=group_id,
866                                               action="delete"))
867                    self.parent.update_data(prev_data=theory_data,
868                                             new_data=data)   
869        self.store_data(uid=page.uid, data_list=page.get_data_list(), 
870                        caption=page.window_caption)
871        if self.sim_page is not None and not self.batch_on:
872            self.sim_page.draw_page()
873        return page
874           
875    def _onEVT_SLICER_PANEL(self, event):
876        """
877        receive and event telling to update a panel with a name starting with
878        event.panel_name. this method update slicer panel
879        for a given interactor.
880       
881        :param event: contains type of slicer , paramaters for updating
882            the panel and panel_name to find the slicer 's panel concerned.
883        """
884        for item in self.parent.panels:
885            name = event.panel_name
886            if self.parent.panels[item].window_caption.startswith(name):
887                self.parent.panels[item].set_slicer(event.type, event.params)
888               
889        self.parent._mgr.Update()
890   
891    def _closed_fitpage(self, event):   
892        """
893        request fitpanel to close a given page when its unique data is removed
894        from the plot. close fitpage only when the a loaded data is removed
895        """   
896        if event is None or event.data is None:
897            return
898        if hasattr(event.data,"is_data"):
899            if not event.data.is_data or \
900                event.data.__class__.__name__ == "Data1D":
901                self.fit_panel.close_page_with_data(event.data) 
902 
903    def _reset_schedule_problem(self, value=0, uid=None):
904        """
905        unschedule or schedule all fitproblem to be fit
906        """
907        # case that uid is not specified
908        if uid == None:
909            for page_id in self.page_finder.keys():
910                self.page_finder[page_id].schedule_tofit(value)
911        # when uid is given
912        else:
913            if uid in self.page_finder.keys():
914                self.page_finder[uid].schedule_tofit(value)
915               
916    def _fit_helper(self, fitproblem, pars, fitter, fit_id,
917                    batch_inputs, batch_outputs):
918        """
919        Create and set fit engine with series of data and model
920        :param pars: list of fittable parameters
921        :param fitter_list: list of fit engine
922        :param value:  structure storing data mapped to their model, range etc..
923        """
924        data = fitproblem.get_fit_data()
925        model = fitproblem.get_model()
926        smearer = fitproblem.get_smearer()
927        qmin, qmax = fitproblem.get_range()
928        #Extra list of parameters and their constraints
929        listOfConstraint = []
930        param = fitproblem.get_model_param()
931        if len(param) > 0:
932            for item in param:
933                ## check if constraint
934                if item[0] != None and item[1] != None:
935                    listOfConstraint.append((item[0],item[1]))
936        for param in model.getParamList():
937            if param not in pars:
938                if param not in batch_inputs.keys():
939                    batch_inputs[param] = []
940                if param not in model.non_fittable:
941                    batch_inputs[param].append(model.getParam(param))
942        fitter.set_model(model, fit_id, pars, constraints=listOfConstraint)
943        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin, 
944                        qmax=qmax)
945        fitter.select_problem_for_fit(id=fit_id, value=1)
946        #fill batch result information
947        if "Data" not in batch_outputs.keys():
948            batch_outputs["Data"] = []
949        batch_outputs["Data"].append(data.name)
950        for key, value in data.meta_data.iteritems():
951            if key not in batch_inputs.keys():
952                batch_inputs[key] = []
953            batch_inputs[key].append(value)
954        param = "temperature"
955        if hasattr(data.sample, param):
956            if param not in  batch_inputs.keys():
957                 batch_inputs[param] = []
958            batch_inputs[param].append(data.sample.temperature)
959   
960    def _onSelect(self,event):
961        """
962        when Select data to fit a new page is created .Its reference is
963        added to self.page_finder
964        """
965        self.panel = event.GetEventObject()
966        Plugin.on_perspective(self, event=event)
967        for plottable in self.panel.graph.plottables:
968            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
969                data_id = self.panel.graph.selected_plottable
970                if plottable == self.panel.plots[data_id]:
971                    data = plottable
972                    self.add_fit_page(data=[data])
973                    return
974            else:
975                data = plottable
976                self.add_fit_page(data=[data])
977        self.set_top_panel()
978           
979    def update_fit(self, result=None, msg=""):
980        """
981        """
982        print "update_fit result", result
983       
984    def _batch_single_fit_complete_helper(self, result, pars, page_id, 
985                            batch_outputs, batch_inputs, elapsed=None):
986        """
987        Display fit result in batch
988        :param result: list of objects received fromt fit engines
989        :param pars: list of  fitted parameters names
990        :param page_id: list of page ids which called fit function
991        :param elapsed: time spent at the fitting level
992        """
993        self._update_fit_button(page_id)
994        msg = "Single Fitting complete "
995        wx.PostEvent(self.parent, StatusEvent(status=msg, info="info",
996                                                      type="stop"))
997        if batch_outputs is None:
998            batch_outputs = {}
999        if self.batch_on:
1000            # format batch_outputs
1001            batch_outputs["Chi2"] = []
1002            for index  in range(len(pars)):
1003                batch_outputs[pars[index]] = []
1004                batch_outputs["error on %s" % pars[index]] = []
1005            for res in result:
1006                if res is None:
1007                    null_value = numpy.nan
1008                    batch_outputs["Chi2"].append(null_value)
1009                    for index  in range(len(pars)):
1010                        batch_outputs[pars[index]].append(null_value)
1011                        item = null_value
1012                        batch_outputs["error on %s" % pars[index]].append(item)
1013                else:
1014                    batch_outputs["Chi2"].append(res.fitness)
1015                    for index  in range(len(pars)):
1016                        batch_outputs[pars[index]].append(res.pvec[index])
1017                        item = res.stderr[index]
1018                        batch_outputs["error on %s" % pars[index]].append(item)
1019            pid = page_id[0]
1020            self.page_finder[pid].set_result(result=batch_outputs)   
1021            self.parent.on_set_batch_result(data_outputs=batch_outputs, 
1022                                            data_inputs=batch_inputs,
1023                                            plugin_name=self.sub_menu)
1024            for uid in page_id:
1025                cpage = self.fit_panel.get_page_by_id(uid)
1026                cpage._on_fit_complete()
1027           
1028    def _single_fit_completed(self, result, pars, page_id, batch_outputs,
1029                          batch_inputs=None,  elapsed=None):
1030        """
1031         Display fit result on one page of the notebook.
1032        :param result: list of object generated when fit ends
1033        :param pars: list of names of parameters fitted
1034        :param page_id: list of page ids which called fit function
1035        :param elapsed: time spent at the fitting level
1036        """ 
1037        self._mac_sleep(0.2)
1038        if page_id[0] in self.fit_thread_list.keys():
1039            del self.fit_thread_list[page_id[0]] 
1040        if self.batch_on:
1041            wx.CallAfter(self._batch_single_fit_complete_helper,
1042                          result, pars, page_id, batch_outputs, 
1043                          batch_inputs, elapsed)
1044            return 
1045        else: 
1046            try:
1047                result = result[0]
1048                if result == None:
1049                    self._update_fit_button(page_id)
1050                    msg= "Single Fitting did not converge!!!"
1051                    wx.PostEvent(self.parent, 
1052                                 StatusEvent(status=msg, 
1053                                             info="warning",
1054                                             type="stop"))
1055                    return
1056                if not numpy.isfinite(result.fitness) or \
1057                        numpy.any(result.pvec == None) or \
1058                        not numpy.all(numpy.isfinite(result.pvec)):
1059                    msg = "Single Fitting did not converge!!!"
1060                    wx.PostEvent(self.parent, 
1061                                 StatusEvent(status=msg, 
1062                                             info="warning",
1063                                             type="stop"))
1064                    self._update_fit_button(page_id)
1065                    return
1066               
1067                for uid in page_id:
1068                    cpage = self.fit_panel.get_page_by_id(uid)
1069                    # Make sure we got all results
1070                    #(CallAfter is important to MAC)
1071                    wx.CallAfter(cpage.onsetValues, result.fitness, pars, 
1072                                 result.pvec, result.stderr)
1073                    cpage._on_fit_complete()
1074                if result.stderr == None:
1075                    msg = "Fit Abort: "
1076                else:
1077                    msg = "Fitting: "
1078                msg += "Completed!!!"
1079                wx.PostEvent(self.parent, StatusEvent(status=msg))
1080            except ValueError:
1081                self._update_fit_button(page_id)
1082                msg = "Single Fitting did not converge!!!"
1083                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1084                                                      type="stop"))
1085            except:
1086                self._update_fit_button(page_id)
1087                msg = "Single Fit completed but Following"
1088                msg += " error occurred:%s" % sys.exc_value
1089                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1090                                                      type="stop"))
1091                raise
1092               
1093    def _simul_fit_completed(self, result, page_id, batch_outputs,
1094                             batch_inputs=None,
1095                              pars=None, 
1096                             elapsed=None):
1097        """
1098        Display result of the fit on related panel(s).
1099        :param result: list of object generated when fit ends
1100        :param pars: list of names of parameters fitted
1101        :param page_id: list of page ids which called fit function
1102        :param elapsed: time spent at the fitting level
1103        """
1104        result = result[0]
1105        self.fit_thread_list = {}
1106        if page_id is None:
1107            page_id = []
1108        ## fit more than 1 model at the same time
1109        self._mac_sleep(0.2) 
1110        try:
1111            msg = "" 
1112            if result == None:
1113                self._update_fit_button(page_id)
1114                msg= "Complex Fitting did not converge!!!"
1115                wx.PostEvent(self.parent, StatusEvent(status=msg,
1116                                                      type="stop"))
1117                return
1118            if not numpy.isfinite(result.fitness) or \
1119                numpy.any(result.pvec == None) or not \
1120                numpy.all(numpy.isfinite(result.pvec)):
1121                self._update_fit_button(page_id)
1122                msg= "Simultaneous Fitting did not converge!!!"
1123                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
1124                return
1125             
1126            for uid in page_id:   
1127                fpdict = self.page_finder[uid]
1128                for value in fpdict.itervalues():
1129                    model = value.get_model()
1130                    data =  value.get_fit_data()
1131                    small_param_name = []
1132                    small_out = []
1133                    small_cov = []
1134                    #Separate result in to data corresponding to each page
1135                    for p in result.parameters:
1136                        model_name, param_name = self.split_string(p.name) 
1137                        if model.name == model_name:
1138                            p_name= model.name+"."+param_name
1139                            if p.name == p_name:     
1140                                if p.value != None and numpy.isfinite(p.value):
1141                                    small_out.append(p.value)
1142                                    small_param_name.append(param_name)
1143                                    small_cov.append(p.stderr)
1144                # Display result on each page
1145                cpage = self.fit_panel.get_page_by_id(uid)
1146                wx.CallAfter(cpage.onsetValues, 
1147                                    result.fitness,
1148                                  small_param_name,
1149                                  small_out,small_cov)
1150                cpage._on_fit_complete()
1151                msg = "Fit completed!"
1152                wx.PostEvent(self.parent, StatusEvent(status=msg))
1153        except Exception:
1154            raise
1155            self._update_fit_button(page_id)
1156            msg = "Complex Fitting did not converge!!!"
1157            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1158                                                  type="stop"))
1159            return
1160        except:
1161            self._update_fit_button(page_id)
1162            msg = "Simultaneous Fit completed"
1163            msg += " but Following error occurred:%s" % sys.exc_value
1164            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1165   
1166    def _update_fit_button(self, page_id):
1167        """
1168        Update Fit button when fit stopped
1169       
1170        : parameter page_id: fitpage where the button is
1171        """
1172        if page_id.__class__.__name__ != 'list':
1173            page_id = [page_id]
1174        for uid in page_id: 
1175            page = self.fit_panel.get_page_by_id(uid)
1176            page._on_fit_complete()
1177       
1178    def _on_show_panel(self, event):
1179        """
1180        """
1181        pass
1182       
1183    def _onset_engine_park(self,event):
1184        """
1185        set engine to park
1186        """
1187        self._on_change_engine('park')
1188       
1189    def _onset_engine_scipy(self,event):
1190        """
1191        set engine to scipy
1192        """
1193        self._on_change_engine('scipy')
1194       
1195    def _on_slicer_event(self, event):
1196        """
1197        Receive a panel as event and send it to guiframe
1198       
1199        :param event: event containing a panel
1200       
1201        """
1202        if event.panel is not None:
1203            new_panel = event.panel
1204            self.slicer_panels.append(event.panel)
1205            # Set group ID if available
1206            event_id = self.parent.popup_panel(new_panel)
1207            new_panel.uid = event_id
1208            self.mypanels.append(new_panel) 
1209       
1210    def _onclearslicer(self, event):
1211        """
1212        Clear the boxslicer when close the panel associate with this slicer
1213        """
1214        name =event.GetPane().caption
1215   
1216        for panel in self.slicer_panels:
1217            if panel.window_caption==name:
1218               
1219                for item in self.parent.panels:
1220                    if hasattr(self.parent.panels[item], "uid"):
1221                        if self.parent.panels[item].uid ==panel.base.uid:
1222                            self.parent.panels[item].onClearSlicer(event)
1223                            self.parent._mgr.Update()
1224                            break 
1225                break
1226   
1227    def _return_engine_type(self):
1228        """
1229        return the current type of engine
1230        """
1231        return self._fit_engine
1232     
1233     
1234    def _on_change_engine(self, engine='park'):
1235        """
1236        Allow to select the type of engine to perform fit
1237       
1238        :param engine: the key work of the engine
1239       
1240        """
1241        ## saving fit engine name
1242        self._fit_engine = engine
1243        ## change menu item state
1244        if engine == "park":
1245            self.menu1.FindItemById(self.park_id).Check(True)
1246            self.menu1.FindItemById(self.scipy_id).Check(False)
1247        else:
1248            self.menu1.FindItemById(self.park_id).Check(False)
1249            self.menu1.FindItemById(self.scipy_id).Check(True)
1250        ## post a message to status bar
1251        msg = "Engine set to: %s" % self._fit_engine
1252        wx.PostEvent(self.parent, 
1253                     StatusEvent(status=msg))
1254        ## send the current engine type to fitpanel
1255        self.fit_panel._on_engine_change(name=self._fit_engine)
1256
1257       
1258    def _on_model_panel(self, evt):
1259        """
1260        react to model selection on any combo box or model menu.plot the model 
1261       
1262        :param evt: wx.combobox event
1263       
1264        """
1265        model = evt.model
1266        uid = evt.uid
1267        qmin = evt.qmin
1268        qmax = evt.qmax
1269        smearer = evt.smearer
1270        caption = evt.caption
1271        enable_smearer = evt.enable_smearer
1272        if model == None:
1273            return
1274        if uid not in self.page_finder.keys():
1275            return
1276        # save the name containing the data name with the appropriate model
1277        self.page_finder[uid].set_model(model)
1278        self.page_finder[uid].enable_smearing(enable_smearer)
1279        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1280        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1281        if self.sim_page is not None and not self.batch_on:
1282            self.sim_page.draw_page()
1283       
1284    def _update1D(self, x, output):
1285        """
1286        Update the output of plotting model 1D
1287        """
1288        msg = "Plot updating ... "
1289        wx.PostEvent(self.parent, StatusEvent(status=msg,type="update"))
1290       
1291    def _complete1D(self, x, y, page_id, elapsed, index, model,
1292                    toggle_mode_on=False, state=None, 
1293                    data=None, update_chisqr=True):
1294        """
1295        Complete plotting 1D data
1296        """ 
1297        try:
1298            new_plot = Data1D(x=x, y=y)
1299            new_plot.is_data = False
1300            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1301            _yaxis, _yunit = data.get_yaxis() 
1302            _xaxis, _xunit = data.get_xaxis() 
1303            new_plot.title = data.name
1304            new_plot.group_id = data.group_id
1305            new_plot.id =  str(page_id) + "model"
1306            if new_plot.id in self.color_dict:
1307                new_plot.custom_color = self.color_dict[new_plot.id] 
1308            #find if this theory was already plotted and replace that plot given
1309            #the same id
1310            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1311            new_plot.name = model.name + " ["+ str(model.__class__.__name__)+"]"
1312            new_plot.xaxis(_xaxis, _xunit)
1313            new_plot.yaxis(_yaxis, _yunit)
1314            self.page_finder[page_id].set_theory_data(data=new_plot, 
1315                                                      fid=data.id)
1316            self.parent.update_theory(data_id=data.id, theory=new_plot,
1317                                       state=state)   
1318            current_pg = self.fit_panel.get_page_by_id(page_id)
1319            title = new_plot.title
1320            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1321                                            title=str(title)))
1322            caption = current_pg.window_caption
1323            self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1324            self.page_finder[page_id].set_theory_data(data=new_plot, 
1325                                                      fid=data.id)
1326            if toggle_mode_on:
1327                wx.PostEvent(self.parent, 
1328                             NewPlotEvent(group_id=str(page_id) + " Model2D",
1329                                               action="Hide"))
1330            else:
1331                if update_chisqr:
1332                    wx.PostEvent(current_pg,
1333                                 Chi2UpdateEvent(output=self._cal_chisqr(
1334                                                                data=data,
1335                                                            page_id=page_id,
1336                                                            index=index)))
1337                else:
1338                    self._plot_residuals(page_id, data, index)
1339
1340            msg = "Computation  completed!"
1341            wx.PostEvent( self.parent, StatusEvent(status=msg, type="stop" ))
1342        except:
1343            raise
1344            #msg = " Error occurred when drawing %s Model 1D: " % new_plot.name
1345            #msg += " %s"  % sys.exc_value
1346            #wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1347   
1348    def _update2D(self, output,time=None):
1349        """
1350        Update the output of plotting model
1351        """
1352        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1353        #updating ... ", type="update"))
1354        #self.ready_fit()
1355 
1356    def _complete2D(self, image, data, model, page_id,  elapsed, index, qmin,
1357                     qmax, toggle_mode_on=False, state=None, 
1358                     update_chisqr=True):
1359        """
1360        Complete get the result of modelthread and create model 2D
1361        that can be plot.
1362        """
1363        new_plot= Data2D(image=image, err_image=data.err_data)
1364        new_plot.name = model.name
1365        new_plot.title = "Analytical model 2D "
1366        new_plot.id = str(page_id) + "model"
1367        new_plot.group_id = str(page_id) + " Model2D"
1368        new_plot.detector = data.detector
1369        new_plot.source = data.source
1370        new_plot.is_data = False 
1371        new_plot.qx_data = data.qx_data
1372        new_plot.qy_data = data.qy_data
1373        new_plot.q_data = data.q_data
1374        new_plot.mask = data.mask
1375        ## plot boundaries
1376        new_plot.ymin = data.ymin
1377        new_plot.ymax = data.ymax
1378        new_plot.xmin = data.xmin
1379        new_plot.xmax = data.xmax
1380        title = data.title
1381        if len(title) > 1:
1382            new_plot.title = "Model2D for " + data.name
1383        new_plot.is_data = False
1384        new_plot.name = model.name + " [" + \
1385                                    str(model.__class__.__name__) + "-2D]"
1386        theory_data = deepcopy(new_plot)
1387        theory_data.name = "Unknown"
1388       
1389        self.page_finder[page_id].set_theory_data(data=theory_data, fid=data.id)
1390        self.parent.update_theory(data_id=data.id, 
1391                                       theory=new_plot,
1392                                       state=state) 
1393        current_pg = self.fit_panel.get_page_by_id(page_id)
1394        title = new_plot.title
1395        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1396                                               title=title))
1397        self.page_finder[page_id].set_theory_data(data=new_plot, fid=data.id)
1398        if toggle_mode_on:
1399            wx.PostEvent(self.parent, 
1400                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1401                                               action="Hide"))
1402        else:
1403            # Chisqr in fitpage
1404            if update_chisqr:
1405                wx.PostEvent(current_pg,
1406                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1407                                                         page_id=page_id,
1408                                                         index=index)))
1409            else:
1410                self._plot_residuals(page_id, data, index)
1411        msg = "Computation  completed!"
1412        wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1413   
1414    def _draw_model2D(self, model, page_id, qmin,
1415                      qmax,
1416                      data=None, smearer=None,
1417                      description=None, enable2D=False,
1418                      state=None,
1419                      toggle_mode_on=False,
1420                       update_chisqr=True):
1421        """
1422        draw model in 2D
1423       
1424        :param model: instance of the model to draw
1425        :param description: the description of the model
1426        :param enable2D: when True allows to draw model 2D
1427        :param qmin: the minimum value to  draw model 2D
1428        :param qmax: the maximum value to draw model 2D
1429        :param qstep: the number of division of Qx and Qy of the model to draw
1430           
1431        """
1432        if not enable2D:
1433            return None
1434        try:
1435            from model_thread import Calc2D
1436            ## If a thread is already started, stop it
1437            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1438                self.calc_2D.stop()
1439            self.calc_2D = Calc2D(model=model, 
1440                                    data=data,
1441                                    page_id=page_id,
1442                                    smearer=smearer,
1443                                    qmin=qmin,
1444                                    qmax=qmax,
1445                                    toggle_mode_on=toggle_mode_on,
1446                                    state=state,
1447                                    completefn=self._complete2D,
1448                                    update_chisqr=update_chisqr)
1449            self.calc_2D.queue()
1450
1451        except:
1452            raise
1453            #msg = " Error occurred when drawing %s Model 2D: " % model.name
1454            #msg += " %s" % sys.exc_value
1455            #wx.PostEvent(self.parent, StatusEvent(status=msg))
1456
1457    def _draw_model1D(self, model, page_id, data, 
1458                      qmin, qmax, smearer=None,
1459                state=None,
1460                toggle_mode_on=False, update_chisqr=True, 
1461                enable1D=True):
1462        """
1463        Draw model 1D from loaded data1D
1464       
1465        :param data: loaded data
1466        :param model: the model to plot
1467       
1468        """
1469        if not enable1D:
1470            return 
1471        try:
1472            from model_thread import Calc1D
1473            ## If a thread is already started, stop it
1474            if (self.calc_1D is not None) and self.calc_1D.isrunning():
1475                self.calc_1D.stop()
1476            self.calc_1D = Calc1D(data=data,
1477                                  model=model,
1478                                  page_id=page_id, 
1479                                  qmin=qmin,
1480                                  qmax=qmax,
1481                                  smearer=smearer,
1482                                  state=state,
1483                                  toggle_mode_on=toggle_mode_on,
1484                                  completefn=self._complete1D,
1485                                  #updatefn = self._update1D,
1486                                  update_chisqr=update_chisqr)
1487            self.calc_1D.queue()
1488        except:
1489            msg = " Error occurred when drawing %s Model 1D: " % model.name
1490            msg += " %s" % sys.exc_value
1491            wx.PostEvent(self.parent, StatusEvent(status=msg))
1492   
1493 
1494   
1495    def _cal_chisqr(self, page_id, data, index=None): 
1496        """
1497        Get handy Chisqr using the output from draw1D and 2D,
1498        instead of calling expansive CalcChisqr in guithread
1499        """
1500        data_copy = deepcopy(data)
1501       
1502        # default chisqr
1503        chisqr = None
1504        #to compute chisq make sure data has valid data
1505        # return None if data == None
1506        if not check_data_validity(data_copy):
1507            return chisqr
1508       
1509        # Get data: data I, theory I, and data dI in order
1510        if data_copy.__class__.__name__ == "Data2D":
1511            if index == None: 
1512                index = numpy.ones(len(data_copy.data),ntype=bool)
1513            if self.weight != None:
1514                data_copy.err_data = self.weight
1515            # get rid of zero error points
1516            index = index & (data_copy.err_data != 0) 
1517            index = index & (numpy.isfinite(data_copy.data)) 
1518            fn = data_copy.data[index] 
1519            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
1520            gn = theory_data.data[index]
1521            en = data_copy.err_data[index]
1522        else:
1523            # 1 d theory from model_thread is only in the range of index
1524            if index == None: 
1525                index = numpy.ones(len(data_copy.y), ntype=bool)
1526            if self.weight != None:
1527                data_copy.dy = self.weight
1528            if data_copy.dy == None or data_copy.dy == []:
1529                dy = numpy.ones(len(data_copy.y))
1530            else:
1531                ## Set consitently w/AbstractFitengine:
1532                # But this should be corrected later.
1533                dy = deepcopy(data_copy.dy)
1534                dy[dy==0] = numpy.ones(len(dy[dy==0])) 
1535            fn = data_copy.y[index] 
1536            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
1537            gn = theory_data.y
1538            en = dy[index]
1539        # residual
1540        res = (fn - gn) / en
1541        residuals = res[numpy.isfinite(res)]
1542        # get chisqr only w/finite
1543        chisqr = numpy.average(residuals * residuals)
1544        self._plot_residuals(page_id, data_copy, index)
1545       
1546        return chisqr
1547   
1548    def _plot_residuals(self, page_id, data=None, index=None): 
1549        """
1550        Plot the residuals
1551       
1552        :param data: data
1553        :param index: index array (bool)
1554        : Note: this is different from the residuals in cal_chisqr()
1555        """
1556        data_copy = deepcopy(data)
1557        # Get data: data I, theory I, and data dI in order
1558        if data_copy.__class__.__name__ == "Data2D":
1559            # build residuals
1560            residuals = Data2D()
1561            #residuals.copy_from_datainfo(data)
1562            # Not for trunk the line below, instead use the line above
1563            data_copy.clone_without_data(len(data_copy.data), residuals)
1564            residuals.data = None
1565            fn = data_copy.data#[index]
1566            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
1567            gn = theory_data.data#[index]
1568            en = self.weight#data_copy.err_data#[index]
1569            residuals.data = (fn - gn) / en
1570            residuals.qx_data = data_copy.qx_data#[index]
1571            residuals.qy_data = data_copy.qy_data #[index]
1572            residuals.q_data = data_copy.q_data#[index]
1573            residuals.err_data = numpy.ones(len(residuals.data))#[index]
1574            residuals.xmin = min(residuals.qx_data)
1575            residuals.xmax = max(residuals.qx_data)
1576            residuals.ymin = min(residuals.qy_data)
1577            residuals.ymax = max(residuals.qy_data)
1578            residuals.q_data = data_copy.q_data#[index]
1579            residuals.mask = data_copy.mask
1580            residuals.scale = 'linear'
1581            # check the lengths
1582            if len(residuals.data) != len(residuals.q_data):
1583                return
1584        else:
1585            # 1 d theory from model_thread is only in the range of index
1586            if data_copy.dy == None or data_copy.dy == []:
1587                dy = numpy.ones(len(data_copy.y))
1588            else:
1589                ## Set consitently w/AbstractFitengine:
1590                ## But this should be corrected later.
1591                dy = self.weight#deepcopy(data_copy.dy)
1592                dy[dy==0] = 1 
1593            fn = data_copy.y[index] 
1594            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
1595            gn = theory_data.y
1596            en = dy[index]
1597            # build residuals
1598            residuals = Data1D()
1599            residuals.y = (fn - gn) / en
1600            residuals.x = data_copy.x[index]
1601            residuals.dy = numpy.ones(len(residuals.y))
1602            residuals.dx = None
1603            residuals.dxl = None
1604            residuals.dxw = None
1605            residuals.ytransform = 'y'
1606            # For latter scale changes
1607            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
1608            residuals.yaxis('\\rm{Residuals} ', 'normalized')
1609        new_plot = residuals
1610        new_plot.name = "Residuals for " + str(data.name)
1611        ## allow to highlight data when plotted
1612        new_plot.interactive = True
1613        ## when 2 data have the same id override the 1 st plotted
1614        new_plot.id = "res" + str(data_copy.id)#name + " residuals"
1615        ##group_id specify on which panel to plot this data
1616        new_plot.group_id = new_plot.id
1617        #new_plot.is_data = True
1618        ##post data to plot
1619        title = new_plot.name
1620        # plot data
1621        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title)) 
1622        #reset weight 
1623        self.weight = None
1624#def profile(fn, *args, **kw):
1625#    import cProfile, pstats, os
1626#    global call_result
1627#    def call():
1628#        global call_result
1629#        call_result = fn(*args, **kw)
1630#    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1631#    stats = pstats.Stats('profile.out')
1632#    #stats.sort_stats('time')
1633#    stats.sort_stats('calls')
1634#    stats.print_stats()
1635#    os.unlink('profile.out')
1636#    return call_result
1637if __name__ == "__main__":
1638    i = Plugin()
1639   
1640   
1641   
1642   
Note: See TracBrowser for help on using the repository browser.