source: sasview/src/sas/perspectives/fitting/fitting.py @ acf8e4a5

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

reference BumpsFit? directly and remove fit engine selection layer

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