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

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 f0a97ec5 was f0a97ec5, checked in by smk78, 8 years ago

Adding

wx.EVT_MENU(owner, wx_id, self.get_python_panel)

breaks the build. Why?

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