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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 0733da4 was 0733da4, checked in by smk78, 8 years ago

Added "Python Shell/Editor?" text to menu drop-down Fitting > Edit Custom
Model. Now need to invoke the functionality. Refs #749

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