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

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

Simplify S(Q) combobox updating code

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