source: sasview/src/sans/perspectives/fitting/fitting.py @ 95d58d3

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 95d58d3 was 6fe5100, checked in by pkienzle, 11 years ago

Bumps first pass. Fitting works but no pretty pictures

  • Property mode set to 100644
File size: 92.0 KB
Line 
1"""
2    Fitting perspective
3"""
4################################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#
9#See the license text in license.txt
10#
11#copyright 2009, University of Tennessee
12################################################################################
13import re
14import sys
15import os
16import wx
17import logging
18import numpy
19import time
20from copy import deepcopy
21import models
22
23from sans.dataloader.loader import Loader
24from sans.guiframe.dataFitting import Data2D
25from sans.guiframe.dataFitting import Data1D
26from sans.guiframe.dataFitting import check_data_validity
27from sans.guiframe.events import NewPlotEvent
28from sans.guiframe.events import StatusEvent 
29from sans.guiframe.events import EVT_SLICER_PANEL
30from sans.guiframe.events import EVT_SLICER_PARS_UPDATE
31from sans.guiframe.gui_style import GUIFRAME_ID
32from sans.guiframe.plugin_base import PluginBase
33from sans.guiframe.data_processor import BatchCell
34from sans.fit.Fitting import Fit
35from .console import ConsoleUpdate
36from .fitproblem import FitProblemDictionary
37from .fitpanel import FitPanel
38from .fit_thread import FitThread
39from .pagestate import Reader
40from .fitpage import Chi2UpdateEvent
41from sans.perspectives.calculator.model_editor import TextDialog
42from sans.perspectives.calculator.model_editor import EditorWindow
43from sans.guiframe.gui_manager import MDIFrame
44
45MAX_NBR_DATA = 4
46SANS_F_TOL = 5e-05
47
48(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
49
50
51if sys.platform == "win32":
52    ON_MAC = False
53else:
54    ON_MAC = True
55
56
57
58class Plugin(PluginBase):
59    """
60    Fitting plugin is used to perform fit
61    """
62    def __init__(self, standalone=False):
63        PluginBase.__init__(self, name="Fitting", standalone=standalone)
64       
65        #list of panel to send to guiframe
66        self.mypanels = []
67        # reference to the current running thread
68        self.calc_2D = None
69        self.calc_1D = None
70       
71        self.color_dict = {}
72       
73        self.fit_thread_list = {}
74        self.residuals = None
75        self.weight = None
76        self.fit_panel = None
77        self.plot_panel = None
78        # Start with a good default
79        self.elapsed = 0.022
80        # the type of optimizer selected, park or scipy
81        self.fitter = None
82        self.fit_panel = None
83        #let fit ready
84        self.fitproblem_count = None
85        #Flag to let the plug-in know that it is running stand alone
86        self.standalone = True
87        ## dictionary of page closed and id
88        self.closed_page_dict = {}
89        ## Fit engine
90        self._fit_engine = 'scipy'
91        self._gui_engine = None
92        ## Relative error desired in the sum of squares (float); scipy only
93        self.ftol = SANS_F_TOL
94        self.batch_reset_flag = True
95        #List of selected data
96        self.selected_data_list = []
97        ## list of slicer panel created to display slicer parameters and results
98        self.slicer_panels = []
99        # model 2D view
100        self.model2D_id = None
101        #keep reference of the simultaneous fit page
102        self.sim_page = None
103        self.sim_menu = None
104        self.batch_page = None
105        self.batch_menu = None
106        self.index_model = 0
107        self.test_model_color = None
108        #Create a reader for fit page's state
109        self.state_reader = None
110        self._extensions = '.fitv'
111        self.scipy_id = wx.NewId()
112        self.park_id = wx.NewId()
113        self.bumps_id = wx.NewId()
114        self.menu1 = None
115        self.new_model_frame = None
116       
117        self.temp_state = []
118        self.state_index = 0
119        self.sfile_ext = None
120        # take care of saving  data, model and page associated with each other
121        self.page_finder = {}
122        # Log startup
123        logging.info("Fitting plug-in started")
124        self.batch_capable = self.get_batch_capable()
125   
126    def get_batch_capable(self):
127        """
128        Check if the plugin has a batch capability
129        """
130        return True
131   
132    def create_fit_problem(self, page_id):
133        """
134        Given an ID create a fitproblem container
135        """
136        self.page_finder[page_id] = FitProblemDictionary()
137       
138    def delete_fit_problem(self, page_id):
139        """
140        Given an ID create a fitproblem container
141        """
142        if page_id in self.page_finder.iterkeys():
143            del self.page_finder[page_id]
144       
145    def add_color(self, color, id):
146        """
147        adds a color as a key with a plot id as its value to a dictionary
148        """
149        self.color_dict[id] = color
150       
151    def on_batch_selection(self, flag):
152        """
153        switch the the notebook of batch mode or not
154        """
155        self.batch_on = flag
156        if self.fit_panel is not None:
157            self.fit_panel.batch_on = self.batch_on
158       
159    def populate_menu(self, owner):
160        """
161        Create a menu for the Fitting plug-in
162       
163        :param id: id to create a menu
164        :param owner: owner of menu
165       
166        :return: list of information to populate the main menu
167       
168        """
169        #Menu for fitting
170        self.menu1 = wx.Menu()
171        id1 = wx.NewId()
172        simul_help = "Add new fit panel"
173        self.menu1.Append(id1, '&New Fit Page', simul_help)
174        wx.EVT_MENU(owner, id1, self.on_add_new_page)
175        self.menu1.AppendSeparator()
176        self.id_simfit = wx.NewId()
177        simul_help = "Constrained or Simultaneous Fit"
178        self.menu1.Append(self.id_simfit, '&Constrained or Simultaneous Fit', simul_help)
179        wx.EVT_MENU(owner, self.id_simfit, self.on_add_sim_page)
180        self.sim_menu = self.menu1.FindItemById(self.id_simfit)
181        self.sim_menu.Enable(False)
182        #combined Batch
183        self.id_batchfit = wx.NewId()
184        batch_help = "Combined Batch"
185        self.menu1.Append(self.id_batchfit, '&Combine Batch Fit', batch_help)
186        wx.EVT_MENU(owner, self.id_batchfit, self.on_add_sim_page)
187        self.batch_menu = self.menu1.FindItemById(self.id_batchfit)
188        self.batch_menu.Enable(False)
189        self.menu1.AppendSeparator()
190        #Set park engine
191        scipy_help = "Scipy Engine: Perform Simple fit. More in Help window...."
192        self.menu1.AppendCheckItem(self.scipy_id, "Simple FitEngine [LeastSq]",
193                                   scipy_help)
194        wx.EVT_MENU(owner, self.scipy_id, self._onset_engine_scipy)
195       
196        park_help = "Park Engine: Perform Complex fit. More in Help window...."
197        self.menu1.AppendCheckItem(self.park_id, "Complex FitEngine [ParkMC]",
198                                   park_help)
199        wx.EVT_MENU(owner, self.park_id, self._onset_engine_park)
200       
201        bumps_help = "Bumps: fitting and uncertainty analysis. More in Help window...."
202        self.menu1.AppendCheckItem(self.bumps_id, "Bumps fit",
203                                   bumps_help)
204        wx.EVT_MENU(owner, self.bumps_id, self._onset_engine_bumps)
205       
206        self.menu1.FindItemById(self.scipy_id).Check(True)
207        self.menu1.FindItemById(self.park_id).Check(False)
208        self.menu1.FindItemById(self.bumps_id).Check(False)
209        self.menu1.AppendSeparator()
210        self.id_tol = wx.NewId()
211        ftol_help = "Change the current FTolerance (=%s) " % str(self.ftol)
212        ftol_help += "of Simple FitEngine..."
213        self.menu1.Append(self.id_tol, "Change FTolerance",
214                                   ftol_help)
215        wx.EVT_MENU(owner, self.id_tol, self.show_ftol_dialog)
216
217        self.id_bumps_options = wx.NewId()
218        bopts_help = "Bumps fitting options"
219        self.menu1.Append(self.id_bumps_options, 'Bumps &Options', bopts_help)
220        wx.EVT_MENU(owner, self.id_bumps_options, self.on_bumps_options)
221        self.bumps_options_menu = self.menu1.FindItemById(self.id_bumps_options)
222        self.bumps_options_menu.Enable(True)
223        self.menu1.AppendSeparator()
224       
225        self.id_reset_flag = wx.NewId()
226        resetf_help = "BatchFit: If checked, the initial param values will be "
227        resetf_help += "propagated from the previous results. "
228        resetf_help += "Otherwise, the same initial param values will be used "
229        resetf_help += "for all fittings."
230        self.menu1.AppendCheckItem(self.id_reset_flag,
231                                   "Chain Fitting [BatchFit Only]",
232                                   resetf_help)
233        wx.EVT_MENU(owner, self.id_reset_flag, self.on_reset_batch_flag)
234        chain_menu = self.menu1.FindItemById(self.id_reset_flag)
235        chain_menu.Check(not self.batch_reset_flag)
236        chain_menu.Enable(self.batch_on)
237       
238        self.menu1.AppendSeparator()
239        self.edit_model_menu = wx.Menu()
240        # Find and put files name in menu
241        try:
242            self.set_edit_menu(owner=owner)
243        except:
244            raise
245       
246        self.id_edit = wx.NewId()
247        editmodel_help = "Edit customized model sample file"
248        self.menu1.AppendMenu(self.id_edit, "Edit Custom Model",
249                              self.edit_model_menu, editmodel_help)
250        #create  menubar items
251        return [(self.menu1, self.sub_menu)]
252   
253    def edit_custom_model(self, event):
254        """
255        Get the python editor panel
256        """
257        id = event.GetId()
258        label = self.edit_menu.GetLabel(id)
259        from sans.perspectives.calculator.pyconsole import PyConsole
260        filename = os.path.join(models.find_plugins_dir(), label)
261        frame = PyConsole(parent=self.parent, manager=self,
262                          panel=self.fit_panel,
263                          title='Advanced Custom Model Editor',
264                          filename=filename)
265        self.put_icon(frame)
266        frame.Show(True)
267   
268    def delete_custom_model(self, event):
269        """
270        Delete custom model file
271        """
272        id = event.GetId()
273        label = self.delete_menu.GetLabel(id)
274        toks = os.path.splitext(label)
275        path = os.path.join(models.find_plugins_dir(), toks[0])
276        try:
277            for ext in ['.py', '.pyc']:
278                p_path = path + ext
279                os.remove(p_path)
280            self.update_custom_combo()
281            if os.path.isfile(p_path):
282                msg = "Sorry! Could not be able to delete the default "
283                msg += "custom model... \n"
284                msg += "Please manually remove the files (.py, .pyc) "
285                msg += "in the 'plugin_models' folder \n"
286                msg += "inside of the SansView application, "
287                msg += "and try it again."
288                wx.MessageBox(msg, 'Info')
289                #wx.PostEvent(self.parent, StatusEvent(status=msg, type='stop',
290                #                                      info='warning'))
291            else:
292                self.delete_menu.Delete(id)
293                for item in self.edit_menu.GetMenuItems():
294                    if item.GetLabel() == label:
295                        self.edit_menu.DeleteItem(item)
296                        msg = "The custom model, %s, has been deleted."% label
297                        wx.PostEvent(self.parent, StatusEvent(status=msg, 
298                                                type='stop', info='info'))
299                        break
300        except:
301            msg ='Delete Error: \nCould not delete the file; Check if in use.'
302            wx.MessageBox(msg, 'Error')
303   
304    def make_sum_model(self, event):
305        """
306        Edit summodel template and make one
307        """
308        id = event.GetId()
309        model_manager = models.ModelManager()
310        model_list = model_manager.get_model_name_list()
311        plug_dir = models.find_plugins_dir()
312        textdial = TextDialog(None, self, -1, 'Easy Sum/Multi(p1, p2) Editor', 
313                              model_list, plug_dir)
314        self.put_icon(textdial)
315        textdial.ShowModal()
316        textdial.Destroy()
317   
318    def make_new_model(self, event):
319        """
320        Make new model
321        """
322        if self.new_model_frame != None:
323            self.new_model_frame.Show(False)
324            self.new_model_frame.Show(True)
325        else:
326            id = event.GetId()
327            dir_path = models.find_plugins_dir()
328            title = "New Custom Model Function"
329            self.new_model_frame = EditorWindow(parent=self, base=self,
330                                                path=dir_path, title=title)
331            self.put_icon(self.new_model_frame)
332        self.new_model_frame.Show(True)
333
334    def update_custom_combo(self):
335        """
336        Update custom model list in the fitpage combo box
337        """
338        custom_model = 'Customized Models'
339        try:
340            # Update edit menus
341            self.set_edit_menu_helper(self.parent, self.edit_custom_model)
342            self.set_edit_menu_helper(self.parent, self.delete_custom_model)
343            temp = self.fit_panel.reset_pmodel_list()
344            if temp:
345                # Set the new custom model list for all fit pages
346                for uid, page in self.fit_panel.opened_pages.iteritems():
347                    if hasattr(page, "formfactorbox"):
348                        page.model_list_box = temp
349                        current_val = page.formfactorbox.GetLabel()
350                        #if page.plugin_rbutton.GetValue():
351                        mod_cat = page.categorybox.GetStringSelection()
352                        if mod_cat == custom_model:
353                            #pos = page.formfactorbox.GetSelection()
354                            page._show_combox_helper()
355                            new_val = page.formfactorbox.GetLabel()
356                            if current_val != new_val and new_val != '':
357                                page.formfactorbox.SetLabel(new_val)
358                            else:
359                                page.formfactorbox.SetLabel(current_val)
360        except:
361            pass
362       
363       
364    def set_edit_menu(self, owner):
365        """
366        Set list of the edit model menu labels
367        """
368        id = wx.NewId()
369        #new_model_menu = wx.Menu()
370        self.edit_model_menu.Append(id, 'New',
371                                   'Add a new model function')
372        wx.EVT_MENU(owner, id, self.make_new_model)
373        id = wx.NewId()
374        self.edit_model_menu.Append(id, 'Sum|Multi(p1, p2)',
375                                    'Sum of two model functions')
376        wx.EVT_MENU(owner, id, self.make_sum_model)
377        e_id = wx.NewId()
378        self.edit_menu = wx.Menu()
379        self.edit_model_menu.AppendMenu(e_id, 
380                                    'Advanced', self.edit_menu) 
381        self.set_edit_menu_helper(owner, self.edit_custom_model)
382
383        d_id = wx.NewId()
384        self.delete_menu = wx.Menu()
385        self.edit_model_menu.AppendMenu(d_id,
386                                        'Delete', self.delete_menu)
387        self.set_edit_menu_helper(owner, self.delete_custom_model)
388   
389    def set_edit_menu_helper(self, owner=None, menu=None):
390        """
391        help for setting list of the edit model menu labels
392        """
393        if menu == None:
394            menu = self.edit_custom_model
395        list_fnames = os.listdir(models.find_plugins_dir())
396        list_fnames.sort()
397        for f_item in list_fnames:
398            name = os.path.basename(f_item)
399            toks = os.path.splitext(name)
400            if toks[-1] == '.py' and not toks[0] == '__init__':
401                if menu == self.edit_custom_model:
402                    if toks[0] == 'easy_sum_of_p1_p2':
403                        continue
404                    submenu = self.edit_menu
405                else:
406                    submenu = self.delete_menu
407                has_file = False
408                for item in submenu.GetMenuItems():
409                    if name == submenu.GetLabel(item.GetId()):
410                        has_file = True
411                if not has_file:
412                    id = wx.NewId()
413                    submenu.Append(id, name)
414                    wx.EVT_MENU(owner, id, menu)
415                    has_file = False
416
417    def put_icon(self, frame):
418        """
419        Put icon in the frame title bar
420        """
421        if hasattr(frame, "IsIconized"):
422            if not frame.IsIconized():
423                try:
424                    icon = self.parent.GetIcon()
425                    frame.SetIcon(icon)
426                except:
427                    pass
428
429    def on_add_sim_page(self, event):
430        """
431        Create a page to access simultaneous fit option
432        """
433        id = event.GetId()
434        caption = "Const & Simul Fit"
435        page = self.sim_page
436        if id == self.id_batchfit:
437            caption = "Combined Batch"
438            page = self.batch_page
439           
440        def set_focus_page(page):
441            page.Show(True)
442            page.Refresh()
443            page.SetFocus()
444            #self.parent._mgr.Update()
445            msg = "%s already opened\n" % str(page.window_caption)
446            wx.PostEvent(self.parent, StatusEvent(status=msg))
447           
448        if page != None:
449            return set_focus_page(page)
450        if caption == "Const & Simul Fit":
451            self.sim_page = self.fit_panel.add_sim_page(caption=caption)
452        else:
453            self.batch_page = self.fit_panel.add_sim_page(caption=caption)
454       
455    def help(self, evt):
456        """
457        Show a general help dialog.
458        """
459        from help_panel import  HelpWindow
460        frame = HelpWindow(None, -1, 'HelpWindow')
461        if hasattr(frame, "IsIconized"):
462            if not frame.IsIconized():
463                try:
464                    icon = self.parent.GetIcon()
465                    frame.SetIcon(icon)
466                except:
467                    pass
468        frame.Show(True)
469       
470    def get_context_menu(self, plotpanel=None):
471        """
472        Get the context menu items available for P(r).them allow fitting option
473        for Data2D and Data1D only.
474       
475        :param graph: the Graph object to which we attach the context menu
476       
477        :return: a list of menu items with call-back function
478       
479        :note: if Data1D was generated from Theory1D
480                the fitting option is not allowed
481               
482        """
483        self.plot_panel = plotpanel
484        graph = plotpanel.graph
485        fit_option = "Select data for fitting"
486        fit_hint =  "Dialog with fitting parameters "
487       
488        if graph.selected_plottable not in plotpanel.plots:
489            return []
490        item = plotpanel.plots[graph.selected_plottable]
491        if item.__class__.__name__ is "Data2D":
492            if hasattr(item, "is_data"):
493                if item.is_data:
494                    return [[fit_option, fit_hint, self._onSelect]]
495                else:
496                    return [] 
497            return [[fit_option, fit_hint, self._onSelect]]
498        else:
499           
500            # if is_data is true , this in an actual data loaded
501            #else it is a data created from a theory model
502            if hasattr(item, "is_data"):
503                if item.is_data:
504                    return [[fit_option, fit_hint, self._onSelect]]
505                else:
506                    return []
507            return [[fit_option, fit_hint, self._onSelect]]
508        return []
509
510    def get_panels(self, parent):
511        """
512        Create and return a list of panel objects
513        """
514        self.parent = parent
515        #self.parent.Bind(EVT_FITSTATE_UPDATE, self.on_set_state_helper)
516        # Creation of the fit panel
517        self.frame = MDIFrame(self.parent, None, 'None', (100, 200))
518        self.fit_panel = FitPanel(parent=self.frame, manager=self)
519        self.frame.set_panel(self.fit_panel)
520        self._frame_set_helper()
521        self.on_add_new_page(event=None)
522        #Set the manager for the main panel
523        self.fit_panel.set_manager(self)
524        # List of windows used for the perspective
525        self.perspective = []
526        self.perspective.append(self.fit_panel.window_name)
527       
528        #index number to create random model name
529        self.index_model = 0
530        self.index_theory = 0
531        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
532        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
533        #self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)
534        #Create reader when fitting panel are created
535        self.state_reader = Reader(self.set_state)
536        #append that reader to list of available reader
537        loader = Loader()
538        loader.associate_file_reader(".fitv", self.state_reader)
539        #Send the fitting panel to guiframe
540        self.mypanels.append(self.fit_panel)
541        return self.mypanels
542   
543    def clear_panel(self):
544        """
545        """
546        self.fit_panel.clear_panel()
547       
548    def set_default_perspective(self):
549        """
550        Call back method that True to notify the parent that the current plug-in
551        can be set as default perspective.
552        when returning False, the plug-in is not candidate for an automatic
553        default perspective setting
554        """
555        return True
556   
557    def delete_data(self, data):
558        """
559        delete  the given data from panel
560        """
561        self.fit_panel.delete_data(data)
562       
563    def set_data(self, data_list=None):
564        """
565        receive a list of data to fit
566        """
567        if data_list is None:
568            data_list = []
569        selected_data_list = []
570        if self.batch_on:
571            page = self.add_fit_page(data=data_list)
572        else:
573            if len(data_list) > MAX_NBR_DATA:
574                from fitting_widgets import DataDialog
575                dlg = DataDialog(data_list=data_list, nb_data=MAX_NBR_DATA)
576                if dlg.ShowModal() == wx.ID_OK:
577                    selected_data_list = dlg.get_data()
578                dlg.Destroy()
579               
580            else:
581                selected_data_list = data_list
582            try:
583                group_id = wx.NewId()
584                for data in selected_data_list:
585                    if data is not None:
586                        # 2D has no same group_id
587                        if data.__class__.__name__ == 'Data2D':
588                            group_id = wx.NewId()
589                        data.group_id = group_id
590                        if group_id not in data.list_group_id:
591                            data.list_group_id.append(group_id)
592                        page = self.add_fit_page(data=[data])
593            except:
594                msg = "Fitting Set_data: " + str(sys.exc_value)
595                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
596   
597    def set_theory(self, theory_list=None):
598        """
599        """
600        #set the model state for a given theory_state:
601        for item in theory_list:
602            try:
603                _, theory_state = item
604                self.fit_panel.set_model_state(theory_state)
605            except:
606                msg = "Fitting: cannot deal with the theory received"
607                logging.error("set_theory " + msg + "\n" + str(sys.exc_value))
608                wx.PostEvent(self.parent,
609                             StatusEvent(status=msg, info="error"))
610           
611    def set_state(self, state=None, datainfo=None, format=None):
612        """
613        Call-back method for the fit page state reader.
614        This method is called when a .fitv/.svs file is loaded.
615       
616        : param state: PageState object
617        : param datainfo: data
618        """
619        #state = self.state_reader.get_state()
620        if state != None:
621            state = state.clone()
622            # store fitting state in temp_state
623            self.temp_state.append(state)
624        else:
625            self.temp_state = []
626        # index to start with for a new set_state
627        self.state_index = 0
628        # state file format
629        self.sfile_ext = format
630       
631        self.on_set_state_helper(event=None)
632
633    def  on_set_state_helper(self, event=None):
634        """
635        Set_state_helper. This actually sets state
636        after plotting data from state file.
637       
638        : event: FitStateUpdateEvent called
639            by dataloader.plot_data from guiframe
640        """
641        if len(self.temp_state) == 0:
642            if self.state_index == 0 and len(self.mypanels) <= 0 \
643            and self.sfile_ext == '.svs':
644                self.temp_state = []
645                self.state_index = 0
646            return
647       
648        try:
649            # Load fitting state
650            state = self.temp_state[self.state_index]
651            #panel state should have model selection to set_state
652            if state.formfactorcombobox != None:
653                #set state
654                data = self.parent.create_gui_data(state.data)
655                data.group_id = state.data.group_id
656                self.parent.add_data(data_list={data.id: data})
657                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
658                                        title=data.title))
659                #need to be fix later make sure we are sendind guiframe.data
660                #to panel
661                state.data = data
662                page = self.fit_panel.set_state(state)
663            else:
664                #just set data because set_state won't work
665                data = self.parent.create_gui_data(state.data)
666                data.group_id = state.data.group_id
667                self.parent.add_data(data_list={data.id: data})
668                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
669                                        title=data.title))
670                page = self.add_fit_page([data])
671                caption = page.window_caption
672                self.store_data(uid=page.uid, data_list=page.get_data_list(),
673                        caption=caption)
674                self.mypanels.append(page)
675               
676            # get ready for the next set_state
677            self.state_index += 1
678
679            #reset state variables to default when all set_state is finished.
680            if len(self.temp_state) == self.state_index:
681               
682                self.temp_state = []
683                #self.state_index = 0
684                # Make sure the user sees the fitting panel after loading
685                #self.parent.set_perspective(self.perspective)
686                self.on_perspective(event=None)
687        except:
688            self.state_index = 0
689            self.temp_state = []
690            raise
691       
692    def set_param2fit(self, uid, param2fit):
693        """
694        Set the list of param names to fit for fitprobelm
695        """
696        self.page_finder[uid].set_param2fit(param2fit)
697       
698    def set_graph_id(self, uid, graph_id):
699        """
700        Set graph_id for fitprobelm
701        """
702        self.page_finder[uid].set_graph_id(graph_id)
703       
704    def get_graph_id(self, uid):
705        """
706        Set graph_id for fitprobelm
707        """
708        return self.page_finder[uid].get_graph_id()
709                         
710    def save_fit_state(self, filepath, fitstate):
711        """
712        save fit page state into file
713        """
714        self.state_reader.write(filename=filepath, fitstate=fitstate)
715
716    def set_fit_weight(self, uid, flag, is2d=False, fid=None):
717        """
718        Set the fit weights of a given page for all
719        its data by default. If fid is provide then set the range
720        only for the data with fid as id
721        :param uid: id corresponding to a fit page
722        :param fid: id corresponding to a fit problem (data, model)
723        :param weight: current dy data
724        """
725        if uid in self.page_finder.keys():
726            self.page_finder[uid].set_weight(flag=flag, is2d=is2d)
727                   
728    def set_fit_range(self, uid, qmin, qmax, fid=None):
729        """
730        Set the fitting range of a given page for all
731        its data by default. If fid is provide then set the range
732        only for the data with fid as id
733        :param uid: id corresponding to a fit page
734        :param fid: id corresponding to a fit problem (data, model)
735        :param qmin: minimum  value of the fit range
736        :param qmax: maximum  value of the fit range
737        """
738        if uid in self.page_finder.keys():
739            self.page_finder[uid].set_range(qmin=qmin, qmax=qmax, fid=fid)
740     
741    def schedule_for_fit(self, value=0, uid=None):
742        """
743        Set the fit problem field to 0 or 1 to schedule that problem to fit.
744        Schedule the specified fitproblem or get the fit problem related to
745        the current page and set value.
746        :param value: integer 0 or 1
747        :param uid: the id related to a page contaning fitting information
748        """
749        if uid in self.page_finder.keys():
750            self.page_finder[uid].schedule_tofit(value)
751         
752    def get_page_finder(self):
753        """
754        return self.page_finder used also by simfitpage.py
755        """
756        return self.page_finder
757   
758    def set_page_finder(self, modelname, names, values):
759        """
760        Used by simfitpage.py to reset a parameter given the string constrainst.
761         
762        :param modelname: the name ot the model for with the parameter
763                            has to reset
764        :param value: can be a string in this case.
765        :param names: the paramter name
766         
767        :note: expecting park used for fit.
768         
769        """
770        sim_page_id = self.sim_page.uid
771        for uid, value in self.page_finder.iteritems():
772            if uid != sim_page_id and uid != self.batch_page.uid:
773                list = value.get_model()
774                model = list[0]
775                if model.name == modelname:
776                    value.set_model_param(names, values)
777                    break
778         
779    def split_string(self, item):
780        """
781        receive a word containing dot and split it. used to split parameterset
782        name into model name and parameter name example: ::
783       
784            paramaterset (item) = M1.A
785            Will return model_name = M1 , parameter name = A
786           
787        """
788        if item.find(".") >= 0:
789            param_names = re.split("\.", item)
790            model_name = param_names[0]
791            ##Assume max len is 3; eg., M0.radius.width
792            if len(param_names) == 3:
793                param_name = param_names[1] + "." + param_names[2]
794            else:
795                param_name = param_names[1]
796            return model_name, param_name
797   
798    def set_ftol(self, ftol=None):
799        """
800        Set ftol: Relative error desired in the sum of chi squares.
801        """
802        # check if it is flaot
803        try:
804            f_tol = float(ftol)
805        except:
806            # default
807            f_tol = SANS_F_TOL
808           
809        self.ftol = f_tol
810        # update ftol menu help strings
811        ftol_help = "Change the current FTolerance (=%s) " % str(self.ftol)
812        ftol_help += "of Simple FitEngine..."
813        if self.menu1 != None:
814            self.menu1.SetHelpString(self.id_tol, ftol_help)
815       
816    def show_ftol_dialog(self, event=None):
817        """
818        Dialog to select ftol for Scipy
819        """
820        from ftol_dialog import ChangeFtol
821        dialog = ChangeFtol(self.parent, self)
822        result = dialog.ShowModal()
823        if result == wx.ID_OK:
824            value = dialog.get_ftol()
825            if value is not None:
826                self.set_ftol(value)
827                msg = "The ftol (LeastSq) is set to %s." % value
828            else:
829                msg = "Error in the selection... No change in ftol."
830            # post event for info
831            wx.PostEvent(self.parent,
832                         StatusEvent(status=msg, info='warning'))
833        dialog.Destroy()
834
835    def on_bumps_options(self, event=None):
836        from bumps.gui.fit_dialog import OpenFitOptions
837        OpenFitOptions()
838
839    def stop_fit(self, uid):
840        """
841        Stop the fit engine
842        """
843        if uid in self.fit_thread_list.keys():
844            calc_fit = self.fit_thread_list[uid]
845            if calc_fit is not  None and calc_fit.isrunning():
846                calc_fit.stop()
847                msg = "Fit stop!"
848                wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
849            del self.fit_thread_list[uid]
850        #set the fit button label of page when fit stop is trigger from
851        #simultaneous fit pane
852        sim_flag = self.sim_page is not None and uid == self.sim_page.uid
853        batch_flag = self.batch_page is not None and uid == self.batch_page.uid
854        if sim_flag or batch_flag:
855            for uid, value in self.page_finder.iteritems():
856                if value.get_scheduled() == 1:
857                    if uid in self.fit_panel.opened_pages.keys():
858                        panel = self.fit_panel.opened_pages[uid]
859                        panel._on_fit_complete()
860 
861    def set_smearer(self, uid, smearer, fid, qmin=None, qmax=None, draw=True,
862                    enable_smearer=False):
863        """
864        Get a smear object and store it to a fit problem of fid as id. If proper
865        flag is enable , will plot the theory with smearing information.
866       
867        :param smearer: smear object to allow smearing data of id fid
868        :param enable_smearer: Define whether or not all (data, model) contained
869            in the structure of id uid will be smeared before fitting.
870        :param qmin: the maximum value of the theory plotting range
871        :param qmax: the maximum value of the theory plotting range
872        :param draw: Determine if the theory needs to be plot
873        """
874        if uid not in self.page_finder.keys():
875            return
876        self.page_finder[uid].enable_smearing(flag=enable_smearer)
877        self.page_finder[uid].set_smearer(smearer, fid=fid)
878        if draw:
879            ## draw model 1D with smeared data
880            data = self.page_finder[uid].get_fit_data(fid=fid)
881            if data is None:
882                msg = "set_mearer requires at least data.\n"
883                msg += "Got data = %s .\n" % str(data)
884                return
885                #raise ValueError, msg
886            model = self.page_finder[uid].get_model(fid=fid)
887            if model is None:
888                return
889            enable1D = issubclass(data.__class__, Data1D)
890            enable2D = issubclass(data.__class__, Data2D)
891            ## if user has already selected a model to plot
892            ## redraw the model with data smeared
893            smear = self.page_finder[uid].get_smearer(fid=fid)
894
895            # compute weight for the current data
896            weight = self.page_finder[uid].get_weight(fid=fid)
897
898            self.draw_model(model=model, data=data, page_id=uid, smearer=smear,
899                enable1D=enable1D, enable2D=enable2D,
900                qmin=qmin, qmax=qmax, weight=weight)
901            self._mac_sleep(0.2)
902           
903    def _mac_sleep(self, sec=0.2):
904        """
905        Give sleep to MAC
906        """
907        if ON_MAC:
908            time.sleep(sec)
909       
910    def draw_model(self, model, page_id, data=None, smearer=None,
911                   enable1D=True, enable2D=False,
912                   state=None,
913                   fid=None,
914                   toggle_mode_on=False,
915                   qmin=None, qmax=None,
916                   update_chisqr=True, weight=None, source='model'):
917        """
918        Draw model.
919       
920        :param model: the model to draw
921        :param name: the name of the model to draw
922        :param data: the data on which the model is based to be drawn
923        :param description: model's description
924        :param enable1D: if true enable drawing model 1D
925        :param enable2D: if true enable drawing model 2D
926        :param qmin:  Range's minimum value to draw model
927        :param qmax:  Range's maximum value to draw model
928        :param qstep: number of step to divide the x and y-axis
929        :param update_chisqr: update chisqr [bool]
930             
931        """
932        #self.weight = weight
933        if issubclass(data.__class__, Data1D) or not enable2D:
934            ## draw model 1D with no loaded data
935            self._draw_model1D(model=model,
936                               data=data,
937                               page_id=page_id,
938                               enable1D=enable1D,
939                               smearer=smearer,
940                               qmin=qmin,
941                               qmax=qmax,
942                               fid=fid,
943                               weight=weight,
944                               toggle_mode_on=toggle_mode_on,
945                               state=state,
946                               update_chisqr=update_chisqr,
947                               source=source)
948        else:
949            ## draw model 2D with no initial data
950            self._draw_model2D(model=model,
951                                page_id=page_id,
952                                data=data,
953                                enable2D=enable2D,
954                                smearer=smearer,
955                                qmin=qmin,
956                                qmax=qmax,
957                                fid=fid,
958                                weight=weight,
959                                state=state,
960                                toggle_mode_on=toggle_mode_on,
961                                update_chisqr=update_chisqr,
962                                source=source)
963           
964    def onFit(self, uid):
965        """
966        Get series of data, model, associates parameters and range and send then
967        to  series of fit engines. Fit data and model, display result to
968        corresponding panels.
969        :param uid: id related to the panel currently calling this fit function.
970        """
971        flag = True
972        ##  count the number of fitproblem schedule to fit
973        fitproblem_count = 0
974        for value in self.page_finder.values():
975            if value.get_scheduled() == 1:
976                fitproblem_count += 1
977        self._gui_engine = self._return_engine_type()
978        self.fitproblem_count = fitproblem_count
979        if self._fit_engine in ("park","bumps"):
980            engineType = "Simultaneous Fit"
981        else:
982            engineType = "Single Fit"
983        fitter_list = []
984        sim_fitter = None
985        is_single_fit = True
986        batch_on = False
987        if self.sim_page is not None and self.sim_page.uid == uid:
988            #simulatanous fit only one engine need to be created
989            ## if simultaneous fit change automatically the engine to park
990            self._on_change_engine(engine='park')
991            sim_fitter = Fit(self._fit_engine)
992            sim_fitter.fitter_id = self.sim_page.uid
993            fitter_list.append(sim_fitter)
994            is_single_fit = False
995            batch_on = self.sim_page.batch_on
996           
997        self.fitproblem_count = fitproblem_count
998        if self._fit_engine == "park":
999            engineType = "Simultaneous Fit"
1000        else:
1001            engineType = "Single Fit"
1002       
1003        self.current_pg = None
1004        list_page_id = []
1005        fit_id = 0
1006        batch_inputs = {}
1007        batch_outputs = {}
1008        for page_id, value in self.page_finder.iteritems():
1009            # For simulfit (uid give with None), do for-loop
1010            # if uid is specified (singlefit), do it only on the page.
1011            if engineType == "Single Fit":
1012                #combine more than 1 batch page on single mode
1013                if self.batch_page is None or self.batch_page.uid != uid:
1014                    if page_id != uid:
1015                        continue
1016            try:
1017                if value.get_scheduled() == 1:
1018                    value.nbr_residuals_computed = 0
1019                    #Get list of parameters name to fit
1020                    pars = []
1021                    templist = []
1022                    page = self.fit_panel.get_page_by_id(page_id)
1023                    self.set_fit_weight(uid=page.uid,
1024                                     flag=page.get_weight_flag(),
1025                                     is2d=page._is_2D())
1026                    templist = page.get_param_list()
1027                    flag = page._update_paramv_on_fit()
1028                    if not flag:
1029                        msg = "Fitting range or parameter values are"
1030                        msg += " invalid in %s" % \
1031                                    page.window_caption
1032                        wx.PostEvent(page.parent.parent,
1033                                     StatusEvent(status=msg, info="error",
1034                                     type="stop"))
1035                        return flag
1036                    for element in templist:
1037                        name = str(element[1])
1038                        pars.append(name)
1039                    fitproblem_list = value.values()
1040                    for fitproblem in  fitproblem_list:
1041                        if sim_fitter is None:
1042                            fitter = Fit(self._fit_engine)
1043                            fitter.fitter_id = page_id
1044                            self._fit_helper(fitproblem=fitproblem,
1045                                             pars=pars,
1046                                             fitter=fitter,
1047                                             fit_id=fit_id,
1048                                             batch_inputs=batch_inputs,
1049                                             batch_outputs=batch_outputs)
1050                            fitter_list.append(fitter)
1051                        else:
1052                            fitter = sim_fitter
1053                            self._fit_helper(fitproblem=fitproblem,
1054                                             pars=pars,
1055                                             fitter=fitter,
1056                                             fit_id=fit_id,
1057                                             batch_inputs=batch_inputs,
1058                                             batch_outputs=batch_outputs)
1059                        fit_id += 1
1060                    list_page_id.append(page_id)
1061                    current_page_id = page_id
1062                    value.clear_model_param()
1063            except KeyboardInterrupt:
1064                flag = True
1065                msg = "Fitting terminated"
1066                wx.PostEvent(self.parent, StatusEvent(status=msg, info="info",
1067                                                      type="stop"))
1068                return flag
1069            except:
1070                flag = False
1071                msg = "%s error: %s" % (engineType, sys.exc_value)
1072                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
1073                                                      type="stop"))
1074                return flag
1075        ## If a thread is already started, stop it
1076        #if self.calc_fit!= None and self.calc_fit.isrunning():
1077        #    self.calc_fit.stop()
1078        msg = "Fitting is in progress..."
1079        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1080       
1081        #Handler used for park engine displayed message
1082        handler = ConsoleUpdate(parent=self.parent,
1083                                manager=self,
1084                                improvement_delta=0.1)
1085        self._mac_sleep(0.2)
1086        ## perform single fit
1087        try:
1088            page = self.fit_panel.get_page_by_id(uid)
1089            batch_on = page.batch_on
1090        except:
1091            try:
1092                #if the id cannot be found then  we deal with a self.sim_page
1093                #or a self.batch_page
1094                if self.sim_page is not None and uid == self.sim_page.uid:
1095                    batch_on = self.sim_page.batch_on
1096                if self.batch_page is not None and uid == self.batch_page.uid:
1097                    batch_on = self.batch_page.batch_on
1098            except:
1099                batch_on = False
1100
1101        # batch fit
1102        if batch_on:
1103            calc_fit = FitThread(handler=handler,
1104                                 fn=fitter_list,
1105                                 pars=pars,
1106                                 batch_inputs=batch_inputs,
1107                                 batch_outputs=batch_outputs,
1108                                 page_id=list_page_id,
1109                                 completefn=self._batch_fit_complete,
1110                                 ftol=self.ftol,
1111                                 reset_flag=self.batch_reset_flag)
1112        else:
1113            # single fit: not batch and not simul fit
1114            if not is_single_fit:
1115                current_page_id = self.sim_page.uid
1116            ## Perform more than 1 fit at the time
1117            calc_fit = FitThread(handler=handler,
1118                                    fn=fitter_list,
1119                                    batch_inputs=batch_inputs,
1120                                    batch_outputs=batch_outputs,
1121                                    page_id=list_page_id,
1122                                    updatefn=handler.update_fit,
1123                                    completefn=self._fit_completed,
1124                                    ftol=self.ftol)
1125        self.fit_thread_list[current_page_id] = calc_fit
1126        calc_fit.queue()
1127        msg = "Fitting is in progress..."
1128        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1129       
1130        self.ready_fit(calc_fit=calc_fit)
1131        return flag
1132   
1133    def ready_fit(self, calc_fit):
1134        """
1135        Ready for another fit
1136        """
1137        if self.fitproblem_count != None and self.fitproblem_count > 1:
1138            calc_fit.ready(2.5)
1139        else:
1140            time.sleep(0.4)
1141           
1142    def remove_plot(self, uid, fid=None, theory=False):
1143        """
1144        remove model plot when a fit page is closed
1145        :param uid: the id related to the fitpage to close
1146        :param fid: the id of the fitproblem(data, model, range,etc)
1147        """
1148        if uid not in self.page_finder.keys():
1149            return
1150        fitproblemList = self.page_finder[uid].get_fit_problem(fid)
1151        for fitproblem in fitproblemList:
1152            data = fitproblem.get_fit_data()
1153            model = fitproblem.get_model()
1154            plot_id = None
1155            if model is not None:
1156                plot_id = data.id + model.name
1157            if theory:
1158                plot_id = data.id + model.name
1159            group_id = data.group_id
1160            wx.PostEvent(self.parent, NewPlotEvent(id=plot_id,
1161                                                   group_id=group_id,
1162                                                   action='remove'))
1163           
1164    def store_data(self, uid, data_list=None, caption=None):
1165        """
1166        Recieve a list of data and store them ans well as a caption of
1167        the fit page where they come from.
1168        :param uid: if related to a fit page
1169        :param data_list: list of data to fit
1170        :param caption: caption of the window related to these data
1171        """
1172        if data_list is None:
1173            data_list = []
1174       
1175        self.page_finder[uid].set_fit_data(data=data_list)
1176        if caption is not None:
1177            self.page_finder[uid].set_fit_tab_caption(caption=caption)
1178           
1179    def on_add_new_page(self, event=None):
1180        """
1181        ask fit panel to create a new empty page
1182        """
1183        try:
1184            page = self.fit_panel.add_empty_page()
1185            # add data associated to the page created
1186            if page != None:
1187                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
1188                                               info="info"))
1189            else:
1190                msg = "Page was already Created"
1191                wx.PostEvent(self.parent, StatusEvent(status=msg,
1192                                                       info="warning"))
1193        except:
1194            msg = "Creating Fit page: %s"%sys.exc_value
1195            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
1196       
1197    def add_fit_page(self, data):
1198        """
1199        given a data, ask to the fitting panel to create a new fitting page,
1200        get this page and store it into the page_finder of this plug-in
1201        :param data: is a list of data
1202        """
1203        page = self.fit_panel.set_data(data)
1204        # page could be None when loading state files
1205        if page == None:
1206            return page
1207        #append Data1D to the panel containing its theory
1208        #if theory already plotted
1209        if page.uid in self.page_finder:
1210            data = page.get_data()
1211            theory_data = self.page_finder[page.uid].get_theory_data(data.id)
1212            if issubclass(data.__class__, Data2D):
1213                data.group_id = wx.NewId()
1214                if theory_data is not None:
1215                    group_id = str(page.uid) + " Model1D"
1216                    wx.PostEvent(self.parent,
1217                             NewPlotEvent(group_id=group_id,
1218                                               action="delete"))
1219                    self.parent.update_data(prev_data=theory_data,
1220                                             new_data=data)
1221            else:
1222                if theory_data is not None:
1223                    group_id = str(page.uid) + " Model2D"
1224                    data.group_id = theory_data.group_id
1225                    wx.PostEvent(self.parent, 
1226                             NewPlotEvent(group_id=group_id,
1227                                               action="delete"))
1228                    self.parent.update_data(prev_data=theory_data,
1229                                             new_data=data)
1230        self.store_data(uid=page.uid, data_list=page.get_data_list(),
1231                        caption=page.window_caption)
1232        if self.sim_page is not None and not self.batch_on:
1233            self.sim_page.draw_page()
1234        if self.batch_page is not None and self.batch_on:
1235            self.batch_page.draw_page()
1236           
1237        return page
1238           
1239    def _onEVT_SLICER_PANEL(self, event):
1240        """
1241        receive and event telling to update a panel with a name starting with
1242        event.panel_name. this method update slicer panel
1243        for a given interactor.
1244       
1245        :param event: contains type of slicer , paramaters for updating
1246            the panel and panel_name to find the slicer 's panel concerned.
1247        """
1248        event.panel_name
1249        for item in self.parent.panels:
1250            name = event.panel_name
1251            if self.parent.panels[item].window_caption.startswith(name):
1252                self.parent.panels[item].set_slicer(event.type, event.params)
1253               
1254        #self.parent._mgr.Update()
1255   
1256    def _closed_fitpage(self, event):
1257        """
1258        request fitpanel to close a given page when its unique data is removed
1259        from the plot. close fitpage only when the a loaded data is removed
1260        """
1261        if event is None or event.data is None:
1262            return
1263        if hasattr(event.data, "is_data"):
1264            if not event.data.is_data or \
1265                event.data.__class__.__name__ == "Data1D":
1266                self.fit_panel.close_page_with_data(event.data)
1267 
1268    def _reset_schedule_problem(self, value=0, uid=None):
1269        """
1270        unschedule or schedule all fitproblem to be fit
1271        """
1272        # case that uid is not specified
1273        if uid == None:
1274            for page_id in self.page_finder.keys():
1275                self.page_finder[page_id].schedule_tofit(value)
1276        # when uid is given
1277        else:
1278            if uid in self.page_finder.keys():
1279                self.page_finder[uid].schedule_tofit(value)
1280               
1281    def _fit_helper(self, fitproblem, pars, fitter, fit_id,
1282                    batch_inputs, batch_outputs):
1283        """
1284        Create and set fit engine with series of data and model
1285        :param pars: list of fittable parameters
1286        :param fitter_list: list of fit engine
1287        :param value:  structure storing data mapped to their model, range etc.
1288        """
1289        data = fitproblem.get_fit_data()
1290        model = fitproblem.get_model()
1291        smearer = fitproblem.get_smearer()
1292        qmin, qmax = fitproblem.get_range()
1293
1294        #Extra list of parameters and their constraints
1295        listOfConstraint = []
1296        param = fitproblem.get_model_param()
1297        if len(param) > 0:
1298            for item in param:
1299                ## check if constraint
1300                if item[0] != None and item[1] != None:
1301                    listOfConstraint.append((item[0], item[1]))
1302        new_model = model
1303        fitter.set_model(new_model, fit_id, pars, data=data,
1304                         constraints=listOfConstraint)
1305        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin,
1306                        qmax=qmax)
1307        fitter.select_problem_for_fit(id=fit_id, value=1)
1308       
1309    def _onSelect(self, event):
1310        """
1311        when Select data to fit a new page is created .Its reference is
1312        added to self.page_finder
1313        """
1314        panel = self.plot_panel
1315        if panel == None:
1316            raise ValueError, "Fitting:_onSelect: NonType panel"
1317        Plugin.on_perspective(self, event=event)
1318        self.select_data(panel)
1319       
1320    def select_data(self, panel):
1321        """
1322        """
1323        for plottable in panel.graph.plottables:
1324            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
1325                data_id = panel.graph.selected_plottable
1326                if plottable == panel.plots[data_id]:
1327                    data = plottable
1328                    self.add_fit_page(data=[data])
1329                    return
1330            else:
1331                data = plottable
1332                self.add_fit_page(data=[data])
1333           
1334    def update_fit(self, result=None, msg=""):
1335        """
1336        """
1337        print "update_fit result", result
1338       
1339    def _batch_fit_complete(self, result, pars, page_id,
1340                            batch_outputs, batch_inputs, elapsed=None):
1341        """
1342        Display fit result in batch
1343        :param result: list of objects received fromt fit engines
1344        :param pars: list of  fitted parameters names
1345        :param page_id: list of page ids which called fit function
1346        :param elapsed: time spent at the fitting level
1347        """
1348        self._mac_sleep(0.2)
1349        uid = page_id[0]
1350        if uid in self.fit_thread_list.keys():
1351            del self.fit_thread_list[uid]
1352         
1353        self._update_fit_button(page_id)
1354        t1 = time.time()
1355        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1356        msg = "Fit completed on %s \n" % str_time
1357        msg += "Duration time: %s s.\n" % str(elapsed)
1358        wx.PostEvent(self.parent, StatusEvent(status=msg, info="info",
1359                                              type="stop"))
1360       
1361        if batch_outputs is None:
1362            batch_outputs = {}
1363       
1364        # format batch_outputs
1365        batch_outputs["Chi2"] = []
1366        #Don't like these loops
1367        # Need to create dictionary of all fitted parameters
1368        # since the number of parameters can differ between each fit result
1369        for list_res in result:
1370            for res in list_res:
1371                model, data = res.inputs[0]
1372                if model is not None and hasattr(model, "model"):
1373                    model = model.model
1374                #get all fittable parameters of the current model
1375                for param in  model.getParamList():
1376                    if param  not in batch_outputs.keys():
1377                        batch_outputs[param] = []
1378                for param in model.getDispParamList():
1379                    if not model.is_fittable(param) and \
1380                        param in batch_outputs.keys():
1381                        del batch_outputs[param]
1382                # Add fitted parameters and their error
1383                for param in res.param_list:
1384                    if param not in batch_outputs.keys():
1385                        batch_outputs[param] = []
1386                    err_param = "error on %s" % str(param)
1387                    if err_param not in batch_inputs.keys():
1388                        batch_inputs[err_param] = []
1389        msg = ""
1390        for list_res in result:
1391            for res in list_res:
1392                pid = res.fitter_id
1393                model, data = res.inputs[0]
1394                correct_result = False
1395                if model is not None and hasattr(model, "model"):
1396                    model = model.model
1397                if data is not None and hasattr(data, "sans_data"):
1398                    data = data.sans_data
1399               
1400                is_data2d = issubclass(data.__class__, Data2D)
1401                #check consistency of arrays
1402                if not is_data2d:
1403                    if len(res.theory) == len(res.index[res.index]) and \
1404                        len(res.index) == len(data.y):
1405                        correct_result = True
1406                else:
1407                    copy_data = deepcopy(data)
1408                    new_theory = copy_data.data
1409                    new_theory[res.index] = res.theory
1410                    new_theory[res.index == False] = numpy.nan
1411                    correct_result = True
1412                #get all fittable parameters of the current model
1413                param_list = model.getParamList()
1414                for param in model.getDispParamList():
1415                    if not model.is_fittable(param) and \
1416                        param in param_list:
1417                        param_list.remove(param)
1418                if not correct_result or res.fitness is None or \
1419                    not numpy.isfinite(res.fitness) or \
1420                    numpy.any(res.pvec == None) or not \
1421                    numpy.all(numpy.isfinite(res.pvec)):
1422                    data_name = str(None)
1423                    if data is not None:
1424                        data_name = str(data.name)
1425                    model_name = str(None)
1426                    if model is not None:
1427                        model_name = str(model.name)
1428                    msg += "Data %s and Model %s did not fit.\n" % (data_name,
1429                                                                    model_name)
1430                    ERROR = numpy.NAN
1431                    cell = BatchCell()
1432                    cell.label = res.fitness
1433                    cell.value = res.fitness
1434                    batch_outputs["Chi2"].append(ERROR)
1435                    for param in param_list:
1436                        # save value of  fixed parameters
1437                        if param not in res.param_list:
1438                            batch_outputs[str(param)].append(ERROR)
1439                        else:
1440                            #save only fitted values
1441                            batch_outputs[param].append(ERROR)
1442                            batch_inputs["error on %s" % str(param)].append(ERROR)
1443                else:
1444                    # ToDo: Why sometimes res.pvec comes with numpy.float64?
1445                    # Need to fix it within ScipyEngine
1446                    if res.pvec.__class__ == numpy.float64:
1447                        res.pvec = [res.pvec]
1448                       
1449                    cell = BatchCell()
1450                    cell.label = res.fitness
1451                    cell.value = res.fitness
1452                    batch_outputs["Chi2"].append(cell)
1453                    # add parameters to batch_results
1454                    for param in param_list:
1455                        # save value of  fixed parameters
1456                        if param not in res.param_list:
1457                            batch_outputs[str(param)].append(model.getParam(param))
1458                        else:
1459                            index = res.param_list.index(param)
1460                            #save only fitted values
1461                            batch_outputs[param].append(res.pvec[index])
1462                            if res.stderr is not None and \
1463                                len(res.stderr) == len(res.param_list):
1464                                item = res.stderr[index]
1465                                batch_inputs["error on %s" % param].append(item)
1466                            else:
1467                                batch_inputs["error on %s" % param].append('-')
1468                            model.setParam(param, res.pvec[index])
1469                #fill the batch result with emtpy value if not in the current
1470                #model
1471                EMPTY = "-"
1472                for key in batch_outputs.keys():
1473                    if key not in param_list and key not in ["Chi2", "Data"]:
1474                        batch_outputs[key].append(EMPTY)
1475                               
1476                self.page_finder[pid].set_batch_result(batch_inputs=batch_inputs,
1477                                                       batch_outputs=batch_outputs)
1478               
1479                cpage = self.fit_panel.get_page_by_id(pid)
1480                cpage._on_fit_complete()
1481                self.page_finder[pid][data.id].set_result(res)
1482                fitproblem = self.page_finder[pid][data.id]
1483                qmin, qmax = fitproblem.get_range()
1484                plot_result = False
1485                if correct_result:
1486                    if not is_data2d:
1487                        self._complete1D(x=data.x[res.index], y=res.theory, page_id=pid,
1488                                         elapsed=None,
1489                                         index=res.index, model=model,
1490                                         weight=None, fid=data.id,
1491                                         toggle_mode_on=False, state=None,
1492                                         data=data, update_chisqr=False,
1493                                         source='fit', plot_result=plot_result)
1494                    else:
1495                        self._complete2D(image=new_theory, data=data,
1496                                         model=model,
1497                                         page_id=pid, elapsed=None,
1498                                         index=res.index,
1499                                         qmin=qmin,
1500                                         qmax=qmax, fid=data.id, weight=None,
1501                                         toggle_mode_on=False, state=None,
1502                                         update_chisqr=False,
1503                                         source='fit', plot_result=plot_result)
1504                self.on_set_batch_result(page_id=pid,
1505                                         fid=data.id,
1506                                         batch_outputs=batch_outputs,
1507                                         batch_inputs=batch_inputs)
1508       
1509        wx.PostEvent(self.parent, StatusEvent(status=msg, error="info",
1510                                              type="stop"))
1511        # Remove parameters that are not shown
1512        cpage = self.fit_panel.get_page_by_id(uid)
1513        tbatch_outputs = {}
1514        shownkeystr = cpage.get_copy_params()
1515        for key in batch_outputs.keys():
1516            if key in ["Chi2", "Data"] or shownkeystr.count(key) > 0:
1517                tbatch_outputs[key] = batch_outputs[key]
1518               
1519        wx.CallAfter(self.parent.on_set_batch_result, tbatch_outputs,
1520                     batch_inputs, self.sub_menu)
1521       
1522    def on_set_batch_result(self, page_id, fid, batch_outputs, batch_inputs):
1523        """
1524        """
1525        pid = page_id
1526        if fid not in self.page_finder[pid]:
1527            return
1528        fitproblem = self.page_finder[pid][fid]
1529        index = self.page_finder[pid].nbr_residuals_computed - 1
1530        residuals = fitproblem.get_residuals()
1531        theory_data = fitproblem.get_theory_data()
1532        data = fitproblem.get_fit_data()
1533        model = fitproblem.get_model()
1534        #fill batch result information
1535        if "Data" not in batch_outputs.keys():
1536            batch_outputs["Data"] = []
1537        from sans.guiframe.data_processor import BatchCell
1538        cell = BatchCell()
1539        cell.label = data.name
1540        cell.value = index
1541       
1542        if theory_data != None:
1543            #Suucessful fit
1544            theory_data.id = wx.NewId()
1545            theory_data.name = model.name + "[%s]" % str(data.name)
1546            if issubclass(theory_data.__class__, Data2D):
1547                group_id = wx.NewId()
1548                theory_data.group_id = group_id
1549                if group_id not in theory_data.list_group_id:
1550                    theory_data.list_group_id.append(group_id)
1551               
1552            try:
1553                # associate residuals plot
1554                if issubclass(residuals.__class__, Data2D):
1555                    group_id = wx.NewId()
1556                    residuals.group_id = group_id
1557                    if group_id not in residuals.list_group_id:
1558                        residuals.list_group_id.append(group_id)
1559                batch_outputs["Chi2"][index].object = [residuals]
1560            except:
1561                pass
1562
1563        cell.object = [data, theory_data]
1564        batch_outputs["Data"].append(cell)
1565        for key, value in data.meta_data.iteritems():
1566            if key not in batch_inputs.keys():
1567                batch_inputs[key] = []
1568            #if key.lower().strip() != "loader":
1569            batch_inputs[key].append(value)
1570        param = "temperature"
1571        if hasattr(data.sample, param):
1572            if param not in  batch_inputs.keys():
1573                batch_inputs[param] = []
1574            batch_inputs[param].append(data.sample.temperature)
1575       
1576    def _fit_completed(self, result, page_id, batch_outputs,
1577                       batch_inputs=None, pars=None, elapsed=None):
1578        """
1579        Display result of the fit on related panel(s).
1580        :param result: list of object generated when fit ends
1581        :param pars: list of names of parameters fitted
1582        :param page_id: list of page ids which called fit function
1583        :param elapsed: time spent at the fitting level
1584        """
1585        t1 = time.time()
1586        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1587        msg = "Fit completed on %s \n" % str_time
1588        msg += "Duration time: %s s.\n" % str(elapsed)
1589        wx.PostEvent(self.parent, StatusEvent(status=msg, info="info",
1590                                                      type="stop"))
1591        # reset fit_engine if changed by simul_fit
1592        if self._fit_engine != self._gui_engine:
1593            self._on_change_engine(self._gui_engine)
1594        self._update_fit_button(page_id)
1595        result = result[0]
1596        self.fit_thread_list = {}
1597        if page_id is None:
1598            page_id = []
1599        ## fit more than 1 model at the same time
1600        self._mac_sleep(0.2)
1601        try:
1602            index = 0
1603            for uid in page_id:
1604                res = result[index]
1605                if res.fitness is None or \
1606                    not numpy.isfinite(res.fitness) or \
1607                    numpy.any(res.pvec == None) or \
1608                    not numpy.all(numpy.isfinite(res.pvec)):
1609                    msg = "Fitting did not converge!!!"
1610                    wx.PostEvent(self.parent,
1611                             StatusEvent(status=msg,
1612                                         info="warning",
1613                                         type="stop"))
1614                    self._update_fit_button(page_id)
1615                else:
1616                    #set the panel when fit result are float not list
1617                    if res.pvec.__class__ == numpy.float64:
1618                        pvec = [res.pvec]
1619                    else:
1620                        pvec = res.pvec
1621                    if res.stderr.__class__ == numpy.float64:
1622                        stderr = [res.stderr]
1623                    else:
1624                        stderr = res.stderr
1625                    cpage = self.fit_panel.get_page_by_id(uid)
1626                    # Make sure we got all results
1627                    #(CallAfter is important to MAC)
1628                    try:
1629                        #if res != None:
1630                        wx.CallAfter(cpage.onsetValues, res.fitness,
1631                                     res.param_list,
1632                                     pvec, stderr)
1633                        index += 1
1634                        wx.CallAfter(cpage._on_fit_complete)
1635                    except KeyboardInterrupt:
1636                        msg = "Singular point: Fitting Stoped."
1637                        wx.PostEvent(self.parent, StatusEvent(status=msg,
1638                                                              info="info",
1639                                                              type="stop"))
1640                    except:
1641                        msg = "Singular point: Fitting Error occurred."
1642                        wx.PostEvent(self.parent, StatusEvent(status=msg,
1643                                                              info="error",
1644                                                              type="stop"))
1645                   
1646        except:
1647            msg = "Fit completed but Following"
1648            msg += " warning occurred: %s" % sys.exc_value
1649            wx.PostEvent(self.parent, StatusEvent(status=msg, info="warning",
1650                                                  type="stop"))
1651           
1652    def _update_fit_button(self, page_id):
1653        """
1654        Update Fit button when fit stopped
1655       
1656        : parameter page_id: fitpage where the button is
1657        """
1658        if page_id.__class__.__name__ != 'list':
1659            page_id = [page_id]
1660        for uid in page_id:
1661            page = self.fit_panel.get_page_by_id(uid)
1662            page._on_fit_complete()
1663       
1664    def _on_show_panel(self, event):
1665        """
1666        """
1667        pass
1668   
1669    def on_reset_batch_flag(self, event):
1670        """
1671        Set batch_reset_flag
1672        """
1673        event.Skip()
1674        if self.menu1 == None:
1675            return
1676        menu_item = self.menu1.FindItemById(self.id_reset_flag)
1677        flag = menu_item.IsChecked()
1678        if not flag:
1679            menu_item.Check(False)
1680            self.batch_reset_flag = True
1681        else:
1682            menu_item.Check(True)
1683            self.batch_reset_flag = False
1684       
1685        ## post a message to status bar
1686        msg = "Set Chain Fitting: %s" % str(not self.batch_reset_flag)
1687        wx.PostEvent(self.parent,
1688                     StatusEvent(status=msg))
1689
1690    def _onset_engine_park(self, event):
1691        """
1692        set engine to park
1693        """
1694        self._on_change_engine('park')
1695       
1696    def _onset_engine_scipy(self, event):
1697        """
1698        set engine to scipy
1699        """
1700        self._on_change_engine('scipy')
1701       
1702    def _onset_engine_bumps(self, event):
1703        """
1704        set engine to bumps
1705        """
1706        self._on_change_engine('bumps')
1707       
1708    def _on_slicer_event(self, event):
1709        """
1710        Receive a panel as event and send it to guiframe
1711       
1712        :param event: event containing a panel
1713       
1714        """
1715        if event.panel is not None:
1716            new_panel = event.panel
1717            self.slicer_panels.append(event.panel)
1718            # Set group ID if available
1719            event_id = self.parent.popup_panel(event.panel)
1720            event.panel.uid = event_id
1721            self.mypanels.append(event.panel)
1722       
1723    def _onclearslicer(self, event):
1724        """
1725        Clear the boxslicer when close the panel associate with this slicer
1726        """
1727        name = event.GetEventObject().frame.GetTitle()
1728        print "name", name
1729        for panel in self.slicer_panels:
1730            if panel.window_caption == name:
1731               
1732                for item in self.parent.panels:
1733                    if hasattr(self.parent.panels[item], "uid"):
1734                        if self.parent.panels[item].uid == panel.base.uid:
1735                            self.parent.panels[item].onClearSlicer(event)
1736                            #self.parent._mgr.Update()
1737                            break
1738                break
1739   
1740    def _return_engine_type(self):
1741        """
1742        return the current type of engine
1743        """
1744        return self._fit_engine
1745     
1746    def _on_change_engine(self, engine='park'):
1747        """
1748        Allow to select the type of engine to perform fit
1749       
1750        :param engine: the key work of the engine
1751       
1752        """
1753        ## saving fit engine name
1754        self._fit_engine = engine
1755        ## change menu item state
1756        if engine == "park":
1757            self.menu1.FindItemById(self.park_id).Check(True)
1758            self.menu1.FindItemById(self.scipy_id).Check(False)
1759            self.menu1.FindItemById(self.bumps_id).Check(False)
1760        elif engine == "scipy":
1761            self.menu1.FindItemById(self.park_id).Check(False)
1762            self.menu1.FindItemById(self.scipy_id).Check(True)
1763            self.menu1.FindItemById(self.bumps_id).Check(False)
1764        else:
1765            self.menu1.FindItemById(self.park_id).Check(False)
1766            self.menu1.FindItemById(self.scipy_id).Check(False)
1767            self.menu1.FindItemById(self.bumps_id).Check(True)
1768        ## post a message to status bar
1769        msg = "Engine set to: %s" % self._fit_engine
1770        wx.PostEvent(self.parent,
1771                     StatusEvent(status=msg))
1772        ## send the current engine type to fitpanel
1773        self.fit_panel._on_engine_change(name=self._fit_engine)
1774
1775    def _on_model_panel(self, evt):
1776        """
1777        react to model selection on any combo box or model menu.plot the model 
1778       
1779        :param evt: wx.combobox event
1780       
1781        """
1782        model = evt.model
1783        uid = evt.uid
1784        qmin = evt.qmin
1785        qmax = evt.qmax
1786        caption = evt.caption
1787        enable_smearer = evt.enable_smearer
1788        if model == None:
1789            return
1790        if uid not in self.page_finder.keys():
1791            return
1792        # save the name containing the data name with the appropriate model
1793        self.page_finder[uid].set_model(model)
1794        self.page_finder[uid].enable_smearing(enable_smearer)
1795        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1796        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1797        if self.sim_page is not None and not self.batch_on:
1798            self.sim_page.draw_page()
1799        if self.batch_page is not None and self.batch_on:
1800            self.batch_page.draw_page()
1801       
1802    def _update1D(self, x, output):
1803        """
1804        Update the output of plotting model 1D
1805        """
1806        msg = "Plot updating ... "
1807        wx.PostEvent(self.parent, StatusEvent(status=msg, type="update"))
1808       
1809    def _complete1D(self, x, y, page_id, elapsed, index, model,
1810                    weight=None, fid=None,
1811                    toggle_mode_on=False, state=None,
1812                    data=None, update_chisqr=True,
1813                    source='model', plot_result=True):
1814        """
1815        Complete plotting 1D data
1816        """
1817        try:
1818            numpy.nan_to_num(y)
1819           
1820            new_plot = Data1D(x=x, y=y)
1821            new_plot.is_data = False
1822            new_plot.dy = numpy.zeros(len(y))
1823            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1824            _yaxis, _yunit = data.get_yaxis()
1825            _xaxis, _xunit = data.get_xaxis()
1826            new_plot.title = data.name
1827
1828            new_plot.group_id = data.group_id
1829            if new_plot.group_id == None:
1830                new_plot.group_id = data.group_id
1831            new_plot.id = str(page_id) + " " + data.name
1832            #if new_plot.id in self.color_dict:
1833            #    new_plot.custom_color = self.color_dict[new_plot.id]
1834            #find if this theory was already plotted and replace that plot given
1835            #the same id
1836            theory_data = self.page_finder[page_id].get_theory_data(fid=data.id)
1837           
1838            if data.is_data:
1839                data_name = str(data.name)
1840            else:
1841                data_name = str(model.__class__.__name__)
1842           
1843            new_plot.name = model.name + " [" + data_name +"]"
1844            new_plot.xaxis(_xaxis, _xunit)
1845            new_plot.yaxis(_yaxis, _yunit)
1846            self.page_finder[page_id].set_theory_data(data=new_plot,
1847                                                      fid=data.id)
1848            self.parent.update_theory(data_id=data.id, theory=new_plot,
1849                                       state=state)
1850            current_pg = self.fit_panel.get_page_by_id(page_id)
1851            title = new_plot.title
1852            batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
1853            if not batch_on:
1854                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1855                                            title=str(title)))
1856            elif plot_result:
1857                top_data_id = self.fit_panel.get_page_by_id(page_id).data.id
1858                if data.id == top_data_id:
1859                    wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1860                                            title=str(title)))
1861            caption = current_pg.window_caption
1862            self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1863           
1864            self.page_finder[page_id].set_theory_data(data=new_plot,
1865                                                      fid=data.id)
1866            if toggle_mode_on:
1867                wx.PostEvent(self.parent,
1868                             NewPlotEvent(group_id=str(page_id) + " Model2D",
1869                                          action="Hide"))
1870            else:
1871                if update_chisqr:
1872                    wx.PostEvent(current_pg,
1873                                 Chi2UpdateEvent(output=self._cal_chisqr(
1874                                                                data=data,
1875                                                                fid=fid,
1876                                                                weight=weight,
1877                                                            page_id=page_id,
1878                                                            index=index)))
1879                else:
1880                    self._plot_residuals(page_id=page_id, data=data, fid=fid,
1881                                         index=index, weight=weight)
1882
1883            msg = "Computation  completed!"
1884            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1885        except:
1886            raise
1887   
1888    def _update2D(self, output, time=None):
1889        """
1890        Update the output of plotting model
1891        """
1892        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1893        #updating ... ", type="update"))
1894        #self.ready_fit()
1895 
1896    def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
1897                qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
1898                     update_chisqr=True, source='model', plot_result=True):
1899        """
1900        Complete get the result of modelthread and create model 2D
1901        that can be plot.
1902        """
1903        numpy.nan_to_num(image)
1904        new_plot = Data2D(image=image, err_image=data.err_data)
1905        new_plot.name = model.name + '2d'
1906        new_plot.title = "Analytical model 2D "
1907        new_plot.id = str(page_id) + " " + data.name
1908        new_plot.group_id = str(page_id) + " Model2D"
1909        new_plot.detector = data.detector
1910        new_plot.source = data.source
1911        new_plot.is_data = False
1912        new_plot.qx_data = data.qx_data
1913        new_plot.qy_data = data.qy_data
1914        new_plot.q_data = data.q_data
1915        new_plot.mask = data.mask
1916        ## plot boundaries
1917        new_plot.ymin = data.ymin
1918        new_plot.ymax = data.ymax
1919        new_plot.xmin = data.xmin
1920        new_plot.xmax = data.xmax
1921        title = data.title
1922       
1923        new_plot.is_data = False
1924        if data.is_data:
1925            data_name = str(data.name)
1926        else:
1927            data_name = str(model.__class__.__name__) + '2d'
1928
1929        if len(title) > 1:
1930            new_plot.title = "Model2D for %s "% model.name + data_name
1931        new_plot.name = model.name + " [" + \
1932                                    data_name + "]"
1933        theory_data = deepcopy(new_plot)
1934       
1935        self.page_finder[page_id].set_theory_data(data=theory_data,
1936                                                  fid=data.id)
1937        self.parent.update_theory(data_id=data.id,
1938                                       theory=new_plot,
1939                                       state=state)
1940        current_pg = self.fit_panel.get_page_by_id(page_id)
1941        title = new_plot.title
1942        if not source == 'fit' and plot_result:
1943            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1944                                               title=title))
1945        if toggle_mode_on:
1946            wx.PostEvent(self.parent,
1947                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1948                                               action="Hide"))
1949        else:
1950            # Chisqr in fitpage
1951            if update_chisqr:
1952                wx.PostEvent(current_pg,
1953                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1954                                                                    weight=weight,
1955                                                                    fid=fid,
1956                                                         page_id=page_id,
1957                                                         index=index)))
1958            else:
1959                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1960                                      index=index, weight=weight)
1961        msg = "Computation  completed!"
1962        wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1963   
1964    def _draw_model2D(self, model, page_id, qmin,
1965                      qmax,
1966                      data=None, smearer=None,
1967                      description=None, enable2D=False,
1968                      state=None,
1969                      fid=None,
1970                      weight=None,
1971                      toggle_mode_on=False,
1972                       update_chisqr=True, source='model'):
1973        """
1974        draw model in 2D
1975       
1976        :param model: instance of the model to draw
1977        :param description: the description of the model
1978        :param enable2D: when True allows to draw model 2D
1979        :param qmin: the minimum value to  draw model 2D
1980        :param qmax: the maximum value to draw model 2D
1981        :param qstep: the number of division of Qx and Qy of the model to draw
1982           
1983        """
1984        if not enable2D:
1985            return None
1986        try:
1987            from model_thread import Calc2D
1988            ## If a thread is already started, stop it
1989            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1990                self.calc_2D.stop()
1991            self.calc_2D = Calc2D(model=model,
1992                                    data=data,
1993                                    page_id=page_id,
1994                                    smearer=smearer,
1995                                    qmin=qmin,
1996                                    qmax=qmax,
1997                                    weight=weight, 
1998                                    fid=fid,
1999                                    toggle_mode_on=toggle_mode_on,
2000                                    state=state,
2001                                    completefn=self._complete2D,
2002                                    update_chisqr=update_chisqr, source=source)
2003            self.calc_2D.queue()
2004        except:
2005            raise
2006
2007    def _draw_model1D(self, model, page_id, data,
2008                      qmin, qmax, smearer=None,
2009                state=None,
2010                weight=None,
2011                fid=None,
2012                toggle_mode_on=False, update_chisqr=True, source='model',
2013                enable1D=True):
2014        """
2015        Draw model 1D from loaded data1D
2016       
2017        :param data: loaded data
2018        :param model: the model to plot
2019       
2020        """
2021        if not enable1D:
2022            return
2023        try:
2024            from model_thread import Calc1D
2025            ## If a thread is already started, stop it
2026            if (self.calc_1D is not None) and self.calc_1D.isrunning():
2027                self.calc_1D.stop()
2028            self.calc_1D = Calc1D(data=data,
2029                                  model=model,
2030                                  page_id=page_id, 
2031                                  qmin=qmin,
2032                                  qmax=qmax,
2033                                  smearer=smearer,
2034                                  state=state,
2035                                  weight=weight,
2036                                  fid=fid,
2037                                  toggle_mode_on=toggle_mode_on,
2038                                  completefn=self._complete1D,
2039                                  #updatefn = self._update1D,
2040                                  update_chisqr=update_chisqr,
2041                                  source=source)
2042            self.calc_1D.queue()
2043        except:
2044            msg = " Error occurred when drawing %s Model 1D: " % model.name
2045            msg += " %s" % sys.exc_value
2046            wx.PostEvent(self.parent, StatusEvent(status=msg))
2047   
2048    def _cal_chisqr(self, page_id, data, weight, fid=None, index=None):
2049        """
2050        Get handy Chisqr using the output from draw1D and 2D,
2051        instead of calling expansive CalcChisqr in guithread
2052        """
2053        try:
2054            data_copy = deepcopy(data)
2055        except:
2056            return
2057        # default chisqr
2058        chisqr = None
2059        #to compute chisq make sure data has valid data
2060        # return None if data == None
2061        if not check_data_validity(data_copy) or data_copy == None:
2062            return chisqr
2063
2064        # Get data: data I, theory I, and data dI in order
2065        if data_copy.__class__.__name__ == "Data2D":
2066            if index == None:
2067                index = numpy.ones(len(data_copy.data), dtype=bool)
2068            if weight != None:
2069                data_copy.err_data = weight
2070            # get rid of zero error points
2071            index = index & (data_copy.err_data != 0)
2072            index = index & (numpy.isfinite(data_copy.data))
2073            fn = data_copy.data[index]
2074            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2075            if theory_data == None:
2076                return chisqr
2077            gn = theory_data.data[index]
2078            en = data_copy.err_data[index]
2079        else:
2080            # 1 d theory from model_thread is only in the range of index
2081            if index == None: 
2082                index = numpy.ones(len(data_copy.y), dtype=bool)
2083            if weight != None:
2084                data_copy.dy = weight
2085            if data_copy.dy == None or data_copy.dy == []:
2086                dy = numpy.ones(len(data_copy.y))
2087            else:
2088                ## Set consitently w/AbstractFitengine:
2089                # But this should be corrected later.
2090                dy = deepcopy(data_copy.dy)
2091                dy[dy == 0] = 1
2092            fn = data_copy.y[index]
2093           
2094            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2095            if theory_data == None:
2096                return chisqr
2097            gn = theory_data.y
2098            en = dy[index]
2099       
2100        # residual
2101        try:
2102            res = (fn - gn) / en
2103        except ValueError:
2104            print "Unmatch lengths %s, %s, %s"% (len(fn), len(gn), len(en))
2105            return
2106       
2107        residuals = res[numpy.isfinite(res)]
2108        # get chisqr only w/finite
2109        chisqr = numpy.average(residuals * residuals)
2110       
2111        self._plot_residuals(page_id=page_id, data=data_copy,
2112                             fid=fid,
2113                             weight=weight, index=index)
2114       
2115        return chisqr
2116   
2117    def _plot_residuals(self, page_id, weight, fid=None,
2118                        data=None, index=None):
2119        """
2120        Plot the residuals
2121       
2122        :param data: data
2123        :param index: index array (bool)
2124        : Note: this is different from the residuals in cal_chisqr()
2125        """
2126        data_copy = deepcopy(data)
2127        # Get data: data I, theory I, and data dI in order
2128        if data_copy.__class__.__name__ == "Data2D":
2129            # build residuals
2130            residuals = Data2D()
2131            #residuals.copy_from_datainfo(data)
2132            # Not for trunk the line below, instead use the line above
2133            data_copy.clone_without_data(len(data_copy.data), residuals)
2134            residuals.data = None
2135            fn = data_copy.data
2136            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2137            gn = theory_data.data
2138            if weight == None:
2139                en = data_copy.err_data
2140            else:
2141                en = weight
2142            residuals.data = (fn - gn) / en
2143            residuals.qx_data = data_copy.qx_data
2144            residuals.qy_data = data_copy.qy_data
2145            residuals.q_data = data_copy.q_data
2146            residuals.err_data = numpy.ones(len(residuals.data))
2147            residuals.xmin = min(residuals.qx_data)
2148            residuals.xmax = max(residuals.qx_data)
2149            residuals.ymin = min(residuals.qy_data)
2150            residuals.ymax = max(residuals.qy_data)
2151            residuals.q_data = data_copy.q_data
2152            residuals.mask = data_copy.mask
2153            residuals.scale = 'linear'
2154            # check the lengths
2155            if len(residuals.data) != len(residuals.q_data):
2156                return
2157        else:
2158            # 1 d theory from model_thread is only in the range of index
2159            if data_copy.dy == None or data_copy.dy == []:
2160                dy = numpy.ones(len(data_copy.y))
2161            else:
2162                if weight == None:
2163                    dy = numpy.ones(len(data_copy.y))
2164                ## Set consitently w/AbstractFitengine:
2165                ## But this should be corrected later.
2166                else:
2167                    dy = weight
2168                dy[dy == 0] = 1
2169            fn = data_copy.y[index]
2170            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2171            gn = theory_data.y
2172            en = dy[index]
2173            # build residuals
2174            residuals = Data1D()
2175            try:
2176                residuals.y = (fn - gn) / en
2177            except:
2178                msg = "ResidualPlot Error: different # of data points in theory"
2179                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
2180                residuals.y = (fn - gn[index]) / en
2181            residuals.x = data_copy.x[index]
2182            residuals.dy = numpy.ones(len(residuals.y))
2183            residuals.dx = None
2184            residuals.dxl = None
2185            residuals.dxw = None
2186            residuals.ytransform = 'y'
2187            # For latter scale changes
2188            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
2189            residuals.yaxis('\\rm{Residuals} ', 'normalized')
2190        theory_name = str(theory_data.name.split()[0])
2191        new_plot = residuals
2192        new_plot.name = "Residuals for " + str(theory_name) + "[" +\
2193                        str(data.name) +"]"
2194        ## allow to highlight data when plotted
2195        new_plot.interactive = True
2196        ## when 2 data have the same id override the 1 st plotted
2197        new_plot.id = "res" + str(data_copy.id) + str(theory_name)
2198        ##group_id specify on which panel to plot this data
2199        group_id = self.page_finder[page_id].get_graph_id()
2200        if group_id == None:
2201            group_id = data.group_id
2202        new_plot.group_id = "res" + str(group_id)
2203        #new_plot.is_data = True
2204        ##post data to plot
2205        title = new_plot.name
2206        self.page_finder[page_id].set_residuals(residuals=new_plot,
2207                                                fid=data.id)
2208        self.parent.update_theory(data_id=data.id, theory=new_plot)
2209        batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
2210        if not batch_on:
2211            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
Note: See TracBrowser for help on using the repository browser.