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

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 bf3ca1e was 934ce649, checked in by Paul Kienzle <pkienzle@…>, 9 years ago

make sure errors in compute get reported to user

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