source: sasview/src/sas/perspectives/fitting/fitting.py @ 225aca8

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 225aca8 was 225aca8, checked in by mathieu, 9 years ago

Merge pull request #3 from SasView?/standalone_cleanup

Standalone cleanup

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