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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since 93c505b was 93c505b, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

redo page update on plugin reload so recalc only happens if model changes

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