source: sasview/src/sas/perspectives/fitting/fitting.py @ 600bea1

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 600bea1 was 7945367, checked in by Paul Kienzle <pkienzle@…>, 9 years ago

make Fit Options dialog persistent and add Help button [NB: awaiting new bumps release]

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