Skip to content

Commit 4d4b105

Browse files
committed
Corrected a bug in OPLSAA ("oplsaa.lt") which incorrectly named several angle and dihedral types (@Angle:NB_OA_CR, @Angle:NB_OA_CR, @dihedral:H~_N~_CT_CT). Those angle and dihedral type names have been restored. Also: the "oplsaa2lt.py" conversion script has been debugged and cleaned up. Most users will not be affecgted by this change.
1 parent a07a385 commit 4d4b105

File tree

4 files changed

+119
-80
lines changed

4 files changed

+119
-80
lines changed

moltemplate/force_fields/convert_OPLSAA_to_LT/oplsaa2lt.py

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
assert version.major > 3 or (version.major == 3 and version.minor >= 7)
1818

1919
__author__ = "Domenico Marson and Andrew Jewett"
20-
__version__ = '1.0.0'
21-
__date__ = '2025-1-31'
20+
__version__ = '1.0.1'
21+
__date__ = '2025-2-01'
2222

2323
g_program_name = __file__.split('/')[-1]
2424

@@ -168,84 +168,126 @@ def get_dihedrals_and_impropers(input_lines) -> tuple[list[Dihedral], list[Impro
168168
return loaded_dihedrals, loaded_impropers
169169

170170

171-
# NOTE: PROBLEM WITH SAME-TYPES BONDED INTERACTIONS...
172-
# The same dihedral (from atom types POV) can have != parameters, based on comment...
173-
# e.g.:
174-
# 102 0.000 5.500 0.00 0.0 O -C -OH-HO carboxylic acid - aliphatic
175-
# 210 0.000 5.000 0.00 0.0 O -C -OH-HO benzoic acids
176-
# the same problem is observed also for bonds and angles...
177-
def check_uniqueness(
171+
def delete_redundant_duplicates(
178172
interactions: list[Dihedral]|list[Improper]|list[Angle]|list[Bond],
179173
) -> None:
174+
""" OPLSAA files contain many duplicates. Decide which interactions to keep and return the list to the caller."""
175+
180176
##### My apologies for this ugly code. #####
181-
# It wasn't planned that way, but we keep finding
182-
# more special cases that need custom code added.
183-
# PAR and SB files have grown over time and we keep
184-
# finding more redundancy problems that need fixing.
185-
186-
# Step1: Remove duplicate interactions.
187-
# Create a dictionary of all the duplicate interactions
188-
# which share the same atom types and coefficients
189-
# (excluding comments).
190-
typescoeff_to_interaction = defaultdict(dict)
177+
# It wasn't planned that way. The PAR and SB files
178+
# that store OPLSAA parameters have grown messy over time.
179+
# We keep finding more redundancy problems that need fixing.
180+
# That's why this code has so many edge cases.
181+
182+
# ----------- Step 1: ---------------
183+
# Organize interactions according to:
184+
# -atom type strings (a tuple which may include wildcards)
185+
# -force-field parameters (This is called "params" in the code.)
186+
# -the "paramstr" (This is the entire string following the atom type list.
187+
# including the parameters AND comments, if present.)
188+
# The result is stored in a dictionary-of-dictionary-of-dictionaries.
189+
# named "types_to_paraminteractions". Lookup interactions this way:
190+
# types_to_paraminteractions[atomtypes][params][paramstr] --> interaction
191+
#
192+
# Later, during Step2, we will use it to figure out which interactions
193+
# are redundant and can be discarded.
194+
types_to_paraminteractions = {}
191195
for interaction in interactions:
192196
types = tuple(interaction.types)
193-
# Find the text containing the force field parameters ("coeffs")
194-
coeff = interaction.coeff_line
195-
# Typically, this is "angle_coeff @angle:C3_N~_H~ 38. 118.4 # "
196-
# But we only want "38. 118.4 # ".
197+
# Find the text containing the force field parameters ("paramstr")
198+
paramstr = interaction.coeff_line
199+
# Typically, this is "angle_coeff @angle:C3_N~_H~ 38. 118.4 # WJ94"
200+
# But we only want "38. 118.4 # WJ94".
197201
# So we ignore the text before the first two spaces:
198-
ispace1 = coeff.find(" ")
199-
ispace2 = coeff.find(" ", ispace1+1)
202+
ispace1 = paramstr.find(" ")
203+
ispace2 = paramstr.find(" ", ispace1+1)
200204
ispace2 = ispace2+1
201-
coeff = coeff[ispace2:].strip() # eg. "38. 118.4 #"
202-
# Finally, strip off the comment
203-
coeff_no_comment = coeff.lstrip("#").split("#")[0].strip()
204-
# Instead of storing the interaction at (types, coeff_no_comment),
205-
# I store an additional dictionary (whose key is (types, coeff)).
206-
# That way I can keep track of all the different comments that
207-
# were added to this (otherwise identical) interaction.
208-
# We want to keep all of them, but discard the ones that are
209-
# trivially similar to each other.
210-
typescoeff_to_interaction[(types, coeff_no_comment)][(types, coeff)] = interaction
211-
# Copy the the non-redudant entries back into "interactions" again.
212-
i = 0
213-
for coeff_no_comment, coeff_interactions in typescoeff_to_interaction.items():
214-
for typescoeff, interaction in coeff_interactions.items():
215-
# Edge case: We want to ignore interactions if
216-
# they lack a comment and are otherwise identical.
217-
# ...so we also check for (coeff != coeff_no_comment).
218-
coeff = typescoeff[1]
219-
coeff_blank_comment = coeff
220-
i_comment = coeff.find("#")
221-
if i_comment > 0:
222-
coeff_blank_comment = coeff[:i_comment+1]
223-
comment = coeff[i_comment+1:]
224-
if len(coeff_interactions) == 1:
225-
interactions[i] = interaction
226-
i += 1
227-
elif coeff not in (coeff_no_comment, coeff_blank_comment):
228-
# For some reason, a lot of comments only contain ' "'
229-
# We want to skip those too.
230-
if comment.strip() != '"':
231-
interactions[i] = interaction
232-
i += 1
233-
234-
del interactions[i:]
205+
paramstr = paramstr[ispace2:].strip() # eg. "38. 118.4 # WJ94"
206+
# Finally, strip off the comment, leaving only the parameters
207+
params = paramstr.lstrip("#").split("#")[0].strip() # eg. "38. 118.4"
208+
if types not in types_to_paraminteractions:
209+
# Multiple interactions can exist for these same atom types
210+
# differing by either the parameters, or comments, or both.
211+
# Store a dictionary that looks up the interaction between these
212+
# atoms according to their params (without comments).
213+
types_to_paraminteractions[types] = {} # We will use this below
214+
215+
# Create a dictionary that looks up all the interactions
216+
# which share the same types and parameters (.ie params), but may
217+
# have different comments. We might want to delete these duplicates
218+
# later, but for now, we keep track of all of them.
219+
params_to_interactions = types_to_paraminteractions[types]
220+
if params not in params_to_interactions:
221+
params_to_interactions[params] = {}
222+
paramstr_to_interactions = params_to_interactions[params]
223+
224+
# For these interactions, its possible that multiple interactions
225+
# may exist in the file with identical atom types, parameters (coeffs)
226+
# AND comments. To get rid of these trivial duplicates, we store them in
227+
# a dictionary, indexed by the original paramstr (including comments).
228+
paramstr_to_interactions[paramstr] = interaction
229+
230+
# ----------- Step 2: ---------------
231+
# Decide which of these interactions should be discarded.
232+
# (The interactions we want to keep will be stored in
233+
# out_interactions, which will be returned to the caller.)
234+
del interactions[:]
235+
for types, params_to_interactions in types_to_paraminteractions.items():
236+
for params, paramstr_to_interactions in params_to_interactions.items():
237+
for paramstr, interaction in paramstr_to_interactions.items():
238+
discarded = True
239+
paramstr_blank_comment = paramstr
240+
i_comment = paramstr.find("#")
241+
if i_comment > 0:
242+
paramstr_blank_comment = paramstr[:i_comment+1]
243+
comment = paramstr[i_comment+1:]
244+
if len(paramstr_to_interactions) == 1:
245+
interactions.append(interaction)
246+
discarded = False
247+
elif (
248+
paramstr not in (params, paramstr_blank_comment) # case 1
249+
and (comment.strip() != '"') # case 2 (see below)
250+
):
251+
# Edge Case 1:
252+
# We want to ignore interactions if
253+
# they lack a comment but they are otherwise identical.
254+
# ...so we also check to make sure that paramstr != params.
255+
# (and also paramstr != paramstr_blank_comment)
256+
#
257+
# Edge Case 2:
258+
# For some reason, a lot of comments only contain '"'. We
259+
# want to skip those too (since they are otherwise identical)
260+
#
261+
# If none of these edge-cases are true,
262+
# then we don't discard the interaction.
263+
interactions.append(interaction)
264+
discarded = False
265+
# If all of the duplicate interactions are identical except for
266+
# the comments following the params, then throw away these
267+
# duplicates (merge them into a single interaction).
268+
if (len(params_to_interactions) == 1) and (not discarded):
269+
break # skip the remaining duplicates for these atoms
235270

271+
272+
# NOTE: PROBLEM WITH SAME-TYPES BONDED INTERACTIONS...
273+
# The same dihedral (from atom types POV) can have != parameters, based on comment...
274+
# e.g.:
275+
# 102 0.000 5.500 0.00 0.0 O -C -OH-HO carboxylic acid - aliphatic
276+
# 210 0.000 5.000 0.00 0.0 O -C -OH-HO benzoic acids
277+
# the same problem is observed also for bonds and angles...
278+
def count_nonredundant_duplicates(
279+
interactions: list[Dihedral]|list[Improper]|list[Angle]|list[Bond],
280+
) -> None:
236281
for idx, it1 in enumerate(interactions):
237282
for it2 in interactions[idx+1:]:
238283
if it1.types == it2.types:
239-
it1_coeff = it1.coeff_line.lstrip("#").split("#")[0]
240-
it2_coeff = it2.coeff_line.lstrip("#").split("#")[0]
241-
# If we are not skipping this interaction, then
242-
# keep track of the number of duplicate interactions of this type
243284
if it1.duplicate_count == 0:
244285
it1.duplicate_count = 1
245286
it2.duplicate_count = it1.duplicate_count + 1
246287

247288

248289

290+
249291
def sort_duplicates(
250292
interactions: list[Dihedral]|list[Improper]|list[Angle]|list[Bond],
251293
) -> None:
@@ -335,10 +377,13 @@ def main(argv):
335377
dihedrals, impropers = get_dihedrals_and_impropers(lines_par)
336378

337379

380+
# Now, let's cleanup all the lists of bonded interactions.
381+
# (Remove redundant entries, and sort by atom-type)
338382
for interactions in [bonds, angles, dihedrals, impropers]:
339383
interactions.sort(key=lambda x: x.typename)
340384
interactions.sort(key=lambda x: x.sort_key)
341-
check_uniqueness(interactions)
385+
delete_redundant_duplicates(interactions)
386+
count_nonredundant_duplicates(interactions)
342387
sort_duplicates(interactions)
343388

344389

moltemplate/force_fields/oplsaa.lt

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5731,8 +5731,7 @@ OPLSAA {
57315731
angle_coeff @angle:CT_CT_CZ 58.35 112.7 # wlj
57325732
angle_coeff @angle:CT_CT_Cl 69. 109.8 # wlj - from MM2
57335733
angle_coeff @angle:CT_CT_F~ 50. 109.5 # PAK F-CT-HC (emd 5-09-94)
5734-
angle_coeff @angle:CT_CT_HC__2 37.5 110.7 # CHARMM 22 parameter file
5735-
angle_coeff @angle:CT_CT_HC__1 37.5 110.7 # CHARMM 22
5734+
angle_coeff @angle:CT_CT_HC 37.5 110.7 # CHARMM 22
57365735
angle_coeff @angle:CT_CT_I~ 75. 112.0 # wlj
57375736
angle_coeff @angle:CT_CT_N2 80.0 111.2 # ARG JCP 76, 1439
57385737
angle_coeff @angle:CT_CT_N3 80.0 111.2 # LYS(OL) JCP 76, 1439
@@ -6296,8 +6295,7 @@ OPLSAA {
62966295
angle_coeff @angle:NB_NB_OA 70. 110.0 # JT-R oxatriazoles
62976296
angle_coeff @angle:NB_NB_SA 70. 114.0 # JT-R thiatriazole
62986297
angle_coeff @angle:NB_NX_CW 56. 113.1 # MKD synonym for NB-NA-CW
6299-
angle_coeff @angle:NB_OA_CR__2 70. 101.3 # JT-R oxatriazole
6300-
angle_coeff @angle:NB_OA_CR__1 70. 101.3 # JT-R oxatriazoles
6298+
angle_coeff @angle:NB_OA_CR 70. 101.3 # JT-R oxatriazoles
63016299
angle_coeff @angle:NB_OA_NB 70. 103.4 # JT-R oxatriazole
63026300
angle_coeff @angle:NB_OS_CR 70. 101.3 # JT-R oxatriazoles
63036301
angle_coeff @angle:NB_OS_DM 10.0 125. # wlj
@@ -7166,8 +7164,7 @@ OPLSAA {
71667164
@angle:CT_CT_CZ @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCZ*_d*_i*
71677165
@angle:CT_CT_Cl @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCl*_d*_i*
71687166
@angle:CT_CT_F~ @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aF~*_d*_i*
7169-
@angle:CT_CT_HC__2 @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aHC*_d*_i*
7170-
@angle:CT_CT_HC__1 @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aHC*_d*_i*
7167+
@angle:CT_CT_HC @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aHC*_d*_i*
71717168
@angle:CT_CT_I~ @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aI~*_d*_i*
71727169
@angle:CT_CT_N2 @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aN2*_d*_i*
71737170
@angle:CT_CT_N3 @atom:*_b*_aCT*_d*_i* @atom:*_b*_aCT*_d*_i* @atom:*_b*_aN3*_d*_i*
@@ -7731,8 +7728,7 @@ OPLSAA {
77317728
@angle:NB_NB_OA @atom:*_b*_aNB*_d*_i* @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOA*_d*_i*
77327729
@angle:NB_NB_SA @atom:*_b*_aNB*_d*_i* @atom:*_b*_aNB*_d*_i* @atom:*_b*_aSA*_d*_i*
77337730
@angle:NB_NX_CW @atom:*_b*_aNB*_d*_i* @atom:*_b*_aNX*_d*_i* @atom:*_b*_aCW*_d*_i*
7734-
@angle:NB_OA_CR__2 @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOA*_d*_i* @atom:*_b*_aCR*_d*_i*
7735-
@angle:NB_OA_CR__1 @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOA*_d*_i* @atom:*_b*_aCR*_d*_i*
7731+
@angle:NB_OA_CR @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOA*_d*_i* @atom:*_b*_aCR*_d*_i*
77367732
@angle:NB_OA_NB @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOA*_d*_i* @atom:*_b*_aNB*_d*_i*
77377733
@angle:NB_OS_CR @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOS*_d*_i* @atom:*_b*_aCR*_d*_i*
77387734
@angle:NB_OS_DM @atom:*_b*_aNB*_d*_i* @atom:*_b*_aOS*_d*_i* @atom:*_b*_aDM*_d*_i*
@@ -8915,8 +8911,7 @@ OPLSAA {
89158911
dihedral_coeff @dihedral:H~_NT_NT_H~ 0.0 0.0 0.3 0.0 # generic
89168912
dihedral_coeff @dihedral:H~_NT_OH_HO 0.0 0.0 0.3 0.0 # generic
89178913
dihedral_coeff @dihedral:H~_NT_OS_CT 0.0 0.0 0.3 0.0 # generic
8918-
dihedral_coeff @dihedral:H~_N~_CT_CT__2 0.000 0.000 0.000 0.0 # N-ethylformamide
8919-
dihedral_coeff @dihedral:H~_N~_CT_CT__1 0.000 0.000 0.000 0.0 # peptides
8914+
dihedral_coeff @dihedral:H~_N~_CT_CT 0.000 0.000 0.000 0.0 # peptides
89208915
dihedral_coeff @dihedral:H~_N~_CT_C~ 0.000 0.000 0.000 0.0 # peptides
89218916
dihedral_coeff @dihedral:H~_N~_C~_C~ 0.000 4.900 0.000 0.0 # dicarbonyls "
89228917
dihedral_coeff @dihedral:H~_N~_OH_HO 2.722 -5.154 0.000 0.0 # hydroxamic acids
@@ -10114,8 +10109,7 @@ OPLSAA {
1011410109
@dihedral:H~_NT_NT_H~ @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dNT*_i* @atom:*_b*_a*_dNT*_i* @atom:*_b*_a*_dH~*_i*
1011510110
@dihedral:H~_NT_OH_HO @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dNT*_i* @atom:*_b*_a*_dOH*_i* @atom:*_b*_a*_dHO*_i*
1011610111
@dihedral:H~_NT_OS_CT @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dNT*_i* @atom:*_b*_a*_dOS*_i* @atom:*_b*_a*_dCT*_i*
10117-
@dihedral:H~_N~_CT_CT__2 @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dCT*_i* @atom:*_b*_a*_dCT*_i*
10118-
@dihedral:H~_N~_CT_CT__1 @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dCT*_i* @atom:*_b*_a*_dCT*_i*
10112+
@dihedral:H~_N~_CT_CT @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dCT*_i* @atom:*_b*_a*_dCT*_i*
1011910113
@dihedral:H~_N~_CT_C~ @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dCT*_i* @atom:*_b*_a*_dC~*_i*
1012010114
@dihedral:H~_N~_C~_C~ @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dC~*_i* @atom:*_b*_a*_dC~*_i*
1012110115
@dihedral:H~_N~_OH_HO @atom:*_b*_a*_dH~*_i* @atom:*_b*_a*_dN~*_i* @atom:*_b*_a*_dOH*_i* @atom:*_b*_a*_dHO*_i*

moltemplate/scripts/moltemplate.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# Copyright (c) 2013
77

88
G_PROGRAM_NAME="moltemplate.sh"
9-
G_VERSION="2.22.1"
10-
G_DATE="2025-1-31"
9+
G_VERSION="2.22.2"
10+
G_DATE="2025-2-01"
1111

1212
echo "${G_PROGRAM_NAME} v${G_VERSION} ${G_DATE}" >&2
1313
echo "" >&2

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@
4545

4646
url='https://github.com/jewettaij/moltemplate',
4747

48-
download_url='https://github.com/jewettaij/moltemplate/archive/v2.22.1.zip',
48+
download_url='https://github.com/jewettaij/moltemplate/archive/v2.22.2.zip',
4949

50-
version='2.22.1',
50+
version='2.22.2',
5151

5252
keywords=['simulation', 'LAMMPS', 'molecule editor', 'molecule builder',
5353
'ESPResSo'],

0 commit comments

Comments
 (0)