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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ddbac66 was ddbac66, checked in by butler, 3 years ago

Fix remaining custom/customized to plugin conversion

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