Changeset 6f140f2 in sasview for fittingview


Ignore:
Timestamp:
Jan 13, 2012 6:52:20 PM (13 years ago)
Author:
Jae Cho <jhjcho@…>
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
b25caad
Parents:
f80236f
Message:

finished implementation of custom model editor unless one finds bugs

Location:
fittingview/src/sans/perspectives/fitting
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • fittingview/src/sans/perspectives/fitting/basepage.py

    rfa02d95 r6f140f2  
    28702870            #name = self.model.__class__.__name__ 
    28712871        frame = HelpWindow(None, -1,  pageToOpen=model_path)     
    2872         frame.Show(True) 
    28732872        if frame.rhelp.HasAnchor(name): 
     2873            frame.Show(True) 
    28742874            frame.rhelp.ScrollToAnchor(name) 
    28752875        else: 
    28762876           msg= "Model does not contains an available description " 
    28772877           msg +="Please try searching in the Help window" 
    2878            wx.PostEvent(self.parent.parent, StatusEvent(status = msg ))       
     2878           wx.PostEvent(self.parent.parent, StatusEvent(status = msg ))   
     2879           if self.model != None: 
     2880               frame.Destroy()  
     2881               msg = 'Model description:\n' 
     2882               msg += self.model.description + '\n' 
     2883               info = "Info" 
     2884               wx.MessageBox(msg, info) 
     2885           else: 
     2886               frame.Show(True)    
    28792887     
    28802888    def on_pd_help_clicked(self, event): 
  • fittingview/src/sans/perspectives/fitting/fitting.py

    r2b878d74 r6f140f2  
    4242from .pagestate import Reader 
    4343from .fitpage import Chi2UpdateEvent 
    44 from .fitting_widgets import TextDialog 
     44from sans.perspectives.calculator.model_editor import TextDialog 
     45from sans.perspectives.calculator.model_editor import EditorWindow 
    4546 
    4647MAX_NBR_DATA = 4 
     
    110111        self.park_id = wx.NewId() 
    111112        self.menu1 = None 
     113        self.new_model_frame = None 
    112114         
    113115        self.temp_state = [] 
     
    243245        filename = os.path.join(models.find_plugins_dir(), label) 
    244246        frame = PyConsole(parent=self.parent, manager=self, panel= self.fit_panel, 
    245                           title='Custom Model Editor', filename=filename) 
     247                          title='Advanced Custom Model Editor', filename=filename) 
    246248        self.put_icon(frame) 
    247249        frame.Show(True)  
     
    256258        model_list = model_manager.get_model_name_list() 
    257259 
    258         textdial = TextDialog(None, -1, 'Easy Custom Sum(p1,p2)', model_list) 
     260        textdial = TextDialog(None, -1, 'Modify Sum(p1, p2) Model', model_list) 
    259261        self.put_icon(textdial) 
    260262        if textdial.ShowModal() == wx.ID_OK: 
     
    277279                    raise 
    278280        textdial.Destroy() 
     281     
     282    def make_new_model(self, event): 
     283        """ 
     284        Make new model  
     285        """ 
     286        if self.new_model_frame != None and self.new_model_frame.IsShown(): 
     287            self.new_model_frame.Show(False) 
     288        else: 
     289            id = event.GetId() 
     290            dir_path = models.find_plugins_dir() 
     291            title = "New Custom Model Function" 
     292            self.new_model_frame = EditorWindow(parent=self, base=self,  
     293                                                path=dir_path, title=title) 
     294            self.put_icon(self.new_model_frame) 
     295        self.new_model_frame.Show(True) 
    279296 
    280297    def update_custom_combo(self): 
     
    303320        """ 
    304321        id = wx.NewId() 
    305         self.edit_model_menu.Append(id, 'Easy Custom Sum(p1, p2)',  
    306                                     'Sum two models')  
     322        #new_model_menu = wx.Menu() 
     323        self.edit_model_menu.Append(id, 'New Model Function',  
     324                                   'Add a new model function')#,  
     325                                   #new_model_menu)  
     326        wx.EVT_MENU(owner, id,  self.make_new_model) 
     327        id = wx.NewId() 
     328        self.edit_model_menu.Append(id, 'Modify Sum(p1, p2)',  
     329                                    'Sum of two model functions')  
    307330        wx.EVT_MENU(owner, id,  self.make_sum_model) 
    308331        e_id = wx.NewId() 
    309332        self.edit_menu =wx.Menu() 
    310333        self.edit_model_menu.AppendMenu(e_id,  
    311                                     'Edit Sample File', self.edit_menu)  
     334                                    'Advanced Edit', self.edit_menu)  
    312335        self.set_edit_menu_helper(owner) 
    313336     
  • fittingview/src/sans/perspectives/fitting/fitting_widgets.py

    r6e01f06 r6f140f2  
    1515import os 
    1616from wx.lib.scrolledpanel import ScrolledPanel 
    17  
    18 #TextDialog size 
    19 if sys.platform.count("win32") > 0: 
    20     FONT_VARIANT = 0 
    21     PNL_WIDTH = 460 
    22     PNL_HITE = 210 
    23 else: 
    24     FONT_VARIANT = 1 
    25     PNL_WIDTH = 500 
    26     PNL_HITE = 250 
    2717 
    2818MAX_NBR_DATA = 4 
     
    200190        else: 
    201191            self._data_text_ctrl.SetForegroundColour('red') 
    202          
    203   
    204 class TextDialog(wx.Dialog): 
    205     """ 
    206     Dialog for easy custom sum models   
    207     """ 
    208     def __init__(self, parent=None, id=None, title='', model_list=[]): 
    209         """ 
    210         Dialog window popup when selecting 'Easy Custom Sum' on the menu 
    211         """ 
    212         wx.Dialog.__init__(self, parent=parent, id=id,  
    213                            title=title, size=(PNL_WIDTH, PNL_HITE)) 
    214         self.parent = parent 
    215         #Font 
    216         self.SetWindowVariant(variant=FONT_VARIANT) 
    217         # default 
    218         self.model_list = model_list 
    219         self.model1_string = "SphereModel" 
    220         self.model2_string = "CylinderModel" 
    221         self._build_sizer() 
    222         self.model1_name = str(self.model1.GetValue()) 
    223         self.model2_name = str(self.model2.GetValue()) 
    224          
    225     def _build_sizer(self): 
    226         """ 
    227         Build gui 
    228         """ 
    229         _BOX_WIDTH = 195 # combobox width 
    230         vbox  = wx.BoxSizer(wx.VERTICAL) 
    231         sizer = wx.GridBagSizer(1, 3) 
    232         sum_description= wx.StaticBox(self, -1, 'Select',  
    233                                        size=(PNL_WIDTH-30, 70)) 
    234         sum_box = wx.StaticBoxSizer(sum_description, wx.VERTICAL) 
    235         model1_box = wx.BoxSizer(wx.HORIZONTAL) 
    236         model2_box = wx.BoxSizer(wx.HORIZONTAL) 
    237         model_vbox = wx.BoxSizer(wx.VERTICAL) 
    238         self.model1 =  wx.ComboBox(self, -1, style=wx.CB_READONLY) 
    239         wx.EVT_COMBOBOX(self.model1, -1, self.on_model1) 
    240         self.model1.SetMinSize((_BOX_WIDTH, -1)) 
    241         self.model1.SetToolTipString("model1") 
    242         self.model2 =  wx.ComboBox(self, -1, style=wx.CB_READONLY) 
    243         wx.EVT_COMBOBOX(self.model2, -1, self.on_model2) 
    244         self.model2.SetMinSize((_BOX_WIDTH, -1)) 
    245         self.model2.SetToolTipString("model2") 
    246         self._set_model_list() 
    247          
    248          # Buttons on the bottom 
    249         self.static_line_1 = wx.StaticLine(self, -1) 
    250         self.okButton = wx.Button(self,wx.ID_OK, 'OK', size=(_BOX_WIDTH/2, 25)) 
    251         self.closeButton = wx.Button(self,wx.ID_CANCEL, 'Cancel',  
    252                                      size=(_BOX_WIDTH/2, 25)) 
    253         # Intro 
    254         explanation  = "  custom model = scale_factor * (model1 + model2)\n" 
    255         model_string = " Model%s (p%s):" 
    256         vbox.Add(sizer) 
    257         ix = 0 
    258         iy = 1 
    259         sizer.Add(wx.StaticText(self, -1, explanation), (iy, ix), 
    260                  (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15) 
    261         model1_box.Add(wx.StaticText(self,-1, model_string% (1, 1)), -1, 0) 
    262         model1_box.Add((_BOX_WIDTH-35,10)) 
    263         model1_box.Add(wx.StaticText(self, -1, model_string% (2, 2)), -1, 0) 
    264         model2_box.Add(self.model1, -1, 0) 
    265         model2_box.Add((20,10)) 
    266         model2_box.Add(self.model2, -1, 0) 
    267         model_vbox.Add(model1_box, -1, 0) 
    268         model_vbox.Add(model2_box, -1, 0) 
    269         sum_box.Add(model_vbox, -1, 10) 
    270         iy += 1 
    271         ix = 0 
    272         sizer.Add(sum_box, (iy, ix), 
    273                   (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15) 
    274         vbox.Add((10,10)) 
    275         vbox.Add(self.static_line_1, 0, wx.EXPAND, 10) 
    276         sizer_button = wx.BoxSizer(wx.HORIZONTAL) 
    277         sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0) 
    278         sizer_button.Add(self.okButton, 0,  
    279                          wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 0) 
    280         sizer_button.Add(self.closeButton, 0, 
    281                           wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 15)         
    282         vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10) 
    283         self.SetSizer(vbox) 
    284         self.Centre() 
    285                   
    286     def _set_model_list(self): 
    287         """ 
    288         Set the list of models 
    289         """ 
    290         # list of model names 
    291         list = self.model_list 
    292         if len(list) > 1: 
    293             list.sort() 
    294         for idx in range(len(list)): 
    295             self.model1.Append(list[idx],idx)  
    296             self.model2.Append(list[idx],idx) 
    297         self.model1.SetStringSelection(self.model1_string) 
    298         self.model2.SetStringSelection(self.model2_string) 
    299             
    300     def on_model1(self, event): 
    301         """ 
    302         Set model1 
    303         """ 
    304         event.Skip() 
    305         self.model1_name = str(self.model1.GetValue()) 
    306         self.model1_string = self.model1_name 
    307              
    308     def on_model2(self, event): 
    309         """ 
    310         Set model2 
    311         """ 
    312         event.Skip() 
    313         self.model2_name = str(self.model2.GetValue()) 
    314         self.model2_string = self.model2_name 
    315   
    316     def getText(self): 
    317         """ 
    318         Returns model name string as list 
    319         """ 
    320         return [self.model1_name, self.model2_name] 
    321      
    322     def write_string(self, fname, name1, name2): 
    323         """ 
    324         Write and Save file 
    325         """ 
    326         try: 
    327             out_f =  open(fname,'w') 
    328         except : 
    329             raise 
    330         lines = SUM_TEMPLATE.split('\n') 
    331         for line in lines: 
    332             if line.count("import %s as P1"): 
    333                 out_f.write(line % (name1, name1) + "\n") 
    334             elif line.count("import %s as P2"): 
    335                 out_f.write(line % (name2, name2) + "\n") 
    336             else: 
    337                 out_f.write(line + "\n") 
    338         out_f.close()  
    339          
    340     def compile_file(self, path): 
    341         """ 
    342         Compile the file in the path 
    343         """ 
    344         try: 
    345             import py_compile 
    346             py_compile.compile(file=path, doraise=True) 
    347         except: 
    348             type, value, traceback = sys.exc_info() 
    349             return value 
    350          
    351     def delete_file(self, path): 
    352         """ 
    353         Delete file in the path 
    354         """ 
    355         try: 
    356             os.remove(path) 
    357         except: 
    358             raise 
    359          
    360          
    361 SUM_TEMPLATE = """ 
    362 # A sample of an experimental model function for Sum(Pmodel1,Pmodel2) 
    363 import copy 
    364 from sans.models.pluginmodel import Model1DPlugin 
    365 # User can change the name of the model (only with single functional model) 
    366 #P1_model:  
    367 from sans.models.%s import %s as P1 
    368  
    369 #P2_model:  
    370 from sans.models.%s import %s as P2 
    371  
    372 class Model(Model1DPlugin): 
    373     name = "" 
    374     def __init__(self): 
    375         Model1DPlugin.__init__(self, name='') 
    376         p_model1 = P1() 
    377         p_model2 = P2() 
    378         ## Setting  model name model description 
    379         self.description="" 
    380         self.name = self._get_name(p_model1.name, p_model2.name) 
    381         self.description = p_model1.name 
    382         self.description += p_model2.name 
    383         self.fill_description(p_model1, p_model2) 
    384  
    385         ## Define parameters 
    386         self.params = {} 
    387  
    388         ## Parameter details [units, min, max] 
    389         self.details = {} 
    390          
    391         # non-fittable parameters 
    392         self.non_fittable = p_model1.non_fittable   
    393         self.non_fittable += p_model2.non_fittable   
    394              
    395         ##models  
    396         self.p_model1= p_model1 
    397         self.p_model2= p_model2 
    398          
    399         
    400         ## dispersion 
    401         self._set_dispersion() 
    402         ## Define parameters 
    403         self._set_params() 
    404         ## New parameter:Scaling factor 
    405         self.params['scale_factor'] = 1 
    406          
    407         ## Parameter details [units, min, max] 
    408         self._set_details() 
    409         self.details['scale_factor'] = ['', None, None] 
    410  
    411          
    412         #list of parameter that can be fitted 
    413         self._set_fixed_params()   
    414         ## parameters with orientation 
    415         for item in self.p_model1.orientation_params: 
    416             new_item = "p1_" + item 
    417             if not new_item in self.orientation_params: 
    418                 self.orientation_params.append(new_item) 
    419              
    420         for item in self.p_model2.orientation_params: 
    421             new_item = "p2_" + item 
    422             if not new_item in self.orientation_params: 
    423                 self.orientation_params.append(new_item) 
    424         # get multiplicity if model provide it, else 1. 
    425         try: 
    426             multiplicity1 = p_model1.multiplicity 
    427             try: 
    428                 multiplicity2 = p_model2.multiplicity 
    429             except: 
    430                 multiplicity2 = 1 
    431         except: 
    432             multiplicity1 = 1 
    433             multiplicity2 = 1 
    434         ## functional multiplicity of the model 
    435         self.multiplicity1 = multiplicity1   
    436         self.multiplicity2 = multiplicity2     
    437         self.multiplicity_info = []    
    438          
    439     def _clone(self, obj): 
    440         obj.params     = copy.deepcopy(self.params) 
    441         obj.description     = copy.deepcopy(self.description) 
    442         obj.details    = copy.deepcopy(self.details) 
    443         obj.dispersion = copy.deepcopy(self.dispersion) 
    444         obj.p_model1  = self.p_model1.clone() 
    445         obj.p_model2  = self.p_model2.clone() 
    446         #obj = copy.deepcopy(self) 
    447         return obj 
    448      
    449     def _get_name(self, name1, name2): 
    450         name = self._get_upper_name(name1) 
    451         name += "+" 
    452         name += self._get_upper_name(name2) 
    453         return name 
    454      
    455     def _get_upper_name(self, name=None): 
    456         if name == None: 
    457             return "" 
    458         upper_name = "" 
    459         str_name = str(name) 
    460         for index in range(len(str_name)): 
    461             if str_name[index].isupper(): 
    462                 upper_name += str_name[index] 
    463         return upper_name 
    464          
    465     def _set_dispersion(self): 
    466         ##set dispersion only from p_model  
    467         for name , value in self.p_model1.dispersion.iteritems(): 
    468             #if name.lower() not in self.p_model1.orientation_params: 
    469             new_name = "p1_" + name 
    470             self.dispersion[new_name]= value  
    471         for name , value in self.p_model2.dispersion.iteritems(): 
    472             #if name.lower() not in self.p_model2.orientation_params: 
    473             new_name = "p2_" + name 
    474             self.dispersion[new_name]= value  
    475              
    476     def function(self, x=0.0):  
    477         return 0 
    478                                 
    479     def getProfile(self): 
    480         try: 
    481             x,y = self.p_model1.getProfile() 
    482         except: 
    483             x = None 
    484             y = None 
    485              
    486         return x, y 
    487      
    488     def _set_params(self): 
    489         for name , value in self.p_model1.params.iteritems(): 
    490             # No 2D-supported 
    491             #if name not in self.p_model1.orientation_params: 
    492             new_name = "p1_" + name 
    493             self.params[new_name]= value 
    494              
    495         for name , value in self.p_model2.params.iteritems(): 
    496             # No 2D-supported 
    497             #if name not in self.p_model2.orientation_params: 
    498             new_name = "p2_" + name 
    499             self.params[new_name]= value 
    500                  
    501         # Set "scale" as initializing 
    502         self._set_scale_factor() 
    503        
    504              
    505     def _set_details(self): 
    506         for name ,detail in self.p_model1.details.iteritems(): 
    507             new_name = "p1_" + name 
    508             #if new_name not in self.orientation_params: 
    509             self.details[new_name]= detail 
    510              
    511         for name ,detail in self.p_model2.details.iteritems(): 
    512             new_name = "p2_" + name 
    513             #if new_name not in self.orientation_params: 
    514             self.details[new_name]= detail 
    515      
    516     def _set_scale_factor(self): 
    517         pass 
    518          
    519                  
    520     def setParam(self, name, value): 
    521         # set param to p1+p2 model 
    522         self._setParamHelper(name, value) 
    523          
    524         ## setParam to p model  
    525         model_pre = name.split('_', 1)[0] 
    526         new_name = name.split('_', 1)[1] 
    527         if model_pre == "p1": 
    528             if new_name in self.p_model1.getParamList(): 
    529                 self.p_model1.setParam(new_name, value) 
    530         elif model_pre == "p2": 
    531              if new_name in self.p_model2.getParamList(): 
    532                 self.p_model2.setParam(new_name, value) 
    533         elif name.lower() == 'scale_factor': 
    534             self.params['scale_factor'] = value 
    535         else: 
    536             raise ValueError, "Model does not contain parameter %s" % name 
    537              
    538     def getParam(self, name): 
    539         # Look for dispersion parameters 
    540         toks = name.split('.') 
    541         if len(toks)==2: 
    542             for item in self.dispersion.keys(): 
    543                 # 2D not supported 
    544                 if item.lower()==toks[0].lower(): 
    545                     for par in self.dispersion[item]: 
    546                         if par.lower() == toks[1].lower(): 
    547                             return self.dispersion[item][par] 
    548         else: 
    549             # Look for standard parameter 
    550             for item in self.params.keys(): 
    551                 if item.lower()==name.lower(): 
    552                     return self.params[item] 
    553         return   
    554         #raise ValueError, "Model does not contain parameter %s" % name 
    555         
    556     def _setParamHelper(self, name, value): 
    557         # Look for dispersion parameters 
    558         toks = name.split('.') 
    559         if len(toks)== 2: 
    560             for item in self.dispersion.keys(): 
    561                 if item.lower()== toks[0].lower(): 
    562                     for par in self.dispersion[item]: 
    563                         if par.lower() == toks[1].lower(): 
    564                             self.dispersion[item][par] = value 
    565                             return 
    566         else: 
    567             # Look for standard parameter 
    568             for item in self.params.keys(): 
    569                 if item.lower()== name.lower(): 
    570                     self.params[item] = value 
    571                     return 
    572              
    573         raise ValueError, "Model does not contain parameter %s" % name 
    574               
    575     
    576     def _set_fixed_params(self): 
    577         for item in self.p_model1.fixed: 
    578             new_item = "p1" + item 
    579             self.fixed.append(new_item) 
    580         for item in self.p_model2.fixed: 
    581             new_item = "p2" + item 
    582             self.fixed.append(new_item) 
    583  
    584         self.fixed.sort() 
    585                  
    586                      
    587     def run(self, x = 0.0): 
    588         self._set_scale_factor() 
    589         return self.params['scale_factor'] * \ 
    590 (self.p_model1.run(x) + self.p_model2.run(x)) 
    591      
    592     def runXY(self, x = 0.0): 
    593         self._set_scale_factor() 
    594         return self.params['scale_factor'] * \ 
    595 (self.p_model1.runXY(x) + self.p_model2.runXY(x)) 
    596      
    597     ## Now (May27,10) directly uses the model eval function  
    598     ## instead of the for-loop in Base Component. 
    599     def evalDistribution(self, x = []): 
    600         self._set_scale_factor() 
    601         return self.params['scale_factor'] * \ 
    602 (self.p_model1.evalDistribution(x) + \ 
    603 self.p_model2.evalDistribution(x)) 
    604  
    605     def set_dispersion(self, parameter, dispersion): 
    606         value= None 
    607         new_pre = parameter.split("_", 1)[0] 
    608         new_parameter = parameter.split("_", 1)[1] 
    609         try: 
    610             if new_pre == 'p1' and \ 
    611 new_parameter in self.p_model1.dispersion.keys(): 
    612                 value= self.p_model1.set_dispersion(new_parameter, dispersion) 
    613             if new_pre == 'p2' and \ 
    614 new_parameter in self.p_model2.dispersion.keys(): 
    615                 value= self.p_model2.set_dispersion(new_parameter, dispersion) 
    616             self._set_dispersion() 
    617             return value 
    618         except: 
    619             raise  
    620  
    621     def fill_description(self, p_model1, p_model2): 
    622         description = "" 
    623         description +="This model gives the summation of  %s and %s. "% \ 
    624 ( p_model1.name, p_model2.name ) 
    625         self.description += description 
    626          
    627 if __name__ == "__main__":  
    628     m1= Model()  
    629     #m1.setParam("p1_scale", 25)   
    630     #m1.setParam("p1_length", 1000) 
    631     #m1.setParam("p2_scale", 100)  
    632     #m1.setParam("p2_rg", 100)  
    633     out1 = m1.runXY(0.01) 
    634  
    635     m2= Model() 
    636     #m2.p_model1.setParam("scale", 25)  
    637     #m2.p_model1.setParam("length", 1000)  
    638     #m2.p_model2.setParam("scale", 100) 
    639     #m2.p_model2.setParam("rg", 100) 
    640     out2 = m2.p_model1.runXY(0.01) + m2.p_model2.runXY(0.01) 
    641     print out1, " = ", out2 
    642     if out1 == out2: 
    643         print "===> Simple Test: Passed!" 
    644     else: 
    645         print "===> Simple Test: Failed!" 
    646 """ 
    647          
    648 if __name__ == "__main__":  
    649     app = wx.PySimpleApp() 
    650     frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"])    
    651     frame.Show(True) 
    652     app.MainLoop()              
    653  
    654            
  • fittingview/src/sans/perspectives/fitting/help_panel.py

    rfdc3cb0 r6f140f2  
    2121        contains help info 
    2222        """ 
     23        self.Show(False) 
    2324        self.SetTitle('Fitting Help')  
    2425        from sans.perspectives.fitting import get_data_path as fit_path 
     
    132133        self.splitter = splitter 
    133134        self.Centre() 
    134         self.Show(True) 
    135135        self.Bind(wx.EVT_SIZE, self.on_Size) 
    136136         
  • fittingview/src/sans/perspectives/fitting/plugin_models/testmodel_2.py

    re9bd127 r6f140f2  
    1616 
    1717from sans.models.pluginmodel import Model1DPlugin  ##DO NOT CHANGE THIS LINE!!! 
    18 from math import *                    ##DO NOT CHANGE THIS LINE!!! 
    19 from numpy import *                ##DO NOT CHANGE THIS LINE!!! 
     18import math                     ##DO NOT CHANGE THIS LINE!!! 
     19import numpy                    ##DO NOT CHANGE THIS LINE!!! 
    2020 
    2121##PLEASE READ COMMENTS CAREFULLY !!! COMMENT ARE IN CAPITAL LETTERS AND AFTER ## 
Note: See TracChangeset for help on using the changeset viewer.