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

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 1dbdb83 was e89aed5, checked in by krzywon, 7 years ago

#189: Can now open the simultaneous and constrained fit panel through a save state, but the constraint loading is still broken.

  • Property mode set to 100755
File size: 87.7 KB
Line 
1"""
2    Fitting perspective
3"""
4################################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#
9#See the license text in license.txt
10#
11#copyright 2009, University of Tennessee
12################################################################################
13import re
14import sys
15import os
16import wx
17import logging
18import numpy
19import time
20from copy import deepcopy
21import traceback
22
23from sas.sascalc.dataloader.loader import Loader
24from sas.sasgui.guiframe.dataFitting import Data2D
25from sas.sasgui.guiframe.dataFitting import Data1D
26from sas.sasgui.guiframe.dataFitting import check_data_validity
27from sas.sasgui.guiframe.events import NewPlotEvent
28from sas.sasgui.guiframe.events import StatusEvent
29from sas.sasgui.guiframe.events import EVT_SLICER_PANEL
30from sas.sasgui.guiframe.events import EVT_SLICER_PARS_UPDATE
31from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
32from sas.sasgui.guiframe.plugin_base import PluginBase
33from sas.sasgui.guiframe.data_processor import BatchCell
34from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
35from sas.sasgui.perspectives.fitting.console import ConsoleUpdate
36from sas.sasgui.perspectives.fitting.fitproblem import FitProblemDictionary
37from sas.sasgui.perspectives.fitting.fitpanel import FitPanel
38from sas.sasgui.perspectives.fitting.resultpanel import ResultPanel, PlotResultEvent
39
40from sas.sasgui.perspectives.fitting.fit_thread import FitThread
41from sas.sasgui.perspectives.fitting.pagestate import Reader
42from sas.sasgui.perspectives.fitting.fitpage import Chi2UpdateEvent
43from sas.sasgui.perspectives.calculator.model_editor import TextDialog
44from sas.sasgui.perspectives.calculator.model_editor import EditorWindow
45from sas.sasgui.guiframe.gui_manager import MDIFrame
46from sas.sasgui.guiframe.documentation_window import DocumentationWindow
47
48from . import models
49
50MAX_NBR_DATA = 4
51
52(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
53
54
55if sys.platform == "win32":
56    ON_MAC = False
57else:
58    ON_MAC = True
59
60import bumps.options
61from bumps.gui.fit_dialog import show_fit_config
62try:
63    from bumps.gui.fit_dialog import EVT_FITTER_CHANGED
64except ImportError:
65    # CRUFT: bumps 0.7.5.8 and below
66    EVT_FITTER_CHANGED = None  # type: wx.PyCommandEvent
67
68class Plugin(PluginBase):
69    """
70    Fitting plugin is used to perform fit
71    """
72    def __init__(self):
73        PluginBase.__init__(self, name="Fitting")
74
75        #list of panel to send to guiframe
76        self.mypanels = []
77        # reference to the current running thread
78        self.calc_2D = None
79        self.calc_1D = None
80
81        self.color_dict = {}
82
83        self.fit_thread_list = {}
84        self.residuals = None
85        self.weight = None
86        self.fit_panel = None
87        self.plot_panel = None
88        # Start with a good default
89        self.elapsed = 0.022
90        self.fit_panel = None
91        ## dictionary of page closed and id
92        self.closed_page_dict = {}
93        ## Relative error desired in the sum of squares (float)
94        self.batch_reset_flag = True
95        #List of selected data
96        self.selected_data_list = []
97        ## list of slicer panel created to display slicer parameters and results
98        self.slicer_panels = []
99        # model 2D view
100        self.model2D_id = None
101        #keep reference of the simultaneous fit page
102        self.sim_page = None
103        self.sim_menu = None
104        self.batch_page = None
105        self.batch_menu = None
106        self.index_model = 0
107        self.test_model_color = None
108        #Create a reader for fit page's state
109        self.state_reader = None
110        self._extensions = '.fitv'
111        self.menu1 = None
112        self.new_model_frame = None
113
114        self.temp_state = []
115        self.state_index = 0
116        self.sfile_ext = None
117        # take care of saving  data, model and page associated with each other
118        self.page_finder = {}
119        # Log startup
120        logging.info("Fitting plug-in started")
121        self.batch_capable = self.get_batch_capable()
122
123    def get_batch_capable(self):
124        """
125        Check if the plugin has a batch capability
126        """
127        return True
128
129    def create_fit_problem(self, page_id):
130        """
131        Given an ID create a fitproblem container
132        """
133        self.page_finder[page_id] = FitProblemDictionary()
134
135    def delete_fit_problem(self, page_id):
136        """
137        Given an ID create a fitproblem container
138        """
139        if page_id in self.page_finder.iterkeys():
140            del self.page_finder[page_id]
141
142    def add_color(self, color, id):
143        """
144        adds a color as a key with a plot id as its value to a dictionary
145        """
146        self.color_dict[id] = color
147
148    def on_batch_selection(self, flag):
149        """
150        switch the the notebook of batch mode or not
151        """
152        self.batch_on = flag
153        if self.fit_panel is not None:
154            self.fit_panel.batch_on = self.batch_on
155
156    def populate_menu(self, owner):
157        """
158        Create a menu for the Fitting plug-in
159
160        :param id: id to create a menu
161        :param owner: owner of menu
162
163        :return: list of information to populate the main menu
164
165        """
166        #Menu for fitting
167        self.menu1 = wx.Menu()
168        id1 = wx.NewId()
169        simul_help = "Add new fit panel"
170        self.menu1.Append(id1, '&New Fit Page', simul_help)
171        wx.EVT_MENU(owner, id1, self.on_add_new_page)
172        self.menu1.AppendSeparator()
173        self.id_simfit = wx.NewId()
174        simul_help = "Constrained or Simultaneous Fit"
175        self.menu1.Append(self.id_simfit, '&Constrained or Simultaneous Fit', simul_help)
176        wx.EVT_MENU(owner, self.id_simfit, self.on_add_sim_page)
177        self.sim_menu = self.menu1.FindItemById(self.id_simfit)
178        self.sim_menu.Enable(False)
179        #combined Batch
180        self.id_batchfit = wx.NewId()
181        batch_help = "Combined Batch"
182        self.menu1.Append(self.id_batchfit, '&Combine Batch Fit', batch_help)
183        wx.EVT_MENU(owner, self.id_batchfit, self.on_add_sim_page)
184        self.batch_menu = self.menu1.FindItemById(self.id_batchfit)
185        self.batch_menu.Enable(False)
186
187        self.menu1.AppendSeparator()
188        self.id_bumps_options = wx.NewId()
189        bopts_help = "Fitting options"
190        self.menu1.Append(self.id_bumps_options, 'Fit &Options', bopts_help)
191        wx.EVT_MENU(owner, self.id_bumps_options, self.on_bumps_options)
192        self.bumps_options_menu = self.menu1.FindItemById(self.id_bumps_options)
193        self.bumps_options_menu.Enable(True)
194
195        self.id_result_panel = wx.NewId()
196        self.menu1.Append(self.id_result_panel, "Fit Results", "Show fit results panel")
197        wx.EVT_MENU(owner, self.id_result_panel, self.on_fit_results)
198        self.menu1.AppendSeparator()
199
200        self.id_reset_flag = wx.NewId()
201        resetf_help = "BatchFit: If checked, the initial param values will be "
202        resetf_help += "propagated from the previous results. "
203        resetf_help += "Otherwise, the same initial param values will be used "
204        resetf_help += "for all fittings."
205        self.menu1.AppendCheckItem(self.id_reset_flag,
206                                   "Chain Fitting [BatchFit Only]",
207                                   resetf_help)
208        wx.EVT_MENU(owner, self.id_reset_flag, self.on_reset_batch_flag)
209        chain_menu = self.menu1.FindItemById(self.id_reset_flag)
210        chain_menu.Check(not self.batch_reset_flag)
211        chain_menu.Enable(self.batch_on)
212
213        self.menu1.AppendSeparator()
214        self.edit_model_menu = wx.Menu()
215        # Find and put files name in menu
216        try:
217            self.set_edit_menu(owner=owner)
218        except:
219            raise
220
221        self.id_edit = wx.NewId()
222        editmodel_help = "Edit customized model sample file"
223        self.menu1.AppendMenu(self.id_edit, "Edit Custom Model",
224                              self.edit_model_menu, editmodel_help)
225        #create  menubar items
226        return [(self.menu1, self.sub_menu)]
227
228    def edit_custom_model(self, event):
229        """
230        Get the python editor panel
231        """
232        event_id = event.GetId()
233        label = self.edit_menu.GetLabel(event_id)
234        from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
235        filename = os.path.join(models.find_plugins_dir(), label)
236        frame = PyConsole(parent=self.parent, manager=self,
237                          panel=self.fit_panel,
238                          title='Advanced Custom Model Editor',
239                          filename=filename)
240        self.put_icon(frame)
241        frame.Show(True)
242
243    def delete_custom_model(self, event):
244        """
245        Delete custom model file
246        """
247        event_id = event.GetId()
248        label = self.delete_menu.GetLabel(event_id)
249        toks = os.path.splitext(label)
250        path = os.path.join(models.find_plugins_dir(), toks[0])
251        try:
252            for ext in ['.py', '.pyc']:
253                p_path = path + ext
254                os.remove(p_path)
255            self.update_custom_combo()
256            if os.path.isfile(p_path):
257                msg = "Sorry! Could not be able to delete the default "
258                msg += "custom model... \n"
259                msg += "Please manually remove the files (.py, .pyc) "
260                msg += "in the 'plugin_models' folder \n"
261                msg += "inside of the SasView application, "
262                msg += "and try it again."
263                wx.MessageBox(msg, 'Info')
264                #evt = StatusEvent(status=msg, type='stop', info='warning')
265                #wx.PostEvent(self.parent, evt)
266            else:
267                self.delete_menu.Delete(event_id)
268                for item in self.edit_menu.GetMenuItems():
269                    if item.GetLabel() == label:
270                        self.edit_menu.DeleteItem(item)
271                        msg = "The custom model, %s, has been deleted." % label
272                        evt = StatusEvent(status=msg, type='stop', info='info')
273                        wx.PostEvent(self.parent, evt)
274                        break
275        except Exception:
276            import traceback; traceback.print_exc()
277            msg = 'Delete Error: \nCould not delete the file; Check if in use.'
278            wx.MessageBox(msg, 'Error')
279
280    def make_sum_model(self, event):
281        """
282        Edit summodel template and make one
283        """
284        event_id = event.GetId()
285        model_manager = models.ModelManager()
286        model_list = model_manager.get_model_name_list()
287        plug_dir = models.find_plugins_dir()
288        textdial = TextDialog(None, self, wx.ID_ANY, 'Easy Sum/Multi(p1, p2) Editor',
289                              model_list, plug_dir)
290        self.put_icon(textdial)
291        textdial.ShowModal()
292        textdial.Destroy()
293
294    def make_new_model(self, event):
295        """
296        Make new model
297        """
298        if self.new_model_frame != None:
299            self.new_model_frame.Show(False)
300            self.new_model_frame.Show(True)
301        else:
302            event_id = event.GetId()
303            dir_path = models.find_plugins_dir()
304            title = "New Custom Model Function"
305            self.new_model_frame = EditorWindow(parent=self, base=self,
306                                                path=dir_path, title=title)
307            self.put_icon(self.new_model_frame)
308        self.new_model_frame.Show(True)
309
310    def load_plugin_models(self, event):
311        """
312        Update of models in plugin_models folder
313        """
314        event_id = event.GetId()
315        self.update_custom_combo()
316
317    def update_custom_combo(self):
318        """
319        Update custom model list in the fitpage combo box
320        """
321        custom_model = 'Customized Models'
322        try:
323            # Update edit menus
324            self.set_edit_menu_helper(self.parent, self.edit_custom_model)
325            self.set_edit_menu_helper(self.parent, self.delete_custom_model)
326            temp = self.fit_panel.reset_pmodel_list()
327            if temp:
328                # Set the new custom model list for all fit pages
329                for uid, page in self.fit_panel.opened_pages.iteritems():
330                    if hasattr(page, "formfactorbox"):
331                        page.model_list_box = temp
332                        current_val = page.formfactorbox.GetLabel()
333                        #if page.plugin_rbutton.GetValue():
334                        mod_cat = page.categorybox.GetStringSelection()
335                        if mod_cat == custom_model:
336                            #pos = page.formfactorbox.GetSelection()
337                            page._show_combox_helper()
338                            new_val = page.formfactorbox.GetLabel()
339                            if current_val != new_val and new_val != '':
340                                page.formfactorbox.SetLabel(new_val)
341                            else:
342                                page.formfactorbox.SetLabel(current_val)
343        except:
344            logging.error("update_custom_combo: %s", sys.exc_value)
345
346    def set_edit_menu(self, owner):
347        """
348        Set list of the edit model menu labels
349        """
350        wx_id = wx.NewId()
351        #new_model_menu = wx.Menu()
352        self.edit_model_menu.Append(wx_id, 'New',
353                                   'Add a new model function')
354        wx.EVT_MENU(owner, wx_id, self.make_new_model)
355       
356        wx_id = wx.NewId()
357        self.edit_model_menu.Append(wx_id, 'Sum|Multi(p1, p2)',
358                                    'Sum of two model functions')
359        wx.EVT_MENU(owner, wx_id, self.make_sum_model)
360
361        e_id = wx.NewId()
362        self.edit_menu = wx.Menu()
363        self.edit_model_menu.AppendMenu(e_id,
364                                    'Advanced', self.edit_menu)
365        self.set_edit_menu_helper(owner, self.edit_custom_model)
366
367        d_id = wx.NewId()
368        self.delete_menu = wx.Menu()
369        self.edit_model_menu.AppendMenu(d_id,
370                                        'Delete', self.delete_menu)
371        self.set_edit_menu_helper(owner, self.delete_custom_model)
372
373        wx_id = wx.NewId()
374        self.edit_model_menu.Append(wx_id, 'Load Models',
375          '(Re)Load all models present in user plugin_models folder')
376        wx.EVT_MENU(owner, wx_id, self.load_plugin_models)
377
378    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
868    def _mac_sleep(self, sec=0.2):
869        """
870        Give sleep to MAC
871        """
872        if ON_MAC:
873            time.sleep(sec)
874
875    def draw_model(self, model, page_id, data=None, smearer=None,
876                   enable1D=True, enable2D=False,
877                   state=None,
878                   fid=None,
879                   toggle_mode_on=False,
880                   qmin=None, qmax=None,
881                   update_chisqr=True, weight=None, source='model'):
882        """
883        Draw model.
884
885        :param model: the model to draw
886        :param name: the name of the model to draw
887        :param data: the data on which the model is based to be drawn
888        :param description: model's description
889        :param enable1D: if true enable drawing model 1D
890        :param enable2D: if true enable drawing model 2D
891        :param qmin:  Range's minimum value to draw model
892        :param qmax:  Range's maximum value to draw model
893        :param qstep: number of step to divide the x and y-axis
894        :param update_chisqr: update chisqr [bool]
895
896        """
897        #self.weight = weight
898        if issubclass(data.__class__, Data1D) or not enable2D:
899            ## draw model 1D with no loaded data
900            self._draw_model1D(model=model,
901                               data=data,
902                               page_id=page_id,
903                               enable1D=enable1D,
904                               smearer=smearer,
905                               qmin=qmin,
906                               qmax=qmax,
907                               fid=fid,
908                               weight=weight,
909                               toggle_mode_on=toggle_mode_on,
910                               state=state,
911                               update_chisqr=update_chisqr,
912                               source=source)
913        else:
914            ## draw model 2D with no initial data
915            self._draw_model2D(model=model,
916                                page_id=page_id,
917                                data=data,
918                                enable2D=enable2D,
919                                smearer=smearer,
920                                qmin=qmin,
921                                qmax=qmax,
922                                fid=fid,
923                                weight=weight,
924                                state=state,
925                                toggle_mode_on=toggle_mode_on,
926                                update_chisqr=update_chisqr,
927                                source=source)
928
929    def onFit(self, uid):
930        """
931        Get series of data, model, associates parameters and range and send then
932        to  series of fitters. Fit data and model, display result to
933        corresponding panels.
934        :param uid: id related to the panel currently calling this fit function.
935        """
936        if uid is None: raise RuntimeError("no page to fit") # Should never happen
937
938        sim_page_uid = getattr(self.sim_page, 'uid', None)
939        batch_page_uid = getattr(self.batch_page, 'uid', None)
940
941        if uid == sim_page_uid:
942            fit_type = 'simultaneous'
943        elif uid == batch_page_uid:
944            fit_type = 'combined_batch'
945        else:
946            fit_type = 'single'
947
948        fitter_list = []
949        sim_fitter = None
950        if fit_type == 'simultaneous':
951            # for simultaneous fitting only one fitter is needed
952            sim_fitter = Fit()
953            sim_fitter.fitter_id = self.sim_page.uid
954            fitter_list.append(sim_fitter)
955
956        self.current_pg = None
957        list_page_id = []
958        fit_id = 0
959        for page_id, page_info in self.page_finder.iteritems():
960            # For simulfit (uid give with None), do for-loop
961            # if uid is specified (singlefit), do it only on the page.
962            if page_id in (sim_page_uid, batch_page_uid): continue
963            if fit_type == "single" and page_id != uid: continue
964
965            try:
966                if page_info.get_scheduled() == 1:
967                    page_info.nbr_residuals_computed = 0
968                    page = self.fit_panel.get_page_by_id(page_id)
969                    self.set_fit_weight(uid=page.uid,
970                                     flag=page.get_weight_flag(),
971                                     is2d=page._is_2D())
972                    if not page.param_toFit:
973                        msg = "No fitting parameters for %s" % page.window_caption
974                        evt = StatusEvent(status=msg, info="error", type="stop")
975                        wx.PostEvent(page.parent.parent, evt)
976                        return False
977                    if not page._update_paramv_on_fit():
978                        msg = "Fitting range or parameter values are"
979                        msg += " invalid in %s" % \
980                                    page.window_caption
981                        evt = StatusEvent(status=msg, info="error", type="stop")
982                        wx.PostEvent(page.parent.parent, evt)
983                        return False
984
985                    pars = [str(element[1]) for element in page.param_toFit]
986                    fitproblem_list = page_info.values()
987                    for fitproblem in  fitproblem_list:
988                        if sim_fitter is None:
989                            fitter = Fit()
990                            fitter.fitter_id = page_id
991                            fitter_list.append(fitter)
992                        else:
993                            fitter = sim_fitter
994                        self._add_problem_to_fit(fitproblem=fitproblem,
995                                             pars=pars,
996                                             fitter=fitter,
997                                             fit_id=fit_id)
998                        fit_id += 1
999                    list_page_id.append(page_id)
1000                    page_info.clear_model_param()
1001            except KeyboardInterrupt:
1002                msg = "Fitting terminated"
1003                evt = StatusEvent(status=msg, info="info", type="stop")
1004                wx.PostEvent(self.parent, evt)
1005                return True
1006            except:
1007                raise
1008                msg = "Fitting error: %s" % str(sys.exc_value)
1009                evt = StatusEvent(status=msg, info="error", type="stop")
1010                wx.PostEvent(self.parent, evt)
1011                return False
1012        ## If a thread is already started, stop it
1013        #if self.calc_fit!= None and self.calc_fit.isrunning():
1014        #    self.calc_fit.stop()
1015        msg = "Fitting is in progress..."
1016        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1017
1018        #Handler used to display fit message
1019        handler = ConsoleUpdate(parent=self.parent,
1020                                manager=self,
1021                                improvement_delta=0.1)
1022        self._mac_sleep(0.2)
1023
1024        # batch fit
1025        batch_inputs = {}
1026        batch_outputs = {}
1027        if fit_type == "simultaneous":
1028            page = self.sim_page
1029        elif fit_type == "combined_batch":
1030            page = self.batch_page
1031        else:
1032            page = self.fit_panel.get_page_by_id(uid)
1033        if page.batch_on:
1034            calc_fit = FitThread(handler=handler,
1035                                 fn=fitter_list,
1036                                 pars=pars,
1037                                 batch_inputs=batch_inputs,
1038                                 batch_outputs=batch_outputs,
1039                                 page_id=list_page_id,
1040                                 completefn=self._batch_fit_complete,
1041                                 reset_flag=self.batch_reset_flag)
1042        else:
1043            ## Perform more than 1 fit at the time
1044            calc_fit = FitThread(handler=handler,
1045                                    fn=fitter_list,
1046                                    batch_inputs=batch_inputs,
1047                                    batch_outputs=batch_outputs,
1048                                    page_id=list_page_id,
1049                                    updatefn=handler.update_fit,
1050                                    completefn=self._fit_completed)
1051        #self.fit_thread_list[current_page_id] = calc_fit
1052        self.fit_thread_list[uid] = calc_fit
1053        calc_fit.queue()
1054        calc_fit.ready(2.5)
1055        msg = "Fitting is in progress..."
1056        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1057
1058        return True
1059
1060    def remove_plot(self, uid, fid=None, theory=False):
1061        """
1062        remove model plot when a fit page is closed
1063        :param uid: the id related to the fitpage to close
1064        :param fid: the id of the fitproblem(data, model, range,etc)
1065        """
1066        if uid not in self.page_finder.keys():
1067            return
1068        fitproblemList = self.page_finder[uid].get_fit_problem(fid)
1069        for fitproblem in fitproblemList:
1070            data = fitproblem.get_fit_data()
1071            model = fitproblem.get_model()
1072            plot_id = None
1073            if model is not None:
1074                plot_id = data.id + model.name
1075            if theory:
1076                plot_id = data.id + model.name
1077            group_id = data.group_id
1078            wx.PostEvent(self.parent, NewPlotEvent(id=plot_id,
1079                                                   group_id=group_id,
1080                                                   action='remove'))
1081
1082    def store_data(self, uid, data_list=None, caption=None):
1083        """
1084        Recieve a list of data and store them ans well as a caption of
1085        the fit page where they come from.
1086        :param uid: if related to a fit page
1087        :param data_list: list of data to fit
1088        :param caption: caption of the window related to these data
1089        """
1090        if data_list is None:
1091            data_list = []
1092
1093        self.page_finder[uid].set_fit_data(data=data_list)
1094        if caption is not None:
1095            self.page_finder[uid].set_fit_tab_caption(caption=caption)
1096
1097    def on_add_new_page(self, event=None):
1098        """
1099        ask fit panel to create a new empty page
1100        """
1101        try:
1102            page = self.fit_panel.add_empty_page()
1103            # add data associated to the page created
1104            if page != None:
1105                evt = StatusEvent(status="Page Created", info="info")
1106                wx.PostEvent(self.parent, evt)
1107            else:
1108                msg = "Page was already Created"
1109                evt = StatusEvent(status=msg, info="warning")
1110                wx.PostEvent(self.parent, evt)
1111        except:
1112            msg = "Creating Fit page: %s" % sys.exc_value
1113            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
1114
1115    def add_fit_page(self, data):
1116        """
1117        given a data, ask to the fitting panel to create a new fitting page,
1118        get this page and store it into the page_finder of this plug-in
1119        :param data: is a list of data
1120        """
1121        page = self.fit_panel.set_data(data)
1122        # page could be None when loading state files
1123        if page == None:
1124            return page
1125        #append Data1D to the panel containing its theory
1126        #if theory already plotted
1127        if page.uid in self.page_finder:
1128            data = page.get_data()
1129            theory_data = self.page_finder[page.uid].get_theory_data(data.id)
1130            if issubclass(data.__class__, Data2D):
1131                data.group_id = wx.NewId()
1132                if theory_data is not None:
1133                    group_id = str(page.uid) + " Model1D"
1134                    wx.PostEvent(self.parent,
1135                             NewPlotEvent(group_id=group_id,
1136                                               action="delete"))
1137                    self.parent.update_data(prev_data=theory_data,
1138                                             new_data=data)
1139            else:
1140                if theory_data is not None:
1141                    group_id = str(page.uid) + " Model2D"
1142                    data.group_id = theory_data.group_id
1143                    wx.PostEvent(self.parent,
1144                             NewPlotEvent(group_id=group_id,
1145                                               action="delete"))
1146                    self.parent.update_data(prev_data=theory_data,
1147                                             new_data=data)
1148        self.store_data(uid=page.uid, data_list=page.get_data_list(),
1149                        caption=page.window_caption)
1150        if self.sim_page is not None and not self.batch_on:
1151            self.sim_page.draw_page()
1152        if self.batch_page is not None and self.batch_on:
1153            self.batch_page.draw_page()
1154
1155        return page
1156
1157    def _onEVT_SLICER_PANEL(self, event):
1158        """
1159        receive and event telling to update a panel with a name starting with
1160        event.panel_name. this method update slicer panel
1161        for a given interactor.
1162
1163        :param event: contains type of slicer , paramaters for updating
1164            the panel and panel_name to find the slicer 's panel concerned.
1165        """
1166        event.panel_name
1167        for item in self.parent.panels:
1168            name = event.panel_name
1169            if self.parent.panels[item].window_caption.startswith(name):
1170                self.parent.panels[item].set_slicer(event.type, event.params)
1171
1172        #self.parent._mgr.Update()
1173
1174    def _closed_fitpage(self, event):
1175        """
1176        request fitpanel to close a given page when its unique data is removed
1177        from the plot. close fitpage only when the a loaded data is removed
1178        """
1179        if event is None or event.data is None:
1180            return
1181        if hasattr(event.data, "is_data"):
1182            if not event.data.is_data or \
1183                event.data.__class__.__name__ == "Data1D":
1184                self.fit_panel.close_page_with_data(event.data)
1185
1186    def _reset_schedule_problem(self, value=0, uid=None):
1187        """
1188        unschedule or schedule all fitproblem to be fit
1189        """
1190        # case that uid is not specified
1191        if uid == None:
1192            for page_id in self.page_finder.keys():
1193                self.page_finder[page_id].schedule_tofit(value)
1194        # when uid is given
1195        else:
1196            if uid in self.page_finder.keys():
1197                self.page_finder[uid].schedule_tofit(value)
1198
1199    def _add_problem_to_fit(self, fitproblem, pars, fitter, fit_id):
1200        """
1201        Create and set fitter with series of data and model
1202        """
1203        data = fitproblem.get_fit_data()
1204        model = fitproblem.get_model()
1205        smearer = fitproblem.get_smearer()
1206        qmin, qmax = fitproblem.get_range()
1207
1208        #Extra list of parameters and their constraints
1209        listOfConstraint = []
1210        param = fitproblem.get_model_param()
1211        if len(param) > 0:
1212            for item in param:
1213                ## check if constraint
1214                if item[0] != None and item[1] != None:
1215                    listOfConstraint.append((item[0], item[1]))
1216        new_model = model
1217        fitter.set_model(new_model, fit_id, pars, data=data,
1218                         constraints=listOfConstraint)
1219        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin,
1220                        qmax=qmax)
1221        fitter.select_problem_for_fit(id=fit_id, value=1)
1222
1223    def _onSelect(self, event):
1224        """
1225        when Select data to fit a new page is created .Its reference is
1226        added to self.page_finder
1227        """
1228        panel = self.plot_panel
1229        if panel == None:
1230            raise ValueError, "Fitting:_onSelect: NonType panel"
1231        Plugin.on_perspective(self, event=event)
1232        self.select_data(panel)
1233
1234    def select_data(self, panel):
1235        """
1236        """
1237        for plottable in panel.graph.plottables:
1238            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
1239                data_id = panel.graph.selected_plottable
1240                if plottable == panel.plots[data_id]:
1241                    data = plottable
1242                    self.add_fit_page(data=[data])
1243                    return
1244            else:
1245                data = plottable
1246                self.add_fit_page(data=[data])
1247
1248    def update_fit(self, result=None, msg=""):
1249        """
1250        """
1251        print "update_fit result", result
1252
1253    def _batch_fit_complete(self, result, pars, page_id,
1254                            batch_outputs, batch_inputs, elapsed=None):
1255        """
1256        Display fit result in batch
1257        :param result: list of objects received from fitters
1258        :param pars: list of  fitted parameters names
1259        :param page_id: list of page ids which called fit function
1260        :param elapsed: time spent at the fitting level
1261        """
1262        self._mac_sleep(0.2)
1263        uid = page_id[0]
1264        if uid in self.fit_thread_list.keys():
1265            del self.fit_thread_list[uid]
1266
1267        wx.CallAfter(self._update_fit_button, page_id)
1268        t1 = time.time()
1269        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1270        msg = "Fit completed on %s \n" % str_time
1271        msg += "Duration time: %s s.\n" % str(elapsed)
1272        evt = StatusEvent(status=msg, info="info", type="stop")
1273        wx.PostEvent(self.parent, evt)
1274
1275        if batch_outputs is None:
1276            batch_outputs = {}
1277
1278        # format batch_outputs
1279        batch_outputs["Chi2"] = []
1280        #Don't like these loops
1281        # Need to create dictionary of all fitted parameters
1282        # since the number of parameters can differ between each fit result
1283        for list_res in result:
1284            for res in list_res:
1285                model, data = res.inputs[0]
1286                if model is not None and hasattr(model, "model"):
1287                    model = model.model
1288                #get all fittable parameters of the current model
1289                for param in  model.getParamList():
1290                    if param  not in batch_outputs.keys():
1291                        batch_outputs[param] = []
1292                for param in model.getDispParamList():
1293                    if not model.is_fittable(param) and \
1294                        param in batch_outputs.keys():
1295                        del batch_outputs[param]
1296                # Add fitted parameters and their error
1297                for param in res.param_list:
1298                    if param not in batch_outputs.keys():
1299                        batch_outputs[param] = []
1300                    err_param = "error on %s" % str(param)
1301                    if err_param not in batch_inputs.keys():
1302                        batch_inputs[err_param] = []
1303        msg = ""
1304        for list_res in result:
1305            for res in list_res:
1306                pid = res.fitter_id
1307                model, data = res.inputs[0]
1308                correct_result = False
1309                if model is not None and hasattr(model, "model"):
1310                    model = model.model
1311                if data is not None and hasattr(data, "sas_data"):
1312                    data = data.sas_data
1313
1314                is_data2d = issubclass(data.__class__, Data2D)
1315                #check consistency of arrays
1316                if not is_data2d:
1317                    if len(res.theory) == len(res.index[res.index]) and \
1318                        len(res.index) == len(data.y):
1319                        correct_result = True
1320                else:
1321                    copy_data = deepcopy(data)
1322                    new_theory = copy_data.data
1323                    new_theory[res.index] = res.theory
1324                    new_theory[res.index == False] = numpy.nan
1325                    correct_result = True
1326                #get all fittable parameters of the current model
1327                param_list = model.getParamList()
1328                for param in model.getDispParamList():
1329                    if not model.is_fittable(param) and \
1330                        param in param_list:
1331                        param_list.remove(param)
1332                if not correct_result or res.fitness is None or \
1333                    not numpy.isfinite(res.fitness) or \
1334                    numpy.any(res.pvec == None) or not \
1335                    numpy.all(numpy.isfinite(res.pvec)):
1336                    data_name = str(None)
1337                    if data is not None:
1338                        data_name = str(data.name)
1339                    model_name = str(None)
1340                    if model is not None:
1341                        model_name = str(model.name)
1342                    msg += "Data %s and Model %s did not fit.\n" % (data_name,
1343                                                                    model_name)
1344                    ERROR = numpy.NAN
1345                    cell = BatchCell()
1346                    cell.label = res.fitness
1347                    cell.value = res.fitness
1348                    batch_outputs["Chi2"].append(ERROR)
1349                    for param in param_list:
1350                        # save value of  fixed parameters
1351                        if param not in res.param_list:
1352                            batch_outputs[str(param)].append(ERROR)
1353                        else:
1354                            #save only fitted values
1355                            batch_outputs[param].append(ERROR)
1356                            batch_inputs["error on %s" % str(param)].append(ERROR)
1357                else:
1358                    # TODO: Why sometimes res.pvec comes with numpy.float64?
1359                    # probably from scipy lmfit
1360                    if res.pvec.__class__ == numpy.float64:
1361                        res.pvec = [res.pvec]
1362
1363                    cell = BatchCell()
1364                    cell.label = res.fitness
1365                    cell.value = res.fitness
1366                    batch_outputs["Chi2"].append(cell)
1367                    # add parameters to batch_results
1368                    for param in param_list:
1369                        # save value of  fixed parameters
1370                        if param not in res.param_list:
1371                            batch_outputs[str(param)].append(model.getParam(param))
1372                        else:
1373                            index = res.param_list.index(param)
1374                            #save only fitted values
1375                            batch_outputs[param].append(res.pvec[index])
1376                            if res.stderr is not None and \
1377                                len(res.stderr) == len(res.param_list):
1378                                item = res.stderr[index]
1379                                batch_inputs["error on %s" % param].append(item)
1380                            else:
1381                                batch_inputs["error on %s" % param].append('-')
1382                            model.setParam(param, res.pvec[index])
1383                #fill the batch result with emtpy value if not in the current
1384                #model
1385                EMPTY = "-"
1386                for key in batch_outputs.keys():
1387                    if key not in param_list and key not in ["Chi2", "Data"]:
1388                        batch_outputs[key].append(EMPTY)
1389
1390                self.page_finder[pid].set_batch_result(batch_inputs=batch_inputs,
1391                                                       batch_outputs=batch_outputs)
1392
1393                cpage = self.fit_panel.get_page_by_id(pid)
1394                cpage._on_fit_complete()
1395                self.page_finder[pid][data.id].set_result(res)
1396                fitproblem = self.page_finder[pid][data.id]
1397                qmin, qmax = fitproblem.get_range()
1398                plot_result = False
1399                if correct_result:
1400                    if not is_data2d:
1401                        self._complete1D(x=data.x[res.index], y=res.theory, page_id=pid,
1402                                         elapsed=None,
1403                                         index=res.index, model=model,
1404                                         weight=None, fid=data.id,
1405                                         toggle_mode_on=False, state=None,
1406                                         data=data, update_chisqr=False,
1407                                         source='fit', plot_result=plot_result)
1408                    else:
1409                        self._complete2D(image=new_theory, data=data,
1410                                         model=model,
1411                                         page_id=pid, elapsed=None,
1412                                         index=res.index,
1413                                         qmin=qmin,
1414                                         qmax=qmax, fid=data.id, weight=None,
1415                                         toggle_mode_on=False, state=None,
1416                                         update_chisqr=False,
1417                                         source='fit', plot_result=plot_result)
1418                self.on_set_batch_result(page_id=pid,
1419                                         fid=data.id,
1420                                         batch_outputs=batch_outputs,
1421                                         batch_inputs=batch_inputs)
1422
1423        evt = StatusEvent(status=msg, error="info", type="stop")
1424        wx.PostEvent(self.parent, evt)
1425        # Remove parameters that are not shown
1426        cpage = self.fit_panel.get_page_by_id(uid)
1427        tbatch_outputs = {}
1428        shownkeystr = cpage.get_copy_params()
1429        for key in batch_outputs.keys():
1430            if key in ["Chi2", "Data"] or shownkeystr.count(key) > 0:
1431                tbatch_outputs[key] = batch_outputs[key]
1432
1433        wx.CallAfter(self.parent.on_set_batch_result, tbatch_outputs,
1434                     batch_inputs, self.sub_menu)
1435
1436    def on_set_batch_result(self, page_id, fid, batch_outputs, batch_inputs):
1437        """
1438        """
1439        pid = page_id
1440        if fid not in self.page_finder[pid]:
1441            return
1442        fitproblem = self.page_finder[pid][fid]
1443        index = self.page_finder[pid].nbr_residuals_computed - 1
1444        residuals = fitproblem.get_residuals()
1445        theory_data = fitproblem.get_theory_data()
1446        data = fitproblem.get_fit_data()
1447        model = fitproblem.get_model()
1448        #fill batch result information
1449        if "Data" not in batch_outputs.keys():
1450            batch_outputs["Data"] = []
1451        from sas.sasgui.guiframe.data_processor import BatchCell
1452        cell = BatchCell()
1453        cell.label = data.name
1454        cell.value = index
1455
1456        if theory_data != None:
1457            #Suucessful fit
1458            theory_data.id = wx.NewId()
1459            theory_data.name = model.name + "[%s]" % str(data.name)
1460            if issubclass(theory_data.__class__, Data2D):
1461                group_id = wx.NewId()
1462                theory_data.group_id = group_id
1463                if group_id not in theory_data.list_group_id:
1464                    theory_data.list_group_id.append(group_id)
1465
1466            try:
1467                # associate residuals plot
1468                if issubclass(residuals.__class__, Data2D):
1469                    group_id = wx.NewId()
1470                    residuals.group_id = group_id
1471                    if group_id not in residuals.list_group_id:
1472                        residuals.list_group_id.append(group_id)
1473                batch_outputs["Chi2"][index].object = [residuals]
1474            except:
1475                pass
1476
1477        cell.object = [data, theory_data]
1478        batch_outputs["Data"].append(cell)
1479        for key, value in data.meta_data.iteritems():
1480            if key not in batch_inputs.keys():
1481                batch_inputs[key] = []
1482            #if key.lower().strip() != "loader":
1483            batch_inputs[key].append(value)
1484        param = "temperature"
1485        if hasattr(data.sample, param):
1486            if param not in  batch_inputs.keys():
1487                batch_inputs[param] = []
1488            batch_inputs[param].append(data.sample.temperature)
1489
1490    def _fit_completed(self, result, page_id, batch_outputs,
1491                       batch_inputs=None, pars=None, elapsed=None):
1492        """
1493        Display result of the fit on related panel(s).
1494        :param result: list of object generated when fit ends
1495        :param pars: list of names of parameters fitted
1496        :param page_id: list of page ids which called fit function
1497        :param elapsed: time spent at the fitting level
1498        """
1499        t1 = time.time()
1500        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1501        msg = "Fit completed on %s \n" % str_time
1502        msg += "Duration time: %s s.\n" % str(elapsed)
1503        evt = StatusEvent(status=msg, info="info", type="stop")
1504        wx.PostEvent(self.parent, evt)
1505        wx.PostEvent(self.result_panel, PlotResultEvent(result=result))
1506        wx.CallAfter(self._update_fit_button, page_id)
1507        result = result[0]
1508        self.fit_thread_list = {}
1509        if page_id is None:
1510            page_id = []
1511        ## fit more than 1 model at the same time
1512        self._mac_sleep(0.2)
1513        try:
1514            index = 0
1515            # Update potential simfit page(s)
1516            if self.sim_page is not None:
1517                self.sim_page._on_fit_complete()
1518            if self.batch_page:
1519                self.batch_page._on_fit_complete()
1520            # Update all fit pages
1521            for uid in page_id:
1522                res = result[index]
1523                if res.fitness is None or \
1524                    not numpy.isfinite(res.fitness) or \
1525                    numpy.any(res.pvec == None) or \
1526                    not numpy.all(numpy.isfinite(res.pvec)):
1527                    msg = "Fitting did not converge!!!"
1528                    evt = StatusEvent(status=msg, info="warning", type="stop")
1529                    wx.PostEvent(self.parent, evt)
1530                    wx.CallAfter(self._update_fit_button, page_id)
1531                else:
1532                    #set the panel when fit result are float not list
1533                    if res.pvec.__class__ == numpy.float64:
1534                        pvec = [res.pvec]
1535                    else:
1536                        pvec = res.pvec
1537                    if res.stderr.__class__ == numpy.float64:
1538                        stderr = [res.stderr]
1539                    else:
1540                        stderr = res.stderr
1541                    cpage = self.fit_panel.get_page_by_id(uid)
1542                    # Make sure we got all results
1543                    #(CallAfter is important to MAC)
1544                    try:
1545                        #if res != None:
1546                        wx.CallAfter(cpage.onsetValues, res.fitness,
1547                                     res.param_list,
1548                                     pvec, stderr)
1549                        index += 1
1550                        wx.CallAfter(cpage._on_fit_complete)
1551                    except KeyboardInterrupt:
1552                        msg = "Singular point: Fitting Stoped."
1553                        evt = StatusEvent(status=msg, info="info", type="stop")
1554                        wx.PostEvent(self.parent, evt)
1555                    except:
1556                        msg = "Singular point: Fitting Error occurred."
1557                        evt = StatusEvent(status=msg, info="error", type="stop")
1558                        wx.PostEvent(self.parent, evt)
1559
1560        except:
1561            msg = ("Fit completed but the following error occurred: %s"
1562                   % sys.exc_value)
1563            #import traceback; msg = "\n".join((traceback.format_exc(), msg))
1564            evt = StatusEvent(status=msg, info="warning", type="stop")
1565            wx.PostEvent(self.parent, evt)
1566
1567    def _update_fit_button(self, page_id):
1568        """
1569        Update Fit button when fit stopped
1570
1571        : parameter page_id: fitpage where the button is
1572        """
1573        if page_id.__class__.__name__ != 'list':
1574            page_id = [page_id]
1575        for uid in page_id:
1576            page = self.fit_panel.get_page_by_id(uid)
1577            page._on_fit_complete()
1578
1579    def _on_show_panel(self, event):
1580        """
1581        """
1582        pass
1583
1584    def on_reset_batch_flag(self, event):
1585        """
1586        Set batch_reset_flag
1587        """
1588        event.Skip()
1589        if self.menu1 == None:
1590            return
1591        menu_item = self.menu1.FindItemById(self.id_reset_flag)
1592        flag = menu_item.IsChecked()
1593        if not flag:
1594            menu_item.Check(False)
1595            self.batch_reset_flag = True
1596        else:
1597            menu_item.Check(True)
1598            self.batch_reset_flag = False
1599
1600        ## post a message to status bar
1601        msg = "Set Chain Fitting: %s" % str(not self.batch_reset_flag)
1602        wx.PostEvent(self.parent, StatusEvent(status=msg))
1603
1604
1605    def _on_slicer_event(self, event):
1606        """
1607        Receive a panel as event and send it to guiframe
1608
1609        :param event: event containing a panel
1610
1611        """
1612        if event.panel is not None:
1613            self.slicer_panels.append(event.panel)
1614            # Set group ID if available
1615            event_id = self.parent.popup_panel(event.panel)
1616            event.panel.uid = event_id
1617            self.mypanels.append(event.panel)
1618
1619    def _onclearslicer(self, event):
1620        """
1621        Clear the boxslicer when close the panel associate with this slicer
1622        """
1623        name = event.GetEventObject().frame.GetTitle()
1624        for panel in self.slicer_panels:
1625            if panel.window_caption == name:
1626
1627                for item in self.parent.panels:
1628                    if hasattr(self.parent.panels[item], "uid"):
1629                        if self.parent.panels[item].uid == panel.base.uid:
1630                            self.parent.panels[item].onClearSlicer(event)
1631                            #self.parent._mgr.Update()
1632                            break
1633                break
1634
1635    def _on_model_panel(self, evt):
1636        """
1637        react to model selection on any combo box or model menu.plot the model
1638
1639        :param evt: wx.combobox event
1640
1641        """
1642        model = evt.model
1643        uid = evt.uid
1644        qmin = evt.qmin
1645        qmax = evt.qmax
1646        caption = evt.caption
1647        enable_smearer = evt.enable_smearer
1648        if model == None:
1649            return
1650        if uid not in self.page_finder.keys():
1651            return
1652        # save the name containing the data name with the appropriate model
1653        self.page_finder[uid].set_model(model)
1654        self.page_finder[uid].enable_smearing(enable_smearer)
1655        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1656        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1657        if self.sim_page is not None and not self.batch_on:
1658            self.sim_page.draw_page()
1659        if self.batch_page is not None and self.batch_on:
1660            self.batch_page.draw_page()
1661
1662    def _update1D(self, x, output):
1663        """
1664        Update the output of plotting model 1D
1665        """
1666        msg = "Plot updating ... "
1667        wx.PostEvent(self.parent, StatusEvent(status=msg, type="update"))
1668
1669    def create_theory_1D(self, x, y, page_id, model, data, state,
1670                         data_description, data_id, dy=None):
1671        """
1672            Create a theory object associate with an existing Data1D
1673            and add it to the data manager.
1674            @param x: x-values of the data
1675            @param y: y_values of the data
1676            @param page_id: fit page ID
1677            @param model: model used for fitting
1678            @param data: Data1D object to create the theory for
1679            @param state: model state
1680            @param data_description: title to use in the data manager
1681            @param data_id: unique data ID
1682        """
1683        new_plot = Data1D(x=x, y=y)
1684        if dy is None:
1685            new_plot.is_data = False
1686            new_plot.dy = numpy.zeros(len(y))
1687            # If this is a theory curve, pick the proper symbol to make it a curve
1688            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1689        else:
1690            new_plot.is_data = True
1691            new_plot.dy = dy
1692        new_plot.interactive = True
1693        new_plot.dx = None
1694        new_plot.dxl = None
1695        new_plot.dxw = None
1696        _yaxis, _yunit = data.get_yaxis()
1697        _xaxis, _xunit = data.get_xaxis()
1698        new_plot.title = data.name
1699        new_plot.group_id = data.group_id
1700        if new_plot.group_id == None:
1701            new_plot.group_id = data.group_id
1702        new_plot.id = data_id
1703        # Find if this theory was already plotted and replace that plot given
1704        # the same id
1705        self.page_finder[page_id].get_theory_data(fid=data.id)
1706
1707        if data.is_data:
1708            data_name = str(data.name)
1709        else:
1710            data_name = str(model.__class__.__name__)
1711
1712        new_plot.name = data_description + " [" + data_name + "]"
1713        new_plot.xaxis(_xaxis, _xunit)
1714        new_plot.yaxis(_yaxis, _yunit)
1715        self.page_finder[page_id].set_theory_data(data=new_plot,
1716                                                  fid=data.id)
1717        self.parent.update_theory(data_id=data.id, theory=new_plot,
1718                                   state=state)
1719        return new_plot
1720
1721    def _complete1D(self, x, y, page_id, elapsed, index, model,
1722                    weight=None, fid=None,
1723                    toggle_mode_on=False, state=None,
1724                    data=None, update_chisqr=True,
1725                    source='model', plot_result=True,
1726                    unsmeared_model=None, unsmeared_data=None,
1727                    unsmeared_error=None, sq_model=None, pq_model=None):
1728        """
1729            Complete plotting 1D data
1730            @param unsmeared_model: fit model, without smearing
1731            @param unsmeared_data: data, rescaled to unsmeared model
1732            @param unsmeared_error: data error, rescaled to unsmeared model
1733        """
1734        try:
1735            numpy.nan_to_num(y)
1736            new_plot = self.create_theory_1D(x, y, page_id, model, data, state,
1737                                             data_description=model.name,
1738                                             data_id=str(page_id) + " " + data.name)
1739            if unsmeared_model is not None:
1740                self.create_theory_1D(x, unsmeared_model, page_id, model, data, state,
1741                                      data_description=model.name + " unsmeared",
1742                                      data_id=str(page_id) + " " + data.name + " unsmeared")
1743
1744                self.create_theory_1D(x, unsmeared_data, page_id, model, data, state,
1745                                      data_description="Data unsmeared",
1746                                      data_id="Data  " + data.name + " unsmeared",
1747                                      dy=unsmeared_error)
1748               
1749            if sq_model is not None and pq_model is not None:
1750                self.create_theory_1D(x, sq_model, page_id, model, data, state,
1751                                      data_description=model.name + " S(q)",
1752                                      data_id=str(page_id) + " " + data.name + " S(q)")
1753                self.create_theory_1D(x, pq_model, page_id, model, data, state,
1754                                      data_description=model.name + " P(q)",
1755                                      data_id=str(page_id) + " " + data.name + " P(q)")
1756
1757
1758            current_pg = self.fit_panel.get_page_by_id(page_id)
1759            title = new_plot.title
1760            batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
1761            if not batch_on:
1762                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1763                                            title=str(title)))
1764            elif plot_result:
1765                top_data_id = self.fit_panel.get_page_by_id(page_id).data.id
1766                if data.id == top_data_id:
1767                    wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1768                                            title=str(title)))
1769            caption = current_pg.window_caption
1770            self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1771
1772            self.page_finder[page_id].set_theory_data(data=new_plot,
1773                                                      fid=data.id)
1774            if toggle_mode_on:
1775                wx.PostEvent(self.parent,
1776                             NewPlotEvent(group_id=str(page_id) + " Model2D",
1777                                          action="Hide"))
1778            else:
1779                if update_chisqr:
1780                    wx.PostEvent(current_pg,
1781                                 Chi2UpdateEvent(output=self._cal_chisqr(
1782                                                                data=data,
1783                                                                fid=fid,
1784                                                                weight=weight,
1785                                                            page_id=page_id,
1786                                                            index=index)))
1787                else:
1788                    self._plot_residuals(page_id=page_id, data=data, fid=fid,
1789                                         index=index, weight=weight)
1790
1791            msg = "Computation  completed!"
1792            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1793        except:
1794            raise
1795
1796    def _calc_exception(self, etype, value, tb):
1797        """
1798        Handle exception from calculator by posting it as an error.
1799        """
1800        logging.error("".join(traceback.format_exception(etype, value, tb)))
1801        msg = traceback.format_exception(etype, value, tb, limit=1)
1802        evt = StatusEvent(status="".join(msg), type="stop", info="error")
1803        wx.PostEvent(self.parent, evt)
1804
1805    def _update2D(self, output, time=None):
1806        """
1807        Update the output of plotting model
1808        """
1809        msg = "Plot updating ... "
1810        wx.PostEvent(self.parent, StatusEvent(msg, type="update"))
1811
1812    def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
1813                qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
1814                     update_chisqr=True, source='model', plot_result=True):
1815        """
1816        Complete get the result of modelthread and create model 2D
1817        that can be plot.
1818        """
1819        numpy.nan_to_num(image)
1820        new_plot = Data2D(image=image, err_image=data.err_data)
1821        new_plot.name = model.name + '2d'
1822        new_plot.title = "Analytical model 2D "
1823        new_plot.id = str(page_id) + " " + data.name
1824        new_plot.group_id = str(page_id) + " Model2D"
1825        new_plot.detector = data.detector
1826        new_plot.source = data.source
1827        new_plot.is_data = False
1828        new_plot.qx_data = data.qx_data
1829        new_plot.qy_data = data.qy_data
1830        new_plot.q_data = data.q_data
1831        new_plot.mask = data.mask
1832        ## plot boundaries
1833        new_plot.ymin = data.ymin
1834        new_plot.ymax = data.ymax
1835        new_plot.xmin = data.xmin
1836        new_plot.xmax = data.xmax
1837        title = data.title
1838
1839        new_plot.is_data = False
1840        if data.is_data:
1841            data_name = str(data.name)
1842        else:
1843            data_name = str(model.__class__.__name__) + '2d'
1844
1845        if len(title) > 1:
1846            new_plot.title = "Model2D for %s " % model.name + data_name
1847        new_plot.name = model.name + " [" + \
1848                                    data_name + "]"
1849        theory_data = deepcopy(new_plot)
1850
1851        self.page_finder[page_id].set_theory_data(data=theory_data,
1852                                                  fid=data.id)
1853        self.parent.update_theory(data_id=data.id,
1854                                       theory=new_plot,
1855                                       state=state)
1856        current_pg = self.fit_panel.get_page_by_id(page_id)
1857        title = new_plot.title
1858        if not source == 'fit' and plot_result:
1859            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1860                                               title=title))
1861        if toggle_mode_on:
1862            wx.PostEvent(self.parent,
1863                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1864                                               action="Hide"))
1865        else:
1866            # Chisqr in fitpage
1867            if update_chisqr:
1868                wx.PostEvent(current_pg,
1869                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1870                                                                    weight=weight,
1871                                                                    fid=fid,
1872                                                         page_id=page_id,
1873                                                         index=index)))
1874            else:
1875                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1876                                      index=index, weight=weight)
1877        msg = "Computation  completed!"
1878        wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1879
1880    def _draw_model2D(self, model, page_id, qmin,
1881                      qmax,
1882                      data=None, smearer=None,
1883                      description=None, enable2D=False,
1884                      state=None,
1885                      fid=None,
1886                      weight=None,
1887                      toggle_mode_on=False,
1888                       update_chisqr=True, source='model'):
1889        """
1890        draw model in 2D
1891
1892        :param model: instance of the model to draw
1893        :param description: the description of the model
1894        :param enable2D: when True allows to draw model 2D
1895        :param qmin: the minimum value to  draw model 2D
1896        :param qmax: the maximum value to draw model 2D
1897        :param qstep: the number of division of Qx and Qy of the model to draw
1898
1899        """
1900        if not enable2D:
1901            return None
1902        try:
1903            from model_thread import Calc2D
1904            ## If a thread is already started, stop it
1905            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1906                self.calc_2D.stop()
1907                ## stop just raises a flag to tell the thread to kill
1908                ## itself -- see the fix in Calc1D implemented to fix
1909                ## an actual problem.  Seems the fix should also go here
1910                ## and may be the cause of other noted instabilities
1911                ##
1912                ##    -PDB August 12, 2014
1913                while self.calc_2D.isrunning():
1914                    time.sleep(0.1)
1915            self.calc_2D = Calc2D(model=model,
1916                                  data=data,
1917                                  page_id=page_id,
1918                                  smearer=smearer,
1919                                  qmin=qmin,
1920                                  qmax=qmax,
1921                                  weight=weight,
1922                                  fid=fid,
1923                                  toggle_mode_on=toggle_mode_on,
1924                                  state=state,
1925                                  completefn=self._complete2D,
1926                                  update_chisqr=update_chisqr,
1927                                  exception_handler=self._calc_exception,
1928                                  source=source)
1929            self.calc_2D.queue()
1930        except:
1931            raise
1932
1933    def _draw_model1D(self, model, page_id, data,
1934                      qmin, qmax, smearer=None,
1935                state=None,
1936                weight=None,
1937                fid=None,
1938                toggle_mode_on=False, update_chisqr=True, source='model',
1939                enable1D=True):
1940        """
1941        Draw model 1D from loaded data1D
1942
1943        :param data: loaded data
1944        :param model: the model to plot
1945
1946        """
1947        if not enable1D:
1948            return
1949        try:
1950            from model_thread import Calc1D
1951            ## If a thread is already started, stop it
1952            if (self.calc_1D is not None) and self.calc_1D.isrunning():
1953                self.calc_1D.stop()
1954                ## stop just raises the flag -- the thread is supposed to
1955                ## then kill itself but cannot.  Paul Kienzle came up with
1956                ## this fix to prevent threads from stepping on each other
1957                ## which was causing a simple custom model to crash Sasview.
1958                ## We still don't know why the fit sometimes lauched a second
1959                ## thread -- something which should also be investigated.
1960                ## The thread approach was implemented in order to be able
1961                ## to lauch a computation in a separate thread from the GUI so
1962                ## that the GUI can still respond to user input including
1963                ## a request to stop the computation.
1964                ## It seems thus that the whole thread approach used here
1965                ## May need rethinking 
1966                ##
1967                ##    -PDB August 12, 2014                 
1968                while self.calc_1D.isrunning():
1969                    time.sleep(0.1)
1970            self.calc_1D = Calc1D(data=data,
1971                                  model=model,
1972                                  page_id=page_id,
1973                                  qmin=qmin,
1974                                  qmax=qmax,
1975                                  smearer=smearer,
1976                                  state=state,
1977                                  weight=weight,
1978                                  fid=fid,
1979                                  toggle_mode_on=toggle_mode_on,
1980                                  completefn=self._complete1D,
1981                                  #updatefn = self._update1D,
1982                                  update_chisqr=update_chisqr,
1983                                  exception_handler=self._calc_exception,
1984                                  source=source)
1985            self.calc_1D.queue()
1986        except:
1987            msg = " Error occurred when drawing %s Model 1D: " % model.name
1988            msg += " %s" % sys.exc_value
1989            wx.PostEvent(self.parent, StatusEvent(status=msg))
1990
1991    def _cal_chisqr(self, page_id, data, weight, fid=None, index=None):
1992        """
1993        Get handy Chisqr using the output from draw1D and 2D,
1994        instead of calling expansive CalcChisqr in guithread
1995        """
1996        try:
1997            data_copy = deepcopy(data)
1998        except:
1999            return
2000        # default chisqr
2001        chisqr = None
2002        #to compute chisq make sure data has valid data
2003        # return None if data == None
2004        if not check_data_validity(data_copy) or data_copy == None:
2005            return chisqr
2006
2007        # Get data: data I, theory I, and data dI in order
2008        if data_copy.__class__.__name__ == "Data2D":
2009            if index == None:
2010                index = numpy.ones(len(data_copy.data), dtype=bool)
2011            if weight != None:
2012                data_copy.err_data = weight
2013            # get rid of zero error points
2014            index = index & (data_copy.err_data != 0)
2015            index = index & (numpy.isfinite(data_copy.data))
2016            fn = data_copy.data[index]
2017            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2018            if theory_data == None:
2019                return chisqr
2020            gn = theory_data.data[index]
2021            en = data_copy.err_data[index]
2022        else:
2023            # 1 d theory from model_thread is only in the range of index
2024            if index == None:
2025                index = numpy.ones(len(data_copy.y), dtype=bool)
2026            if weight != None:
2027                data_copy.dy = weight
2028            if data_copy.dy == None or data_copy.dy == []:
2029                dy = numpy.ones(len(data_copy.y))
2030            else:
2031                ## Set consistently w/AbstractFitengine:
2032                # But this should be corrected later.
2033                dy = deepcopy(data_copy.dy)
2034                dy[dy == 0] = 1
2035            fn = data_copy.y[index]
2036
2037            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2038            if theory_data == None:
2039                return chisqr
2040            gn = theory_data.y
2041            en = dy[index]
2042
2043        # residual
2044        try:
2045            res = (fn - gn) / en
2046        except ValueError:
2047            print "Unmatch lengths %s, %s, %s" % (len(fn), len(gn), len(en))
2048            return
2049
2050        residuals = res[numpy.isfinite(res)]
2051        # get chisqr only w/finite
2052        chisqr = numpy.average(residuals * residuals)
2053
2054        self._plot_residuals(page_id=page_id, data=data_copy,
2055                             fid=fid,
2056                             weight=weight, index=index)
2057
2058        return chisqr
2059
2060    def _plot_residuals(self, page_id, weight, fid=None,
2061                        data=None, index=None):
2062        """
2063        Plot the residuals
2064
2065        :param data: data
2066        :param index: index array (bool)
2067        : Note: this is different from the residuals in cal_chisqr()
2068        """
2069        data_copy = deepcopy(data)
2070        # Get data: data I, theory I, and data dI in order
2071        if data_copy.__class__.__name__ == "Data2D":
2072            # build residuals
2073            residuals = Data2D()
2074            #residuals.copy_from_datainfo(data)
2075            # Not for trunk the line below, instead use the line above
2076            data_copy.clone_without_data(len(data_copy.data), residuals)
2077            residuals.data = None
2078            fn = data_copy.data
2079            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2080            gn = theory_data.data
2081            if weight == None:
2082                en = data_copy.err_data
2083            else:
2084                en = weight
2085            residuals.data = (fn - gn) / en
2086            residuals.qx_data = data_copy.qx_data
2087            residuals.qy_data = data_copy.qy_data
2088            residuals.q_data = data_copy.q_data
2089            residuals.err_data = numpy.ones(len(residuals.data))
2090            residuals.xmin = min(residuals.qx_data)
2091            residuals.xmax = max(residuals.qx_data)
2092            residuals.ymin = min(residuals.qy_data)
2093            residuals.ymax = max(residuals.qy_data)
2094            residuals.q_data = data_copy.q_data
2095            residuals.mask = data_copy.mask
2096            residuals.scale = 'linear'
2097            # check the lengths
2098            if len(residuals.data) != len(residuals.q_data):
2099                return
2100        else:
2101            # 1 d theory from model_thread is only in the range of index
2102            if data_copy.dy == None or data_copy.dy == []:
2103                dy = numpy.ones(len(data_copy.y))
2104            else:
2105                if weight == None:
2106                    dy = numpy.ones(len(data_copy.y))
2107                ## Set consitently w/AbstractFitengine:
2108                ## But this should be corrected later.
2109                else:
2110                    dy = weight
2111                dy[dy == 0] = 1
2112            fn = data_copy.y[index]
2113            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2114            gn = theory_data.y
2115            en = dy[index]
2116            # build residuals
2117            residuals = Data1D()
2118            try:
2119                residuals.y = (fn - gn) / en
2120            except:
2121                msg = "ResidualPlot Error: different # of data points in theory"
2122                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
2123                residuals.y = (fn - gn[index]) / en
2124            residuals.x = data_copy.x[index]
2125            residuals.dy = numpy.ones(len(residuals.y))
2126            residuals.dx = None
2127            residuals.dxl = None
2128            residuals.dxw = None
2129            residuals.ytransform = 'y'
2130            # For latter scale changes
2131            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
2132            residuals.yaxis('\\rm{Residuals} ', 'normalized')
2133        theory_name = str(theory_data.name.split()[0])
2134        new_plot = residuals
2135        new_plot.name = "Residuals for " + str(theory_name) + "[" + \
2136                        str(data.name) + "]"
2137        ## allow to highlight data when plotted
2138        new_plot.interactive = True
2139        ## when 2 data have the same id override the 1 st plotted
2140        new_plot.id = "res" + str(data_copy.id) + str(theory_name)
2141        ##group_id specify on which panel to plot this data
2142        group_id = self.page_finder[page_id].get_graph_id()
2143        if group_id == None:
2144            group_id = data.group_id
2145        new_plot.group_id = "res" + str(group_id)
2146        #new_plot.is_data = True
2147        ##post data to plot
2148        title = new_plot.name
2149        self.page_finder[page_id].set_residuals(residuals=new_plot,
2150                                                fid=data.id)
2151        self.parent.update_theory(data_id=data.id, theory=new_plot)
2152        batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
2153        if not batch_on:
2154            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
Note: See TracBrowser for help on using the repository browser.