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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 0315b63 was 0315b63, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'ticket-887-reorg' into ticket-853-fit-gui-to-calc

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