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

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 50fcb09 was 00f7ff1, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

move sim fit state to sascalc pagestate

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