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

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 3a22ce7 was ca4d985, checked in by Mathieu Doucet <doucetm@…>, 8 years ago

S(q), P(q) plots. Fixes #209

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