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