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

Last change on this file since 4ba4b69 was a28e52b, checked in by krzywon, 8 years ago

#738: Simultaneous fit page now can perform fits on loading saved projects. Loading errors left to fix.

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