Unverified Commit 06fe748a authored by peastman's avatar peastman Committed by GitHub
Browse files

Merge pull request #2120 from jing-huang/drude

Implementing Drude polarizable force fields in the CHARMM file parsers
parents a9278f11 31f7f355
...@@ -12,7 +12,7 @@ the ParmEd program and was ported for use with OpenMM. ...@@ -12,7 +12,7 @@ the ParmEd program and was ported for use with OpenMM.
Copyright (c) 2014 the Authors Copyright (c) 2014 the Authors
Author: Jason M. Swails Author: Jason M. Swails
Contributors: Contributors: Jing Huang
Date: Sep. 17, 2014 Date: Sep. 17, 2014
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
...@@ -114,6 +114,7 @@ class CharmmParameterSet(object): ...@@ -114,6 +114,7 @@ class CharmmParameterSet(object):
self.improper_types = dict() self.improper_types = dict()
self.cmap_types = dict() self.cmap_types = dict()
self.nbfix_types = dict() self.nbfix_types = dict()
self.nbthole_types = dict()
self.parametersets = [] self.parametersets = []
# Load all of the files # Load all of the files
...@@ -261,6 +262,9 @@ class CharmmParameterSet(object): ...@@ -261,6 +262,9 @@ class CharmmParameterSet(object):
if line.startswith('NBFIX'): if line.startswith('NBFIX'):
section = 'NBFIX' section = 'NBFIX'
continue continue
if line.startswith('THOLE'):
section = 'NBTHOLE'
continue
if line.startswith('HBOND'): if line.startswith('HBOND'):
section = None section = None
continue continue
...@@ -493,6 +497,22 @@ class CharmmParameterSet(object): ...@@ -493,6 +497,22 @@ class CharmmParameterSet(object):
except IndexError: except IndexError:
raise CharmmFileError('Could not parse NBFIX terms.') raise CharmmFileError('Could not parse NBFIX terms.')
self.nbfix_types[(min(at1, at2), max(at1, at2))] = (emin, rmin) self.nbfix_types[(min(at1, at2), max(at1, at2))] = (emin, rmin)
continue
# Here parse the possible nbthole section
if section == 'NBTHOLE':
words = line.split()
try:
at1 = words[0]
at2 = words[1]
nbt = abs(conv(words[2], float, 'NBTHOLE a'))
try:
self.atom_types_str[at1].add_nbthole(at2, nbt)
self.atom_types_str[at2].add_nbthole(at1, nbt)
except KeyError:
pass
except IndexError:
raise CharmmFileError('Could not parse NBTHOLE terms.')
self.nbthole_types[(min(at1, at2), max(at1, at2))] = (nbt)
# If there were any CMAP terms stored in the parameter set, the last one # If there were any CMAP terms stored in the parameter set, the last one
# defined will not have been added to the set. Add it now. # defined will not have been added to the set. Add it now.
if current_cmap is not None: if current_cmap is not None:
......
...@@ -11,7 +11,7 @@ the ParmEd program and was ported for use with OpenMM. ...@@ -11,7 +11,7 @@ the ParmEd program and was ported for use with OpenMM.
Copyright (c) 2014-2016 the Authors Copyright (c) 2014-2016 the Authors
Author: Jason M. Swails Author: Jason M. Swails
Contributors: Contributors: Jing Huang
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
...@@ -57,7 +57,7 @@ from simtk.openmm.app.internal.charmm.exceptions import ( ...@@ -57,7 +57,7 @@ from simtk.openmm.app.internal.charmm.exceptions import (
import warnings import warnings
TINY = 1e-8 TINY = 1e-8
WATNAMES = ('WAT', 'HOH', 'TIP3', 'TIP4', 'TIP5', 'SPCE', 'SPC') WATNAMES = ('WAT', 'HOH', 'TIP3', 'TIP4', 'TIP5', 'SPCE', 'SPC', 'SWM4', 'SWM6')
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
xrange = range xrange = range
...@@ -130,6 +130,12 @@ class CharmmPsfFile(object): ...@@ -130,6 +130,12 @@ class CharmmPsfFile(object):
- acceptor_list # hbond acceptors? - acceptor_list # hbond acceptors?
- group_list # list of nonbonded interaction groups - group_list # list of nonbonded interaction groups
Four additional lists for Drude psf:
- drudeconsts_list
- drudepair_list
- lonepair_list
- aniso_list
Additional attribute is available if a CharmmParameterSet is loaded into Additional attribute is available if a CharmmParameterSet is loaded into
this structure. this structure.
...@@ -186,6 +192,8 @@ class CharmmPsfFile(object): ...@@ -186,6 +192,8 @@ class CharmmPsfFile(object):
line.strip()) line.strip())
# Store the flags # Store the flags
psf_flags = line.split()[1:] psf_flags = line.split()[1:]
# Determine whether it's a Drude polarizable system
IsDrudePSF = 'DRUDE' in psf_flags
# Now get all of the sections and store them in a dict # Now get all of the sections and store them in a dict
psf.readline() psf.readline()
# Now get all of the sections # Now get all of the sections
...@@ -203,6 +211,8 @@ class CharmmPsfFile(object): ...@@ -203,6 +211,8 @@ class CharmmPsfFile(object):
# Parse all of the atoms # Parse all of the atoms
residue_list = ResidueList() residue_list = ResidueList()
atom_list = AtomList() atom_list = AtomList()
if IsDrudePSF:
drudeconsts_list = TrackedList()
for i in xrange(natom): for i in xrange(natom):
words = psfsections['NATOM'][1][i].split() words = psfsections['NATOM'][1][i].split()
system = words[1] system = words[1]
...@@ -226,18 +236,30 @@ class CharmmPsfFile(object): ...@@ -226,18 +236,30 @@ class CharmmPsfFile(object):
atom = residue_list.add_atom(system, resid, resname, name, atom = residue_list.add_atom(system, resid, resname, name,
attype, charge, mass, inscode, props) attype, charge, mass, inscode, props)
atom_list.append(atom) atom_list.append(atom)
if IsDrudePSF:
alpha = conv(words[9], float, 'alpha')
thole = conv(words[10], float, 'thole')
drudeconsts_list.append([alpha, thole])
atom_list.assign_indexes() atom_list.assign_indexes()
atom_list.changed = False atom_list.changed = False
# Now get the number of bonds # Now get the number of bonds
nbond = conv(psfsections['NBOND'][0], int, 'number of bonds') nbond = conv(psfsections['NBOND'][0], int, 'number of bonds')
holder = psfsections['NBOND'][1] holder = psfsections['NBOND'][1]
bond_list = TrackedList() bond_list = TrackedList()
if IsDrudePSF:
drudepair_list = TrackedList()
if len(holder) != nbond * 2: if len(holder) != nbond * 2:
raise CharmmPSFError('Got %d indexes for %d bonds' % raise CharmmPSFError('Got %d indexes for %d bonds' %
(len(holder), nbond)) (len(holder), nbond))
for i in range(nbond): for i in range(nbond):
id1 = holder[2*i ] - 1 id1 = holder[2*i ] - 1
id2 = holder[2*i+1] - 1 id2 = holder[2*i+1] - 1
# ignore any bond pair involving Drude or lonepairs: possible using atom's prop
if (atom_list[id1].name[0]=='D' or atom_list[id2].name[0]=='D'):
drudepair_list.append([min(id1,id2), max(id1,id2)])
elif (atom_list[id1].name[0:2]=='LP' or atom_list[id2].name[0:2]=='LP' or atom_list[id1].name=='OM' or atom_list[id2].name=='OM'):
pass
else:
bond_list.append(Bond(atom_list[id1], atom_list[id2])) bond_list.append(Bond(atom_list[id1], atom_list[id2]))
bond_list.changed = False bond_list.changed = False
# Now get the number of angles and the angle list # Now get the number of angles and the angle list
...@@ -341,10 +363,46 @@ class CharmmPsfFile(object): ...@@ -341,10 +363,46 @@ class CharmmPsfFile(object):
'Resetting molecularity.', CharmmPSFWarning) 'Resetting molecularity.', CharmmPSFWarning)
# We have a CHARMM PSF file; now do NUMLP/NUMLPH sections # We have a CHARMM PSF file; now do NUMLP/NUMLPH sections
numlp, numlph = psfsections['NUMLP NUMLPH'][0] numlp, numlph = psfsections['NUMLP NUMLPH'][0]
holder = psfsections['NUMLP NUMLPH'][1]
lonepair_list = TrackedList()
if numlp != 0 or numlph != 0: if numlp != 0 or numlph != 0:
raise NotImplementedError('Cannot currently handle PSFs with lone ' lp_distance_list=[]
'pairs defined in the NUMLP/NUMLPH ' lp_angle_list=[]
'section.') lp_dihe_list=[]
for i in range(numlp):
lpline = holder[i].split()
if len(lpline)!=6 or lpline[0] != '3' or lpline[2] != 'F' or int(lpline[1]) != 4*i+1 :
raise CharmmPSFError('Lonepair format error')
else:
lp_distance_list.append(float(lpline[3]))
lp_angle_list.append(float(lpline[4]))
lp_dihe_list.append(float(lpline[5]))
for i in range(numlp):
lpline = holder[int(i/2)+numlp].split()
icolumn = (i%2) * 4
id1=int(lpline[icolumn]) -1
id2=int(lpline[icolumn+1])-1
id3=int(lpline[icolumn+2])-1
id4=int(lpline[icolumn+3])-1
lonepair_list.append([id1, id2, id3, id4, lp_distance_list[i], lp_angle_list[i], lp_dihe_list[i]])
# In Drude psf, here comes anisotropic section
if IsDrudePSF:
numaniso = psfsections['NUMANISO'][0]
holder = psfsections['NUMANISO'][1]
aniso_list = TrackedList()
if numaniso != 0:
k_list=[]
for i in range(numaniso):
lpline = holder[i].split()
k_list.append([float(lpline[0]),float(lpline[1]),float(lpline[2])])
for i in range(numaniso):
lpline = holder[int(i/2)+numaniso].split()
icolumn = (i%2) * 4
id1=int(lpline[icolumn]) -1
id2=int(lpline[icolumn+1])-1
id3=int(lpline[icolumn+2])-1
id4=int(lpline[icolumn+3])-1
aniso_list.append([id1, id2, id3, id4, k_list[i][0], k_list[i][1], k_list[i][2]])
# Now do the CMAPs # Now do the CMAPs
ncrterm = conv(psfsections['NCRTERM'][0], int, 'Number of cross-terms') ncrterm = conv(psfsections['NCRTERM'][0], int, 'Number of cross-terms')
holder = psfsections['NCRTERM'][1] holder = psfsections['NCRTERM'][1]
...@@ -375,6 +433,11 @@ class CharmmPsfFile(object): ...@@ -375,6 +433,11 @@ class CharmmPsfFile(object):
self.dihedral_list = dihedral_list self.dihedral_list = dihedral_list
self.dihedral_parameter_list = TrackedList() self.dihedral_parameter_list = TrackedList()
self.improper_list = improper_list self.improper_list = improper_list
self.lonepair_list = lonepair_list
if IsDrudePSF:
self.drudeconsts_list = drudeconsts_list
self.drudepair_list = drudepair_list
self.aniso_list = aniso_list
self.cmap_list = cmap_list self.cmap_list = cmap_list
self.donor_list = donor_list self.donor_list = donor_list
self.acceptor_list = acceptor_list self.acceptor_list = acceptor_list
...@@ -449,8 +512,8 @@ class CharmmPsfFile(object): ...@@ -449,8 +512,8 @@ class CharmmPsfFile(object):
# blank line) as well as any sections that have 0 members. # blank line) as well as any sections that have 0 members.
line = psf.readline().strip() line = psf.readline().strip()
data = [] data = []
if title == 'NATOM' or title == 'NTITLE': if title == 'NATOM' or title == 'NTITLE' or title == 'NUMLP NUMLPH' or title == 'NUMANISO':
# Store these two sections as strings (ATOM section we will parse # Store these four sections as strings (ATOM section we will parse
# later). The rest of the sections are integer pointers # later). The rest of the sections are integer pointers
while line: while line:
data.append(line) data.append(line)
...@@ -777,7 +840,7 @@ class CharmmPsfFile(object): ...@@ -777,7 +840,7 @@ class CharmmPsfFile(object):
u.kilojoule_per_mole) u.kilojoule_per_mole)
ene_conv = dihe_frc_conv ene_conv = dihe_frc_conv
# Create the system and determine if any of our atoms have NBFIX (and # Create the system and determine if any of our atoms have NBFIX or NBTHOLE (and
# therefore requires a CustomNonbondedForce instead) # therefore requires a CustomNonbondedForce instead)
typenames = set() typenames = set()
system = mm.System() system = mm.System()
...@@ -796,6 +859,24 @@ class CharmmPsfFile(object): ...@@ -796,6 +859,24 @@ class CharmmPsfFile(object):
raise StopIteration raise StopIteration
except StopIteration: except StopIteration:
pass pass
has_nbthole_terms = False
try:
for i, typename in enumerate(typenames):
typ = params.atom_types_str[typename]
for j in range(i, len(typenames)):
if typenames[j] in typ.nbthole:
has_nbthole_terms = True
raise StopIteration
except StopIteration:
pass
# test if the system containing the Drude particles
has_drude_particle = False
try:
if self.drudeconsts_list:
has_drude_particle = True
except AttributeError:
pass
# Set up the constraints # Set up the constraints
if verbose and (constraints is not None and not rigidWater): if verbose and (constraints is not None and not rigidWater):
print('Adding constraints...') print('Adding constraints...')
...@@ -822,6 +903,25 @@ class CharmmPsfFile(object): ...@@ -822,6 +903,25 @@ class CharmmPsfFile(object):
bond.atom2.residue.resname in WATNAMES): bond.atom2.residue.resname in WATNAMES):
system.addConstraint(bond.atom1.idx, bond.atom2.idx, system.addConstraint(bond.atom1.idx, bond.atom2.idx,
bond.bond_type.req*length_conv) bond.bond_type.req*length_conv)
# Add virtual sites
if verbose: print('Adding lonepairs...')
for lpsite in self.lonepair_list:
index=lpsite[0]
atom1=lpsite[1]
atom2=lpsite[2]
atom3=lpsite[3]
if lpsite[4] > 0 : #relative
r = lpsite[4] /10.0 # in nanometer
xweights = [-1.0, 0.0, 1.0]
elif lpsite[4] < 0: # bisector
r = lpsite[4] / (-10.0)
xweights = [-1.0, 0.5, 0.5]
theta = lpsite[5] * pi / 180.0
phi = (180.0 - lpsite[6]) * pi / 180.0
p = [r*cos(theta), r*sin(theta)*cos(phi), r*sin(theta)*sin(phi)]
p = [x if abs(x) > 1e-10 else 0 for x in p] # Avoid tiny numbers caused by roundoff error
system.setVirtualSite(index, mm.LocalCoordinatesSite(atom1, atom3, atom2, mm.Vec3(1.0, 0.0, 0.0), mm.Vec3(xweights[0],xweights[1],xweights[2]), mm.Vec3(0.0, -1.0, 1.0), mm.Vec3(p[0],p[1],p[2])))
# Add Bond forces # Add Bond forces
if verbose: print('Adding bonds...') if verbose: print('Adding bonds...')
force = mm.HarmonicBondForce() force = mm.HarmonicBondForce()
...@@ -1069,8 +1169,8 @@ class CharmmPsfFile(object): ...@@ -1069,8 +1169,8 @@ class CharmmPsfFile(object):
if lj_idx_list[j] > 0: continue # already assigned if lj_idx_list[j] > 0: continue # already assigned
if atom2 is atom: if atom2 is atom:
lj_idx_list[j] = num_lj_types lj_idx_list[j] = num_lj_types
elif not atom.nbfix: elif not atom.nbfix and not atom.nbthole:
# Only non-NBFIXed atom types can be compressed # Only non-NBFIXed and non-NBTholed atom types can be compressed
ljtype2 = (atom2.rmin, atom2.epsilon) ljtype2 = (atom2.rmin, atom2.epsilon)
if ljtype == ljtype2: if ljtype == ljtype2:
lj_idx_list[j] = num_lj_types lj_idx_list[j] = num_lj_types
...@@ -1125,6 +1225,68 @@ class CharmmPsfFile(object): ...@@ -1125,6 +1225,68 @@ class CharmmPsfFile(object):
for i in lj_idx_list: for i in lj_idx_list:
cforce.addParticle((i - 1,)) # adjust for indexing from 0 cforce.addParticle((i - 1,)) # adjust for indexing from 0
# Add NBTHOLE terms
if has_drude_particle and has_nbthole_terms:
nbt_idx_list = [0 for atom in self.atom_list]
nbt_alpha_list = [0 for atom in self.atom_list] # only save alpha for NBThole pairs
num_nbt_types = 0
nbt_type_list = []
nbt_set_list = []
for i, atom in enumerate(self.atom_list):
atom = atom.type
if not atom.nbthole: continue # get them as zero
if nbt_idx_list[i]: continue # already assigned
num_nbt_types += 1
nbt_idx_list[i] = num_nbt_types
nbt_idx_list[i+1] = num_nbt_types
nbt_alpha_list[i] = pow(-1*self.drudeconsts_list[i][0],-1./6.)
nbt_alpha_list[i+1] = pow(-1*self.drudeconsts_list[i][0],-1./6.)
nbt_type_list.append(atom)
nbt_set_list.append([i,i+1])
for j in range(i+1, len(self.atom_list)):
atom2 = self.atom_list[j].type
if nbt_idx_list[j] > 0: continue # already assigned
if atom2 is atom:
nbt_idx_list[j] = num_nbt_types
nbt_idx_list[j+1] = num_nbt_types
nbt_alpha_list[j] = pow(-1*self.drudeconsts_list[j][0],-1./6.)
nbt_alpha_list[j+1] = pow(-1*self.drudeconsts_list[j][0],-1./6.)
nbt_set_list[num_nbt_types-1].append(j)
nbt_set_list[num_nbt_types-1].append(j+1)
num_total_nbt=num_nbt_types+1 # use zero index for all the atoms with no nbthole
nbt_interset_list=[]
# need to get all other particles as an independent group, so in total num_nbt_types+1
coef = [0 for i in range(num_total_nbt*num_total_nbt)]
for i in range(num_nbt_types):
for j in range(num_nbt_types):
namej = nbt_type_list[j].name
nbt_value = nbt_type_list[i].nbthole.get(namej,0)
if abs(nbt_value)>TINY and i<j : nbt_interset_list.append([i+1,j+1])
coef[i+1+num_total_nbt*(j+1)]=nbt_value
nbtforce = mm.CustomNonbondedForce('-138.935456*charge1*charge2*(1.0+0.5*screen*r)*exp(-1.0*screen*r)/r; screen=coef(type1, type2) * alpha1*alpha2*10.0')
nbtforce.addTabulatedFunction('coef', mm.Discrete2DFunction(num_total_nbt, num_total_nbt, coef))
nbtforce.addPerParticleParameter('charge')
nbtforce.addPerParticleParameter('alpha')
nbtforce.addPerParticleParameter('type')
nbtforce.setForceGroup(self.NONBONDED_FORCE_GROUP)
# go through all the particles to set up per-particle parameters
for i in range(system.getNumParticles()):
c=force.getParticleParameters(i)
cc=c[0]/u.elementary_charge
aa=nbt_alpha_list[i]
ti=nbt_idx_list[i]
nbtforce.addParticle([cc,aa,ti])
# set interaction group
for a in nbt_interset_list:
ai=a[0]
aj=a[1]
nbtforce.addInteractionGroup(nbt_set_list[ai-1],nbt_set_list[aj-1])
nbtforce.setNonbondedMethod(nbtforce.CutoffPeriodic)
nbtforce.setCutoffDistance(0.5*u.nanometer)
nbtforce.setUseSwitchingFunction(False)
# now, add the actual force to the system
system.addForce(nbtforce)
# Add 1-4 interactions # Add 1-4 interactions
excluded_atom_pairs = set() # save these pairs so we don't zero them out excluded_atom_pairs = set() # save these pairs so we don't zero them out
sigma_scale = 2**(-1/6) sigma_scale = 2**(-1/6)
...@@ -1150,20 +1312,99 @@ class CharmmPsfFile(object): ...@@ -1150,20 +1312,99 @@ class CharmmPsfFile(object):
) )
# Add excluded atoms # Add excluded atoms
# Drude and lonepairs will be excluded based on their parent atoms
parent_exclude_list=[]
for atom in self.atom_list:
parent_exclude_list.append([])
for lpsite in self.lonepair_list:
idx = lpsite[1]
idxa = lpsite[0]
parent_exclude_list[idx].append(idxa)
force.addException(idx, idxa, 0.0, 0.1, 0.0)
if has_drude_particle:
for pair in self.drudepair_list:
idx = pair[0]
idxa = pair[1]
parent_exclude_list[idx].append(idxa)
force.addException(idx, idxa, 0.0, 0.1, 0.0)
# If lonepairs and Drude particles are bonded to the same parent atom, add exception
for excludeterm in parent_exclude_list:
if(len(excludeterm) >= 2):
for i in range(len(excludeterm)):
for j in range(i):
force.addException(excludeterm[j], excludeterm[i], 0.0, 0.1, 0.0)
# Exclude all bonds and angles, as well as the lonepair/Drude attached onto them
for atom in self.atom_list: for atom in self.atom_list:
# Exclude all bonds and angles
for atom2 in atom.bond_partners: for atom2 in atom.bond_partners:
if atom2.idx > atom.idx: if atom2.idx > atom.idx:
force.addException(atom.idx, atom2.idx, 0.0, 0.1, 0.0) for excludeatom in [atom.idx]+parent_exclude_list[atom.idx]:
for excludeatom2 in [atom2.idx]+parent_exclude_list[atom2.idx]:
force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0)
for atom2 in atom.angle_partners: for atom2 in atom.angle_partners:
if atom2.idx > atom.idx: if atom2.idx > atom.idx:
force.addException(atom.idx, atom2.idx, 0.0, 0.1, 0.0) for excludeatom in [atom.idx]+parent_exclude_list[atom.idx]:
for excludeatom2 in [atom2.idx]+parent_exclude_list[atom2.idx]:
force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0)
for atom2 in atom.dihedral_partners: for atom2 in atom.dihedral_partners:
if atom2.idx <= atom.idx: continue if atom2.idx <= atom.idx: continue
if ((atom.idx, atom2.idx) in excluded_atom_pairs): if ((atom.idx, atom2.idx) in excluded_atom_pairs):
continue continue
force.addException(atom.idx, atom2.idx, 0.0, 0.1, 0.0) force.addException(atom.idx, atom2.idx, 0.0, 0.1, 0.0)
system.addForce(force) system.addForce(force)
# Add Drude particles (Drude force)
if has_drude_particle:
if verbose: print('Adding Drude force and Thole screening...')
drudeforce = mm.DrudeForce()
drudeforce.setForceGroup(7)
for pair in self.drudepair_list:
parentatom=pair[0]
drudeatom=pair[1]
p=[-1, -1, -1]
a11 = 0
a22 = 0
charge = self.atom_list[drudeatom].charge
polarizability = self.drudeconsts_list[parentatom][0]/(-1000.0)
for aniso in self.aniso_list: # not smart to do another loop, but the number of aniso is small anyway
if (parentatom==aniso[0]):
p[0]=aniso[1]
p[1]=aniso[2]
p[2]=aniso[3]
k11=aniso[4]
k22=aniso[5]
k33=aniso[6]
# solve out DrudeK, which should equal 500.0
a = k11+k22+3*k33
b = 2*k11*k22+4*k11*k33+4*k22*k33+6*k33*k33
c = 3*k33*(k11+k33)*(k22+k33)
DrudeK = (sqrt(b*b-4*a*c)-b)/2/a
a11=round(DrudeK/(k11+k33+DrudeK),5)
a22=round(DrudeK/(k22+k33+DrudeK),5)
drudeforce.addParticle(drudeatom, parentatom, p[0], p[1], p[2], charge, polarizability, a11, a22 )
system.addForce(drudeforce)
particleMap = {}
for i in range(drudeforce.getNumParticles()):
particleMap[drudeforce.getParticleParameters(i)[0]] = i
for bond in self.bond_list:
alpha1 = self.drudeconsts_list[bond.atom1.idx][0]
alpha2 = self.drudeconsts_list[bond.atom2.idx][0]
if abs(alpha1) > TINY and abs(alpha2) > TINY: # both are Drude parent atoms
thole1 = self.drudeconsts_list[bond.atom1.idx][1]
thole2 = self.drudeconsts_list[bond.atom2.idx][1]
drude1 = bond.atom1.idx + 1 # CHARMM psf has hard-coded rule that the Drude is next to its parent
drude2 = bond.atom2.idx + 1
drudeforce.addScreenedPair(particleMap[drude1], particleMap[drude2], thole1+thole2)
for ang in self.angle_list:
alpha1 = self.drudeconsts_list[ang.atom1.idx][0]
alpha2 = self.drudeconsts_list[ang.atom3.idx][0]
if abs(alpha1) > TINY and abs(alpha2) > TINY: # both are Drude parent atoms
thole1 = self.drudeconsts_list[ang.atom1.idx][1]
thole2 = self.drudeconsts_list[ang.atom3.idx][1]
drude1 = ang.atom1.idx + 1 # CHARMM psf has hard-coded rule that the Drude is next to its parent
drude2 = ang.atom3.idx + 1
drudeforce.addScreenedPair(particleMap[drude1], particleMap[drude2], thole1+thole2)
# If we needed a CustomNonbondedForce, map all of the exceptions from # If we needed a CustomNonbondedForce, map all of the exceptions from
# the NonbondedForce to the CustomNonbondedForce # the NonbondedForce to the CustomNonbondedForce
if has_nbfix_terms: if has_nbfix_terms:
...@@ -1171,6 +1412,10 @@ class CharmmPsfFile(object): ...@@ -1171,6 +1412,10 @@ class CharmmPsfFile(object):
ii, jj, q, eps, sig = force.getExceptionParameters(i) ii, jj, q, eps, sig = force.getExceptionParameters(i)
cforce.addExclusion(ii, jj) cforce.addExclusion(ii, jj)
system.addForce(cforce) system.addForce(cforce)
if has_drude_particle and has_nbthole_terms:
for i in range(force.getNumExceptions()):
ii, jj, q, eps, sig = force.getExceptionParameters(i)
nbtforce.addExclusion(ii, jj)
# Add GB model if we're doing one # Add GB model if we're doing one
if implicitSolvent is not None: if implicitSolvent is not None:
......
...@@ -100,6 +100,9 @@ class AtomType(object): ...@@ -100,6 +100,9 @@ class AtomType(object):
nbfix : dict nbfix : dict
Dictionary that maps nbfix terms with other atom types. Dict entries Dictionary that maps nbfix terms with other atom types. Dict entries
are (rmin, epsilon) -- precombined values for that particular atom pair are (rmin, epsilon) -- precombined values for that particular atom pair
nbthole : dict
Dictionary that maps nbthole terms with other atom types. Dict entries
are the value of a -- screening factor for that pair
Examples Examples
-------- --------
...@@ -137,6 +140,8 @@ class AtomType(object): ...@@ -137,6 +140,8 @@ class AtomType(object):
# Store each NBFIX term as a dict with the atom type string matching to # Store each NBFIX term as a dict with the atom type string matching to
# a 2-element tuple that is rmin, epsilon # a 2-element tuple that is rmin, epsilon
self.nbfix = dict() self.nbfix = dict()
# Likewise, store each NBTHOLE term as a dict
self.nbthole = dict()
def __eq__(self, other): def __eq__(self, other):
""" """
...@@ -173,6 +178,10 @@ class AtomType(object): ...@@ -173,6 +178,10 @@ class AtomType(object):
if epsilon14 is None: epsilon14 = epsilon if epsilon14 is None: epsilon14 = epsilon
self.nbfix[typename] = (rmin, epsilon, rmin14, epsilon14) self.nbfix[typename] = (rmin, epsilon, rmin14, epsilon14)
def add_nbthole(self, typename, nbt):
""" Adds a new NBTHOLE screening factor for this atom """
self.nbthole[typename] = (nbt)
def __str__(self): def __str__(self):
return self.name return self.name
......
...@@ -116,6 +116,26 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -116,6 +116,26 @@ class TestCharmmFiles(unittest.TestCase):
ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole) ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole)
self.assertAlmostEqual(ene, 15490.0033559, delta=0.05) self.assertAlmostEqual(ene, 15490.0033559, delta=0.05)
def test_drude(self):
"""Tests CHARMM systems with Drude force field"""
warnings.filterwarnings('ignore', category=CharmmPSFWarning)
psf = CharmmPsfFile('systems/ala3_solv_drude.psf')
crd = CharmmCrdFile('systems/ala3_solv_drude.crd')
params = CharmmParameterSet('systems/toppar_drude_master_protein_2013e.str')
# Box dimensions (cubic box)
psf.setBox(33.2*angstroms, 33.2*angstroms, 33.2*angstroms)
# Now compute the full energy
plat = Platform.getPlatformByName('Reference')
system = psf.createSystem(params, nonbondedMethod=PME)
integrator = DrudeLangevinIntegrator(300*kelvin, 1.0/picosecond, 1*kelvin, 10/picosecond, 0.001*picoseconds)
con = Context(system, integrator, plat)
con.setPositions(crd.positions)
state = con.getState(getEnergy=True, enforcePeriodicBox=True)
ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole)
self.assertAlmostEqual(ene, -1831.54, delta=0.5)
def test_InsCode(self): def test_InsCode(self):
""" Test the parsing of PSF files that contain insertion codes in their residue numbers """ """ Test the parsing of PSF files that contain insertion codes in their residue numbers """
psf = CharmmPsfFile('systems/4TVP-dmj_wat-ion.psf') psf = CharmmPsfFile('systems/4TVP-dmj_wat-ion.psf')
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment