source: sasview/src/sas/sasgui/perspectives/calculator/model_editor.py @ 5876f7e

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 5876f7e was 5876f7e, checked in by butler, 8 years ago

links help to custom model editors (sum model and easy editor)
(model_editor.py). This is the final linking of new documentation to
the GUI

  • Property mode set to 100644
File size: 59.3 KB
Line 
1'''
2This module provides three model editor classes: the composite model editor,
3the easy editor which provides a simple interface with tooltip help to enter
4the parameters of the model and their default value and a panel to input a
5function of y (usually the intensity).  It also provides a drop down of
6standard available math functions.  Finally a full python editor panel for
7complete customizatin is provided.
8
9:TODO the writiong of the file and name checking (and maybe some other
10funtions?) should be moved to a computational module which could be called
11fropm a python script.  Basically one just needs to pass the name,
12description text and function text (or in the case of the composite editor
13the names of the first and second model and the operator to be used).
14'''
15
16################################################################################
17#This software was developed by the University of Tennessee as part of the
18#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
19#project funded by the US National Science Foundation.
20#
21#See the license text in license.txt
22#
23#copyright 2009, University of Tennessee
24################################################################################
25import wx
26import sys
27import os
28import math
29import re
30from wx.py.editwindow import EditWindow
31from sas.sasgui.guiframe.documentation_window import DocumentationWindow
32
33
34if sys.platform.count("win32") > 0:
35    FONT_VARIANT = 0
36    PNL_WIDTH = 450
37    PNL_HEIGHT = 320
38else:
39    FONT_VARIANT = 1
40    PNL_WIDTH = 590
41    PNL_HEIGHT = 350
42M_NAME = 'Model'
43EDITOR_WIDTH = 800
44EDITOR_HEIGTH = 735
45PANEL_WIDTH = 500
46_BOX_WIDTH = 55
47
48
49def _compile_file(path):
50    """
51    Compile the file in the path
52    """
53    try:
54        import py_compile
55        py_compile.compile(file=path, doraise=True)
56        return ''
57    except:
58        _, value, _ = sys.exc_info()
59        return value
60
61def _delete_file(path):
62    """
63    Delete file in the path
64    """
65    try:
66        os.remove(path)
67    except:
68        raise
69
70
71class TextDialog(wx.Dialog):
72    """
73    Dialog for easy custom composite models.  Provides a wx.Dialog panel
74    to choose two existing models (including pre-existing custom models which
75    may themselves be composite models) as well as an operation on those models
76    (add or multiply) the resulting model will add a scale parameter for summed
77    models and a background parameter for a multiplied model.
78
79    The user also gives a brief help for the model in a description box and
80    must provide a unique name which is verified as unique before the new
81    model is saved.
82
83    This Dialog pops up for the user when they press 'Sum|Multi(p1,p2)' under
84    'Edit Custom Model' under 'Fitting' menu.  This is currently called as
85    a Modal Dialog.
86
87    :TODO the build in compiler currently balks at when it tries to import
88    a model whose name contains spaces or symbols (such as + ... underscore
89    should be fine).  Have fixed so the editor cannot save such a file name
90    but if a file is dropped in the plugin directory from outside this class
91    will create a file that cannot be compiled.  Should add the check to
92    the write method or to the on_modelx method.
93
94    - PDB:April 5, 2015
95    """
96    def __init__(self, parent=None, base=None, id=None, title='',
97                 model_list=[], plugin_dir=None):
98        """
99        This class is run when instatiated.  The __init__ initializes and
100        calls the internal methods necessary.  On exiting the wx.Dialog
101        window should be destroyed.
102        """
103        wx.Dialog.__init__(self, parent=parent, id=id,
104                           title=title, size=(PNL_WIDTH, PNL_HEIGHT))
105        self.parent = base
106        #Font
107        self.SetWindowVariant(variant=FONT_VARIANT)
108        # default
109        self.overwrite_name = False
110        self.plugin_dir = plugin_dir
111        self.model_list = model_list
112        self.model1_string = "sphere"
113        self.model2_string = "cylinder"
114        self.name = 'Sum' + M_NAME
115        self.factor = 'scale_factor'
116        self._notes = ''
117        self._operator = '+'
118        self._operator_choice = None
119        self.explanation = ''
120        self.explanationctr = None
121        self.type = None
122        self.name_sizer = None
123        self.name_tcl = None
124        self.desc_sizer = None
125        self.desc_tcl = None
126        self._selection_box = None
127        self.model1 = None
128        self.model2 = None
129        self.static_line_1 = None
130        self.ok_button = None
131        self.close_button = None
132        self._msg_box = None
133        self.msg_sizer = None
134        self.fname = None
135        self.cm_list = None
136        self.is_p1_custom = False
137        self.is_p2_custom = False
138        self._build_sizer()
139        self.model1_name = str(self.model1.GetValue())
140        self.model2_name = str(self.model2.GetValue())
141        self.good_name = True
142        self.fill_oprator_combox()
143
144    def _layout_name(self):
145        """
146        Do the layout for file/function name related widgets
147        """
148        #container for new model name input
149        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
150
151        #set up label and input box with tool tip and event handling
152        name_txt = wx.StaticText(self, -1, 'Function Name : ')
153        self.name_tcl = wx.TextCtrl(self, -1, value='MySumFunction')
154        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
155        hint_name = "Unique Sum/Multiply Model Function Name."
156        self.name_tcl.SetToolTipString(hint_name)
157
158        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.TOP, 10),
159                                 (self.name_tcl, -1,
160                                  wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM,
161                                  10)])
162
163
164    def _layout_description(self):
165        """
166        Do the layout for description related widgets
167        """
168        #container for new model description input
169        self.desc_sizer = wx.BoxSizer(wx.HORIZONTAL)
170
171        #set up description label and input box with tool tip and event handling
172        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
173        self.desc_tcl = wx.TextCtrl(self, -1)
174        hint_desc = "Write a short description of this model function."
175        self.desc_tcl.SetToolTipString(hint_desc)
176
177        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
178                                 (self.desc_tcl, -1,
179                                  wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM,
180                                  10)])
181
182
183    def _layout_model_selection(self):
184        """
185        Do the layout for model selection related widgets
186        """
187        box_width = 195 # combobox width
188
189        #First set up main sizer for the selection
190        selection_box_title = wx.StaticBox(self, -1, 'Select',
191                                           size=(PNL_WIDTH - 30, 70))
192        self._selection_box = wx.StaticBoxSizer(selection_box_title,
193                                                wx.VERTICAL)
194
195        #Next create the help labels for the model selection
196        select_help_box = wx.BoxSizer(wx.HORIZONTAL)
197        model_string = " Model%s (p%s):"
198        select_help_box.Add(wx.StaticText(self, -1, model_string % (1, 1)),
199                            0, 0)
200        select_help_box.Add((box_width - 25, 10), 0, 0)
201        select_help_box.Add(wx.StaticText(self, -1, model_string % (2, 2)),
202                            0, 0)
203        self._selection_box.Add(select_help_box, 0, 0)
204
205        #Next create the actual selection box with 3 combo boxes
206        selection_box_choose = wx.BoxSizer(wx.HORIZONTAL)
207
208        self.model1 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
209        wx.EVT_COMBOBOX(self.model1, -1, self.on_model1)
210        self.model1.SetMinSize((box_width * 5 / 6, -1))
211        self.model1.SetToolTipString("model1")
212
213        self._operator_choice = wx.ComboBox(self, -1, size=(50, -1),
214                                            style=wx.CB_READONLY)
215        wx.EVT_COMBOBOX(self._operator_choice, -1, self.on_select_operator)
216        operation_tip = "Add: +, Multiply: * "
217        self._operator_choice.SetToolTipString(operation_tip)
218
219        self.model2 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
220        wx.EVT_COMBOBOX(self.model2, -1, self.on_model2)
221        self.model2.SetMinSize((box_width * 5 / 6, -1))
222        self.model2.SetToolTipString("model2")
223        self._set_model_list()
224
225        selection_box_choose.Add(self.model1, 0, 0)
226        selection_box_choose.Add((15, 10))
227        selection_box_choose.Add(self._operator_choice, 0, 0)
228        selection_box_choose.Add((15, 10))
229        selection_box_choose.Add(self.model2, 0, 0)
230        # add some space between labels and selection
231        self._selection_box.Add((20, 5), 0, 0)
232        self._selection_box.Add(selection_box_choose, 0, 0)
233
234    def _build_sizer(self):
235        """
236        Build GUI with calls to _layout_name, _layout Description
237        and _layout_model_selection which each build a their portion of the
238        GUI.
239        """
240        mainsizer = wx.BoxSizer(wx.VERTICAL) # create main sizer for dialog
241
242        # build fromm top by calling _layout_name and _layout_description
243        # and adding to main sizer
244        self._layout_name()
245        mainsizer.Add(self.name_sizer, 0, wx.EXPAND)
246        self._layout_description()
247        mainsizer.Add(self.desc_sizer, 0, wx.EXPAND)
248
249        # Add an explanation of dialog (short help)
250        self.explanationctr = wx.StaticText(self, -1, self.explanation)
251        self.fill_explanation_helpstring(self._operator)
252        mainsizer.Add(self.explanationctr, 0, wx.LEFT | wx.EXPAND, 15)
253
254        # Add the selection box stuff with border and labels built
255        # by _layout_model_selection
256        self._layout_model_selection()
257        mainsizer.Add(self._selection_box, 0, wx.LEFT, 15)
258
259        # Add a space and horizontal line before the notification
260        #messages and the buttons at the bottom
261        mainsizer.Add((10, 10))
262        self.static_line_1 = wx.StaticLine(self, -1)
263        mainsizer.Add(self.static_line_1, 0, wx.EXPAND, 10)
264
265        # Add action status notification line (null at startup)
266        self._msg_box = wx.StaticText(self, -1, self._notes)
267        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
268        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 0)
269        mainsizer.Add(self.msg_sizer, 0,
270                      wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE | wx.BOTTOM, 10)
271
272        # Finally add the buttons (apply and close) on the bottom
273        # Eventually need to add help here
274        self.ok_button = wx.Button(self, wx.ID_OK, 'Apply')
275        _app_tip = "Save the new Model."
276        self.ok_button.SetToolTipString(_app_tip)
277        self.ok_button.Bind(wx.EVT_BUTTON, self.check_name)
278        self.help_button = wx.Button(self, -1, 'HELP')
279        _app_tip = "Help on composite model creation."
280        self.help_button.SetToolTipString(_app_tip)
281        self.help_button.Bind(wx.EVT_BUTTON, self.on_help)
282        self.close_button = wx.Button(self, wx.ID_CANCEL, 'Close')
283        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
284        sizer_button.AddMany([((20, 20), 1, 0),
285                              (self.ok_button, 0, 0),
286                              (self.help_button, 0, 0),
287                              (self.close_button, 0, wx.LEFT | wx.RIGHT, 10)])
288        mainsizer.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
289
290        self.SetSizer(mainsizer)
291        self.Centre()
292
293    def on_change_name(self, event=None):
294        """
295        Change name
296        """
297        if event is not None:
298            event.Skip()
299        self.name_tcl.SetBackgroundColour('white')
300        self.Refresh()
301
302    def check_name(self, event=None):
303        """
304        Check that proposed new model name is a valid Python module name
305        and that it does not already exist. If not show error message and
306        pink background in text box else call on_apply
307
308        :TODO this should be separated out from the GUI code.  For that we
309        need to pass it the name (or if we want to keep the default name
310        option also need to pass the self._operator attribute) We just need
311        the function to return an error code that the name is good or if
312        not why (not a valid name, name exists already).  The rest of the
313        error handling should be done in this module. so on_apply would then
314        start by checking the name and then either raise errors or do the
315        deed.
316        """
317        #Get the function/file name
318        mname = M_NAME
319        self.on_change_name(None)
320        title = self.name_tcl.GetValue().lstrip().rstrip()
321        if title == '':
322            text = self._operator
323            if text.count('+') > 0:
324                mname = 'Sum'
325            else:
326                mname = 'Multi'
327            mname += M_NAME
328            title = mname
329        self.name = title
330        t_fname = title + '.py'
331
332        #First check if the name is a valid Python name
333        if re.match('^[A-Za-z0-9_]*$', title):
334            self.good_name = True
335        else:
336            self.good_name = False
337            msg = ("%s is not a valid Python name. Only alphanumeric \n" \
338                   "and underscore allowed" % self.name)
339
340        #Now check if the name already exists
341        if not self.overwrite_name and self.good_name:
342            #Create list of existing model names for comparison
343            list_fnames = os.listdir(self.plugin_dir)
344            # fake existing regular model name list
345            m_list = [model + ".py" for model in self.model_list]
346            list_fnames.append(m_list)
347            if t_fname in list_fnames and title != mname:
348                self.good_name = False
349                msg = "Name exists already."
350
351        if self.good_name == False:
352            self.name_tcl.SetBackgroundColour('pink')
353            info = 'Error'
354            wx.MessageBox(msg, info)
355            self._notes = msg
356            color = 'red'
357            self._msg_box.SetLabel(msg)
358            self._msg_box.SetForegroundColour(color)
359            return self.good_name
360        self.fname = os.path.join(self.plugin_dir, t_fname)
361        s_title = title
362        if len(title) > 20:
363            s_title = title[0:19] + '...'
364        self._notes = "Model function (%s) has been set! \n" % str(s_title)
365        self.good_name = True
366        self.on_apply(self.fname)
367        return self.good_name
368
369    def on_apply(self, path):
370        """
371        This method is a misnomer - it is not bound to the apply button
372        event.  Instead the apply button event goes to check_name which
373        then calls this method if the name of the new file is acceptable.
374
375        :TODO this should be bound to the apply button.  The first line
376        should call the check_name method which itself should be in another
377        module separated from the the GUI modules.
378        """
379        self.name_tcl.SetBackgroundColour('white')
380        try:
381            label = self.get_textnames()
382            fname = path
383            name1 = label[0]
384            name2 = label[1]
385            self.write_string(fname, name1, name2)
386            self.compile_file(fname)
387            self.parent.update_custom_combo()
388            msg = self._notes
389            info = 'Info'
390            color = 'blue'
391        except:
392            msg = "Easy Custom Sum/Multipy: Error occurred..."
393            info = 'Error'
394            color = 'red'
395        self._msg_box.SetLabel(msg)
396        self._msg_box.SetForegroundColour(color)
397        if self.parent.parent != None:
398            from sas.sasgui.guiframe.events import StatusEvent
399            wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
400                                                         info=info))
401        else:
402            raise
403
404    def on_help(self, event):
405        """
406        Bring up the Composite Model Editor Documentation whenever
407        the HELP button is clicked.
408
409        Calls DocumentationWindow with the path of the location within the
410        documentation tree (after /doc/ ....".  Note that when using old
411        versions of Wx (before 2.9) and thus not the release version of
412        installers, the help comes up at the top level of the file as
413        webbrowser does not pass anything past the # to the browser when it is
414        running "file:///...."
415
416    :param evt: Triggers on clicking the help button
417    """
418
419        _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
420        _PageAnchor = "#sum-multi-p1-p2"
421        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor,
422                                          "Composite Model Editor Help")
423
424    def _set_model_list(self):
425        """
426        Set the list of models
427        """
428        # list of model names
429        # get regular models
430        main_list = self.model_list
431        # get custom models
432        self.update_cm_list()
433        # add custom models to model list
434        for name in self.cm_list:
435            if name not in main_list:
436                main_list.append(name)
437
438        if len(main_list) > 1:
439            main_list.sort()
440        for idx in range(len(main_list)):
441            self.model1.Append(str(main_list[idx]), idx)
442            self.model2.Append(str(main_list[idx]), idx)
443        self.model1.SetStringSelection(self.model1_string)
444        self.model2.SetStringSelection(self.model2_string)
445
446    def update_cm_list(self):
447        """
448        Update custom model list
449        """
450        cm_list = []
451        al_list = os.listdir(self.plugin_dir)
452        for c_name in al_list:
453            if c_name.split('.')[-1] == 'py' and \
454                    c_name.split('.')[0] != '__init__':
455                name = str(c_name.split('.')[0])
456                cm_list.append(name)
457        self.cm_list = cm_list
458
459    def on_model1(self, event):
460        """
461        Set model1
462        """
463        event.Skip()
464        self.update_cm_list()
465        self.model1_name = str(self.model1.GetValue())
466        self.model1_string = self.model1_name
467        if self.model1_name in self.cm_list:
468            self.is_p1_custom = True
469        else:
470            self.is_p1_custom = False
471
472    def on_model2(self, event):
473        """
474        Set model2
475        """
476        event.Skip()
477        self.update_cm_list()
478        self.model2_name = str(self.model2.GetValue())
479        self.model2_string = self.model2_name
480        if self.model2_name in self.cm_list:
481            self.is_p2_custom = True
482        else:
483            self.is_p2_custom = False
484
485    def on_select_operator(self, event=None):
486        """
487        On Select an Operator
488        """
489        # For Mac
490        if event != None:
491            event.Skip()
492        item = event.GetEventObject()
493        text = item.GetValue()
494        self.fill_explanation_helpstring(text)
495
496    def fill_explanation_helpstring(self, operator):
497        """
498        Choose the equation to use depending on whether we now have
499        a sum or multiply model then create the appropriate string
500        """
501
502        name = ''
503
504        if operator == '*':
505            name = 'Multi'
506            factor = 'BackGround'
507            f_oper = '+'
508        else:
509            name = 'Sum'
510            factor = 'scale_factor'
511            f_oper = '*'
512
513        self.factor = factor
514        self._operator = operator
515        self.explanation = "  Custom Model = %s %s (model1 %s model2)\n" % \
516                           (self.factor, f_oper, self._operator)
517        self.explanationctr.SetLabel(self.explanation)
518        self.name = name + M_NAME
519
520
521    def fill_oprator_combox(self):
522        """
523        fill the current combobox with the operator
524        """
525        operator_list = [' +', ' *']
526        for oper in operator_list:
527            pos = self._operator_choice.Append(str(oper))
528            self._operator_choice.SetClientData(pos, str(oper.strip()))
529        self._operator_choice.SetSelection(0)
530
531    def get_textnames(self):
532        """
533        Returns model name string as list
534        """
535        return [self.model1_name, self.model2_name]
536
537    def write_string(self, fname, name1, name2):
538        """
539        Write and Save file
540        """
541        self.fname = fname
542        description = self.desc_tcl.GetValue().lstrip().rstrip()
543        if description == '':
544            description = name1 + self._operator + name2
545        text = self._operator_choice.GetValue()
546        if text.count('+') > 0:
547            factor = 'scale_factor'
548            f_oper = '*'
549            default_val = '1.0'
550        else:
551            factor = 'BackGround'
552            f_oper = '+'
553            default_val = '0.0'
554        path = self.fname
555        try:
556            out_f = open(path, 'w')
557        except:
558            raise
559        lines = SUM_TEMPLATE.split('\n')
560        for line in lines:
561            try:
562                if line.count("scale_factor"):
563                    line = line.replace('scale_factor', factor)
564                    #print "scale_factor", line
565                if line.count("= %s"):
566                    out_f.write(line % (default_val) + "\n")
567                elif line.count("import Model as P1"):
568                    if self.is_p1_custom:
569                        line = line.replace('#', '')
570                        out_f.write(line % name1 + "\n")
571                    else:
572                        out_f.write(line + "\n")
573                elif line.count("import %s as P1"):
574                    if not self.is_p1_custom:
575                        line = line.replace('#', '')
576                        out_f.write(line % (name1) + "\n")
577                    else:
578                        out_f.write(line + "\n")
579                elif line.count("import Model as P2"):
580                    if self.is_p2_custom:
581                        line = line.replace('#', '')
582                        out_f.write(line % name2 + "\n")
583                    else:
584                        out_f.write(line + "\n")
585                elif line.count("import %s as P2"):
586                    if not self.is_p2_custom:
587                        line = line.replace('#', '')
588                        out_f.write(line % (name2) + "\n")
589                    else:
590                        out_f.write(line + "\n")
591                elif line.count("P1 = make_class"):
592                    out_f.write(line % (name1) + "\n")
593                elif line.count("P2 = make_class"):
594                    out_f.write(line % (name2) + "\n")
595
596                elif line.count("self.description = '%s'"):
597                    out_f.write(line % description + "\n")
598                #elif line.count("run") and line.count("%s"):
599                #    out_f.write(line % self._operator + "\n")
600                #elif line.count("evalDistribution") and line.count("%s"):
601                #    out_f.write(line % self._operator + "\n")
602                elif line.count("return") and line.count("%s") == 2:
603                    #print "line return", line
604                    out_f.write(line % (f_oper, self._operator) + "\n")
605                elif line.count("out2")and line.count("%s"):
606                    out_f.write(line % self._operator + "\n")
607                else:
608                    out_f.write(line + "\n")
609            except:
610                raise
611        out_f.close()
612        #else:
613        #    msg = "Name exists already."
614
615    def compile_file(self, path):
616        """
617        Compile the file in the path
618        """
619        path = self.fname
620        _compile_file(path)
621
622    def delete_file(self, path):
623        """
624        Delete file in the path
625        """
626        _delete_file(path)
627
628
629class EditorPanel(wx.ScrolledWindow):
630    """
631    Custom model function editor
632    """
633    def __init__(self, parent, base, path, title, *args, **kwds):
634        kwds['name'] = title
635#        kwds["size"] = (EDITOR_WIDTH, EDITOR_HEIGTH)
636        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
637        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
638        self.SetScrollbars(1,1,1,1)
639        self.parent = parent
640        self.base = base
641        self.path = path
642        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
643        self.font.SetPointSize(10)
644        self.reader = None
645        self.name = 'untitled'
646        self.overwrite_name = False
647        self.is_2d = False
648        self.fname = None
649        self.main_sizer = None
650        self.name_sizer = None
651        self.name_hsizer = None
652        self.name_tcl = None
653        self.desc_sizer = None
654        self.desc_tcl = None
655        self.param_sizer = None
656        self.param_tcl = None
657        self.function_sizer = None
658        self.func_horizon_sizer = None
659        self.button_sizer = None
660        self.param_strings = ''
661        self.function_strings = ''
662        self._notes = ""
663        self._msg_box = None
664        self.msg_sizer = None
665        self.warning = ""
666        self._description = "New Custom Model"
667        self.function_tcl = None
668        self.math_combo = None
669        self.bt_apply = None
670        self.bt_close = None
671        #self._default_save_location = os.getcwd()
672        self._do_layout()
673
674
675
676    def _define_structure(self):
677        """
678        define initial sizer
679        """
680        #w, h = self.parent.GetSize()
681        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
682        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
683        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
684        self.desc_sizer = wx.BoxSizer(wx.VERTICAL)
685        self.param_sizer = wx.BoxSizer(wx.VERTICAL)
686        self.function_sizer = wx.BoxSizer(wx.VERTICAL)
687        self.func_horizon_sizer = wx.BoxSizer(wx.HORIZONTAL)
688        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
689        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
690
691    def _layout_name(self):
692        """
693        Do the layout for file/function name related widgets
694        """
695        #title name [string]
696        name_txt = wx.StaticText(self, -1, 'Function Name : ')
697        overwrite_cb = wx.CheckBox(self, -1, "Overwrite?", (10, 10))
698        overwrite_cb.SetValue(False)
699        overwrite_cb.SetToolTipString("Overwrite it if already exists?")
700        wx.EVT_CHECKBOX(self, overwrite_cb.GetId(), self.on_over_cb)
701        #overwrite_cb.Show(False)
702        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
703        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
704        self.name_tcl.SetValue('MyFunction')
705        self.name_tcl.SetFont(self.font)
706        hint_name = "Unique Model Function Name."
707        self.name_tcl.SetToolTipString(hint_name)
708        self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT | wx.TOP, 0),
709                                  (overwrite_cb, 0, wx.LEFT, 20)])
710        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.TOP, 10),
711                                 (self.name_hsizer, 0,
712                                  wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
713
714
715    def _layout_description(self):
716        """
717        Do the layout for description related widgets
718        """
719        #title name [string]
720        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
721        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
722        self.desc_tcl.SetValue('')
723        hint_desc = "Write a short description of the model function."
724        self.desc_tcl.SetToolTipString(hint_desc)
725        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
726                                 (self.desc_tcl, 0,
727                                  wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
728    def _layout_param(self):
729        """
730        Do the layout for parameter related widgets
731        """
732        param_txt = wx.StaticText(self, -1, 'Fit Parameters (if any): ')
733
734        param_tip = "#Set the parameters and initial values.\n"
735        param_tip += "#Example:\n"
736        param_tip += "A = 1\nB = 1"
737        #param_txt.SetToolTipString(param_tip)
738        newid = wx.NewId()
739        self.param_tcl = EditWindow(self, newid, wx.DefaultPosition,
740                                    wx.DefaultSize,
741                                    wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
742        self.param_tcl.setDisplayLineNumbers(True)
743        self.param_tcl.SetToolTipString(param_tip)
744
745        self.param_sizer.AddMany([(param_txt, 0, wx.LEFT, 10),
746                                  (self.param_tcl, 1, wx.EXPAND | wx.ALL, 10)])
747
748    def _layout_function(self):
749        """
750        Do the layout for function related widgets
751        """
752        function_txt = wx.StaticText(self, -1, 'Function(x) : ')
753        hint_function = "#Example:\n"
754        hint_function += "if x <= 0:\n"
755        hint_function += "    y = A + B\n"
756        hint_function += "else:\n"
757        hint_function += "    y = A + B * cos(2 * pi * x)\n"
758        hint_function += "return y\n"
759        math_txt = wx.StaticText(self, -1, '*Useful math functions: ')
760        math_combo = self._fill_math_combo()
761
762        newid = wx.NewId()
763        self.function_tcl = EditWindow(self, newid, wx.DefaultPosition,
764                                       wx.DefaultSize,
765                                       wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
766        self.function_tcl.setDisplayLineNumbers(True)
767        self.function_tcl.SetToolTipString(hint_function)
768
769        self.func_horizon_sizer.Add(function_txt)
770        self.func_horizon_sizer.Add(math_txt, 0, wx.LEFT, 250)
771        self.func_horizon_sizer.Add(math_combo, 0, wx.LEFT, 10)
772
773        self.function_sizer.Add(self.func_horizon_sizer, 0, wx.LEFT, 10)
774        self.function_sizer.Add(self.function_tcl, 1, wx.EXPAND | wx.ALL, 10)
775
776    def _layout_msg(self):
777        """
778        Layout msg
779        """
780        self._msg_box = wx.StaticText(self, -1, self._notes,
781                                      size=(PANEL_WIDTH, -1))
782        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 10)
783
784    def _layout_button(self):
785        """
786        Do the layout for the button widgets
787        """
788        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
789        self.bt_apply.SetToolTipString("Save changes into the imported data.")
790        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
791
792        self.bt_help = wx.Button(self, -1, "HELP", size=(_BOX_WIDTH, -1))
793        self.bt_help.SetToolTipString("Get Help For Model Editor")
794        self.bt_help.Bind(wx.EVT_BUTTON, self.on_help)
795
796        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
797        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
798        self.bt_close.SetToolTipString("Close this panel.")
799
800        self.button_sizer.AddMany([(self.bt_apply, 0,
801                                    wx.LEFT, EDITOR_WIDTH * 0.8),
802                                   (self.bt_help, 0,
803                                    wx.LEFT,15),
804                                   (self.bt_close, 0,
805                                    wx.LEFT | wx.BOTTOM, 15)])
806
807    def _do_layout(self):
808        """
809        Draw the current panel
810        """
811        self._define_structure()
812        self._layout_name()
813        self._layout_description()
814        self._layout_param()
815        self._layout_function()
816        self._layout_msg()
817        self._layout_button()
818        self.main_sizer.AddMany([(self.name_sizer, 0, wx.EXPAND | wx.ALL, 5),
819                                 (wx.StaticLine(self), 0,
820                                  wx.ALL | wx.EXPAND, 5),
821                                 (self.desc_sizer, 0, wx.EXPAND | wx.ALL, 5),
822                                 (wx.StaticLine(self), 0,
823                                  wx.ALL | wx.EXPAND, 5),
824                                 (self.param_sizer, 1, wx.EXPAND | wx.ALL, 5),
825                                 (wx.StaticLine(self), 0,
826                                  wx.ALL | wx.EXPAND, 5),
827                                 (self.function_sizer, 2,
828                                  wx.EXPAND | wx.ALL, 5),
829                                 (wx.StaticLine(self), 0,
830                                  wx.ALL | wx.EXPAND, 5),
831                                 (self.msg_sizer, 0, wx.EXPAND | wx.ALL, 5),
832                                 (self.button_sizer, 0, wx.EXPAND | wx.ALL, 5)])
833        self.SetSizer(self.main_sizer)
834        self.SetAutoLayout(True)
835
836    def _fill_math_combo(self):
837        """
838        Fill up the math combo box
839        """
840        self.math_combo = wx.ComboBox(self, -1, size=(100, -1),
841                                      style=wx.CB_READONLY)
842        for item in dir(math):
843            if item.count("_") < 1:
844                try:
845                    exec "float(math.%s)" % item
846                    self.math_combo.Append(str(item))
847                except:
848                    self.math_combo.Append(str(item) + "()")
849        self.math_combo.Bind(wx.EVT_COMBOBOX, self._on_math_select)
850        self.math_combo.SetSelection(0)
851        return self.math_combo
852
853    def _on_math_select(self, event):
854        """
855        On math selection on ComboBox
856        """
857        event.Skip()
858        label = self.math_combo.GetValue()
859        self.function_tcl.SetFocus()
860        # Put the text at the cursor position
861        pos = self.function_tcl.GetCurrentPos()
862        self.function_tcl.InsertText(pos, label)
863        # Put the cursor at appropriate position
864        length = len(label)
865        print length
866        if label[length-1] == ')':
867            length -= 1
868        f_pos = pos + length
869        self.function_tcl.GotoPos(f_pos)
870
871    def get_notes(self):
872        """
873        return notes
874        """
875        return self._notes
876
877    def on_change_name(self, event=None):
878        """
879        Change name
880        """
881        if event is not None:
882            event.Skip()
883        self.name_tcl.SetBackgroundColour('white')
884        self.Refresh()
885
886    def check_name(self):
887        """
888        Check name if exist already
889        """
890        self._notes = ''
891        self.on_change_name(None)
892        plugin_dir = self.path
893        list_fnames = os.listdir(plugin_dir)
894        # function/file name
895        title = self.name_tcl.GetValue().lstrip().rstrip()
896        self.name = title
897        t_fname = title + '.py'
898        if not self.overwrite_name:
899            if t_fname in list_fnames:
900                self.name_tcl.SetBackgroundColour('pink')
901                return False
902        self.fname = os.path.join(plugin_dir, t_fname)
903        s_title = title
904        if len(title) > 20:
905            s_title = title[0:19] + '...'
906        self._notes += "Model function name is set "
907        self._notes += "to %s. \n" % str(s_title)
908        return True
909
910    def on_over_cb(self, event):
911        """
912        Set overwrite name flag on cb event
913        """
914        if event is not None:
915            event.Skip()
916        cb_value = event.GetEventObject()
917        self.overwrite_name = cb_value.GetValue()
918
919    def on_click_apply(self, event):
920        """
921        Changes are saved in data object imported to edit.
922
923        checks firs for valid name, then if it already exists then checks
924        that a function was entered and finally that if entered it contains at
925        least a return statement.  If all passes writes file then tries to
926        compile.  If compile fails or import module fails or run method fails
927        tries to remove any .py and pyc files that may have been created and
928        sets error message.
929
930        :todo this code still could do with a careful going over to clean
931        up and simplify. the non GUI methods such as this one should be removed
932        to computational code of SasView. Most of those computational methods
933        would be the same for both the simple editors.
934        """
935        #must post event here
936        event.Skip()
937        name = self.name_tcl.GetValue().lstrip().rstrip()
938        info = 'Info'
939        msg = ''
940        # Sort out the errors if occur
941        # First check for valid python name then if the name already exists
942        if not re.match('^[A-Za-z0-9_]*$', name):
943            msg = "not a valid python name. Name must include only alpha \n"
944            msg += "numeric or underline characters and no spaces"
945        elif self.check_name():
946            description = self.desc_tcl.GetValue()
947            param_str = self.param_tcl.GetText()
948            func_str = self.function_tcl.GetText()
949            # No input for the model function
950            if func_str.lstrip().rstrip():
951                if func_str.count('return') > 0:
952                    self.write_file(self.fname, description, param_str,
953                                    func_str)
954                    tr_msg = _compile_file(self.fname)
955                    msg = str(tr_msg.__str__())
956                    # Compile error
957                    if msg:
958                        msg.replace("  ", "\n")
959                        msg += "\nCompiling Failed"
960                        try:
961                            # try to remove pyc file if exists
962                            _delete_file(self.fname)
963                            _delete_file(self.fname + "c")
964                        except:
965                            pass
966                else:
967                    msg = "Error: The func(x) must 'return' a value at least.\n"
968                    msg += "For example: \n\nreturn 2*x"
969            else:
970                msg = 'Error: Function is not defined.'
971        else:
972            msg = "Name exists already."
973        # Prepare the messagebox
974        if self.base != None and not msg:
975            self.base.update_custom_combo()
976            Model = None
977            try:
978                exec "from %s import Model" % name
979            except:
980                msg = 'new model fails to import in python'
981                try:
982                    # try to remove pyc file if exists
983                    _delete_file(self.fname + "c")
984                except:
985                    pass
986        if self.base != None and not msg:
987            try:
988                Model().run(0.01)
989            except:
990                msg = "new model fails on run method:"
991                _, value, _ = sys.exc_info()
992                msg += "in %s:\n%s\n" % (name, value)
993                try:
994                    # try to remove pyc file if exists
995                    _delete_file(self.fname + "c")
996                except:
997                    pass
998        # Prepare the messagebox
999        if msg:
1000            info = 'Error'
1001            color = 'red'
1002        else:
1003            msg = "Successful! "
1004            msg += "  " + self._notes
1005            msg += " Please look for it in the Customized Models."
1006            info = 'Info'
1007            color = 'blue'
1008        self._msg_box.SetLabel(msg)
1009        self._msg_box.SetForegroundColour(color)
1010        # Send msg to the top window
1011        if self.base != None:
1012            from sas.sasgui.guiframe.events import StatusEvent
1013            wx.PostEvent(self.base.parent, StatusEvent(status=msg, info=info))
1014        self.warning = msg
1015
1016
1017    def write_file(self, fname, desc_str, param_str, func_str):
1018        """
1019        Write content in file
1020
1021        :param fname: full file path
1022        :param desc_str: content of the description strings
1023        :param param_str: content of params; Strings
1024        :param func_str: content of func; Strings
1025        """
1026        try:
1027            out_f = open(fname, 'w')
1028        except:
1029            raise
1030        # Prepare the content of the function
1031        lines = CUSTOM_TEMPLATE.split('\n')
1032
1033        has_scipy = func_str.count("scipy.")
1034        self.is_2d = func_str.count("#self.ndim = 2")
1035        line_2d = ''
1036        if self.is_2d:
1037            line_2d = CUSTOM_2D_TEMP.split('\n')
1038        line_test = TEST_TEMPLATE.split('\n')
1039        local_params = ''
1040        spaces = '        '#8spaces
1041        # write function here
1042        for line in lines:
1043            # The location where to put the strings is
1044            # hard-coded in the template as shown below.
1045            if line.count("#self.params here"):
1046                for param_line in param_str.split('\n'):
1047                    p_line = param_line.lstrip().rstrip()
1048                    if p_line:
1049                        p0_line = self.set_param_helper(p_line)
1050                        local_params += self.set_function_helper(p_line)
1051                        out_f.write(p0_line)
1052            elif line.count("#local params here"):
1053                if local_params:
1054                    out_f.write(local_params)
1055            elif line.count("self.description = "):
1056                des0 = self.name + "\\n"
1057                desc = str(desc_str.lstrip().rstrip().replace('\"', ''))
1058                out_f.write(line % (des0 + desc) + "\n")
1059            elif line.count("def function(self, x=0.0%s):"):
1060                if self.is_2d:
1061                    y_str = ', y=0.0'
1062                    out_f.write(line % y_str + "\n")
1063                else:
1064                    out_f.write(line % '' + "\n")
1065            elif line.count("#function here"):
1066                for func_line in func_str.split('\n'):
1067                    f_line = func_line.rstrip()
1068                    if f_line:
1069                        out_f.write(spaces + f_line + "\n")
1070                if not func_str:
1071                    dep_var = 'y'
1072                    if self.is_2d:
1073                        dep_var = 'z'
1074                    out_f.write(spaces + 'return %s' % dep_var + "\n")
1075            elif line.count("#import scipy?"):
1076                if has_scipy:
1077                    out_f.write("import scipy" + "\n")
1078            #elif line.count("name = "):
1079            #    out_f.write(line % self.name + "\n")
1080            elif line:
1081                out_f.write(line + "\n")
1082        # run string for 2d
1083        if line_2d:
1084            for line in line_2d:
1085                out_f.write(line + "\n")
1086        # Test strins
1087        for line in line_test:
1088            out_f.write(line + "\n")
1089
1090        out_f.close()
1091
1092    def set_param_helper(self, line):
1093        """
1094        Get string in line to define the params dictionary
1095
1096        :param line: one line of string got from the param_str
1097        """
1098        params_str = ''
1099        spaces = '        '#8spaces
1100        items = line.split(";")
1101        for item in items:
1102            name = item.split("=")[0].lstrip().rstrip()
1103            try:
1104                value = item.split("=")[1].lstrip().rstrip()
1105                float(value)
1106            except:
1107                value = 1.0 # default
1108            params_str += spaces + "self.params['%s'] = %s\n" % (name, value)
1109
1110        return params_str
1111
1112    def set_function_helper(self, line):
1113        """
1114        Get string in line to define the local params
1115
1116        :param line: one line of string got from the param_str
1117        """
1118        params_str = ''
1119        spaces = '        '#8spaces
1120        items = line.split(";")
1121        for item in items:
1122            name = item.split("=")[0].lstrip().rstrip()
1123            params_str += spaces + "%s = self.params['%s']\n" % (name, name)
1124        return params_str
1125
1126    def get_warning(self):
1127        """
1128        Get the warning msg
1129        """
1130        return self.warning
1131
1132    def on_help(self, event):
1133        """
1134        Bring up the Custom Model Editor Documentation whenever
1135        the HELP button is clicked.
1136
1137        Calls DocumentationWindow with the path of the location within the
1138        documentation tree (after /doc/ ....".  Note that when using old
1139        versions of Wx (before 2.9) and thus not the release version of
1140        installers, the help comes up at the top level of the file as
1141        webbrowser does not pass anything past the # to the browser when it is
1142        running "file:///...."
1143
1144    :param evt: Triggers on clicking the help button
1145    """
1146
1147        _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
1148        _PageAnchor = "#custom-model-editor"
1149        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor,
1150                                          "Custom Model Editor Help")
1151
1152    def on_close(self, event):
1153        """
1154        leave data as it is and close
1155        """
1156        self.parent.Show(False)#Close()
1157        event.Skip()
1158
1159class EditorWindow(wx.Frame):
1160    """
1161    Editor Window
1162    """
1163    def __init__(self, parent, base, path, title,
1164                 size=(EDITOR_WIDTH, EDITOR_HEIGTH), *args, **kwds):
1165        """
1166        Init
1167        """
1168        kwds["title"] = title
1169        kwds["size"] = size
1170        wx.Frame.__init__(self, parent=None, *args, **kwds)
1171        self.parent = parent
1172        self.panel = EditorPanel(parent=self, base=parent,
1173                                 path=path, title=title)
1174        self.Show(True)
1175        wx.EVT_CLOSE(self, self.on_close)
1176
1177    def on_close(self, event):
1178        """
1179        On close event
1180        """
1181        self.Show(False)
1182        #if self.parent != None:
1183        #    self.parent.new_model_frame = None
1184        #self.Destroy()
1185
1186## Templates for custom models
1187CUSTOM_TEMPLATE = """
1188from sas.models.pluginmodel import Model1DPlugin
1189from math import *
1190import os
1191import sys
1192import numpy
1193#import scipy?
1194class Model(Model1DPlugin):
1195    name = ""
1196    def __init__(self):
1197        Model1DPlugin.__init__(self, name=self.name)
1198        #set name same as file name
1199        self.name = self.get_fname()
1200        #self.params here
1201        self.description = "%s"
1202        self.set_details()
1203    def function(self, x=0.0%s):
1204        #local params here
1205        #function here
1206"""
1207CUSTOM_2D_TEMP = """
1208    def run(self, x=0.0, y=0.0):
1209        if x.__class__.__name__ == 'list':
1210            x_val = x[0]
1211            y_val = y[0]*0.0
1212            return self.function(x_val, y_val)
1213        elif x.__class__.__name__ == 'tuple':
1214            msg = "Tuples are not allowed as input to BaseComponent models"
1215            raise ValueError, msg
1216        else:
1217            return self.function(x, 0.0)
1218    def runXY(self, x=0.0, y=0.0):
1219        if x.__class__.__name__ == 'list':
1220            return self.function(x, y)
1221        elif x.__class__.__name__ == 'tuple':
1222            msg = "Tuples are not allowed as input to BaseComponent models"
1223            raise ValueError, msg
1224        else:
1225            return self.function(x, y)
1226    def evalDistribution(self, qdist):
1227        if qdist.__class__.__name__ == 'list':
1228            msg = "evalDistribution expects a list of 2 ndarrays"
1229            if len(qdist)!=2:
1230                raise RuntimeError, msg
1231            if qdist[0].__class__.__name__ != 'ndarray':
1232                raise RuntimeError, msg
1233            if qdist[1].__class__.__name__ != 'ndarray':
1234                raise RuntimeError, msg
1235            v_model = numpy.vectorize(self.runXY, otypes=[float])
1236            iq_array = v_model(qdist[0], qdist[1])
1237            return iq_array
1238        elif qdist.__class__.__name__ == 'ndarray':
1239            v_model = numpy.vectorize(self.runXY, otypes=[float])
1240            iq_array = v_model(qdist)
1241            return iq_array
1242"""
1243TEST_TEMPLATE = """
1244    def get_fname(self):
1245        path = sys._getframe().f_code.co_filename
1246        basename  = os.path.basename(path)
1247        name, _ = os.path.splitext(basename)
1248        return name
1249######################################################################
1250## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!
1251if __name__ == "__main__":
1252    m= Model()
1253    out1 = m.runXY(0.0)
1254    out2 = m.runXY(0.01)
1255    isfine1 = numpy.isfinite(out1)
1256    isfine2 = numpy.isfinite(out2)
1257    print "Testing the value at Q = 0.0:"
1258    print out1, " : finite? ", isfine1
1259    print "Testing the value at Q = 0.01:"
1260    print out2, " : finite? ", isfine2
1261    if isfine1 and isfine2:
1262        print "===> Simple Test: Passed!"
1263    else:
1264        print "===> Simple Test: Failed!"
1265"""
1266SUM_TEMPLATE = """
1267# A sample of an experimental model function for Sum/Multiply(Pmodel1,Pmodel2)
1268import copy
1269from sas.models.pluginmodel import Model1DPlugin
1270from sasmodels.sasview_model import make_class
1271from sasmodels.core import load_model_info
1272# User can change the name of the model (only with single functional model)
1273#P1_model:
1274#from %s import Model as P1
1275
1276#P2_model:
1277#from %s import Model as P2
1278import os
1279import sys
1280
1281class Model(Model1DPlugin):
1282    name = ""
1283    def __init__(self):
1284        Model1DPlugin.__init__(self, name='')
1285        P1 = make_class(load_model_info('%s'))
1286        P2 = make_class(load_model_info('%s'))
1287        p_model1 = P1()
1288        p_model2 = P2()
1289        ## Setting  model name model description
1290        self.description = '%s'
1291        self.name = self.get_fname()
1292        if self.name.rstrip().lstrip() == '':
1293            self.name = self._get_name(p_model1.name, p_model2.name)
1294        if self.description.rstrip().lstrip() == '':
1295            self.description = p_model1.name
1296            self.description += p_model2.name
1297            self.fill_description(p_model1, p_model2)
1298
1299        ## Define parameters
1300        self.params = {}
1301
1302        ## Parameter details [units, min, max]
1303        self.details = {}
1304        ## Magnetic Panrameters
1305        self.magnetic_params = []
1306        # non-fittable parameters
1307        self.non_fittable = p_model1.non_fittable
1308        self.non_fittable += p_model2.non_fittable
1309
1310        ##models
1311        self.p_model1= p_model1
1312        self.p_model2= p_model2
1313
1314
1315        ## dispersion
1316        self._set_dispersion()
1317        ## Define parameters
1318        self._set_params()
1319        ## New parameter:scaling_factor
1320        self.params['scale_factor'] = %s
1321
1322        ## Parameter details [units, min, max]
1323        self._set_details()
1324        self.details['scale_factor'] = ['', None, None]
1325
1326
1327        #list of parameter that can be fitted
1328        self._set_fixed_params()
1329        ## parameters with orientation
1330        for item in self.p_model1.orientation_params:
1331            new_item = "p1_" + item
1332            if not new_item in self.orientation_params:
1333                self.orientation_params.append(new_item)
1334
1335        for item in self.p_model2.orientation_params:
1336            new_item = "p2_" + item
1337            if not new_item in self.orientation_params:
1338                self.orientation_params.append(new_item)
1339        ## magnetic params
1340        for item in self.p_model1.magnetic_params:
1341            new_item = "p1_" + item
1342            if not new_item in self.magnetic_params:
1343                self.magnetic_params.append(new_item)
1344
1345        for item in self.p_model2.magnetic_params:
1346            new_item = "p2_" + item
1347            if not new_item in self.magnetic_params:
1348                self.magnetic_params.append(new_item)
1349        # get multiplicity if model provide it, else 1.
1350        try:
1351            multiplicity1 = p_model1.multiplicity
1352            try:
1353                multiplicity2 = p_model2.multiplicity
1354            except:
1355                multiplicity2 = 1
1356        except:
1357            multiplicity1 = 1
1358            multiplicity2 = 1
1359        ## functional multiplicity of the model
1360        self.multiplicity1 = multiplicity1
1361        self.multiplicity2 = multiplicity2
1362        self.multiplicity_info = []
1363
1364    def _clone(self, obj):
1365        obj.params     = copy.deepcopy(self.params)
1366        obj.description     = copy.deepcopy(self.description)
1367        obj.details    = copy.deepcopy(self.details)
1368        obj.dispersion = copy.deepcopy(self.dispersion)
1369        obj.p_model1  = self.p_model1.clone()
1370        obj.p_model2  = self.p_model2.clone()
1371        #obj = copy.deepcopy(self)
1372        return obj
1373
1374    def _get_name(self, name1, name2):
1375        p1_name = self._get_upper_name(name1)
1376        if not p1_name:
1377            p1_name = name1
1378        name = p1_name
1379        name += "_and_"
1380        p2_name = self._get_upper_name(name2)
1381        if not p2_name:
1382            p2_name = name2
1383        name += p2_name
1384        return name
1385
1386    def _get_upper_name(self, name=None):
1387        if name == None:
1388            return ""
1389        upper_name = ""
1390        str_name = str(name)
1391        for index in range(len(str_name)):
1392            if str_name[index].isupper():
1393                upper_name += str_name[index]
1394        return upper_name
1395
1396    def _set_dispersion(self):
1397        ##set dispersion only from p_model
1398        for name , value in self.p_model1.dispersion.iteritems():
1399            #if name.lower() not in self.p_model1.orientation_params:
1400            new_name = "p1_" + name
1401            self.dispersion[new_name]= value
1402        for name , value in self.p_model2.dispersion.iteritems():
1403            #if name.lower() not in self.p_model2.orientation_params:
1404            new_name = "p2_" + name
1405            self.dispersion[new_name]= value
1406
1407    def function(self, x=0.0):
1408        return 0
1409
1410    def getProfile(self):
1411        try:
1412            x,y = self.p_model1.getProfile()
1413        except:
1414            x = None
1415            y = None
1416
1417        return x, y
1418
1419    def _set_params(self):
1420        for name , value in self.p_model1.params.iteritems():
1421            # No 2D-supported
1422            #if name not in self.p_model1.orientation_params:
1423            new_name = "p1_" + name
1424            self.params[new_name]= value
1425
1426        for name , value in self.p_model2.params.iteritems():
1427            # No 2D-supported
1428            #if name not in self.p_model2.orientation_params:
1429            new_name = "p2_" + name
1430            self.params[new_name]= value
1431
1432        # Set "scale" as initializing
1433        self._set_scale_factor()
1434
1435
1436    def _set_details(self):
1437        for name ,detail in self.p_model1.details.iteritems():
1438            new_name = "p1_" + name
1439            #if new_name not in self.orientation_params:
1440            self.details[new_name]= detail
1441
1442        for name ,detail in self.p_model2.details.iteritems():
1443            new_name = "p2_" + name
1444            #if new_name not in self.orientation_params:
1445            self.details[new_name]= detail
1446
1447    def _set_scale_factor(self):
1448        pass
1449
1450
1451    def setParam(self, name, value):
1452        # set param to this (p1, p2) model
1453        self._setParamHelper(name, value)
1454
1455        ## setParam to p model
1456        model_pre = ''
1457        new_name = ''
1458        name_split = name.split('_', 1)
1459        if len(name_split) == 2:
1460            model_pre = name.split('_', 1)[0]
1461            new_name = name.split('_', 1)[1]
1462        if model_pre == "p1":
1463            if new_name in self.p_model1.getParamList():
1464                self.p_model1.setParam(new_name, value)
1465        elif model_pre == "p2":
1466             if new_name in self.p_model2.getParamList():
1467                self.p_model2.setParam(new_name, value)
1468        elif name == 'scale_factor':
1469            self.params['scale_factor'] = value
1470        else:
1471            raise ValueError, "Model does not contain parameter %s" % name
1472
1473    def getParam(self, name):
1474        # Look for dispersion parameters
1475        toks = name.split('.')
1476        if len(toks)==2:
1477            for item in self.dispersion.keys():
1478                # 2D not supported
1479                if item.lower()==toks[0].lower():
1480                    for par in self.dispersion[item]:
1481                        if par.lower() == toks[1].lower():
1482                            return self.dispersion[item][par]
1483        else:
1484            # Look for standard parameter
1485            for item in self.params.keys():
1486                if item.lower()==name.lower():
1487                    return self.params[item]
1488        return
1489        #raise ValueError, "Model does not contain parameter %s" % name
1490
1491    def _setParamHelper(self, name, value):
1492        # Look for dispersion parameters
1493        toks = name.split('.')
1494        if len(toks)== 2:
1495            for item in self.dispersion.keys():
1496                if item.lower()== toks[0].lower():
1497                    for par in self.dispersion[item]:
1498                        if par.lower() == toks[1].lower():
1499                            self.dispersion[item][par] = value
1500                            return
1501        else:
1502            # Look for standard parameter
1503            for item in self.params.keys():
1504                if item.lower()== name.lower():
1505                    self.params[item] = value
1506                    return
1507
1508        raise ValueError, "Model does not contain parameter %s" % name
1509
1510
1511    def _set_fixed_params(self):
1512        for item in self.p_model1.fixed:
1513            new_item = "p1" + item
1514            self.fixed.append(new_item)
1515        for item in self.p_model2.fixed:
1516            new_item = "p2" + item
1517            self.fixed.append(new_item)
1518
1519        self.fixed.sort()
1520
1521
1522    def run(self, x = 0.0):
1523        self._set_scale_factor()
1524        return self.params['scale_factor'] %s \
1525(self.p_model1.run(x) %s self.p_model2.run(x))
1526
1527    def runXY(self, x = 0.0):
1528        self._set_scale_factor()
1529        return self.params['scale_factor'] %s \
1530(self.p_model1.runXY(x) %s self.p_model2.runXY(x))
1531
1532    ## Now (May27,10) directly uses the model eval function
1533    ## instead of the for-loop in Base Component.
1534    def evalDistribution(self, x = []):
1535        self._set_scale_factor()
1536        return self.params['scale_factor'] %s \
1537(self.p_model1.evalDistribution(x) %s \
1538self.p_model2.evalDistribution(x))
1539
1540    def set_dispersion(self, parameter, dispersion):
1541        value= None
1542        new_pre = parameter.split("_", 1)[0]
1543        new_parameter = parameter.split("_", 1)[1]
1544        try:
1545            if new_pre == 'p1' and \
1546new_parameter in self.p_model1.dispersion.keys():
1547                value= self.p_model1.set_dispersion(new_parameter, dispersion)
1548            if new_pre == 'p2' and \
1549new_parameter in self.p_model2.dispersion.keys():
1550                value= self.p_model2.set_dispersion(new_parameter, dispersion)
1551            self._set_dispersion()
1552            return value
1553        except:
1554            raise
1555
1556    def fill_description(self, p_model1, p_model2):
1557        description = ""
1558        description += "This model gives the summation or multiplication of"
1559        description += "%s and %s. "% ( p_model1.name, p_model2.name )
1560        self.description += description
1561
1562    def get_fname(self):
1563        path = sys._getframe().f_code.co_filename
1564        basename  = os.path.basename(path)
1565        name, _ = os.path.splitext(basename)
1566        return name
1567
1568if __name__ == "__main__":
1569    m1= Model()
1570    #m1.setParam("p1_scale", 25)
1571    #m1.setParam("p1_length", 1000)
1572    #m1.setParam("p2_scale", 100)
1573    #m1.setParam("p2_rg", 100)
1574    out1 = m1.runXY(0.01)
1575
1576    m2= Model()
1577    #m2.p_model1.setParam("scale", 25)
1578    #m2.p_model1.setParam("length", 1000)
1579    #m2.p_model2.setParam("scale", 100)
1580    #m2.p_model2.setParam("rg", 100)
1581    out2 = m2.p_model1.runXY(0.01) %s m2.p_model2.runXY(0.01)\n
1582    print "My name is %s."% m1.name
1583    print out1, " = ", out2
1584    if out1 == out2:
1585        print "===> Simple Test: Passed!"
1586    else:
1587        print "===> Simple Test: Failed!"
1588"""
1589
1590if __name__ == "__main__":
1591#    app = wx.PySimpleApp()
1592    main_app = wx.App()
1593    main_frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"],
1594                       plugin_dir='../fitting/plugin_models')
1595    main_frame.ShowModal()
1596    main_app.MainLoop()
1597
1598#if __name__ == "__main__":
1599#    from sas.sasgui.perspectives.fitting import models
1600#    dir_path = models.find_plugins_dir()
1601#    app = wx.App()
1602#    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
1603#    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.