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

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 5156918 was 5156918, checked in by wojciech, 7 years ago

Merge branch 'master' of https://github.com/SasView/sasview into ticket-854

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