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

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 05228b0 was 05228b0, checked in by Paul Kienzle <pkienzle@…>, 8 years ago

use the new bumps fitter changed event to update the active fitter indicator

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