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

Last change on this file since cf1be88 was aba4559, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

fix comments

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