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

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 243fbc0 was 243fbc0, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

move bumps version to 0.7.5.9

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