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

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

clean up plugin-model handling code; preserve active parameter values when plugin is updated

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