source: sasview/src/sas/sasgui/perspectives/fitting/fitting.py @ 105ef92

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 105ef92 was 105ef92, checked in by gonzalezm, 8 years ago

Added option to Fitting/Edit? Custom Model menu to update the menu of Costamized Models from the content of the plugin_models folder

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