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

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 252d959 was 252d959, checked in by lewis, 7 years ago

Improve S(Q) combobox behaviour when plugins reloaded

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