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

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.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 7673ecd was 7673ecd, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

refactor support for sum model; put tracebacks in logging errors

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