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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 1c206d9 was 9706d88, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

merge fixups

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