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

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

Update S(Q) combobox when plugins reloaded

Rather buggy. Seems related to ticket #865

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