source: sasview/src/sas/sasgui/perspectives/fitting/fitting.py @ 06dfd39

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 06dfd39 was aac161f1, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 9 years ago

Ticket 411: added stop button to simultaneous fits.

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