f76f24deff25049de18fc8a6ff7c44ec35fb89e6
[alexxy/gromacs.git] / src / gromacs / gmxlib / nonbonded / nb_kernel_adress_c / make_nb_kernel_adress_c.py
1 #!/usr/bin/python
2 #
3 # This file is part of the GROMACS molecular simulation package.
4 #
5 # Copyright (c) 2012,2013, by the GROMACS development team, led by
6 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 # and including many others, as listed in the AUTHORS file in the
8 # top-level source directory and at http://www.gromacs.org.
9 #
10 # GROMACS is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU Lesser General Public License
12 # as published by the Free Software Foundation; either version 2.1
13 # of the License, or (at your option) any later version.
14 #
15 # GROMACS is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 # Lesser General Public License for more details.
19 #
20 # You should have received a copy of the GNU Lesser General Public
21 # License along with GROMACS; if not, see
22 # http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24 #
25 # If you want to redistribute modifications to GROMACS, please
26 # consider that scientific software is very special. Version
27 # control is crucial - bugs must be traceable. We will be happy to
28 # consider code for inclusion in the official distribution, but
29 # derived work must not be called official GROMACS. Details are found
30 # in the README & COPYING files - if they are missing, get the
31 # official version at http://www.gromacs.org.
32 #
33 # To help us fund GROMACS development, we humbly ask that you cite
34 # the research papers on the package. Check out http://www.gromacs.org.
35
36 import sys
37 import os
38 sys.path.append ( "../preprocessor" )
39 from gmxpreprocess import gmxpreprocess
40
41 # "The happiest programs are programs that write other programs."
42 #
43 #
44 # This script controls the generation of Gromacs nonbonded kernels.
45 #
46 # We no longer generate kernels on-the-fly, so this file is not run
47 # during a Gromacs compile - only when we need to update the kernels (=rarely).
48 #
49 # To maximize performance, each combination of interactions in Gromacs
50 # has a separate nonbonded kernel without conditionals in the code.
51 # To avoid writing hundreds of different routines for each architecture,
52 # we instead use a custom preprocessor so we can encode the conditionals
53 # and expand for-loops (e.g, for water-water interactions)
54 # from a general kernel template. While that file will contain quite a
55 # few preprocessor directives, it is still an order of magnitude easier
56 # to maintain than ~200 different kernels (not to mention it avoids bugs).
57 #
58 # To actually generate the kernels, this program iteratively calls the
59 # preprocessor with different define settings corresponding to all
60 # combinations of coulomb/van-der-Waals/geometry options.
61 #
62 # A main goal in the design was to make this new generator _general_. For
63 # this reason we have used a lot of different fields to identify a particular
64 # kernel and interaction. Basically, each kernel will have a name like
65 #
66 # nbkernel_ElecXX_VdwYY_GeomZZ_VF_QQ()
67 #
68 # Where XX/YY/ZZ/VF are strings to identify what the kernel computes.
69 #
70 # Elec/Vdw describe the type of interaction for electrostatics and van der Waals.
71 # The geometry settings correspond e.g. to water-water or water-particle kernels,
72 # and finally the VF setting is V,F,or VF depending on whether we calculate
73 # only the potential, only the force, or both of them. The final string (QQ)
74 # is the architecture/language/optimization of the kernel.
75 #
76 Arch       = 'c'
77
78 # Explanation of the 'properties':
79 #
80 # It is cheap to compute r^2, and the kernels require various other functions of r for
81 # different kinds of interaction. Depending on the needs of the kernel and the available
82 # processor instructions, this will be done in different ways.
83 #
84 # 'rinv' means we need 1/r, which is calculated as 1/sqrt(r^2).
85 # 'rinvsq' means we need 1/(r*r). This is calculated as rinv*rinv if we already did rinv, otherwise 1/r^2.
86 # 'r' is similarly calculated as r^2*rinv when needed
87 # 'table' means the interaction is tabulated, in which case we will calculate a table index before the interaction
88 # 'shift' means the interaction will be modified by a constant to make it zero at the cutoff.
89 # 'cutoff' means the interaction is set to 0.0 outside the cutoff
90 #
91
92 FileHeader = \
93 '/*\n' \
94 ' * This file is part of the GROMACS molecular simulation package.\n' \
95 ' *\n' \
96 ' * Copyright (c) 2012, by the GROMACS development team, led by\n' \
97 ' * David van der Spoel, Berk Hess, Erik Lindahl, and including many\n' \
98 ' * others, as listed in the AUTHORS file in the top-level source\n' \
99 ' * directory and at http://www.gromacs.org.\n' \
100 ' *\n' \
101 ' * GROMACS is free software; you can redistribute it and/or\n' \
102 ' * modify it under the terms of the GNU Lesser General Public License\n' \
103 ' * as published by the Free Software Foundation; either version 2.1\n' \
104 ' * of the License, or (at your option) any later version.\n' \
105 ' *\n' \
106 ' * GROMACS is distributed in the hope that it will be useful,\n' \
107 ' * but WITHOUT ANY WARRANTY; without even the implied warranty of\n' \
108 ' * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n' \
109 ' * Lesser General Public License for more details.\n' \
110 ' *\n' \
111 ' * You should have received a copy of the GNU Lesser General Public\n' \
112 ' * License along with GROMACS; if not, see\n' \
113 ' * http://www.gnu.org/licenses, or write to the Free Software Foundation,\n' \
114 ' * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.\n' \
115 ' *\n' \
116 ' * If you want to redistribute modifications to GROMACS, please\n' \
117 ' * consider that scientific software is very special. Version\n' \
118 ' * control is crucial - bugs must be traceable. We will be happy to\n' \
119 ' * consider code for inclusion in the official distribution, but\n' \
120 ' * derived work must not be called official GROMACS. Details are found\n' \
121 ' * in the README & COPYING files - if they are missing, get the\n' \
122 ' * official version at http://www.gromacs.org.\n' \
123 ' *\n' \
124 ' * To help us fund GROMACS development, we humbly ask that you cite\n' \
125 ' * the research papers on the package. Check out http://www.gromacs.org.\n' \
126 ' */\n' \
127 '/*\n' \
128 ' * Note: this file was generated by the GROMACS '+Arch+' kernel generator.\n' \
129 ' */\n'
130
131 ###############################################
132 # ELECTROSTATICS
133 # Interactions and flags for them
134 ###############################################
135 ElectrostaticsList = {
136     'None'                    : [],
137     'Coulomb'                 : ['rinv','rinvsq'],
138     'ReactionField'           : ['rinv','rinvsq'],
139     'GeneralizedBorn'         : ['rinv','r'],
140     'CubicSplineTable'        : ['rinv','r','table'],
141     'Ewald'                   : ['rinv','rinvsq','r'],
142 }
143
144
145 ###############################################
146 # VAN DER WAALS
147 # Interactions and flags for them
148 ###############################################
149 VdwList = {
150     'None'                    : [],
151     'LennardJones'            : ['rinvsq'],
152     'Buckingham'              : ['rinv','rinvsq','r'],
153     'CubicSplineTable'        : ['rinv','r','table'],
154 }
155
156
157 ###############################################
158 # MODIFIERS
159 # Different ways to adjust/modify interactions to conserve energy
160 ###############################################
161 ModifierList = {
162     'None'                    : [],
163     'ExactCutoff'             : ['exactcutoff'],        # Zero the interaction outside the cutoff, used for reaction-field-zero
164     'PotentialShift'          : ['shift','exactcutoff'],
165     'PotentialSwitch'         : ['rinv','r','switch','exactcutoff']
166 }
167
168
169 ###############################################
170 # GEOMETRY COMBINATIONS
171 ###############################################
172 GeometryNameList = [
173     [ 'Particle' , 'Particle' ],
174     [ 'Water3'   , 'Particle' ],
175     [ 'Water3'   , 'Water3'   ],     
176     [ 'Water4'   , 'Particle' ],
177     [ 'Water4'   , 'Water4'   ]
178 ]
179
180
181 ###############################################
182 # POTENTIAL / FORCE
183 ###############################################
184 VFList = [
185     'PotentialAndForce',
186 # 'Potential',   # Not used yet
187     'Force'
188 ]
189
190
191 ###############################################
192 # GEOMETRY PROPERTIES
193 ###############################################
194 # Dictionaries with lists telling which interactions are present
195 # 1,2,3 means particles 1,2,3 (but not 0) have electrostatics!
196 GeometryElectrostatics = {
197     'Particle'  : [ 0 ],
198     'Particle2' : [ 0 , 1 ],
199     'Particle3' : [ 0 , 1 , 2 ],
200     'Particle4' : [ 0 , 1 , 2 , 3 ],
201     'Water3'    : [ 0 , 1 , 2 ],
202     'Water4'    : [ 1 , 2 , 3 ]
203 }
204
205 GeometryVdw = {
206     'Particle'  : [ 0 ],
207     'Particle2' : [ 0 , 1 ],
208     'Particle3' : [ 0 , 1 , 2 ],
209     'Particle4' : [ 0 , 1 , 2 , 3 ],
210     'Water3'    : [ 0 ],
211     'Water4'    : [ 0 ]
212 }
213
214
215 ###############################################
216 # ADRESS PROPERTIES
217 ###############################################
218 AdressList = [
219     'CG', 'EX'
220 ]
221
222 # Dictionary to abbreviate all strings (mixed from all the lists)
223 Abbreviation = {
224     'None'                    : 'None',
225     'Coulomb'                 : 'Coul',
226     'Ewald'                   : 'Ew',
227     'ReactionField'           : 'RF',
228     'GeneralizedBorn'         : 'GB',
229     'CubicSplineTable'        : 'CSTab',
230     'LennardJones'            : 'LJ',
231     'Buckingham'              : 'Bham',
232     'PotentialShift'          : 'Sh',
233     'PotentialSwitch'         : 'Sw',
234     'ExactCutoff'             : 'Cut',
235     'PotentialAndForce'       : 'VF',
236     'Potential'               : 'V',
237     'Force'                   : 'F',
238     'Water3'                  : 'W3',
239     'Water4'                  : 'W4',
240     'Particle'                : 'P1',
241     'Particle2'               : 'P2',
242     'Particle3'               : 'P3',
243     'Particle4'               : 'P4'
244 }
245
246
247 ###############################################
248 # Functions
249 ###############################################
250
251 # Return a string with the kernel name from current settings
252 def MakeKernelFileName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelRes):
253     ElecStr = 'Elec' + Abbreviation[KernelElec]
254     if(KernelElecMod!='None'):
255         ElecStr = ElecStr + Abbreviation[KernelElecMod]
256     VdwStr  = 'Vdw'  + Abbreviation[KernelVdw]
257     if(KernelVdwMod!='None'):
258         VdwStr = VdwStr + Abbreviation[KernelVdwMod]
259     GeomStr = 'Geom' + Abbreviation[KernelGeom[0]] + Abbreviation[KernelGeom[1]]
260     return 'nb_kernel_' + ElecStr + '_' + VdwStr + '_' + GeomStr + '_' + KernelRes + '_' + Arch
261
262 def MakeKernelName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF,KernelRes):
263     ElecStr = 'Elec' + Abbreviation[KernelElec]
264     if(KernelElecMod!='None'):
265         ElecStr = ElecStr + Abbreviation[KernelElecMod]
266     VdwStr  = 'Vdw'  + Abbreviation[KernelVdw]
267     if(KernelVdwMod!='None'):
268         VdwStr = VdwStr + Abbreviation[KernelVdwMod]
269     GeomStr = 'Geom' + Abbreviation[KernelGeom[0]] + Abbreviation[KernelGeom[1]]
270     VFStr   = Abbreviation[KernelVF]
271     return 'nb_kernel_' + ElecStr + '_' + VdwStr + '_' + GeomStr + '_' + VFStr + '_' + KernelRes + '_' + Arch
272
273 # Return a string with a declaration to use for the kernel;
274 # this will be a sequence of string combinations as well as the actual function name
275 # Dont worry about field widths - that is just pretty-printing for the header!
276 def MakeKernelDecl(KernelName,KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelOther,KernelVF):
277     KernelStr   = '\"'+KernelName+'\"'
278     ArchStr     = '\"'+Arch+'\"'
279     ElecStr     = '\"'+KernelElec+'\"'
280     ElecModStr  = '\"'+KernelElecMod+'\"'
281     VdwStr      = '\"'+KernelVdw+'\"'
282     VdwModStr   = '\"'+KernelVdwMod+'\"'
283     GeomStr     = '\"'+KernelGeom[0]+KernelGeom[1]+'\"'
284     OtherStr    = '\"'+KernelOther+'\"'
285     VFStr       = '\"'+KernelVF+'\"'
286
287     ThisSpec = ArchStr+', '+ElecStr+', '+ElecModStr+', '+VdwStr+', '+VdwModStr+', '+GeomStr+', '+OtherStr+', '+VFStr
288     ThisDecl = '    { '+KernelName+', '+KernelStr+', '+ThisSpec+' }'
289     return ThisDecl
290
291
292 # Returns 1 if this kernel should be created, 0 if we should skip it
293 # This routine is not critical - it is not the end of the world if we create more kernels,
294 # but since the number is pretty large we save both space and compile-time by reducing it a bit.
295 def KeepKernel(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF):
296
297     # No need for kernels without interactions
298     if(KernelElec=='None' and KernelVdw=='None'):
299         return 0
300
301     # No need for modifiers without interactions
302     if((KernelElec=='None' and KernelElecMod!='None') or (KernelVdw=='None' and KernelVdwMod!='None')):
303         return 0
304
305     # No need for LJ-only water optimization, or water optimization with implicit solvent.
306     if('Water' in KernelGeom[0] and (KernelElec=='None' or 'GeneralizedBorn' in KernelElec)):
307         return 0
308
309     # Non-matching table settings are pointless
310     if( ('Table' in KernelElec) and ('Table' in KernelVdw) and KernelElec!=KernelVdw ):
311         return 0
312
313     # Try to reduce the number of different switch/shift options to get a reasonable number of kernels
314     # For electrostatics, reaction-field can use 'exactcutoff', and ewald can use switch or shift.
315     if(KernelElecMod=='ExactCutoff' and KernelElec!='ReactionField'):
316         return 0
317     if(KernelElecMod in ['PotentialShift','PotentialSwitch'] and KernelElec!='Ewald'):
318         return 0
319     # For Vdw, we support switch and shift for Lennard-Jones/Buckingham
320     if((KernelVdwMod=='ExactCutoff') or
321        (KernelVdwMod in ['PotentialShift','PotentialSwitch'] and KernelVdw not in ['LennardJones','Buckingham'])):
322         return 0
323
324     # Choose either switch or shift and don't mix them...
325     if((KernelElecMod=='PotentialShift' and KernelVdwMod=='PotentialSwitch') or
326        (KernelElecMod=='PotentialSwitch' and KernelVdwMod=='PotentialShift')):
327         return 0
328
329     # Don't use a Vdw kernel with a modifier if the electrostatics one does not have one
330     if(KernelElec!='None' and KernelElecMod=='None' and KernelVdwMod!='None'):
331         return 0
332
333     # Don't use an electrostatics kernel with a modifier if the vdw one does not have one,
334     # unless the electrostatics one is reaction-field with exact cutoff.
335     if(KernelVdw!='None' and KernelVdwMod=='None' and KernelElecMod!='None'):
336         if(KernelElec=='ReactionField' and KernelVdw!='CubicSplineTable'):
337             return 0
338         elif(KernelElec!='ReactionField'):
339             return 0
340
341     return 1
342
343
344
345 #
346 # The preprocessor will automatically expand the interactions for water and other
347 # geometries inside the kernel, but to get this right we need to setup a couple
348 # of defines - we do them in a separate routine to keep the main loop clean.
349 #
350 # While this routine might look a bit complex it is actually quite straightforward,
351 # and the best news is that you wont have to modify _anything_ for a new geometry
352 # as long as you correctly define its Electrostatics/Vdw geometry in the lists above!
353 #
354 def SetDefines(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF,defines):
355     # What is the _name_ for the i/j group geometry?
356     igeometry            = KernelGeom[0]
357     jgeometry            = KernelGeom[1]
358     # define so we can access it in the source when the preprocessor runs
359     defines['GEOMETRY_I'] = igeometry
360     defines['GEOMETRY_J'] = jgeometry
361
362     # For the i/j groups, extract a python list of which sites have electrostatics
363     # For SPC/TIP3p this will be [1,1,1], while TIP4p (no elec on first site) will be [0,1,1,1]
364     ielec                = GeometryElectrostatics[igeometry]
365     jelec                = GeometryElectrostatics[jgeometry]
366     # Zero out the corresponding lists in case we dont do Elec
367     if(KernelElec=='None'):
368         ielec = []
369         jelec = []
370
371     # Extract similar interaction lists for Vdw interactions (example for SPC: [1,0,0])
372     iVdw                 = GeometryVdw[igeometry]
373     jVdw                 = GeometryVdw[jgeometry]
374
375     # Zero out the corresponding lists in case we dont do Vdw
376     if(KernelVdw=='None'):
377         iVdw = []
378         jVdw = []
379
380     # iany[] and jany[] contains lists of the particles actually used (for interactions) in this kernel
381     iany = list(set(ielec+iVdw))  # convert to+from set to make elements unique
382     jany = list(set(jelec+jVdw))
383
384     defines['PARTICLES_ELEC_I'] = ielec
385     defines['PARTICLES_ELEC_J'] = jelec
386     defines['PARTICLES_VDW_I']  = iVdw
387     defines['PARTICLES_VDW_J']  = jVdw
388     defines['PARTICLES_I']      = iany
389     defines['PARTICLES_J']      = jany
390
391     # elecij,Vdwij are sets with pairs of particles for which the corresponding interaction is done
392     # (and anyij again corresponds to either electrostatics or Vdw)
393     elecij = []
394     Vdwij  = []
395     anyij  = []
396
397     for i in ielec:
398         for j in jelec:
399             elecij.append([i,j])
400
401     for i in iVdw:
402         for j in jVdw:
403             Vdwij.append([i,j])
404
405     for i in iany:
406         for j in jany:
407             if [i,j] in elecij or [i,j] in Vdwij:
408                 anyij.append([i,j])
409
410     defines['PAIRS_IJ']     = anyij
411
412     # Make an 2d list-of-distance-properties-to-calculate for i,j
413     ni = max(iany)+1
414     nj = max(jany)+1
415     # Each element properties[i][j] is an empty list
416     properties = [ [ [] for j in range(0,nj) ] for i in range (0,ni) ]
417     # Add properties to each set
418     for i in range(0,ni):
419         for j in range(0,nj):
420             if [i,j] in elecij:
421                 properties[i][j] = properties[i][j] + ['electrostatics'] + ElectrostaticsList[KernelElec] + ModifierList[KernelElecMod]
422             if [i,j] in Vdwij:
423                 properties[i][j] = properties[i][j] + ['vdw'] + VdwList[KernelVdw] + ModifierList[KernelVdwMod]
424             # Add rinv if we need r
425             if 'r' in properties[i][j]:
426                 properties[i][j] = properties[i][j] + ['rinv']
427             # Add rsq if we need rinv or rinsq
428             if 'rinv' in properties[i][j] or 'rinvsq' in properties[i][j]:
429                 properties[i][j] = properties[i][j] + ['rsq']
430
431     defines['INTERACTION_FLAGS']    = properties
432
433
434
435 def PrintStatistics(ratio):
436     ratio = 100.0*ratio
437     print '\rGenerating %s nonbonded kernels... %5.1f%%' % (Arch,ratio),
438     sys.stdout.flush()
439
440
441
442 defines = {}
443 kerneldecl = []
444
445 cnt     = 0.0
446 nelec   = len(ElectrostaticsList)
447 nVdw    = len(VdwList)
448 nmod    = len(ModifierList)
449 ngeom   = len(GeometryNameList)
450
451 ntot    = nelec*nmod*nVdw*nmod*ngeom
452
453 numKernels = 0
454
455 fpdecl = open('nb_kernel_' + Arch + '.c','w')
456 fpdecl.write( FileHeader )
457 fpdecl.write( '#ifndef nb_kernel_' + Arch + '_h\n' )
458 fpdecl.write( '#define nb_kernel_' + Arch + '_h\n\n' )
459 fpdecl.write( '#include "../nb_kernel.h"\n\n' )
460
461 for KernelElec in ElectrostaticsList:
462     defines['KERNEL_ELEC'] = KernelElec
463
464     for KernelElecMod in ModifierList:
465         defines['KERNEL_MOD_ELEC'] = KernelElecMod
466
467         for KernelVdw in VdwList:
468             defines['KERNEL_VDW'] = KernelVdw
469
470             for KernelVdwMod in ModifierList:
471                 defines['KERNEL_MOD_VDW'] = KernelVdwMod
472
473                 for KernelGeom in GeometryNameList:
474
475                     cnt += 1
476                     KernelFilename = MakeKernelFileName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom) + '.c'
477                     fpkernel = open(KernelFilename,'w')
478                     defines['INCLUDE_HEADER'] = 1  # Include header first time in new file
479                     DoHeader = 1
480
481                     for KernelVF in VFList:
482
483                         KernelName = MakeKernelName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF)
484
485                         defines['KERNEL_NAME'] = KernelName
486                         defines['KERNEL_VF']   = KernelVF
487
488                         # Check if this is a valid/sane/usable combination
489                         if not KeepKernel(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF):
490                             continue;
491
492                         # The overall kernel settings determine what the _kernel_ calculates, but for the water
493                         # kernels this does not mean that every pairwise interaction has e.g. Vdw interactions.
494                         # This routine sets defines of what to calculate for each pair of particles in those cases.
495                         SetDefines(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF,defines)
496
497                         if(DoHeader==1):
498                             fpkernel.write( FileHeader )
499
500                         gmxpreprocess('nb_kernel_template_' + Arch + '.pre', KernelName+'.tmp' , defines, force=1,contentType='C')
501                         numKernels = numKernels + 1
502
503                         defines['INCLUDE_HEADER'] = 0   # Header has been included once now
504                         DoHeader=0
505
506                         # Append temp file contents to the common kernelfile
507                         fptmp = open(KernelName+'.tmp','r')
508                         fpkernel.writelines(fptmp.readlines())
509                         fptmp.close()
510                         os.remove(KernelName+'.tmp')
511
512                         # Add a declaration for this kernel
513                         fpdecl.write('nb_kernel_t ' + KernelName + ';\n');
514
515                         # Add declaration to the buffer
516                         KernelOther=''
517                         kerneldecl.append(MakeKernelDecl(KernelName,KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelOther,KernelVF))
518
519                     filesize = fpkernel.tell()
520                     fpkernel.close()
521                     if(filesize==0):
522                         os.remove(KernelFilename)
523
524                     PrintStatistics(cnt/ntot)
525                 pass
526             pass
527         pass
528     pass
529 pass
530
531 # Write out the list of settings and corresponding kernels to the declaration file
532 fpdecl.write( '\n\n' )
533 fpdecl.write( 'nb_kernel_info_t\n' )
534 fpdecl.write( '    kernellist_'+Arch+'[] =\n' )
535 fpdecl.write( '{\n' )
536 for decl in kerneldecl[0:-1]:
537     fpdecl.write( decl + ',\n' )
538 fpdecl.write( kerneldecl[-1] + '\n' )
539 fpdecl.write( '};\n\n' )
540 fpdecl.write( 'int\n' )
541 fpdecl.write( '    kernellist_'+Arch+'_size = sizeof(kernellist_'+Arch+')/sizeof(kernellist_'+Arch+'[0]);\n\n')
542 fpdecl.write( '#endif\n')
543 fpdecl.close()