source: sasview/src/sas/perspectives/fitting/fitting.py @ bf6b8d1

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 bf6b8d1 was f76bf17, checked in by krzywon, 10 years ago

A fix ticket for #324 - logging now captures and records warnings in the
cosole log. Removed a redundant separator in the Fitting menu that was
there from when the fitting engines were still available. Changed the
logging type of a successful data load from a warning to info.

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