source: sasview/src/sas/sasgui/perspectives/fitting/fitting.py @ 1a8e13f0

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 1a8e13f0 was 1a8e13f0, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

remove unnecessary sleep

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