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

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

move fit pagestate from gui to calc

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