source: sasview/src/sas/perspectives/fitting/fitting.py @ 7cd87c2

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 7cd87c2 was 39356b5, checked in by Doucet, Mathieu <doucetm@…>, 10 years ago

The mechanism using set_default_perspective in plugins is broken.

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