3 # This file is part of the GROMACS molecular simulation package.
5 # Copyright (c) 2012,2013,2014,2015,2017,2018, 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.
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.
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.
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.
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.
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.
38 sys.path.append("../preprocessor")
39 sys.path.append("../../../../../admin")
40 from gmxpreprocess import gmxpreprocess
41 from copyright import create_copyright_header
43 # "The happiest programs are programs that write other programs."
46 # This script controls the generation of GROMACS nonbonded kernels.
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).
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).
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.
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
68 # nbkernel_ElecXX_VdwYY_GeomZZ_VF_QQ()
70 # Where XX/YY/ZZ/VF are strings to identify what the kernel computes.
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.
80 # Explanation of the 'properties':
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.
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
94 FileHeader = create_copyright_header('2012,2013,2014.2015,2017')
96 * Note: this file was generated by the GROMACS """+Arch+""" kernel generator.
100 ###############################################
102 # Interactions and flags for them
103 ###############################################
104 ElectrostaticsList = {
106 'Coulomb' : ['rinv','rinvsq'],
107 'ReactionField' : ['rinv','rinvsq'],
108 'CubicSplineTable' : ['rinv','r','table'],
109 'Ewald' : ['rinv','rinvsq','r'],
113 ###############################################
115 # Interactions and flags for them
116 ###############################################
119 'LennardJones' : ['rinvsq'],
120 'Buckingham' : ['rinv','rinvsq','r'],
121 'CubicSplineTable' : ['rinv','r','table'],
122 'LJEwald' : ['rinv','rinvsq','r'],
126 ###############################################
128 # Different ways to adjust/modify interactions to conserve energy
129 ###############################################
132 'ExactCutoff' : ['exactcutoff'], # Zero the interaction outside the cutoff, used for reaction-field-zero
133 'PotentialShift' : ['shift','exactcutoff'],
134 'PotentialSwitch' : ['rinv','r','switch','exactcutoff']
138 ###############################################
139 # GEOMETRY COMBINATIONS
140 ###############################################
142 [ 'Particle' , 'Particle' ],
143 [ 'Water3' , 'Particle' ],
144 [ 'Water3' , 'Water3' ],
145 [ 'Water4' , 'Particle' ],
146 [ 'Water4' , 'Water4' ]
150 ###############################################
152 ###############################################
155 # 'Potential', # Not used yet
160 ###############################################
161 # GEOMETRY PROPERTIES
162 ###############################################
163 # Dictionaries with lists telling which interactions are present
164 # 1,2,3 means particles 1,2,3 (but not 0) have electrostatics!
165 GeometryElectrostatics = {
167 'Particle2' : [ 0 , 1 ],
168 'Particle3' : [ 0 , 1 , 2 ],
169 'Particle4' : [ 0 , 1 , 2 , 3 ],
170 'Water3' : [ 0 , 1 , 2 ],
171 'Water4' : [ 1 , 2 , 3 ]
176 'Particle2' : [ 0 , 1 ],
177 'Particle3' : [ 0 , 1 , 2 ],
178 'Particle4' : [ 0 , 1 , 2 , 3 ],
186 # Dictionary to abbreviate all strings (mixed from all the lists)
191 'ReactionField' : 'RF',
192 'CubicSplineTable' : 'CSTab',
193 'LennardJones' : 'LJ',
194 'Buckingham' : 'Bham',
196 'PotentialShift' : 'Sh',
197 'PotentialSwitch' : 'Sw',
198 'ExactCutoff' : 'Cut',
199 'PotentialAndForce' : 'VF',
211 ###############################################
213 ###############################################
215 # Return a string with the kernel name from current settings
216 def MakeKernelFileName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom):
217 ElecStr = 'Elec' + Abbreviation[KernelElec]
218 if(KernelElecMod!='None'):
219 ElecStr = ElecStr + Abbreviation[KernelElecMod]
220 VdwStr = 'Vdw' + Abbreviation[KernelVdw]
221 if(KernelVdwMod!='None'):
222 VdwStr = VdwStr + Abbreviation[KernelVdwMod]
223 GeomStr = 'Geom' + Abbreviation[KernelGeom[0]] + Abbreviation[KernelGeom[1]]
224 return 'nb_kernel_' + ElecStr + '_' + VdwStr + '_' + GeomStr + '_' + Arch
226 def MakeKernelName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF):
227 ElecStr = 'Elec' + Abbreviation[KernelElec]
228 if(KernelElecMod!='None'):
229 ElecStr = ElecStr + Abbreviation[KernelElecMod]
230 VdwStr = 'Vdw' + Abbreviation[KernelVdw]
231 if(KernelVdwMod!='None'):
232 VdwStr = VdwStr + Abbreviation[KernelVdwMod]
233 GeomStr = 'Geom' + Abbreviation[KernelGeom[0]] + Abbreviation[KernelGeom[1]]
234 VFStr = Abbreviation[KernelVF]
235 return 'nb_kernel_' + ElecStr + '_' + VdwStr + '_' + GeomStr + '_' + VFStr + '_' + Arch
237 # Return a string with a declaration to use for the kernel;
238 # this will be a sequence of string combinations as well as the actual function name
239 # Dont worry about field widths - that is just pretty-printing for the header!
240 def MakeKernelDecl(KernelName,KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelOther,KernelVF):
241 KernelStr = '\"'+KernelName+'\"'
242 ArchStr = '\"'+Arch+'\"'
243 ElecStr = '\"'+KernelElec+'\"'
244 ElecModStr = '\"'+KernelElecMod+'\"'
245 VdwStr = '\"'+KernelVdw+'\"'
246 VdwModStr = '\"'+KernelVdwMod+'\"'
247 GeomStr = '\"'+KernelGeom[0]+KernelGeom[1]+'\"'
248 OtherStr = '\"'+KernelOther+'\"'
249 VFStr = '\"'+KernelVF+'\"'
251 ThisSpec = ArchStr+', '+ElecStr+', '+ElecModStr+', '+VdwStr+', '+VdwModStr+', '+GeomStr+', '+OtherStr+', '+VFStr
252 ThisDecl = ' { '+KernelName+', '+KernelStr+', '+ThisSpec+' }'
256 # Returns 1 if this kernel should be created, 0 if we should skip it
257 # This routine is not critical - it is not the end of the world if we create more kernels,
258 # but since the number is pretty large we save both space and compile-time by reducing it a bit.
259 def KeepKernel(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF):
261 # No need for kernels without interactions
262 if(KernelElec=='None' and KernelVdw=='None'):
265 # No need for modifiers without interactions
266 if((KernelElec=='None' and KernelElecMod!='None') or (KernelVdw=='None' and KernelVdwMod!='None')):
269 # No need for LJ-only water optimization, or water optimization with implicit solvent.
270 if('Water' in KernelGeom[0] and KernelElec=='None'):
273 # Non-matching table settings are pointless
274 if( ('Table' in KernelElec) and ('Table' in KernelVdw) and KernelElec!=KernelVdw ):
277 # Try to reduce the number of different switch/shift options to get a reasonable number of kernels
278 # For electrostatics, reaction-field can use 'exactcutoff', and ewald can use switch or shift.
279 if(KernelElecMod=='ExactCutoff' and KernelElec!='ReactionField'):
281 if(KernelElecMod in ['PotentialShift','PotentialSwitch'] and KernelElec!='Ewald'):
283 # For Vdw, we support switch and shift for Lennard-Jones/Buckingham
284 if((KernelVdwMod=='ExactCutoff') or
285 (KernelVdwMod in ['PotentialShift','PotentialSwitch'] and KernelVdw not in ['LennardJones','Buckingham','LJEwald'])):
288 # For LJEwald, we only support shift
289 if(KernelVdw=='LJEwald' and KernelVdwMod=='PotentialSwitch'):
292 # Choose either switch or shift and don't mix them...
293 if((KernelElecMod=='PotentialShift' and KernelVdwMod=='PotentialSwitch') or
294 (KernelElecMod=='PotentialSwitch' and KernelVdwMod=='PotentialShift')):
297 # Don't use a Vdw kernel with a modifier if the electrostatics one does not have one
298 if(KernelElec!='None' and KernelElecMod=='None' and KernelVdwMod!='None'):
301 # Don't use an electrostatics kernel with a modifier if the vdw one does not have one,
302 # unless the electrostatics one is reaction-field with exact cutoff.
303 if(KernelVdw!='None' and KernelVdwMod=='None' and KernelElecMod!='None'):
304 if(KernelElec=='ReactionField' and KernelVdw!='CubicSplineTable'):
306 elif(KernelElec!='ReactionField'):
309 #Only do LJ-PME if we are also doing PME for electrostatics, or no electrostatics at all.
310 if(KernelVdw=='LJEwald' and KernelElec not in ['Ewald','None']):
318 # The preprocessor will automatically expand the interactions for water and other
319 # geometries inside the kernel, but to get this right we need to setup a couple
320 # of defines - we do them in a separate routine to keep the main loop clean.
322 # While this routine might look a bit complex it is actually quite straightforward,
323 # and the best news is that you wont have to modify _anything_ for a new geometry
324 # as long as you correctly define its Electrostatics/Vdw geometry in the lists above!
326 def SetDefines(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF,defines):
327 # What is the _name_ for the i/j group geometry?
328 igeometry = KernelGeom[0]
329 jgeometry = KernelGeom[1]
330 # define so we can access it in the source when the preprocessor runs
331 defines['GEOMETRY_I'] = igeometry
332 defines['GEOMETRY_J'] = jgeometry
334 # For the i/j groups, extract a python list of which sites have electrostatics
335 # For SPC/TIP3p this will be [1,1,1], while TIP4p (no elec on first site) will be [0,1,1,1]
336 ielec = GeometryElectrostatics[igeometry]
337 jelec = GeometryElectrostatics[jgeometry]
338 # Zero out the corresponding lists in case we dont do Elec
339 if(KernelElec=='None'):
343 # Extract similar interaction lists for Vdw interactions (example for SPC: [1,0,0])
344 iVdw = GeometryVdw[igeometry]
345 jVdw = GeometryVdw[jgeometry]
347 # Zero out the corresponding lists in case we dont do Vdw
348 if(KernelVdw=='None'):
352 # iany[] and jany[] contains lists of the particles actually used (for interactions) in this kernel
353 iany = list(set(ielec+iVdw)) # convert to+from set to make elements unique
354 jany = list(set(jelec+jVdw))
356 defines['PARTICLES_ELEC_I'] = ielec
357 defines['PARTICLES_ELEC_J'] = jelec
358 defines['PARTICLES_VDW_I'] = iVdw
359 defines['PARTICLES_VDW_J'] = jVdw
360 defines['PARTICLES_I'] = iany
361 defines['PARTICLES_J'] = jany
363 # elecij,Vdwij are sets with pairs of particles for which the corresponding interaction is done
364 # (and anyij again corresponds to either electrostatics or Vdw)
379 if [i,j] in elecij or [i,j] in Vdwij:
382 defines['PAIRS_IJ'] = anyij
384 # Make an 2d list-of-distance-properties-to-calculate for i,j
387 # Each element properties[i][j] is an empty list
388 properties = [ [ [] for j in range(0,nj) ] for i in range (0,ni) ]
389 # Add properties to each set
390 for i in range(0,ni):
391 for j in range(0,nj):
393 properties[i][j] = properties[i][j] + ['electrostatics'] + ElectrostaticsList[KernelElec] + ModifierList[KernelElecMod]
395 properties[i][j] = properties[i][j] + ['vdw'] + VdwList[KernelVdw] + ModifierList[KernelVdwMod]
396 # Add rinv if we need r
397 if 'r' in properties[i][j]:
398 properties[i][j] = properties[i][j] + ['rinv']
399 # Add rsq if we need rinv or rinsq
400 if 'rinv' in properties[i][j] or 'rinvsq' in properties[i][j]:
401 properties[i][j] = properties[i][j] + ['rsq']
403 defines['INTERACTION_FLAGS'] = properties
407 def PrintStatistics(ratio):
409 print '\rGenerating %s nonbonded kernels... %5.1f%%' % (Arch,ratio),
418 nelec = len(ElectrostaticsList)
420 nmod = len(ModifierList)
421 ngeom = len(GeometryNameList)
423 ntot = nelec*nmod*nVdw*nmod*ngeom
427 fpdecl = open('nb_kernel_' + Arch + '.c','w')
428 fpdecl.write( FileHeader )
429 fpdecl.write( '#include "gmxpre.h"\n\n' )
430 fpdecl.write( '#include "gromacs/gmxlib/nonbonded/nb_kernel.h"\n\n' )
432 for KernelElec in ElectrostaticsList:
433 defines['KERNEL_ELEC'] = KernelElec
435 for KernelElecMod in ModifierList:
436 defines['KERNEL_MOD_ELEC'] = KernelElecMod
438 for KernelVdw in VdwList:
439 defines['KERNEL_VDW'] = KernelVdw
441 for KernelVdwMod in ModifierList:
442 defines['KERNEL_MOD_VDW'] = KernelVdwMod
444 for KernelGeom in GeometryNameList:
447 KernelFilename = MakeKernelFileName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom) + '.c'
448 fpkernel = open(KernelFilename,'w')
449 defines['INCLUDE_HEADER'] = 1 # Include header first time in new file
452 for KernelVF in VFList:
454 KernelName = MakeKernelName(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF)
456 defines['KERNEL_NAME'] = KernelName
457 defines['KERNEL_VF'] = KernelVF
459 # Check if this is a valid/sane/usable combination
460 if not KeepKernel(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF):
463 # The overall kernel settings determine what the _kernel_ calculates, but for the water
464 # kernels this does not mean that every pairwise interaction has e.g. Vdw interactions.
465 # This routine sets defines of what to calculate for each pair of particles in those cases.
466 SetDefines(KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelVF,defines)
469 fpkernel.write( FileHeader )
471 gmxpreprocess('nb_kernel_template_' + Arch + '.pre', KernelName+'.tmp' , defines, force=1,contentType='C')
472 numKernels = numKernels + 1
474 defines['INCLUDE_HEADER'] = 0 # Header has been included once now
477 # Append temp file contents to the common kernelfile
478 fptmp = open(KernelName+'.tmp','r')
479 fpkernel.writelines(fptmp.readlines())
481 os.remove(KernelName+'.tmp')
483 # Add a declaration for this kernel
484 fpdecl.write('nb_kernel_t ' + KernelName + ';\n');
486 # Add declaration to the buffer
488 kerneldecl.append(MakeKernelDecl(KernelName,KernelElec,KernelElecMod,KernelVdw,KernelVdwMod,KernelGeom,KernelOther,KernelVF))
490 filesize = fpkernel.tell()
493 os.remove(KernelFilename)
495 PrintStatistics(cnt/ntot)
502 # Write out the list of settings and corresponding kernels to the declaration file
503 fpdecl.write( '\n\n' )
504 fpdecl.write( 'nb_kernel_info_t\n' )
505 fpdecl.write( ' kernellist_'+Arch+'[] =\n' )
506 fpdecl.write( '{\n' )
507 for decl in kerneldecl[0:-1]:
508 fpdecl.write( decl + ',\n' )
509 fpdecl.write( kerneldecl[-1] + '\n' )
510 fpdecl.write( '};\n\n' )
511 fpdecl.write( 'int\n' )
512 fpdecl.write( ' kernellist_'+Arch+'_size = sizeof(kernellist_'+Arch+')/sizeof(kernellist_'+Arch+'[0]);\n')