source: sasview/src/sas/sasgui/perspectives/fitting/fitting.py @ 914c49d5

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 914c49d5 was a1b8fee, checked in by andyfaff, 8 years ago

MAINT: from future import print_function

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