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
RevLine 
[f76bf17]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################################################################################
[a534432]13from __future__ import print_function
14
[f76bf17]15import re
16import sys
17import os
18import wx
19import logging
[9a5097c]20import numpy as np
[f76bf17]21import time
22from copy import deepcopy
[934ce649]23import traceback
[f76bf17]24
[00f7ff1]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
[b699768]33from sas.sascalc.dataloader.loader import Loader
[2a0f33f]34from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
[00f7ff1]35from sas.sascalc.fit.pagestate import Reader, PageState, SimFitPageState
[277257f]36from sas.sascalc.fit import models
[2a0f33f]37
[d85c194]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
[f76bf17]50
[00f7ff1]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
[934ce649]63
[463e7ffc]64logger = logging.getLogger(__name__)
[c155a16]65
[f76bf17]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    """
[f21d496]81    def __init__(self):
82        PluginBase.__init__(self, name="Fitting")
[f76bf17]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
[c155a16]129        logger.info("Fitting plug-in started")
[f76bf17]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
[73cbeec]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
[f76bf17]208        self.id_result_panel = wx.NewId()
209        self.menu1.Append(self.id_result_panel, "Fit Results", "Show fit results panel")
[9776c82]210        wx.EVT_MENU(owner, self.id_result_panel, self.on_fit_results)
[f76bf17]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()
[ec72ceb]235        self.menu1.AppendMenu(self.id_edit, "Plugin Model Operations",
[e92a352]236                              self.edit_model_menu)
[f76bf17]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        """
[00f7ff1]244        from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
245
[f21d496]246        event_id = event.GetId()
247        label = self.edit_menu.GetLabel(event_id)
[f76bf17]248        filename = os.path.join(models.find_plugins_dir(), label)
249        frame = PyConsole(parent=self.parent, manager=self,
250                          panel=self.fit_panel,
[ec72ceb]251                          title='Advanced Plugin Model Editor',
[f76bf17]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        """
[f21d496]260        event_id = event.GetId()
261        label = self.delete_menu.GetLabel(event_id)
[f76bf17]262        toks = os.path.splitext(label)
263        path = os.path.join(models.find_plugins_dir(), toks[0])
[489f53a]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
[f76bf17]268        try:
269            for ext in ['.py', '.pyc']:
270                p_path = path + ext
[489f53a]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
[f76bf17]275                os.remove(p_path)
276            self.update_custom_combo()
277            if os.path.isfile(p_path):
[e92a352]278                msg = "Sorry! unable to delete the default "
279                msg += "plugin model... \n"
[f76bf17]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')
[934ce649]285                #evt = StatusEvent(status=msg, type='stop', info='warning')
286                #wx.PostEvent(self.parent, evt)
[f76bf17]287            else:
[f21d496]288                self.delete_menu.Delete(event_id)
[f76bf17]289                for item in self.edit_menu.GetMenuItems():
290                    if item.GetLabel() == label:
291                        self.edit_menu.DeleteItem(item)
[e92a352]292                        msg = "The plugin model, %s, has been deleted." % label
[934ce649]293                        evt = StatusEvent(status=msg, type='stop', info='info')
294                        wx.PostEvent(self.parent, evt)
[f76bf17]295                        break
[7673ecd]296        except Exception:
[00f7ff1]297            traceback.print_exc()
[f76bf17]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        """
[f21d496]305        event_id = event.GetId()
[f76bf17]306        model_manager = models.ModelManager()
[277257f]307        model_list = model_manager.composable_models()
[f76bf17]308        plug_dir = models.find_plugins_dir()
[6f16e25]309        textdial = TextDialog(None, self, wx.ID_ANY, 'Easy Sum/Multi(p1, p2) Editor',
[f76bf17]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        """
[a534432]319        if self.new_model_frame is not None:
[f76bf17]320            self.new_model_frame.Show(False)
321            self.new_model_frame.Show(True)
322        else:
[f21d496]323            event_id = event.GetId()
[f76bf17]324            dir_path = models.find_plugins_dir()
[ec72ceb]325            title = "New Plugin Model Function"
[f76bf17]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
[105ef92]331    def load_plugin_models(self, event):
332        """
333        Update of models in plugin_models folder
334        """
335        event_id = event.GetId()
[c807957]336        self.update_custom_combo()
[105ef92]337
[f76bf17]338    def update_custom_combo(self):
339        """
340        Update custom model list in the fitpage combo box
341        """
[ec72ceb]342        custom_model = 'Plugin Models'
[f76bf17]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)
[277257f]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)
[69363c7]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()
[277257f]381        except Exception:
[c155a16]382            logger.error("update_custom_combo: %s", sys.exc_value)
[f76bf17]383
384    def set_edit_menu(self, owner):
385        """
386        Set list of the edit model menu labels
387        """
[f21d496]388        wx_id = wx.NewId()
[f76bf17]389        #new_model_menu = wx.Menu()
[ec72ceb]390        self.edit_model_menu.Append(wx_id, 'New Plugin Model',
[277257f]391                                    'Add a new model function')
[f21d496]392        wx.EVT_MENU(owner, wx_id, self.make_new_model)
[2a0f33f]393
[f21d496]394        wx_id = wx.NewId()
395        self.edit_model_menu.Append(wx_id, 'Sum|Multi(p1, p2)',
[f76bf17]396                                    'Sum of two model functions')
[f21d496]397        wx.EVT_MENU(owner, wx_id, self.make_sum_model)
[105ef92]398
[f76bf17]399        e_id = wx.NewId()
400        self.edit_menu = wx.Menu()
401        self.edit_model_menu.AppendMenu(e_id,
[ec72ceb]402                                    'Advanced Plugin Editor', self.edit_menu)
[f76bf17]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,
[ec72ceb]408                                        'Delete Plugin Models', self.delete_menu)
[f76bf17]409        self.set_edit_menu_helper(owner, self.delete_custom_model)
410
[105ef92]411        wx_id = wx.NewId()
[ec72ceb]412        self.edit_model_menu.Append(wx_id, 'Load Plugin Models',
[105ef92]413          '(Re)Load all models present in user plugin_models folder')
414        wx.EVT_MENU(owner, wx_id, self.load_plugin_models)
[2a0f33f]415
[f76bf17]416    def set_edit_menu_helper(self, owner=None, menu=None):
417        """
418        help for setting list of the edit model menu labels
419        """
[a534432]420        if menu is None:
[f76bf17]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:
[f21d496]439                    wx_id = wx.NewId()
440                    submenu.Append(wx_id, name)
441                    wx.EVT_MENU(owner, wx_id, menu)
[f76bf17]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        """
[f21d496]460        event_id = event.GetId()
[f76bf17]461        caption = "Const & Simul Fit"
462        page = self.sim_page
[f21d496]463        if event_id == self.id_batchfit:
[f76bf17]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
[a534432]475        if page is not None:
[f76bf17]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)
[05228b0]549
[243fbc0]550        # CRUFT: EVT_FITTER_CHANGED is not None for bumps 0.7.5.9 and above
[05228b0]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
[f76bf17]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:
[098f3d2]585            self.add_fit_page(data=data_list)
[f76bf17]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)
[098f3d2]605                        self.add_fit_page(data=[data])
[f76bf17]606            except:
[098f3d2]607                msg = "Fitting set_data: " + str(sys.exc_value)
[f76bf17]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)
[7673ecd]618            except Exception:
[f76bf17]619                msg = "Fitting: cannot deal with the theory received"
[934ce649]620                evt = StatusEvent(status=msg, info="error")
[c155a16]621                logger.error("set_theory " + msg + "\n" + str(sys.exc_value))
[934ce649]622                wx.PostEvent(self.parent, evt)
[f76bf17]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        """
[e89aed5]632        if isinstance(state, PageState):
[f76bf17]633            state = state.clone()
634            self.temp_state.append(state)
[e89aed5]635        elif isinstance(state, SimFitPageState):
[00f7ff1]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)
[f76bf17]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
[a534432]667            if state.formfactorcombobox is not None:
[f76bf17]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,
[277257f]673                             title=data.title))
[f76bf17]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,
[277257f]684                             title=data.title))
[f76bf17]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(),
[277257f]688                                caption=caption)
[f76bf17]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        """
[098f3d2]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
[f76bf17]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:
[f21d496]789                model_list = value.get_model()
790                model = model_list[0]
[f76bf17]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:
[277257f]805            param_names = re.split(r"\.", item)
[f76bf17]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):
[9776c82]815        """
816        Open the bumps options panel.
817        """
[05228b0]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)
[7945367]827
828    def on_help(self, algorithm_id):
[86b049b]829        _TreeLocation = "user/sasgui/perspectives/fitting/optimizer.html"
[7945367]830        _anchor = "#fit-"+algorithm_id
[6f16e25]831        DocumentationWindow(self.parent, wx.ID_ANY, _TreeLocation, _anchor, "Optimizer Help")
[7945367]832
[f76bf17]833
[9776c82]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
[73cbeec]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
[f76bf17]848    def stop_fit(self, uid):
849        """
[acf8e4a5]850        Stop the fit
[f76bf17]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,
[277257f]908                            enable1D=enable1D, enable2D=enable2D,
909                            qmin=qmin, qmax=qmax, weight=weight)
[f76bf17]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,
[277257f]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)
[f76bf17]964
965    def onFit(self, uid):
966        """
967        Get series of data, model, associates parameters and range and send then
[acf8e4a5]968        to  series of fitters. Fit data and model, display result to
[f76bf17]969        corresponding panels.
970        :param uid: id related to the panel currently calling this fit function.
971        """
[277257f]972        if uid is None:
973            raise RuntimeError("no page to fit") # Should never happen
[f76bf17]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':
[acf8e4a5]988            # for simultaneous fitting only one fitter is needed
989            sim_fitter = Fit()
[f76bf17]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,
[277257f]1007                                        flag=page.get_weight_flag(),
1008                                        is2d=page._is_2D())
[f76bf17]1009                    if not page.param_toFit:
1010                        msg = "No fitting parameters for %s" % page.window_caption
[934ce649]1011                        evt = StatusEvent(status=msg, info="error", type="stop")
1012                        wx.PostEvent(page.parent.parent, evt)
[f76bf17]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
[934ce649]1018                        evt = StatusEvent(status=msg, info="error", type="stop")
1019                        wx.PostEvent(page.parent.parent, evt)
[f76bf17]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:
[acf8e4a5]1026                            fitter = Fit()
[f76bf17]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,
[277257f]1032                                                 pars=pars,
1033                                                 fitter=fitter,
1034                                                 fit_id=fit_id)
[f76bf17]1035                        fit_id += 1
1036                    list_page_id.append(page_id)
1037                    page_info.clear_model_param()
1038            except KeyboardInterrupt:
1039                msg = "Fitting terminated"
[934ce649]1040                evt = StatusEvent(status=msg, info="info", type="stop")
1041                wx.PostEvent(self.parent, evt)
[f76bf17]1042                return True
1043            except:
[a3f125f0]1044                raise
[f76bf17]1045                msg = "Fitting error: %s" % str(sys.exc_value)
[934ce649]1046                evt = StatusEvent(status=msg, info="error", type="stop")
1047                wx.PostEvent(self.parent, evt)
[f76bf17]1048                return False
1049        ## If a thread is already started, stop it
[a534432]1050        #if self.calc_fitis not None and self.calc_fit.isrunning():
[f76bf17]1051        #    self.calc_fit.stop()
1052        msg = "Fitting is in progress..."
1053        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1054
[acf8e4a5]1055        #Handler used to display fit message
[f76bf17]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,
[277257f]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)
[f76bf17]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
[a534432]1140            if page is not None:
[934ce649]1141                evt = StatusEvent(status="Page Created", info="info")
1142                wx.PostEvent(self.parent, evt)
[f76bf17]1143            else:
1144                msg = "Page was already Created"
[934ce649]1145                evt = StatusEvent(status=msg, info="warning")
1146                wx.PostEvent(self.parent, evt)
[277257f]1147        except Exception:
[f76bf17]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
[a534432]1159        if page is None:
[f76bf17]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,
[277257f]1171                                 NewPlotEvent(group_id=group_id,
1172                                              action="delete"))
[f76bf17]1173                    self.parent.update_data(prev_data=theory_data,
[277257f]1174                                            new_data=data)
[f76bf17]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,
[277257f]1180                                 NewPlotEvent(group_id=group_id,
1181                                              action="delete"))
[f76bf17]1182                    self.parent.update_data(prev_data=theory_data,
[277257f]1183                                            new_data=data)
[f76bf17]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
[a534432]1227        if uid is None:
[f76bf17]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        """
[acf8e4a5]1237        Create and set fitter with series of data and model
[f76bf17]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
[a534432]1250                if item[0] is not None and item[1] is not None:
[f76bf17]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
[a534432]1265        if panel is None:
[f76bf17]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        """
[a534432]1287        print("update_fit result", result)
[f76bf17]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
[acf8e4a5]1293        :param result: list of objects received from fitters
[f76bf17]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
[373d4ee]1302        wx.CallAfter(self._update_fit_button, page_id)
[f76bf17]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)
[934ce649]1307        evt = StatusEvent(status=msg, info="info", type="stop")
1308        wx.PostEvent(self.parent, evt)
[f76bf17]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)
[0900627]1350                # Check consistency of arrays
[f76bf17]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
[9a5097c]1359                    new_theory[res.index == False] = np.nan
[f76bf17]1360                    correct_result = True
[0900627]1361                # Get all fittable parameters of the current model
[f76bf17]1362                param_list = model.getParamList()
1363                for param in model.getDispParamList():
[c4170068]1364                    if '.' in param and param in param_list:
[0900627]1365                        # Ensure polydispersity results are displayed
[c4170068]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 \
[f76bf17]1371                        param in param_list:
1372                        param_list.remove(param)
1373                if not correct_result or res.fitness is None or \
[9a5097c]1374                    not np.isfinite(res.fitness) or \
[a534432]1375                        np.any(res.pvec is None) or not \
[9a5097c]1376                        np.all(np.isfinite(res.pvec)):
[f76bf17]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)
[9a5097c]1385                    ERROR = np.NAN
[f76bf17]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:
[0900627]1391                        # Save value of  fixed parameters
[f76bf17]1392                        if param not in res.param_list:
1393                            batch_outputs[str(param)].append(ERROR)
1394                        else:
[0900627]1395                            # Save only fitted values
[f76bf17]1396                            batch_outputs[param].append(ERROR)
1397                            batch_inputs["error on %s" % str(param)].append(ERROR)
1398                else:
[9a5097c]1399                    # TODO: Why sometimes res.pvec comes with np.float64?
[acf8e4a5]1400                    # probably from scipy lmfit
[9a5097c]1401                    if res.pvec.__class__ == np.float64:
[f76bf17]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
[934ce649]1464        evt = StatusEvent(status=msg, error="info", type="stop")
1465        wx.PostEvent(self.parent, evt)
[f76bf17]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
[a534432]1496        if theory_data is not None:
[f76bf17]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)
[934ce649]1543        evt = StatusEvent(status=msg, info="info", type="stop")
1544        wx.PostEvent(self.parent, evt)
[f76bf17]1545        wx.PostEvent(self.result_panel, PlotResultEvent(result=result))
[373d4ee]1546        wx.CallAfter(self._update_fit_button, page_id)
[f76bf17]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
[aac161f1]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
[f76bf17]1560            for uid in page_id:
1561                res = result[index]
[1a5d5f2]1562                fit_msg = res.mesg
[f76bf17]1563                if res.fitness is None or \
[9a5097c]1564                    not np.isfinite(res.fitness) or \
[a534432]1565                        np.any(res.pvec is None) or \
[9a5097c]1566                    not np.all(np.isfinite(res.pvec)):
[1a5d5f2]1567                    fit_msg += "\nFitting did not converge!!!"
[373d4ee]1568                    wx.CallAfter(self._update_fit_button, page_id)
[f76bf17]1569                else:
1570                    #set the panel when fit result are float not list
[9a5097c]1571                    if res.pvec.__class__ == np.float64:
[f76bf17]1572                        pvec = [res.pvec]
1573                    else:
1574                        pvec = res.pvec
[9a5097c]1575                    if res.stderr.__class__ == np.float64:
[f76bf17]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:
[a534432]1583                        #if res is not None:
[f76bf17]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:
[1a5d5f2]1590                        fit_msg += "\nSingular point: Fitting stopped."
[277257f]1591                    except Exception:
[1a5d5f2]1592                        fit_msg += "\nSingular point: Fitting error occurred."
1593                if fit_msg:
[277257f]1594                    evt = StatusEvent(status=fit_msg, info="warning", type="stop")
1595                    wx.PostEvent(self.parent, evt)
[f76bf17]1596
[277257f]1597        except Exception:
[e1442d4]1598            msg = ("Fit completed but the following error occurred: %s"
1599                   % sys.exc_value)
[00f7ff1]1600            #msg = "\n".join((traceback.format_exc(), msg))
[934ce649]1601            evt = StatusEvent(status=msg, info="warning", type="stop")
1602            wx.PostEvent(self.parent, evt)
[f76bf17]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()
[a534432]1626        if self.menu1 is None:
[f76bf17]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)
[934ce649]1639        wx.PostEvent(self.parent, StatusEvent(status=msg))
[f76bf17]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
[a534432]1685        if model is None:
[f76bf17]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
[c807957]1706    def create_theory_1D(self, x, y, page_id, model, data, state,
[804fefa]1707                         data_description, data_id, dy=None):
[c807957]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)
[6a0c75d1]1721        if dy is None:
1722            new_plot.is_data = False
[9a5097c]1723            new_plot.dy = np.zeros(len(y))
[6a0c75d1]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
[804fefa]1729        new_plot.interactive = True
1730        new_plot.dx = None
1731        new_plot.dxl = None
1732        new_plot.dxw = None
[c807957]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
[a534432]1737        if new_plot.group_id is None:
[c807957]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,
[277257f]1755                                  state=state)
[c807957]1756        return new_plot
1757
[f76bf17]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,
[804fefa]1762                    source='model', plot_result=True,
1763                    unsmeared_model=None, unsmeared_data=None,
[ca4d985]1764                    unsmeared_error=None, sq_model=None, pq_model=None):
[f76bf17]1765        """
[c807957]1766            Complete plotting 1D data
1767            @param unsmeared_model: fit model, without smearing
[804fefa]1768            @param unsmeared_data: data, rescaled to unsmeared model
[edbe974]1769            @param unsmeared_error: data error, rescaled to unsmeared model
[f76bf17]1770        """
[69363c7]1771<<<<<<< HEAD
[2a0f33f]1772
[69363c7]1773=======
1774>>>>>>> master
[2a0f33f]1775        number_finite = np.count_nonzero(np.isfinite(y))
[a534432]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)
[2d9526d]1780        plots_to_update = [] # List of plottables that have changed since last calculation
1781        # Create the new theories
[a534432]1782        if unsmeared_model is not None:
[69363c7]1783            unsmeared_model_plot = self.create_theory_1D(x, unsmeared_model,
[2d9526d]1784                                  page_id, model, data, state,
[a534432]1785                                  data_description=model.name + " unsmeared",
1786                                  data_id=str(page_id) + " " + data.name + " unsmeared")
[2d9526d]1787            plots_to_update.append(unsmeared_model_plot)
[a534432]1788
1789            if unsmeared_data is not None and unsmeared_error is not None:
[69363c7]1790                unsmeared_data_plot = self.create_theory_1D(x, unsmeared_data,
[2d9526d]1791                                      page_id, model, data, state,
[a534432]1792                                      data_description="Data unsmeared",
1793                                      data_id="Data  " + data.name + " unsmeared",
1794                                      dy=unsmeared_error)
[2d9526d]1795                plots_to_update.append(unsmeared_data_plot)
[e61a9b1]1796        if sq_model is not None and pq_model is not None:
[467068d]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,
[e61a9b1]1799                                  data_description=model.name + " S(q)",
[467068d]1800                                  data_id=sq_id)
[2d9526d]1801            plots_to_update.append(sq_plot)
[467068d]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,
[e61a9b1]1804                                  data_description=model.name + " P(q)",
[467068d]1805                                  data_id=pq_id)
[2d9526d]1806            plots_to_update.append(pq_plot)
1807        # Update the P(Q), S(Q) and unsmeared theory plots if they exist
[69363c7]1808        wx.PostEvent(self.parent, NewPlotEvent(plots=plots_to_update,
[2d9526d]1809                                              action='update'))
[a534432]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,
[277257f]1824                                                  fid=data.id)
[a534432]1825        if toggle_mode_on:
1826            wx.PostEvent(self.parent,
1827                         NewPlotEvent(group_id=str(page_id) + " Model2D",
[277257f]1828                                      action="Hide"))
[a534432]1829        else:
1830            if update_chisqr:
[277257f]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))
[a534432]1837            else:
1838                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1839                                     index=index, weight=weight)
[f76bf17]1840
[a534432]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."
[277257f]1844            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
[2a0f33f]1845        else:
[a534432]1846            msg = "Computation  completed!"
[82373f5]1847            if number_finite != y.size:
[5f768c2]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.")
[f76bf17]1850            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1851
[934ce649]1852    def _calc_exception(self, etype, value, tb):
1853        """
1854        Handle exception from calculator by posting it as an error.
1855        """
[c155a16]1856        logger.error("".join(traceback.format_exception(etype, value, tb)))
[934ce649]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
[f76bf17]1861    def _update2D(self, output, time=None):
1862        """
1863        Update the output of plotting model
1864        """
[934ce649]1865        msg = "Plot updating ... "
1866        wx.PostEvent(self.parent, StatusEvent(msg, type="update"))
[f76bf17]1867
1868    def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
[277257f]1869                    qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
1870                    update_chisqr=True, source='model', plot_result=True):
[f76bf17]1871        """
1872        Complete get the result of modelthread and create model 2D
1873        that can be plot.
1874        """
[2a0f33f]1875        number_finite = np.count_nonzero(np.isfinite(image))
[9a5097c]1876        np.nan_to_num(image)
[f76bf17]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,
[277257f]1911                                  theory=new_plot,
1912                                  state=state)
[f76bf17]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:
[277257f]1916            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
[f76bf17]1917        if toggle_mode_on:
1918            wx.PostEvent(self.parent,
[277257f]1919                         NewPlotEvent(group_id=str(page_id) + " Model1D",
1920                                      action="Hide"))
[f76bf17]1921        else:
1922            # Chisqr in fitpage
1923            if update_chisqr:
[277257f]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))
[f76bf17]1930            else:
1931                self._plot_residuals(page_id=page_id, data=data, fid=fid,
[277257f]1932                                     index=index, weight=weight)
[a534432]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."
[277257f]1937            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
[a534432]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"))
[f76bf17]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,
[277257f]1953                      update_chisqr=True, source='model'):
[f76bf17]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                ##
[2a0f33f]1976                ##    -PDB August 12, 2014
[f76bf17]1977                while self.calc_2D.isrunning():
1978                    time.sleep(0.1)
1979            self.calc_2D = Calc2D(model=model,
[934ce649]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)
[f76bf17]1993            self.calc_2D.queue()
1994        except:
1995            raise
1996
1997    def _draw_model1D(self, model, page_id, data,
1998                      qmin, qmax, smearer=None,
[277257f]1999                      state=None, weight=None, fid=None,
2000                      toggle_mode_on=False, update_chisqr=True, source='model',
2001                      enable1D=True):
[f76bf17]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()
[2a0f33f]2015                ## stop just raises the flag -- the thread is supposed to
[f76bf17]2016                ## then kill itself but cannot.  Paul Kienzle came up with
2017                ## this fix to prevent threads from stepping on each other
[ddbac66]2018                ## which was causing a simple custom plugin model to crash
2019                ##Sasview.
[f76bf17]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
[2a0f33f]2027                ## May need rethinking
[f76bf17]2028                ##
[d99f008]2029                ##    -PDB August 12, 2014
[f76bf17]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,
[934ce649]2045                                  exception_handler=self._calc_exception,
[f76bf17]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
[a534432]2065        # return None if data is None
2066        if not check_data_validity(data_copy) or data_copy is None:
[f76bf17]2067            return chisqr
2068
2069        # Get data: data I, theory I, and data dI in order
2070        if data_copy.__class__.__name__ == "Data2D":
[a534432]2071            if index is None:
[9a5097c]2072                index = np.ones(len(data_copy.data), dtype=bool)
[a534432]2073            if weight is not None:
[f76bf17]2074                data_copy.err_data = weight
2075            # get rid of zero error points
2076            index = index & (data_copy.err_data != 0)
[9a5097c]2077            index = index & (np.isfinite(data_copy.data))
[f76bf17]2078            fn = data_copy.data[index]
2079            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
[a534432]2080            if theory_data is None:
[f76bf17]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
[a534432]2086            if index is None:
[9a5097c]2087                index = np.ones(len(data_copy.y), dtype=bool)
[a534432]2088            if weight is not None:
[f76bf17]2089                data_copy.dy = weight
[a534432]2090            if data_copy.dy is None or data_copy.dy == []:
[9a5097c]2091                dy = np.ones(len(data_copy.y))
[f76bf17]2092            else:
[acf8e4a5]2093                ## Set consistently w/AbstractFitengine:
[f76bf17]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)
[a534432]2100            if theory_data is None:
[f76bf17]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:
[a534432]2109            print("Unmatch lengths %s, %s, %s" % (len(fn), len(gn), len(en)))
[f76bf17]2110            return
2111
[9a5097c]2112        residuals = res[np.isfinite(res)]
[f76bf17]2113        # get chisqr only w/finite
[9a5097c]2114        chisqr = np.average(residuals * residuals)
[f76bf17]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
[a534432]2143            if weight is None:
[f76bf17]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
[9a5097c]2151            residuals.err_data = np.ones(len(residuals.data))
[f76bf17]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
[a534432]2164            if data_copy.dy is None or data_copy.dy == []:
[9a5097c]2165                dy = np.ones(len(data_copy.y))
[f76bf17]2166            else:
[a534432]2167                if weight is None:
[9a5097c]2168                    dy = np.ones(len(data_copy.y))
[f76bf17]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]
[9a5097c]2187            residuals.dy = np.ones(len(residuals.y))
[f76bf17]2188            residuals.dx = None
2189            residuals.dxl = None
2190            residuals.dxw = None
2191            residuals.ytransform = 'y'
[2a0f33f]2192            # For latter scale changes
[f76bf17]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()
[a534432]2205        if group_id is None:
[f76bf17]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.