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

Last change on this file since f78c7d2 was f78c7d2, checked in by wojciech, 7 years ago

OpenCL dialog prototype

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