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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since c4170068 was c4170068, checked in by lewis, 7 years ago

Ensure polysidpersity params are shown in batch fit

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