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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 20f07dbc was 20f07dbc, checked in by wojciech, 7 years ago

src/sas/sasgui/perspectives/fitting/fitting.py

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