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

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

Show warning message before deleting plug-in model. Refs #754

  • Property mode set to 100755
File size: 89.3 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 not model.is_fittable(param) and \
1343                        param in param_list:
1344                        param_list.remove(param)
1345                if not correct_result or res.fitness is None or \
1346                    not np.isfinite(res.fitness) or \
1347                        np.any(res.pvec is None) or not \
1348                        np.all(np.isfinite(res.pvec)):
1349                    data_name = str(None)
1350                    if data is not None:
1351                        data_name = str(data.name)
1352                    model_name = str(None)
1353                    if model is not None:
1354                        model_name = str(model.name)
1355                    msg += "Data %s and Model %s did not fit.\n" % (data_name,
1356                                                                    model_name)
1357                    ERROR = np.NAN
1358                    cell = BatchCell()
1359                    cell.label = res.fitness
1360                    cell.value = res.fitness
1361                    batch_outputs["Chi2"].append(ERROR)
1362                    for param in param_list:
1363                        # save value of  fixed parameters
1364                        if param not in res.param_list:
1365                            batch_outputs[str(param)].append(ERROR)
1366                        else:
1367                            #save only fitted values
1368                            batch_outputs[param].append(ERROR)
1369                            batch_inputs["error on %s" % str(param)].append(ERROR)
1370                else:
1371                    # TODO: Why sometimes res.pvec comes with np.float64?
1372                    # probably from scipy lmfit
1373                    if res.pvec.__class__ == np.float64:
1374                        res.pvec = [res.pvec]
1375
1376                    cell = BatchCell()
1377                    cell.label = res.fitness
1378                    cell.value = res.fitness
1379                    batch_outputs["Chi2"].append(cell)
1380                    # add parameters to batch_results
1381                    for param in param_list:
1382                        # save value of  fixed parameters
1383                        if param not in res.param_list:
1384                            batch_outputs[str(param)].append(model.getParam(param))
1385                        else:
1386                            index = res.param_list.index(param)
1387                            #save only fitted values
1388                            batch_outputs[param].append(res.pvec[index])
1389                            if res.stderr is not None and \
1390                                len(res.stderr) == len(res.param_list):
1391                                item = res.stderr[index]
1392                                batch_inputs["error on %s" % param].append(item)
1393                            else:
1394                                batch_inputs["error on %s" % param].append('-')
1395                            model.setParam(param, res.pvec[index])
1396                #fill the batch result with emtpy value if not in the current
1397                #model
1398                EMPTY = "-"
1399                for key in batch_outputs.keys():
1400                    if key not in param_list and key not in ["Chi2", "Data"]:
1401                        batch_outputs[key].append(EMPTY)
1402
1403                self.page_finder[pid].set_batch_result(batch_inputs=batch_inputs,
1404                                                       batch_outputs=batch_outputs)
1405
1406                cpage = self.fit_panel.get_page_by_id(pid)
1407                cpage._on_fit_complete()
1408                self.page_finder[pid][data.id].set_result(res)
1409                fitproblem = self.page_finder[pid][data.id]
1410                qmin, qmax = fitproblem.get_range()
1411                plot_result = False
1412                if correct_result:
1413                    if not is_data2d:
1414                        self._complete1D(x=data.x[res.index], y=res.theory, page_id=pid,
1415                                         elapsed=None,
1416                                         index=res.index, model=model,
1417                                         weight=None, fid=data.id,
1418                                         toggle_mode_on=False, state=None,
1419                                         data=data, update_chisqr=False,
1420                                         source='fit', plot_result=plot_result)
1421                    else:
1422                        self._complete2D(image=new_theory, data=data,
1423                                         model=model,
1424                                         page_id=pid, elapsed=None,
1425                                         index=res.index,
1426                                         qmin=qmin,
1427                                         qmax=qmax, fid=data.id, weight=None,
1428                                         toggle_mode_on=False, state=None,
1429                                         update_chisqr=False,
1430                                         source='fit', plot_result=plot_result)
1431                self.on_set_batch_result(page_id=pid,
1432                                         fid=data.id,
1433                                         batch_outputs=batch_outputs,
1434                                         batch_inputs=batch_inputs)
1435
1436        evt = StatusEvent(status=msg, error="info", type="stop")
1437        wx.PostEvent(self.parent, evt)
1438        # Remove parameters that are not shown
1439        cpage = self.fit_panel.get_page_by_id(uid)
1440        tbatch_outputs = {}
1441        shownkeystr = cpage.get_copy_params()
1442        for key in batch_outputs.keys():
1443            if key in ["Chi2", "Data"] or shownkeystr.count(key) > 0:
1444                tbatch_outputs[key] = batch_outputs[key]
1445
1446        wx.CallAfter(self.parent.on_set_batch_result, tbatch_outputs,
1447                     batch_inputs, self.sub_menu)
1448
1449    def on_set_batch_result(self, page_id, fid, batch_outputs, batch_inputs):
1450        """
1451        """
1452        pid = page_id
1453        if fid not in self.page_finder[pid]:
1454            return
1455        fitproblem = self.page_finder[pid][fid]
1456        index = self.page_finder[pid].nbr_residuals_computed - 1
1457        residuals = fitproblem.get_residuals()
1458        theory_data = fitproblem.get_theory_data()
1459        data = fitproblem.get_fit_data()
1460        model = fitproblem.get_model()
1461        #fill batch result information
1462        if "Data" not in batch_outputs.keys():
1463            batch_outputs["Data"] = []
1464        from sas.sasgui.guiframe.data_processor import BatchCell
1465        cell = BatchCell()
1466        cell.label = data.name
1467        cell.value = index
1468
1469        if theory_data is not None:
1470            #Suucessful fit
1471            theory_data.id = wx.NewId()
1472            theory_data.name = model.name + "[%s]" % str(data.name)
1473            if issubclass(theory_data.__class__, Data2D):
1474                group_id = wx.NewId()
1475                theory_data.group_id = group_id
1476                if group_id not in theory_data.list_group_id:
1477                    theory_data.list_group_id.append(group_id)
1478
1479            try:
1480                # associate residuals plot
1481                if issubclass(residuals.__class__, Data2D):
1482                    group_id = wx.NewId()
1483                    residuals.group_id = group_id
1484                    if group_id not in residuals.list_group_id:
1485                        residuals.list_group_id.append(group_id)
1486                batch_outputs["Chi2"][index].object = [residuals]
1487            except:
1488                pass
1489
1490        cell.object = [data, theory_data]
1491        batch_outputs["Data"].append(cell)
1492        for key, value in data.meta_data.iteritems():
1493            if key not in batch_inputs.keys():
1494                batch_inputs[key] = []
1495            #if key.lower().strip() != "loader":
1496            batch_inputs[key].append(value)
1497        param = "temperature"
1498        if hasattr(data.sample, param):
1499            if param not in  batch_inputs.keys():
1500                batch_inputs[param] = []
1501            batch_inputs[param].append(data.sample.temperature)
1502
1503    def _fit_completed(self, result, page_id, batch_outputs,
1504                       batch_inputs=None, pars=None, elapsed=None):
1505        """
1506        Display result of the fit on related panel(s).
1507        :param result: list of object generated when fit ends
1508        :param pars: list of names of parameters fitted
1509        :param page_id: list of page ids which called fit function
1510        :param elapsed: time spent at the fitting level
1511        """
1512        t1 = time.time()
1513        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1514        msg = "Fit completed on %s \n" % str_time
1515        msg += "Duration time: %s s.\n" % str(elapsed)
1516        evt = StatusEvent(status=msg, info="info", type="stop")
1517        wx.PostEvent(self.parent, evt)
1518        wx.PostEvent(self.result_panel, PlotResultEvent(result=result))
1519        wx.CallAfter(self._update_fit_button, page_id)
1520        result = result[0]
1521        self.fit_thread_list = {}
1522        if page_id is None:
1523            page_id = []
1524        ## fit more than 1 model at the same time
1525        try:
1526            index = 0
1527            # Update potential simfit page(s)
1528            if self.sim_page is not None:
1529                self.sim_page._on_fit_complete()
1530            if self.batch_page:
1531                self.batch_page._on_fit_complete()
1532            # Update all fit pages
1533            for uid in page_id:
1534                res = result[index]
1535                fit_msg = res.mesg
1536                if res.fitness is None or \
1537                    not np.isfinite(res.fitness) or \
1538                        np.any(res.pvec is None) or \
1539                    not np.all(np.isfinite(res.pvec)):
1540                    fit_msg += "\nFitting did not converge!!!"
1541                    wx.CallAfter(self._update_fit_button, page_id)
1542                else:
1543                    #set the panel when fit result are float not list
1544                    if res.pvec.__class__ == np.float64:
1545                        pvec = [res.pvec]
1546                    else:
1547                        pvec = res.pvec
1548                    if res.stderr.__class__ == np.float64:
1549                        stderr = [res.stderr]
1550                    else:
1551                        stderr = res.stderr
1552                    cpage = self.fit_panel.get_page_by_id(uid)
1553                    # Make sure we got all results
1554                    #(CallAfter is important to MAC)
1555                    try:
1556                        #if res is not None:
1557                        wx.CallAfter(cpage.onsetValues, res.fitness,
1558                                     res.param_list,
1559                                     pvec, stderr)
1560                        index += 1
1561                        wx.CallAfter(cpage._on_fit_complete)
1562                    except KeyboardInterrupt:
1563                        fit_msg += "\nSingular point: Fitting stopped."
1564                    except:
1565                        fit_msg += "\nSingular point: Fitting error occurred."
1566                if fit_msg:
1567                   evt = StatusEvent(status=fit_msg, info="warning", type="stop")
1568                   wx.PostEvent(self.parent, evt)
1569
1570        except:
1571            msg = ("Fit completed but the following error occurred: %s"
1572                   % sys.exc_value)
1573            #import traceback; msg = "\n".join((traceback.format_exc(), msg))
1574            evt = StatusEvent(status=msg, info="warning", type="stop")
1575            wx.PostEvent(self.parent, evt)
1576
1577    def _update_fit_button(self, page_id):
1578        """
1579        Update Fit button when fit stopped
1580
1581        : parameter page_id: fitpage where the button is
1582        """
1583        if page_id.__class__.__name__ != 'list':
1584            page_id = [page_id]
1585        for uid in page_id:
1586            page = self.fit_panel.get_page_by_id(uid)
1587            page._on_fit_complete()
1588
1589    def _on_show_panel(self, event):
1590        """
1591        """
1592        pass
1593
1594    def on_reset_batch_flag(self, event):
1595        """
1596        Set batch_reset_flag
1597        """
1598        event.Skip()
1599        if self.menu1 is None:
1600            return
1601        menu_item = self.menu1.FindItemById(self.id_reset_flag)
1602        flag = menu_item.IsChecked()
1603        if not flag:
1604            menu_item.Check(False)
1605            self.batch_reset_flag = True
1606        else:
1607            menu_item.Check(True)
1608            self.batch_reset_flag = False
1609
1610        ## post a message to status bar
1611        msg = "Set Chain Fitting: %s" % str(not self.batch_reset_flag)
1612        wx.PostEvent(self.parent, StatusEvent(status=msg))
1613
1614
1615    def _on_slicer_event(self, event):
1616        """
1617        Receive a panel as event and send it to guiframe
1618
1619        :param event: event containing a panel
1620
1621        """
1622        if event.panel is not None:
1623            self.slicer_panels.append(event.panel)
1624            # Set group ID if available
1625            event_id = self.parent.popup_panel(event.panel)
1626            event.panel.uid = event_id
1627            self.mypanels.append(event.panel)
1628
1629    def _onclearslicer(self, event):
1630        """
1631        Clear the boxslicer when close the panel associate with this slicer
1632        """
1633        name = event.GetEventObject().frame.GetTitle()
1634        for panel in self.slicer_panels:
1635            if panel.window_caption == name:
1636
1637                for item in self.parent.panels:
1638                    if hasattr(self.parent.panels[item], "uid"):
1639                        if self.parent.panels[item].uid == panel.base.uid:
1640                            self.parent.panels[item].onClearSlicer(event)
1641                            #self.parent._mgr.Update()
1642                            break
1643                break
1644
1645    def _on_model_panel(self, evt):
1646        """
1647        react to model selection on any combo box or model menu.plot the model
1648
1649        :param evt: wx.combobox event
1650
1651        """
1652        model = evt.model
1653        uid = evt.uid
1654        qmin = evt.qmin
1655        qmax = evt.qmax
1656        caption = evt.caption
1657        enable_smearer = evt.enable_smearer
1658        if model is None:
1659            return
1660        if uid not in self.page_finder.keys():
1661            return
1662        # save the name containing the data name with the appropriate model
1663        self.page_finder[uid].set_model(model)
1664        self.page_finder[uid].enable_smearing(enable_smearer)
1665        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1666        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1667        if self.sim_page is not None and not self.batch_on:
1668            self.sim_page.draw_page()
1669        if self.batch_page is not None and self.batch_on:
1670            self.batch_page.draw_page()
1671
1672    def _update1D(self, x, output):
1673        """
1674        Update the output of plotting model 1D
1675        """
1676        msg = "Plot updating ... "
1677        wx.PostEvent(self.parent, StatusEvent(status=msg, type="update"))
1678
1679    def create_theory_1D(self, x, y, page_id, model, data, state,
1680                         data_description, data_id, dy=None):
1681        """
1682            Create a theory object associate with an existing Data1D
1683            and add it to the data manager.
1684            @param x: x-values of the data
1685            @param y: y_values of the data
1686            @param page_id: fit page ID
1687            @param model: model used for fitting
1688            @param data: Data1D object to create the theory for
1689            @param state: model state
1690            @param data_description: title to use in the data manager
1691            @param data_id: unique data ID
1692        """
1693        new_plot = Data1D(x=x, y=y)
1694        if dy is None:
1695            new_plot.is_data = False
1696            new_plot.dy = np.zeros(len(y))
1697            # If this is a theory curve, pick the proper symbol to make it a curve
1698            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1699        else:
1700            new_plot.is_data = True
1701            new_plot.dy = dy
1702        new_plot.interactive = True
1703        new_plot.dx = None
1704        new_plot.dxl = None
1705        new_plot.dxw = None
1706        _yaxis, _yunit = data.get_yaxis()
1707        _xaxis, _xunit = data.get_xaxis()
1708        new_plot.title = data.name
1709        new_plot.group_id = data.group_id
1710        if new_plot.group_id is None:
1711            new_plot.group_id = data.group_id
1712        new_plot.id = data_id
1713        # Find if this theory was already plotted and replace that plot given
1714        # the same id
1715        self.page_finder[page_id].get_theory_data(fid=data.id)
1716
1717        if data.is_data:
1718            data_name = str(data.name)
1719        else:
1720            data_name = str(model.__class__.__name__)
1721
1722        new_plot.name = data_description + " [" + data_name + "]"
1723        new_plot.xaxis(_xaxis, _xunit)
1724        new_plot.yaxis(_yaxis, _yunit)
1725        self.page_finder[page_id].set_theory_data(data=new_plot,
1726                                                  fid=data.id)
1727        self.parent.update_theory(data_id=data.id, theory=new_plot,
1728                                   state=state)
1729        return new_plot
1730
1731    def _complete1D(self, x, y, page_id, elapsed, index, model,
1732                    weight=None, fid=None,
1733                    toggle_mode_on=False, state=None,
1734                    data=None, update_chisqr=True,
1735                    source='model', plot_result=True,
1736                    unsmeared_model=None, unsmeared_data=None,
1737                    unsmeared_error=None, sq_model=None, pq_model=None):
1738        """
1739            Complete plotting 1D data
1740            @param unsmeared_model: fit model, without smearing
1741            @param unsmeared_data: data, rescaled to unsmeared model
1742            @param unsmeared_error: data error, rescaled to unsmeared model
1743        """
1744
1745        number_finite = np.count_nonzero(np.isfinite(y))
1746        np.nan_to_num(y)
1747        new_plot = self.create_theory_1D(x, y, page_id, model, data, state,
1748                                         data_description=model.name,
1749                                         data_id=str(page_id) + " " + data.name)
1750        if unsmeared_model is not None:
1751            self.create_theory_1D(x, unsmeared_model, page_id, model, data, state,
1752                                  data_description=model.name + " unsmeared",
1753                                  data_id=str(page_id) + " " + data.name + " unsmeared")
1754
1755            if unsmeared_data is not None and unsmeared_error is not None:
1756                self.create_theory_1D(x, unsmeared_data, page_id, model, data, state,
1757                                      data_description="Data unsmeared",
1758                                      data_id="Data  " + data.name + " unsmeared",
1759                                      dy=unsmeared_error)
1760        # Comment this out until we can get P*S models with correctly populated parameters
1761        #if sq_model is not None and pq_model is not None:
1762        #    self.create_theory_1D(x, sq_model, page_id, model, data, state,
1763        #                          data_description=model.name + " S(q)",
1764        #                          data_id=str(page_id) + " " + data.name + " S(q)")
1765        #    self.create_theory_1D(x, pq_model, page_id, model, data, state,
1766        #                          data_description=model.name + " P(q)",
1767        #                          data_id=str(page_id) + " " + data.name + " P(q)")
1768
1769        current_pg = self.fit_panel.get_page_by_id(page_id)
1770        title = new_plot.title
1771        batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
1772        if not batch_on:
1773            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=str(title)))
1774        elif plot_result:
1775            top_data_id = self.fit_panel.get_page_by_id(page_id).data.id
1776            if data.id == top_data_id:
1777                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=str(title)))
1778        caption = current_pg.window_caption
1779        self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1780
1781        self.page_finder[page_id].set_theory_data(data=new_plot,
1782                                                      fid=data.id)
1783        if toggle_mode_on:
1784            wx.PostEvent(self.parent,
1785                         NewPlotEvent(group_id=str(page_id) + " Model2D",
1786                                          action="Hide"))
1787        else:
1788            if update_chisqr:
1789                wx.PostEvent(current_pg,
1790                             Chi2UpdateEvent(output=self._cal_chisqr(
1791                                                                data=data,
1792                                                                fid=fid,
1793                                                                weight=weight,
1794                                                                page_id=page_id,
1795                                                                index=index)))
1796            else:
1797                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1798                                     index=index, weight=weight)
1799
1800        if not number_finite:
1801            logger.error("Using the present parameters the model does not return any finite value. ")
1802            msg = "Computing Error: Model did not return any finite value."
1803            wx.PostEvent(self.parent, StatusEvent(status = msg, info="error"))
1804        else:
1805            msg = "Computation  completed!"
1806            if number_finite != y.size:
1807                msg += ' PROBLEM: For some Q values the model returns non finite intensities!'
1808                logger.error("For some Q values the model returns non finite intensities.")
1809            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1810
1811    def _calc_exception(self, etype, value, tb):
1812        """
1813        Handle exception from calculator by posting it as an error.
1814        """
1815        logger.error("".join(traceback.format_exception(etype, value, tb)))
1816        msg = traceback.format_exception(etype, value, tb, limit=1)
1817        evt = StatusEvent(status="".join(msg), type="stop", info="error")
1818        wx.PostEvent(self.parent, evt)
1819
1820    def _update2D(self, output, time=None):
1821        """
1822        Update the output of plotting model
1823        """
1824        msg = "Plot updating ... "
1825        wx.PostEvent(self.parent, StatusEvent(msg, type="update"))
1826
1827    def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
1828                qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
1829                     update_chisqr=True, source='model', plot_result=True):
1830        """
1831        Complete get the result of modelthread and create model 2D
1832        that can be plot.
1833        """
1834        number_finite = np.count_nonzero(np.isfinite(image))
1835        np.nan_to_num(image)
1836        new_plot = Data2D(image=image, err_image=data.err_data)
1837        new_plot.name = model.name + '2d'
1838        new_plot.title = "Analytical model 2D "
1839        new_plot.id = str(page_id) + " " + data.name
1840        new_plot.group_id = str(page_id) + " Model2D"
1841        new_plot.detector = data.detector
1842        new_plot.source = data.source
1843        new_plot.is_data = False
1844        new_plot.qx_data = data.qx_data
1845        new_plot.qy_data = data.qy_data
1846        new_plot.q_data = data.q_data
1847        new_plot.mask = data.mask
1848        ## plot boundaries
1849        new_plot.ymin = data.ymin
1850        new_plot.ymax = data.ymax
1851        new_plot.xmin = data.xmin
1852        new_plot.xmax = data.xmax
1853        title = data.title
1854
1855        new_plot.is_data = False
1856        if data.is_data:
1857            data_name = str(data.name)
1858        else:
1859            data_name = str(model.__class__.__name__) + '2d'
1860
1861        if len(title) > 1:
1862            new_plot.title = "Model2D for %s " % model.name + data_name
1863        new_plot.name = model.name + " [" + \
1864                                    data_name + "]"
1865        theory_data = deepcopy(new_plot)
1866
1867        self.page_finder[page_id].set_theory_data(data=theory_data,
1868                                                  fid=data.id)
1869        self.parent.update_theory(data_id=data.id,
1870                                       theory=new_plot,
1871                                       state=state)
1872        current_pg = self.fit_panel.get_page_by_id(page_id)
1873        title = new_plot.title
1874        if not source == 'fit' and plot_result:
1875            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1876                                               title=title))
1877        if toggle_mode_on:
1878            wx.PostEvent(self.parent,
1879                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1880                                               action="Hide"))
1881        else:
1882            # Chisqr in fitpage
1883            if update_chisqr:
1884                wx.PostEvent(current_pg,
1885                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1886                                                                    weight=weight,
1887                                                                    fid=fid,
1888                                                         page_id=page_id,
1889                                                         index=index)))
1890            else:
1891                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1892                                      index=index, weight=weight)
1893
1894        if not number_finite:
1895            logger.error("Using the present parameters the model does not return any finite value. ")
1896            msg = "Computing Error: Model did not return any finite value."
1897            wx.PostEvent(self.parent, StatusEvent(status = msg, info="error"))
1898        else:
1899            msg = "Computation  completed!"
1900            if number_finite != image.size:
1901                msg += ' PROBLEM: For some Qx,Qy values the model returns non finite intensities!'
1902                logger.error("For some Qx,Qy values the model returns non finite intensities.")
1903            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1904
1905    def _draw_model2D(self, model, page_id, qmin,
1906                      qmax,
1907                      data=None, smearer=None,
1908                      description=None, enable2D=False,
1909                      state=None,
1910                      fid=None,
1911                      weight=None,
1912                      toggle_mode_on=False,
1913                       update_chisqr=True, source='model'):
1914        """
1915        draw model in 2D
1916
1917        :param model: instance of the model to draw
1918        :param description: the description of the model
1919        :param enable2D: when True allows to draw model 2D
1920        :param qmin: the minimum value to  draw model 2D
1921        :param qmax: the maximum value to draw model 2D
1922        :param qstep: the number of division of Qx and Qy of the model to draw
1923
1924        """
1925        if not enable2D:
1926            return None
1927        try:
1928            from model_thread import Calc2D
1929            ## If a thread is already started, stop it
1930            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1931                self.calc_2D.stop()
1932                ## stop just raises a flag to tell the thread to kill
1933                ## itself -- see the fix in Calc1D implemented to fix
1934                ## an actual problem.  Seems the fix should also go here
1935                ## and may be the cause of other noted instabilities
1936                ##
1937                ##    -PDB August 12, 2014
1938                while self.calc_2D.isrunning():
1939                    time.sleep(0.1)
1940            self.calc_2D = Calc2D(model=model,
1941                                  data=data,
1942                                  page_id=page_id,
1943                                  smearer=smearer,
1944                                  qmin=qmin,
1945                                  qmax=qmax,
1946                                  weight=weight,
1947                                  fid=fid,
1948                                  toggle_mode_on=toggle_mode_on,
1949                                  state=state,
1950                                  completefn=self._complete2D,
1951                                  update_chisqr=update_chisqr,
1952                                  exception_handler=self._calc_exception,
1953                                  source=source)
1954            self.calc_2D.queue()
1955        except:
1956            raise
1957
1958    def _draw_model1D(self, model, page_id, data,
1959                      qmin, qmax, smearer=None,
1960                state=None,
1961                weight=None,
1962                fid=None,
1963                toggle_mode_on=False, update_chisqr=True, source='model',
1964                enable1D=True):
1965        """
1966        Draw model 1D from loaded data1D
1967
1968        :param data: loaded data
1969        :param model: the model to plot
1970
1971        """
1972        if not enable1D:
1973            return
1974        try:
1975            from model_thread import Calc1D
1976            ## If a thread is already started, stop it
1977            if (self.calc_1D is not None) and self.calc_1D.isrunning():
1978                self.calc_1D.stop()
1979                ## stop just raises the flag -- the thread is supposed to
1980                ## then kill itself but cannot.  Paul Kienzle came up with
1981                ## this fix to prevent threads from stepping on each other
1982                ## which was causing a simple custom plugin model to crash
1983                ##Sasview.
1984                ## We still don't know why the fit sometimes lauched a second
1985                ## thread -- something which should also be investigated.
1986                ## The thread approach was implemented in order to be able
1987                ## to lauch a computation in a separate thread from the GUI so
1988                ## that the GUI can still respond to user input including
1989                ## a request to stop the computation.
1990                ## It seems thus that the whole thread approach used here
1991                ## May need rethinking
1992                ##
1993                ##    -PDB August 12, 2014
1994                while self.calc_1D.isrunning():
1995                    time.sleep(0.1)
1996            self.calc_1D = Calc1D(data=data,
1997                                  model=model,
1998                                  page_id=page_id,
1999                                  qmin=qmin,
2000                                  qmax=qmax,
2001                                  smearer=smearer,
2002                                  state=state,
2003                                  weight=weight,
2004                                  fid=fid,
2005                                  toggle_mode_on=toggle_mode_on,
2006                                  completefn=self._complete1D,
2007                                  #updatefn = self._update1D,
2008                                  update_chisqr=update_chisqr,
2009                                  exception_handler=self._calc_exception,
2010                                  source=source)
2011            self.calc_1D.queue()
2012        except:
2013            msg = " Error occurred when drawing %s Model 1D: " % model.name
2014            msg += " %s" % sys.exc_value
2015            wx.PostEvent(self.parent, StatusEvent(status=msg))
2016
2017    def _cal_chisqr(self, page_id, data, weight, fid=None, index=None):
2018        """
2019        Get handy Chisqr using the output from draw1D and 2D,
2020        instead of calling expansive CalcChisqr in guithread
2021        """
2022        try:
2023            data_copy = deepcopy(data)
2024        except:
2025            return
2026        # default chisqr
2027        chisqr = None
2028        #to compute chisq make sure data has valid data
2029        # return None if data is None
2030        if not check_data_validity(data_copy) or data_copy is None:
2031            return chisqr
2032
2033        # Get data: data I, theory I, and data dI in order
2034        if data_copy.__class__.__name__ == "Data2D":
2035            if index is None:
2036                index = np.ones(len(data_copy.data), dtype=bool)
2037            if weight is not None:
2038                data_copy.err_data = weight
2039            # get rid of zero error points
2040            index = index & (data_copy.err_data != 0)
2041            index = index & (np.isfinite(data_copy.data))
2042            fn = data_copy.data[index]
2043            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2044            if theory_data is None:
2045                return chisqr
2046            gn = theory_data.data[index]
2047            en = data_copy.err_data[index]
2048        else:
2049            # 1 d theory from model_thread is only in the range of index
2050            if index is None:
2051                index = np.ones(len(data_copy.y), dtype=bool)
2052            if weight is not None:
2053                data_copy.dy = weight
2054            if data_copy.dy is None or data_copy.dy == []:
2055                dy = np.ones(len(data_copy.y))
2056            else:
2057                ## Set consistently w/AbstractFitengine:
2058                # But this should be corrected later.
2059                dy = deepcopy(data_copy.dy)
2060                dy[dy == 0] = 1
2061            fn = data_copy.y[index]
2062
2063            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2064            if theory_data is None:
2065                return chisqr
2066            gn = theory_data.y
2067            en = dy[index]
2068
2069        # residual
2070        try:
2071            res = (fn - gn) / en
2072        except ValueError:
2073            print("Unmatch lengths %s, %s, %s" % (len(fn), len(gn), len(en)))
2074            return
2075
2076        residuals = res[np.isfinite(res)]
2077        # get chisqr only w/finite
2078        chisqr = np.average(residuals * residuals)
2079
2080        self._plot_residuals(page_id=page_id, data=data_copy,
2081                             fid=fid,
2082                             weight=weight, index=index)
2083
2084        return chisqr
2085
2086    def _plot_residuals(self, page_id, weight, fid=None,
2087                        data=None, index=None):
2088        """
2089        Plot the residuals
2090
2091        :param data: data
2092        :param index: index array (bool)
2093        : Note: this is different from the residuals in cal_chisqr()
2094        """
2095        data_copy = deepcopy(data)
2096        # Get data: data I, theory I, and data dI in order
2097        if data_copy.__class__.__name__ == "Data2D":
2098            # build residuals
2099            residuals = Data2D()
2100            #residuals.copy_from_datainfo(data)
2101            # Not for trunk the line below, instead use the line above
2102            data_copy.clone_without_data(len(data_copy.data), residuals)
2103            residuals.data = None
2104            fn = data_copy.data
2105            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2106            gn = theory_data.data
2107            if weight is None:
2108                en = data_copy.err_data
2109            else:
2110                en = weight
2111            residuals.data = (fn - gn) / en
2112            residuals.qx_data = data_copy.qx_data
2113            residuals.qy_data = data_copy.qy_data
2114            residuals.q_data = data_copy.q_data
2115            residuals.err_data = np.ones(len(residuals.data))
2116            residuals.xmin = min(residuals.qx_data)
2117            residuals.xmax = max(residuals.qx_data)
2118            residuals.ymin = min(residuals.qy_data)
2119            residuals.ymax = max(residuals.qy_data)
2120            residuals.q_data = data_copy.q_data
2121            residuals.mask = data_copy.mask
2122            residuals.scale = 'linear'
2123            # check the lengths
2124            if len(residuals.data) != len(residuals.q_data):
2125                return
2126        else:
2127            # 1 d theory from model_thread is only in the range of index
2128            if data_copy.dy is None or data_copy.dy == []:
2129                dy = np.ones(len(data_copy.y))
2130            else:
2131                if weight is None:
2132                    dy = np.ones(len(data_copy.y))
2133                ## Set consitently w/AbstractFitengine:
2134                ## But this should be corrected later.
2135                else:
2136                    dy = weight
2137                dy[dy == 0] = 1
2138            fn = data_copy.y[index]
2139            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2140            gn = theory_data.y
2141            en = dy[index]
2142            # build residuals
2143            residuals = Data1D()
2144            try:
2145                residuals.y = (fn - gn) / en
2146            except:
2147                msg = "ResidualPlot Error: different # of data points in theory"
2148                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
2149                residuals.y = (fn - gn[index]) / en
2150            residuals.x = data_copy.x[index]
2151            residuals.dy = np.ones(len(residuals.y))
2152            residuals.dx = None
2153            residuals.dxl = None
2154            residuals.dxw = None
2155            residuals.ytransform = 'y'
2156            # For latter scale changes
2157            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
2158            residuals.yaxis('\\rm{Residuals} ', 'normalized')
2159        theory_name = str(theory_data.name.split()[0])
2160        new_plot = residuals
2161        new_plot.name = "Residuals for " + str(theory_name) + "[" + \
2162                        str(data.name) + "]"
2163        ## allow to highlight data when plotted
2164        new_plot.interactive = True
2165        ## when 2 data have the same id override the 1 st plotted
2166        new_plot.id = "res" + str(data_copy.id) + str(theory_name)
2167        ##group_id specify on which panel to plot this data
2168        group_id = self.page_finder[page_id].get_graph_id()
2169        if group_id is None:
2170            group_id = data.group_id
2171        new_plot.group_id = "res" + str(group_id)
2172        #new_plot.is_data = True
2173        ##post data to plot
2174        title = new_plot.name
2175        self.page_finder[page_id].set_residuals(residuals=new_plot,
2176                                                fid=data.id)
2177        self.parent.update_theory(data_id=data.id, theory=new_plot)
2178        batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
2179        if not batch_on:
2180            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
Note: See TracBrowser for help on using the repository browser.