source: sasview/sansmodels/src/sans/models/c_extensions/WrapperGenerator.py @ 76b5220

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 76b5220 was 59b9b675, checked in by Jae Cho <jhjcho@…>, 14 years ago

1) reverting diperser(used only in test) files for unittest. 2)changed the sigma of polydispersion to ratio for length parameters. 3) update an unittest

  • Property mode set to 100644
File size: 14.3 KB
Line 
1#!/usr/bin/env python
2""" WrapperGenerator class to generate model code automatically.
3"""
4
5import os, sys
6import re
7
8class WrapperGenerator:
9    """ Python wrapper generator for C models
10   
11        The developer must provide a header file describing
12        the new model.
13       
14        To provide the name of the Python class to be
15        generated, the .h file must contain the following
16        string in the comments:
17       
18        // [PYTHONCLASS] = my_model
19       
20        where my_model must be replaced by the name of the
21        class that you want to import from sans.models.
22        (example: [PYTHONCLASS] = MyModel
23          will create a class MyModel in sans.models.MyModel.
24          It will also create a class CMyModel in
25          sans_extension.c_models.)
26         
27        Also in comments, each parameter of the params
28        dictionary must be declared with a default value
29        in the following way:
30       
31        // [DEFAULT]=param_name=default_value
32       
33        (example:
34            //  [DEFAULT]=radius=20.0
35        )
36         
37        See cylinder.h for an example.
38       
39       
40        A .c file corresponding to the .h file should also
41        be provided (example: my_model.h, my_model.c).
42   
43        The .h file should define two function definitions. For example,
44        cylinder.h defines the following:
45       
46            /// 1D scattering function
47            double cylinder_analytical_1D(CylinderParameters *pars, double q);
48           
49            /// 2D scattering function
50            double cylinder_analytical_2D(CylinderParameters *pars, double q, double phi);
51           
52        The .c file implements those functions.
53       
54        @author: Mathieu Doucet / UTK
55        @contact: mathieu.doucet@nist.gov
56    """
57   
58    def __init__(self, filename):
59        """ Initialization """
60       
61        ## Name of .h file to generate wrapper from
62        self.file = filename
63       
64        # Info read from file
65       
66        ## Name of python class to write
67        self.pythonClass = None
68        ## Parser in struct section
69        self.inStruct = False
70        ## Name of struct for the c object
71        self.structName = None
72        ## Dictionary of parameters
73        self.params = {}
74        ## ModelCalculation module flag
75        self.modelCalcFlag = False
76        ## List of default parameters (text)
77        self.default_list = ""
78        ##description
79        self.description=''
80        ## Dictionary of units
81        self.details = ""
82       
83    def __repr__(self):
84        """ Simple output for printing """
85       
86        rep  = "Python class: %s\n" % self.pythonClass
87        rep += "  struc name: %s\n" % self.structName
88        rep += "  params:     %s\n" % self.params
89        rep += "  description: %s\n"% self.description
90        return rep
91       
92    def read(self):
93        """ Reads in the .h file to catch parameters of the wrapper """
94       
95        # Check if the file is there
96        if not os.path.isfile(self.file):
97            raise ValueError, "File %s is not a regular file" % self.file
98       
99        # Read file
100        f = open(self.file,'r')
101        buf = f.read()
102       
103        self.default_list = "List of default parameters:\n"
104        #lines = string.split(buf,'\n')
105        lines = buf.split('\n')
106        self.details  = "## Parameter details [units, min, max]\n"
107        self.details += "        self.details = {}\n"
108        # Catch Description
109        key = "[DESCRIPTION]"
110        find_description= 0
111        temp=""
112        for line in lines:
113            if line.count(key)>0 :
114               
115                try:
116                    find_description= 1
117                    index = line.index(key)
118                    toks = line[index:].split("=",1 )
119                    temp=toks[1].lstrip().rstrip()
120                    text='text'
121                    key2="<%s>"%text.lower()
122                    if re.match(key2,temp)!=None:
123                        #index2 = line.index(key2)
124                        #temp = temp[index2:]
125                        toks2=temp.split(key2,1)
126                        self.description=toks2[1]
127                        text='text'
128                        key2="</%s>"%text.lower()
129                        if re.search(key2,toks2[1])!=None:
130                            temp=toks2[1].split(key2,1)
131                            self.description=temp[0]
132                            break
133                        #print self.description
134                    else:
135                        self.description=temp
136                        break
137                except:
138                     raise ValueError, "Could not parse file %s" % self.file
139            elif find_description==1:
140                text='text'
141                key2="</%s>"%text.lower()
142                #print "second line",line,key2,re.search(key2,line)
143                if re.search(key2,line)!=None:
144                    tok=line.split(key2,1)
145                    temp=tok[0].split("//",1)
146                    self.description+=tok[1].lstrip().rstrip()
147                    break
148                else:
149                    #if re.search("*",line)!=None:
150                    #    temp=line.split("*",1)
151                    #    self.description+='\n'+temp[1].lstrip().rstrip()
152                    if re.search("//",line)!=None:
153                        temp=line.split("//",1)
154                        self.description+='\n\t\t'+temp[1].lstrip().rstrip()
155                       
156                    else:
157                        self.description+='\n\t\t'+line.lstrip().rstrip()
158                   
159               
160        for line in lines:
161           
162            # Catch class name
163            key = "[PYTHONCLASS]"
164            if line.count(key)>0:
165                try:
166                    index = line.index(key)
167                    #toks = string.split( line[index:], "=" )
168                    toks = line[index:].split("=" )
169                    self.pythonClass = toks[1].lstrip().rstrip()
170                except:
171                    raise ValueError, "Could not parse file %s" % self.file
172               
173            # Catch struct name
174            if line.count("typedef struct")>0:
175                # We are entering a struct block
176                self.inStruct = True
177           
178            if self.inStruct and line.count("}")>0:
179                # We are exiting a struct block
180                self.inStruct = False
181   
182                # Catch the name of the struct
183                index = line.index("}")
184                #toks = string.split(line[index+1:],";")
185                toks = line[index+1:].split(";")
186                # Catch pointer definition
187                #toks2 = string.split(toks[0],',')
188                toks2 = toks[0].split(',')
189                self.structName = toks2[0].lstrip().rstrip()
190         
191            # Catch struct content
192            key = "[DEFAULT]"
193            if self.inStruct and line.count(key)>0:
194                # Found a new parameter
195                try:
196                    index = line.index(key)
197                    toks = line[index:].split("=")
198                    toks2 = toks[2].split()
199                    val = float(toks2[0])
200                    self.params[toks[1]] = val
201                    #self.pythonClass = toks[1].lstrip().rstrip()
202                    units = ""
203                    if len(toks2) >= 2:
204                        units = toks2[1]
205                    self.default_list += "         %-15s = %s %s\n" % \
206                        (toks[1], val, units)
207                   
208                    # Check for min and max
209                    min = "None"
210                    max = "None"
211                    if len(toks2) == 4:
212                        min = toks2[2]
213                        max = toks2[3]
214                   
215                    self.details += "        self.details['%s'] = ['%s', %s, %s]\n" % \
216                        (toks[1].lstrip().rstrip(), units.lstrip().rstrip(), min, max)
217                except:
218                    raise ValueError, "Could not parse input file %s \n  %s" % \
219                        (self.file, sys.exc_value)
220               
221               
222            # Catch need for numerical calculations
223            key = "CalcParameters calcPars"
224            if line.count(key)>0:
225                self.modelCalcFlag = True
226           
227               
228               
229    def write_c_wrapper(self):
230        """ Writes the C file to create the python extension class
231            The file is written in C[PYTHONCLASS].c
232        """
233       
234        file = open("C"+self.pythonClass+'.c', 'w')
235        template = open("classTemplate.txt", 'r')
236       
237        tmp_buf = template.read()
238        #tmp_lines = string.split(tmp_buf,'\n')
239        tmp_lines = tmp_buf.split('\n')
240       
241        for tmp_line in tmp_lines:
242           
243            # Catch class name
244            newline = self.replaceToken(tmp_line, 
245                                        "[PYTHONCLASS]", 'C'+self.pythonClass)
246            ##catch description
247            #newline = self.replaceToken(tmp_line,
248            #                            "[DESCRIPTION]", self.description)
249            # Catch class name
250            newline = self.replaceToken(newline, 
251                                        "[MODELSTRUCT]", self.structName)
252           
253            # Dictionary initialization
254            param_str = "// Initialize parameter dictionary\n"
255            for par in self.params:
256                param_str += "        PyDict_SetItemString(self->params,\"%s\",Py_BuildValue(\"d\",%f));\n" % \
257                    (par, self.params[par])
258               
259            newline = self.replaceToken(newline,
260                                        "[INITDICTIONARY]", param_str)
261           
262            # Read dictionary
263            param_str = "// Reader parameter dictionary\n"
264            for par in self.params:
265                param_str += "    self->model_pars.%s = PyFloat_AsDouble( PyDict_GetItemString(self->params, \"%s\") );\n" % \
266                    (par, par)
267               
268            newline = self.replaceToken(newline, "[READDICTIONARY]", param_str)
269               
270            # Name of .c file
271            #toks = string.split(self.file,'.')
272            toks = self.file.split('.')
273            newline = self.replaceToken(newline, "[C_FILENAME]", toks[0])
274           
275            # Include file
276            newline = self.replaceToken(newline, 
277                                        "[INCLUDE_FILE]", self.file)           
278               
279            # Numerical calcs dealloc
280            dealloc_str = "\n"
281            if self.modelCalcFlag:
282                dealloc_str = "    modelcalculations_dealloc(&(self->model_pars.calcPars));\n"
283            newline = self.replaceToken(newline, 
284                                        "[NUMERICAL_DEALLOC]", dealloc_str)     
285               
286            # Numerical calcs init
287            init_str = "\n"
288            if self.modelCalcFlag:
289                init_str = "        modelcalculations_init(&(self->model_pars.calcPars));\n"
290            newline = self.replaceToken(newline, 
291                                        "[NUMERICAL_INIT]", init_str)     
292               
293            # Numerical calcs reset
294            reset_str = "\n"
295            if self.modelCalcFlag:
296                reset_str = "modelcalculations_reset(&(self->model_pars.calcPars));\n"
297            newline = self.replaceToken(newline, 
298                                        "[NUMERICAL_RESET]", reset_str)     
299               
300            # Write new line to the wrapper .c file
301            file.write(newline+'\n')
302           
303           
304        file.close()
305       
306    def write_python_wrapper(self):
307        """ Writes the python file to create the python extension class
308            The file is written in ../[PYTHONCLASS].py
309        """
310       
311        file = open("../"+self.pythonClass+'.py', 'w')
312        template = open("modelTemplate.txt", 'r')
313       
314        tmp_buf = template.read()
315        tmp_lines = tmp_buf.split('\n')
316       
317        for tmp_line in tmp_lines:
318           
319            # Catch class name
320            newline = self.replaceToken(tmp_line, 
321                                        "[CPYTHONCLASS]", 'C'+self.pythonClass)
322           
323            # Catch class name
324            newline = self.replaceToken(newline, 
325                                        "[PYTHONCLASS]", self.pythonClass)
326           
327            # Include file
328            newline = self.replaceToken(newline, 
329                                        "[INCLUDE_FILE]", self.file)   
330                   
331            # Include file
332            newline = self.replaceToken(newline, 
333                                        "[DEFAULT_LIST]", self.default_list)
334            # Model Description
335            newline = self.replaceToken(newline, 
336                                        "[DESCRIPTION]", self.description)
337            # Parameter details
338            newline = self.replaceToken(newline, 
339                                        "[PAR_DETAILS]", self.details)
340            #print"write",tmp_line
341            # Write new line to the wrapper .c file
342            file.write(newline+'\n')
343           
344        file.close()
345       
346       
347    def replaceToken(self, line, key, value): #pylint: disable-msg=R0201
348        """ Replace a token in the template file
349            @param line: line of text to inspect
350            @param key: token to look for
351            @param value: string value to replace the token with
352            @return: new string value
353        """
354        lenkey = len(key)
355        newline = line
356        while newline.count(key)>0:
357            index = newline.index(key)
358            newline = newline[:index]+value+newline[index+lenkey:]
359        return newline
360       
361       
362# main
363if __name__ == '__main__':
364    if len(sys.argv)>1:
365        print "Will look for file %s" % sys.argv[1]
366        app = WrapperGenerator(sys.argv[1])
367    else:
368        app = WrapperGenerator("test.h")
369    app.read()
370    app.write_c_wrapper()
371    app.write_python_wrapper()
372    print app
373   
374# End of file       
Note: See TracBrowser for help on using the repository browser.