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

Last change on this file since d07f863 was a9f9ca4, checked in by butler, 8 years ago

finish fixing all places that needed to point to combined batch instead
of constratined/simultaneous fits addressing #597

  • Property mode set to 100644
File size: 43.0 KB
RevLine 
[959eb01]1"""
[a9f9ca4]2    Simultaneous or Batch fit page
[959eb01]3"""
[a9f9ca4]4# Note that this is used for both Simultaneous/Constrained fit AND for
5# combined batch fit.  This is done through setting of the batch_on parameter.
6# There are the a half dozen or so places where an if statement is used as in
7# if not batch_on:
8#     xxxx
9# else:
10#     xxxx
11# This is just wrong but dont have time to fix this go. Proper approach would be
12# to strip all parts of the code that depend on batch_on and create the top
13# level class from which a contrained/simultaneous fit page and a combined
14# batch page inherit.
15#
16#            04/09/2017   --PDB
17
[959eb01]18import sys
19from collections import namedtuple
20
21import wx
22import wx.lib.newevent
23from wx.lib.scrolledpanel import ScrolledPanel
24
25from sas.sasgui.guiframe.events import StatusEvent, PanelOnFocusEvent
26from sas.sasgui.guiframe.panel_base import PanelBase
27from sas.sasgui.guiframe.utils import IdList
28from sas.sasgui.guiframe.documentation_window import DocumentationWindow
29
30# Control panel width
31if sys.platform.count("darwin") == 0:
32    PANEL_WID = 420
33    FONT_VARIANT = 0
34else:
35    PANEL_WID = 490
36    FONT_VARIANT = 1
37
38
39# Each constraint requires five widgets and sizer.  Package them in
40# a named tuple for easy access.
41ConstraintLine = namedtuple('ConstraintLine',
42        'model_cbox param_cbox egal_txt constraint btRemove sizer')
43
44
45def get_fittableParam(model):
46    """
47    return list of fittable parameters from a model
48
49    :param model: the model used
50
51    """
52    fittable_param = []
53    for item in model.getParamList():
54        if not item  in model.getDispParamList():
55            if not item in model.non_fittable:
56                fittable_param.append(item)
57
58    for item in model.fixed:
59        fittable_param.append(item)
60
61    return fittable_param
62
63
64class SimultaneousFitPage(ScrolledPanel, PanelBase):
65    """
66    Simultaneous fitting panel
67    All that needs to be defined are the
68    two data members window_name and window_caption
69    """
70    # Internal name for the AUI manager
71    window_name = "Simultaneous Fit Page"
72    # Title to appear on top of the window
73    window_caption = "Simultaneous Fit Page"
74    ID_DOC = wx.NewId()
75    ID_SET_ALL = wx.NewId()
76    ID_FIT = wx.NewId()
77    ID_ADD = wx.NewId()
78    _id_pool = IdList()
79
80    def __init__(self, parent, page_finder={}, id=wx.ID_ANY, batch_on=False,
81                 *args, **kwargs):
82        ScrolledPanel.__init__(self, parent, id=id,
83                               style=wx.FULL_REPAINT_ON_RESIZE,
84                               *args, **kwargs)
85        PanelBase.__init__(self, parent)
86        """
87        Simultaneous page display
88        """
89        self._ids = iter(self._id_pool)
90        self.SetupScrolling()
91        # Font size
92        self.SetWindowVariant(variant=FONT_VARIANT)
93        self.uid = wx.NewId()
94        self.parent = parent
95        self.batch_on = batch_on
96        # store page_finder
97        self.page_finder = page_finder
98        # list containing info to set constraint
99        # look like self.constraint_dict[page_id]= page
100        self.constraint_dict = {}
101        # item list
102        # self.constraints_list=[combobox1, combobox2,=,textcrtl, button ]
103        self.constraints_list = []
104        # list of current model
105        self.model_list = []
106        # selected model to fit
107        self.model_to_fit = []
108        # Control the fit state
109        self.fit_started = False
110        # number of constraint
111        self.nb_constraint = 0
112        self.state = SimFitPageState()
113        self.model_cbox_left = None
114        self.model_cbox_right = None
115        # draw page
116        self.define_page_structure()
117        self.draw_page()
118        self._set_save_flag(False)
119
120    def define_page_structure(self):
121        """
122        Create empty sizers, their hierarchy and set the sizer for the panel
123        """
124        self.vbox = wx.BoxSizer(wx.VERTICAL)
125        self.data_selection_sizer = wx.BoxSizer(wx.VERTICAL)
126        self.constraints_sizer = wx.BoxSizer(wx.VERTICAL)
127        self.run_fit_sizer = wx.BoxSizer(wx.VERTICAL)
128
129        self.data_selection_sizer.SetMinSize((PANEL_WID, -1))
130        self.constraints_sizer.SetMinSize((PANEL_WID, -1))
131        self.run_fit_sizer.SetMinSize((PANEL_WID, -1))
132        self.vbox.Add(self.data_selection_sizer)
133        self.vbox.Add(self.constraints_sizer)
134        self.vbox.Add(self.run_fit_sizer)
135        self.SetSizer(self.vbox)
136        self.Centre()
137
138    def set_state(self):
139        """
140        Define a set of state parameters for saving simultaneous fits.
141        """
142        self._set_constraint()
143        self.state.fit_page_no = self.uid
144        self.state.select_all = self.cb1.GetValue()
145        self.state.model_list = self.model_list
146        self.state.model_to_fit = self.model_to_fit
147        self.state.no_constraint = self.nb_constraint
148        self.state.constraint_dict = self.constraint_dict
149        self.state.constraints_list = self.constraints_list
150        return self.get_state()
151
152    def get_state(self):
153        """
154        Return the state of the current page
155        :return: self.state
156        """
157        return self.state
158
159    def draw_page(self):
160        """
161        Construct the Simultaneous/Constrained fit page. fills the first
162        region (sizer1) with the list of available fit page pairs of data
163        and models.  Then fills sizer2 with the checkbox for adding
164        constraints, and finally fills sizer3 with the fit button and
165        instructions.
166        """
167
168        # create blank list of constraints
169        self.model_list = []
170        self.model_to_fit = []
171        self.constraints_list = []
172        self.constraint_dict = {}
173        self.nb_constraint = 0
174        self.model_cbox_left = None
175        self.model_cbox_right = None
176
177        if len(self.model_list) > 0:
178            for item in self.model_list:
179                item[0].SetValue(False)
180                self.manager.schedule_for_fit(value=0, uid=item[2])
181
182        #-------------------------------------------------------
183        # setup sizer1 (which fitpages to include)
184        self.data_selection_sizer.Clear(True)
185        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit Combinations")
186        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
187        sizer_title = wx.BoxSizer(wx.HORIZONTAL)
188        sizer_couples = wx.GridBagSizer(5, 5)
189
190        # The wx GUI has a flag to enable a menu item, but can still be
191        # reached via scripting. There is no guearantee future GUI
192        # implementations force this check, either.
193        # IMHO, this if statement should stay -- JRK 2016-OCT-05
194        if len(self.page_finder) == 0:
195            msg = " No fit combinations are found! \n\n"
196            msg += " Please load data and set up "
197            msg += "at least one fit panels first..."
198            sizer_title.Add(wx.StaticText(self, wx.ID_ANY, msg))
199        else:
200            # store model
201            self._store_model()
202
203            self.cb1 = wx.CheckBox(self, wx.ID_ANY, 'Select all')
204            self.cb1.SetValue(False)
205            wx.EVT_CHECKBOX(self, self.cb1.GetId(), self.check_all_model_name)
206
207            sizer_title.Add((10, 10), 0,
208                wx.TOP | wx.BOTTOM | wx.EXPAND | wx.ADJUST_MINSIZE, border=5)
209            sizer_title.Add(self.cb1, 0,
210                wx.TOP | wx.BOTTOM | wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE,
211                border=5)
212
213            # draw list of model and data names
214            self._fill_sizer_model_list(sizer_couples)
215
216        boxsizer1.Add(sizer_title, flag=wx.TOP | wx.BOTTOM, border=5)
217        boxsizer1.Add(sizer_couples, 1, flag=wx.TOP | wx.BOTTOM, border=5)
218        self.data_selection_sizer.Add(boxsizer1, 1, wx.EXPAND | wx.ALL, 10)
219        # self.sizer1.Layout()
220
221        #--------------------------------------------------------
222        # set up the other 2 sizers: the constraints list and the
223        # buttons (fit, help etc) sizer at the bottom of the page.
224        # Note: the if statement should be removed along with the above
225        # if statement as soon as it can be properly tested.
226        # Nov. 22 2015  --PDB
227        # As above, this page can be accessed through other means than the
228        # base SasView GUI.
229        # Oct. 5, 2016 --JRK
230        if len(self.page_finder) > 0:
231            # draw the sizer containing constraint info
232            if not self.batch_on:
233                self._fill_sizer_constraint()
234            # draw fit button sizer
235            self._fill_sizer_fit()
236
237    def _fill_sizer_model_list(self, sizer):
238        """
239        Receive a dictionary containing information to display model name
240        """
241        ix = 0
242        iy = 0
243        sizer.Clear(True)
244
245        new_name = wx.StaticText(self, wx.ID_ANY, '  Model Title ',
246                                 style=wx.ALIGN_CENTER)
247        new_name.SetBackgroundColour('orange')
248        new_name.SetForegroundColour(wx.WHITE)
249        sizer.Add(new_name, (iy, ix), (1, 1),
250                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
251        ix += 2
252        model_type = wx.StaticText(self, wx.ID_ANY, '  Model ')
253        model_type.SetBackgroundColour('grey')
254        model_type.SetForegroundColour(wx.WHITE)
255        sizer.Add(model_type, (iy, ix), (1, 1),
256                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
257        ix += 1
258        data_used = wx.StaticText(self, wx.ID_ANY, '  Data ')
259        data_used.SetBackgroundColour('grey')
260        data_used.SetForegroundColour(wx.WHITE)
261        sizer.Add(data_used, (iy, ix), (1, 1),
262                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
263        ix += 1
264        tab_used = wx.StaticText(self, wx.ID_ANY, '  FitPage ')
265        tab_used.SetBackgroundColour('grey')
266        tab_used.SetForegroundColour(wx.WHITE)
267        sizer.Add(tab_used, (iy, ix), (1, 1),
268                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
269        for id, value in self.page_finder.iteritems():
270            if id not in self.parent.opened_pages:
271                continue
272
273            if self.batch_on != self.parent.get_page_by_id(id).batch_on:
274                continue
275
276            data_list = []
277            model_list = []
278            # get data name and model objetta
279            for fitproblem in value.get_fit_problem():
280
281                data = fitproblem.get_fit_data()
282                if not data.is_data:
283                    continue
284                name = '-'
285                if data is not None and data.is_data:
286                    name = str(data.name)
287                data_list.append(name)
288
289                model = fitproblem.get_model()
290                if model is None:
291                    continue
292                model_list.append(model)
293
294            if len(model_list) == 0:
295                continue
296            # Draw sizer
297            ix = 0
298            iy += 1
299            model = model_list[0]
300            name = '_'
301            if model is not None:
302                name = str(model.name)
303            cb = wx.CheckBox(self, wx.ID_ANY, name)
304            cb.SetValue(False)
305            cb.Enable(model is not None and data.is_data)
306            sizer.Add(cb, (iy, ix), (1, 1),
307                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
308            wx.EVT_CHECKBOX(self, cb.GetId(), self.check_model_name)
309            ix += 2
310            model_type = wx.StaticText(self, wx.ID_ANY,
311                                       model.__class__.__name__)
312            sizer.Add(model_type, (iy, ix), (1, 1),
313                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
314            if self.batch_on:
315                data_used = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
316                data_used.AppendItems(data_list)
317                data_used.SetSelection(0)
318            else:
319                data_used = wx.StaticText(self, wx.ID_ANY, data_list[0])
320
321            ix += 1
322            sizer.Add(data_used, (iy, ix), (1, 1),
323                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
324            ix += 1
325            caption = value.get_fit_tab_caption()
326            tab_caption_used = wx.StaticText(self, wx.ID_ANY, str(caption))
327            sizer.Add(tab_caption_used, (iy, ix), (1, 1),
328                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
329
330            self.model_list.append([cb, value, id, model])
331
332        iy += 1
333        sizer.Add((20, 20), (iy, ix), (1, 1),
334                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
335
336    def _fill_sizer_constraint(self):
337        """
338        Fill sizer containing constraint info
339        """
340        msg = "Select at least 1 model to add constraint "
341        wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
342
343        self.constraints_sizer.Clear(True)
344        if self.batch_on:
345            if self.constraints_sizer.IsShown():
346                self.constraints_sizer.Show(False)
347            return
348        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit Constraints")
349        box_sizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
350        sizer_title = wx.BoxSizer(wx.HORIZONTAL)
351        self.sizer_all_constraints = wx.BoxSizer(wx.HORIZONTAL)
352        self.sizer_constraints = wx.BoxSizer(wx.VERTICAL)
353        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
354
355        self.hide_constraint = wx.RadioButton(self, wx.ID_ANY, 'No', (10, 10),
356                                              style=wx.RB_GROUP)
357        self.show_constraint = wx.RadioButton(self, wx.ID_ANY, 'Yes', (10, 30))
358        self.Bind(wx.EVT_RADIOBUTTON, self._display_constraint,
359                  id=self.hide_constraint.GetId())
360        self.Bind(wx.EVT_RADIOBUTTON, self._display_constraint,
361                  id=self.show_constraint.GetId())
362        if self.batch_on:
363            self.hide_constraint.Enable(False)
364            self.show_constraint.Enable(False)
365        self.hide_constraint.SetValue(True)
366        self.show_constraint.SetValue(False)
367
368        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Model"))
369        sizer_title.Add((10, 10))
370        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Parameter"))
371        sizer_title.Add((10, 10))
372        sizer_title.Add(wx.StaticText(self, wx.ID_ANY, " Add Constraint?"))
373        sizer_title.Add((10, 10))
374        sizer_title.Add(self.show_constraint)
375        sizer_title.Add(self.hide_constraint)
376        sizer_title.Add((10, 10))
377
378        self.btAdd = wx.Button(self, self.ID_ADD, 'Add')
379        self.btAdd.Bind(wx.EVT_BUTTON, self._on_add_constraint,
380                        id=self.btAdd.GetId())
381        self.btAdd.SetToolTipString("Add another constraint?")
382        self.btAdd.Hide()
383
384        text_hint = wx.StaticText(self, wx.ID_ANY,
385                                  "Example: [M0][parameter] = M1.parameter")
386        sizer_button.Add(text_hint, 0,
387                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
388        sizer_button.Add(self.btAdd, 0,
389                         wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)
390
391        box_sizer1.Add(sizer_title, flag=wx.TOP | wx.BOTTOM, border=10)
392        box_sizer1.Add(self.sizer_all_constraints, flag=wx.TOP | wx.BOTTOM,
393                       border=10)
394        box_sizer1.Add(self.sizer_constraints, flag=wx.TOP | wx.BOTTOM,
395                       border=10)
396        box_sizer1.Add(sizer_button, flag=wx.TOP | wx.BOTTOM, border=10)
397
398        self.constraints_sizer.Add(box_sizer1, 0, wx.EXPAND | wx.ALL, 10)
399
400    def _fill_sizer_fit(self):
401        """
402        Draw fit button
403        """
404        self.run_fit_sizer.Clear(True)
405        box_description = wx.StaticBox(self, wx.ID_ANY, "Fit ")
406        boxsizer1 = wx.StaticBoxSizer(box_description, wx.VERTICAL)
407        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
408
409        # Fit button
410        self.btFit = wx.Button(self, self.ID_FIT, 'Fit', size=wx.DefaultSize)
411        self.btFit.Bind(wx.EVT_BUTTON, self.on_fit, id=self.btFit.GetId())
412        self.btFit.SetToolTipString("Perform fit.")
413
414        # General Help button
415        self.btHelp = wx.Button(self, wx.ID_HELP, 'HELP')
[a9f9ca4]416        if self.batch_on:
417            self.btHelp.SetToolTipString("Combined Batch Fitting help.")
418        else:
419            self.btHelp.SetToolTipString("Simultaneous/Constrained Fitting help.")
[959eb01]420        self.btHelp.Bind(wx.EVT_BUTTON, self._on_help)
421
422        # hint text on button line
423        if self.batch_on:
424            text = " Fit in Parallel all Data sets\n"
425            text += "and model selected."
426        else:
427            text = " At least one set of model and data\n"
428            text += " must be selected for fitting."
429        text_hint = wx.StaticText(self, wx.ID_ANY, text)
430
431        sizer_button.Add(text_hint)
432        sizer_button.Add(self.btFit, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
433        sizer_button.Add(self.btHelp, 0, wx.LEFT | wx.ADJUST_MINSIZE, 10)
434
435        boxsizer1.Add(sizer_button, flag=wx.TOP | wx.BOTTOM, border=10)
436        self.run_fit_sizer.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
437
438    def on_remove(self, event):
439        """
440        Remove constraint fields
441        """
442        if len(self.constraints_list) == 1:
443            self.hide_constraint.SetValue(True)
444            self._hide_constraint()
445            return
446        if len(self.constraints_list) == 0:
447            return
448        wx.CallAfter(self._remove_after, event.GetId())
449        # self._onAdd_constraint(None)
450
451    def _remove_after(self, id):
452        for item in self.constraints_list:
453            if id == item.btRemove.GetId():
454                self.sizer_constraints.Hide(item.sizer)
455                item.sizer.Clear(True)
456                self.sizer_constraints.Remove(item.sizer)
457                self.constraints_list.remove(item)
458                self.nb_constraint -= 1
459                self.constraints_sizer.Layout()
460                self.FitInside()
461                break
462
463    def on_fit(self, event):
464        """
465        signal for fitting
466
467        """
468        if self.fit_started:
469            self._stop_fit()
470            self.fit_started = False
471            return
472
473        flag = False
474        # check if the current page a simultaneous fit page or a batch page
475        if self == self._manager.sim_page:
476            flag = (self._manager.sim_page.uid == self.uid)
477
478        # making sure all parameters content a constraint
479        if not self.batch_on and self.show_constraint.GetValue():
480            if not self._set_constraint():
481                return
482        # model was actually selected from this page to be fit
483        if len(self.model_to_fit) >= 1:
484            self.manager._reset_schedule_problem(value=0)
485            for item in self.model_list:
486                if item[0].GetValue():
487                    self.manager.schedule_for_fit(value=1, uid=item[2])
488            try:
489                self.fit_started = True
490                wx.CallAfter(self.set_fitbutton)
491                if not self.manager.onFit(uid=self.uid):
492                    return
493            except:
494                msg = "Select at least one parameter to fit in the FitPages."
495                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
496        else:
497            msg = "Select at least one model check box to fit "
498            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
499        self.set_state()
500
501    def _on_fit_complete(self):
502        """
503        Set the completion flag and display the updated fit button label.
504        """
505        self.fit_started = False
506        self.set_fitbutton()
507
508    def _stop_fit(self, event=None):
509        """
510        Attempt to stop the fitting thread
511
512        :param event: Event handler when stop fit is clicked
513        """
514        if event is not None:
515            event.Skip()
516        self.manager.stop_fit(self.uid)
517        self.manager._reset_schedule_problem(value=0)
518        self._on_fit_complete()
519
520    def set_fitbutton(self):
521        """
522        Set fit button label depending on the fit_started
523        """
524        label = "Stop" if self.fit_started else "Fit"
525        color = "red" if self.fit_started else "black"
526
527        self.btFit.SetLabel(label)
528        self.btFit.SetForegroundColour(color)
529        self.btFit.Enable(True)
530
531    def _on_help(self, event):
532        """
533        Bring up the simultaneous Fitting Documentation whenever the HELP
534        button is clicked.
535
536        Calls DocumentationWindow with the path of the location within the
537        documentation tree (after /doc/ ....".  Note that when using old
538        versions of Wx (before 2.9) and thus not the release version of
539        installers, the help comes up at the top level of the file as
540        web browser does not pass anything past the # to the browser when it is
541        running "file:///...."
542
543    :param event: Triggers on clicking the help button
544    """
[a9f9ca4]545        _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
[9b5e70c]546        if not self.batch_on:
547            _PageAnchor = "#simultaneous-fit-mode"
[a9f9ca4]548            _doc_viewer = DocumentationWindow(self, self.ID_DOC, _TreeLocation,
549                                          _PageAnchor,
550                                          "Simultaneous/Constrained Fitting Help")
[9b5e70c]551        else:
552            _PageAnchor = "#combined-batch-fit-mode"
[a9f9ca4]553            _doc_viewer = DocumentationWindow(self, self.ID_DOC, _TreeLocation,
[959eb01]554                                          _PageAnchor,
[a9f9ca4]555                                          "Combined Batch Fit Help")
[959eb01]556
557    def set_manager(self, manager):
558        """
559        set panel manager
560
561        :param manager: instance of plugin fitting
562        """
563        self.manager = manager
564
565    def check_all_model_name(self, event=None):
566        """
567        check all models names
568        """
569        self.model_to_fit = []
570        if self.cb1.GetValue():
571            for item in self.model_list:
572                if item[0].IsEnabled():
573                    item[0].SetValue(True)
574                    self.model_to_fit.append(item)
575
576            # constraint info
577            self._store_model()
578            if not self.batch_on:
579                # display constraint fields
580                if (self.show_constraint.GetValue() and
581                                 len(self.constraints_list) == 0):
582                    self._show_all_constraint()
583                    self._show_constraint()
584        else:
585            for item in self.model_list:
586                item[0].SetValue(False)
587
588            if not self.batch_on:
589                # constraint info
590                self._hide_constraint()
591
592        self._update_easy_setup_cb()
593        self.FitInside()
594
595    def check_model_name(self, event):
596        """
597        Save information related to checkbox and their states
598        """
599        self.model_to_fit = []
600        for item in self.model_list:
601            if item[0].GetValue():
602                self.model_to_fit.append(item)
603            else:
604                if item in self.model_to_fit:
605                    self.model_to_fit.remove(item)
606                    self.cb1.SetValue(False)
607
608        # display constraint fields
609        if len(self.model_to_fit) >= 1:
610            self._store_model()
611            if not self.batch_on and self.show_constraint.GetValue() and\
612                             len(self.constraints_list) == 0:
613                self._show_all_constraint()
614                self._show_constraint()
615
616        elif len(self.model_to_fit) < 1:
617            # constraint info
618            self._hide_constraint()
619
620        self._update_easy_setup_cb()
621        # set the value of the main check button
622        if len(self.model_list) == len(self.model_to_fit):
623            self.cb1.SetValue(True)
624            self.FitInside()
625            return
626        else:
627            self.cb1.SetValue(False)
628            self.FitInside()
629
630    def _update_easy_setup_cb(self):
631        """
632        Update easy setup combobox on selecting a model
633        """
634        if self.model_cbox_left is None or self.model_cbox_right is None:
635            return
636
637        models = [(item[3].name, item[3]) for item in self.model_to_fit]
638        setComboBoxItems(self.model_cbox_left, models)
639        setComboBoxItems(self.model_cbox_right, models)
640        for item in self.constraints_list:
641            setComboBoxItems(item[0], models)
642        if self.model_cbox_left.GetSelection() == wx.NOT_FOUND:
643            self.model_cbox_left.SetSelection(0)
644        self.constraints_sizer.Layout()
645
646    def _store_model(self):
647        """
648         Store selected model
649        """
650        if len(self.model_to_fit) < 1:
651            return
652        for item in self.model_to_fit:
653            model = item[3]
654            page_id = item[2]
655            self.constraint_dict[page_id] = model
656
657    def _display_constraint(self, event):
658        """
659        Show fields to add constraint
660        """
661        if len(self.model_to_fit) < 1:
662            msg = "Select at least 1 model to add constraint "
663            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
664            # hide button
665            self._hide_constraint()
666            return
667        if self.show_constraint.GetValue():
668            self._show_all_constraint()
669            self._show_constraint()
670            self.FitInside()
671            return
672        else:
673            self._hide_constraint()
674            return
675
676    def _show_all_constraint(self):
677        """
678        Show constraint fields
679        """
680        box_description = wx.StaticBox(self, wx.ID_ANY, "Easy Setup ")
681        box_sizer = wx.StaticBoxSizer(box_description, wx.HORIZONTAL)
682        sizer_constraint = wx.BoxSizer(wx.HORIZONTAL)
683        self.model_cbox_left = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
684        self.model_cbox_left.Clear()
685        self.model_cbox_right = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
686        self.model_cbox_right.Clear()
687        wx.EVT_COMBOBOX(self.model_cbox_left, wx.ID_ANY, self._on_select_modelcb)
688        wx.EVT_COMBOBOX(self.model_cbox_right, wx.ID_ANY, self._on_select_modelcb)
689        egal_txt = wx.StaticText(self, wx.ID_ANY, " = ")
690        self.set_button = wx.Button(self, self.ID_SET_ALL, 'Set All')
691        self.set_button.Bind(wx.EVT_BUTTON, self._on_set_all_equal,
692                             id=self.set_button.GetId())
693        set_tip = "Add constraints for all the adjustable parameters "
694        set_tip += "(checked in FitPages) if exist."
695        self.set_button.SetToolTipString(set_tip)
696        self.set_button.Disable()
697
698        for id, model in self.constraint_dict.iteritems():
699            # check if all parameters have been selected for constraint
700            # then do not allow add constraint on parameters
701            self.model_cbox_left.Append(str(model.name), model)
702        self.model_cbox_left.Select(0)
703        for id, model in self.constraint_dict.iteritems():
704            # check if all parameters have been selected for constraint
705            # then do not allow add constraint on parameters
706            self.model_cbox_right.Append(str(model.name), model)
707        box_sizer.Add(self.model_cbox_left,
708                             flag=wx.RIGHT | wx.EXPAND, border=10)
709        # box_sizer.Add(wx.StaticText(self, wx.ID_ANY, ".parameters"),
710        #                     flag=wx.RIGHT | wx.EXPAND, border=5)
711        box_sizer.Add(egal_txt, flag=wx.RIGHT | wx.EXPAND, border=5)
712        box_sizer.Add(self.model_cbox_right,
713                             flag=wx.RIGHT | wx.EXPAND, border=10)
714        # box_sizer.Add(wx.StaticText(self, wx.ID_ANY, ".parameters"),
715        #                     flag=wx.RIGHT | wx.EXPAND, border=5)
716        box_sizer.Add((20, -1))
717        box_sizer.Add(self.set_button, flag=wx.RIGHT | wx.EXPAND, border=5)
718        sizer_constraint.Add(box_sizer, flag=wx.RIGHT | wx.EXPAND, border=5)
719        self.sizer_all_constraints.Insert(before=0,
720                             item=sizer_constraint,
721                             flag=wx.TOP | wx.BOTTOM | wx.EXPAND, border=5)
722        self.FitInside()
723
724    def _on_select_modelcb(self, event):
725        """
726        On select model left or right combobox
727        """
728        event.Skip()
729        flag = True
730        if self.model_cbox_left.GetValue().strip() == '':
731            flag = False
732        if self.model_cbox_right.GetValue().strip() == '':
733            flag = False
734        if (self.model_cbox_left.GetValue() ==
735                self.model_cbox_right.GetValue()):
736            flag = False
737        self.set_button.Enable(flag)
738
739    def _on_set_all_equal(self, event):
740        """
741        On set button
742        """
743        event.Skip()
744        length = len(self.constraints_list)
745        if length < 1:
746            return
747        param_list = []
748        param_list_b = []
749        selection = self.model_cbox_left.GetCurrentSelection()
750        model_left = self.model_cbox_left.GetValue()
751        model = self.model_cbox_left.GetClientData(selection)
752        selection_b = self.model_cbox_right.GetCurrentSelection()
753        model_right = self.model_cbox_right.GetValue()
754        model_b = self.model_cbox_right.GetClientData(selection_b)
755        for id, dic_model in self.constraint_dict.iteritems():
756            if model == dic_model:
757                param_list = self.page_finder[id].get_param2fit()
758            if model_b == dic_model:
759                param_list_b = self.page_finder[id].get_param2fit()
760            if len(param_list) > 0 and len(param_list_b) > 0:
761                break
762        num_cbox = 0
763        has_param = False
764        for param in param_list:
765            num_cbox += 1
766            if param in param_list_b:
767                item = self.constraints_list[-1]
768                item.model_cbox.SetStringSelection(model_left)
769                self._on_select_model(None)
770                item.param_cbox.Clear()
771                item.param_cbox.Append(str(param), model)
772                item.param_cbox.SetStringSelection(str(param))
773                item.constraint.SetValue(str(model_right + "." + str(param)))
774                has_param = True
775                if num_cbox == (len(param_list) + 1):
776                    break
777                self._show_constraint()
778
779        self.FitInside()
780        if not has_param:
781            msg = " There is no adjustable parameter (checked to fit)"
782            msg += " either one of the models."
783            wx.PostEvent(self.parent.parent, StatusEvent(info="warning",
784                                                         status=msg))
785        else:
786            msg = " The constraints are added."
787            wx.PostEvent(self.parent.parent, StatusEvent(info="info",
788                                                         status=msg))
789
790    def _show_constraint(self):
791        """
792        Show constraint fields
793        :param dict: dictionary mapping constraint values
794        """
795        self.btAdd.Show(True)
796        if len(self.constraints_list) != 0:
797            nb_fit_param = 0
798            for id, model in self.constraint_dict.iteritems():
799                nb_fit_param += len(self.page_finder[id].get_param2fit())
800            # Don't add anymore
801            if len(self.constraints_list) == nb_fit_param:
802                msg = "Cannot add another constraint. Maximum of number "
803                msg += "Parameters name reached %s" % str(nb_fit_param)
804                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
805                self.sizer_constraints.Layout()
806                self.constraints_sizer.Layout()
807                return
808        if len(self.model_to_fit) < 1:
809            msg = "Select at least 1 model to add constraint "
810            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
811            self.sizer_constraints.Layout()
812            self.constraints_sizer.Layout()
813            return
814
815        sizer_constraint = wx.BoxSizer(wx.HORIZONTAL)
816
817        # Model list
818        model_cbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY)
819        model_cbox.Clear()
820        for id, model in self.constraint_dict.iteritems():
821            # check if all parameters have been selected for constraint
822            # then do not allow add constraint on parameters
823            model_cbox.Append(str(model.name), model)
824        wx.EVT_COMBOBOX(model_cbox, wx.ID_ANY, self._on_select_model)
825
826        # Parameters in model
827        param_cbox = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_READONLY,
828                                 size=(100, -1))
829        param_cbox.Hide()
830        wx.EVT_COMBOBOX(param_cbox, wx.ID_ANY, self._on_select_param)
831
832        egal_txt = wx.StaticText(self, wx.ID_ANY, " = ")
833
834        # Parameter constraint
835        constraint = wx.TextCtrl(self, wx.ID_ANY)
836
837        # Remove button
838        #btRemove = wx.Button(self, self.ID_REMOVE, 'Remove')
839        bt_remove = wx.Button(self, self._ids.next(), 'Remove')
840        bt_remove.Bind(wx.EVT_BUTTON, self.on_remove,
841                      id=bt_remove.GetId())
842        bt_remove.SetToolTipString("Remove constraint.")
843        bt_remove.Hide()
844
845        # Hid the add button, if it exists
846        if hasattr(self, "btAdd"):
847            self.btAdd.Hide()
848
849        sizer_constraint.Add((5, -1))
850        sizer_constraint.Add(model_cbox, flag=wx.RIGHT | wx.EXPAND, border=10)
851        sizer_constraint.Add(param_cbox, flag=wx.RIGHT | wx.EXPAND, border=5)
852        sizer_constraint.Add(egal_txt, flag=wx.RIGHT | wx.EXPAND, border=5)
853        sizer_constraint.Add(constraint, flag=wx.RIGHT | wx.EXPAND, border=10)
854        sizer_constraint.Add(bt_remove, flag=wx.RIGHT | wx.EXPAND, border=10)
855
856        self.sizer_constraints.Insert(before=self.nb_constraint,
857                item=sizer_constraint, flag=wx.TOP | wx.BOTTOM | wx.EXPAND,
858                border=5)
859        c = ConstraintLine(model_cbox, param_cbox, egal_txt,
860                           constraint, bt_remove, sizer_constraint)
861        self.constraints_list.append(c)
862
863        self.nb_constraint += 1
864        self.sizer_constraints.Layout()
865        self.constraints_sizer.Layout()
866        self.Layout()
867
868    def _hide_constraint(self):
869        """
870        hide buttons related constraint
871        """
872        for id in self.page_finder.iterkeys():
873            self.page_finder[id].clear_model_param()
874
875        self.nb_constraint = 0
876        self.constraint_dict = {}
877        if hasattr(self, "btAdd"):
878            self.btAdd.Hide()
879        self._store_model()
880        if self.model_cbox_left is not None:
881            self.model_cbox_left.Clear()
882            self.model_cbox_left = None
883        if self.model_cbox_right is not None:
884            self.model_cbox_right.Clear()
885            self.model_cbox_right = None
886        self.constraints_list = []
887        self.sizer_all_constraints.Clear(True)
888        self.sizer_all_constraints.Layout()
889        self.sizer_constraints.Clear(True)
890        self.sizer_constraints.Layout()
891        self.constraints_sizer.Layout()
892        self.Layout()
893        self.FitInside()
894
895    def _on_select_model(self, event):
896        """
897        fill combo box with list of parameters
898        """
899        if not self.constraints_list:
900            return
901
902        # This way PC/MAC both work, instead of using event.GetClientData().
903        model_cbox = self.constraints_list[-1].model_cbox
904        n = model_cbox.GetCurrentSelection()
905        if n == wx.NOT_FOUND:
906            return
907
908        model = model_cbox.GetClientData(n)
909        param_list = []
910        for id, dic_model in self.constraint_dict.iteritems():
911            if model == dic_model:
912                param_list = self.page_finder[id].get_param2fit()
913                break
914
915        param_cbox = self.constraints_list[-1].param_cbox
916        param_cbox.Clear()
917        # insert only fittable paramaters
918        for param in param_list:
919            param_cbox.Append(str(param), model)
920        param_cbox.Show(True)
921
922        bt_remove = self.constraints_list[-1].btRemove
923        bt_remove.Show(True)
924        self.btAdd.Show(True)
925#        self.Layout()
926        self.FitInside()
927
928    def _on_select_param(self, event):
929        """
930        Store the appropriate constraint in the page_finder
931        """
932        # This way PC/MAC both work, instead of using event.GetClientData().
933        # n = self.param_cbox.GetCurrentSelection()
934        # model = self.param_cbox.GetClientData(n)
935        # param = event.GetString()
936
937        if self.constraints_list:
938            self.constraints_list[-1].egal_txt.Show(True)
939            self.constraints_list[-1].constraint.Show(True)
940
941    def _on_add_constraint(self, event):
942        """
943        Add another line for constraint
944        """
945        if not self.show_constraint.GetValue():
946            msg = " Select Yes to add Constraint "
947            wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
948            return
949        # check that a constraint is added
950        # before allow to add another constraint
951        for item in self.constraints_list:
952            if item.model_cbox.GetString(0) == "":
953                msg = " Select a model Name! "
954                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
955                return
956            if item.param_cbox.GetString(0) == "":
957                msg = " Select a parameter Name! "
958                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
959                return
960            if item.constraint.GetValue().lstrip().rstrip() == "":
961                model = item.param_cbox.GetClientData(
962                                        item.param_cbox.GetCurrentSelection())
963                if model is not None:
964                    msg = " Enter a constraint for %s.%s! " % (model.name,
965                                        item.param_cbox.GetString(0))
966                else:
967                    msg = " Enter a constraint"
968                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
969                return
970        # some model or parameters can be constrained
971        self._show_constraint()
972        self.FitInside()
973
974    def _set_constraint(self):
975        """
976        get values from the constraint textcrtl ,parses them into model name
977        parameter name and parameters values.
978        store them in a list self.params .when when params is not empty
979        set_model uses it to reset the appropriate model
980        and its appropriates parameters
981        """
982        for item in self.constraints_list:
983            select0 = item.model_cbox.GetSelection()
984            if select0 == wx.NOT_FOUND:
985                continue
986            model = item.model_cbox.GetClientData(select0)
987            select1 = item.param_cbox.GetSelection()
988            if select1 == wx.NOT_FOUND:
989                continue
990            param = item.param_cbox.GetString(select1)
991            constraint = item.constraint.GetValue().lstrip().rstrip()
992            if param.lstrip().rstrip() == "":
993                param = None
994                msg = " Constraint will be ignored!. missing parameters"
995                msg += " in combobox to set constraint! "
996                wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
997            for id, value in self.constraint_dict.iteritems():
998                if model == value:
999                    if constraint == "":
1000                        msg = " Constraint will be ignored!. missing value"
1001                        msg += " in textcrtl to set constraint! "
1002                        wx.PostEvent(self.parent.parent,
1003                                     StatusEvent(status=msg))
1004                        constraint = None
1005                    if str(param) in self.page_finder[id].get_param2fit():
1006                        msg = " Checking constraint for parameter: %s ", param
1007                        wx.PostEvent(self.parent.parent,
1008                                     StatusEvent(info="info", status=msg))
1009                    else:
1010                        model_name = item[0].GetLabel()
1011                        fitpage = self.page_finder[id].get_fit_tab_caption()
1012                        msg = "All constrainted parameters must be set "
1013                        msg += " adjustable: '%s.%s' " % (model_name, param)
1014                        msg += "is NOT checked in '%s'. " % fitpage
1015                        msg += " Please check it to fit or"
1016                        msg += " remove the line of the constraint."
1017                        wx.PostEvent(self.parent.parent,
1018                                StatusEvent(info="error", status=msg))
1019                        return False
1020
1021                    for fid in self.page_finder[id].iterkeys():
1022                        # wrap in param/constraint in str() to remove unicode
1023                        self.page_finder[id].set_model_param(str(param),
1024                                str(constraint), fid=fid)
1025                    break
1026        return True
1027
1028    def on_set_focus(self, event=None):
1029        """
1030        The derivative class is on focus if implemented
1031        """
1032        if self.parent is not None:
1033            if self.parent.parent is not None:
1034                wx.PostEvent(self.parent.parent, PanelOnFocusEvent(panel=self))
1035            self.page_finder = self.parent._manager.get_page_finder()
1036
1037
1038def setComboBoxItems(cbox, items):
1039    assert isinstance(cbox, wx.ComboBox)
1040    selected = cbox.GetStringSelection()
1041    cbox.Clear()
1042    for k, (name, value) in enumerate(items):
1043        cbox.Append(name, value)
1044    cbox.SetStringSelection(selected)
1045
1046
1047class SimFitPageState:
1048    """
1049    State of the simultaneous fit page for saving purposes
1050    """
1051
1052    def __init__(self):
1053        # Sim Fit Page Number
1054        self.fit_page_no = None
1055        # Select all data
1056        self.select_all = False
1057        # Data sets sent to fit page
1058        self.model_list = []
1059        # Data sets to be fit
1060        self.model_to_fit = []
1061        # Number of constraints
1062        self.no_constraint = 0
1063        # Dictionary of constraints
1064        self.constraint_dict = {}
1065        # List of constraints
1066        self.constraints_list = []
1067
1068    def load_from_save_state(self, fit):
1069        """
1070        Load in a simultaneous/constrained fit from a save state
1071        :param fit: Fitpanel object
1072        :return: None
1073        """
1074
1075        model_map = {}
1076        if fit.fit_panel.sim_page is None:
1077            fit.fit_panel.add_sim_page()
1078        sim_page = fit.fit_panel.sim_page
1079
1080        # Process each model and associate old M# with new M#
1081        i = 0
1082        for model in sim_page.model_list:
1083            model_id = self._format_id(model[1].keys()[0])
1084            for saved_model in self.model_list:
1085                save_id = saved_model.pop('name')
1086                saved_model['name'] = save_id
1087                save_id = self._format_id(save_id)
1088                if save_id == model_id:
1089                    model_map[saved_model.pop('fit_page_source')] = \
1090                        model[3].name
1091                    check = bool(saved_model.pop('checked'))
1092                    sim_page.model_list[i][0].SetValue(check)
1093                    break
1094            i += 1
1095        sim_page.check_model_name(None)
1096
1097        if len(self.constraints_list) > 0:
1098            sim_page.hide_constraint.SetValue(False)
1099            sim_page.show_constraint.SetValue(True)
1100            sim_page._display_constraint(None)
1101
1102        for index, item in enumerate(self.constraints_list):
1103            model_cbox = item.pop('model_cbox')
1104            if model_cbox != "":
1105                constraint_value = item.pop('constraint')
1106                param = item.pop('param_cbox')
1107                equality = item.pop('egal_txt')
1108                for key, value in model_map.iteritems():
1109                    model_cbox.replace(key, value)
1110                    constraint_value.replace(key, value)
1111
1112                sim_page.constraints_list[index][0].SetValue(model_cbox)
1113                sim_page._on_select_model(None)
1114                sim_page.constraints_list[index][1].SetValue(param)
1115                sim_page.constraints_list[index][2].SetLabel(equality)
1116                sim_page.constraints_list[index][3].SetValue(constraint_value)
1117                sim_page._on_add_constraint(None)
1118                sim_page._manager.sim_page = sim_page
1119
1120    def _format_id(self, original_id):
1121        original_id = original_id.rstrip('1234567890.')
1122        new_id_list = original_id.split()
1123        new_id = ' '.join(new_id_list)
[9b5e70c]1124        return new_id
Note: See TracBrowser for help on using the repository browser.