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

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 ab0b93f was 69363c7, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'master' into ticket-853-fit-gui-to-calc

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