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

Last change on this file since dbc1f73 was 98b9f32, checked in by GitHub <noreply@…>, 5 years ago

Merge branch 'master' into py37-sasgui

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