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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 9c3d784 was 9c3d784, checked in by andyfaff, 7 years ago

MAINT: using print function

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