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

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

Add comment

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