1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.potential.parameters;
39
40 import ffx.utilities.FFXProperty;
41
42 import javax.annotation.Nullable;
43 import java.util.Arrays;
44 import java.util.Comparator;
45 import java.util.HashMap;
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48
49 import static ffx.potential.parameters.ForceField.ForceFieldType.IMPTORS;
50 import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
51 import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
52 import static java.lang.Double.parseDouble;
53 import static java.lang.Integer.parseInt;
54 import static org.apache.commons.math3.util.FastMath.cos;
55 import static org.apache.commons.math3.util.FastMath.sin;
56 import static org.apache.commons.math3.util.FastMath.toRadians;
57
58
59
60
61
62
63
64 @FFXProperty(name = "imptors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
65 [4 integers and up to 3 real/real/integer triples]
66 Provides the values for a single AMBER-style improper torsional angle parameter.
67 The first four integer modifiers give the atom class numbers for the atoms involved in the improper torsional angle to be defined.
68 By convention, the third atom class of the four is the trigonal atom on which the improper torsion is centered.
69 The torsional angle computed is literally that defined by the four atom classes in the order specified by the keyword.
70 Each of the remaining triples of real/real/integer modifiers give the half-amplitude,
71 phase offset in degrees and periodicity of a particular improper torsional term, respectively.
72 Periodicities through 3-fold are allowed for improper torsional parameters.
73 """)
74 public final class ImproperTorsionType extends BaseType implements Comparator<String> {
75
76
77
78
79 private static final Logger logger = Logger.getLogger(ImproperTorsionType.class.getName());
80
81
82
83
84 public final int[] atomClasses;
85
86
87
88 public final double k;
89
90
91
92 public final double phase;
93
94
95
96 public final int periodicity;
97
98
99
100 public final double cos;
101
102
103
104 public final double sin;
105
106
107
108
109 @FFXProperty(name = "imptorunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
110 Sets the scale factor needed to convert the energy value computed by the AMBER-style improper
111 torsional angle potential into units of kcal/mole.
112 The correct value is force field dependent and typically provided in the header of the master force field parameter file.
113 """)
114 public double impTorUnit = DEFAULT_IMPTOR_UNIT;
115 public static final double DEFAULT_IMPTOR_UNIT = 1.0;
116
117
118
119
120
121
122
123
124
125 public ImproperTorsionType(int[] atomClasses, double k, double phase, int periodicity) {
126 super(IMPTORS, sortKey(atomClasses));
127 this.atomClasses = atomClasses;
128 double symm = 1.0;
129 this.periodicity = periodicity;
130 this.k = k / symm;
131 this.phase = phase;
132 cos = cos(toRadians(phase));
133 sin = sin(toRadians(phase));
134
135 assert (periodicity == 2);
136 }
137
138
139
140
141
142
143
144
145
146
147 public static ImproperTorsionType average(@Nullable ImproperTorsionType improperTorsionType1,
148 @Nullable ImproperTorsionType improperTorsionType2,
149 @Nullable int[] atomClasses) {
150
151 if (improperTorsionType1 == null || improperTorsionType2 == null || atomClasses == null) {
152 return null;
153 }
154
155 int periodicity = improperTorsionType1.periodicity;
156 if (periodicity != improperTorsionType2.periodicity) {
157 return null;
158 }
159
160 double forceConstant = (improperTorsionType1.k + improperTorsionType2.k) / 2.0;
161 double phase = (improperTorsionType1.phase + improperTorsionType2.phase) / 2.0;
162
163 return new ImproperTorsionType(atomClasses, forceConstant, phase, periodicity);
164 }
165
166
167
168
169
170
171
172
173 public static ImproperTorsionType parse(String input, String[] tokens) {
174 if (tokens.length < 8) {
175 logger.log(Level.WARNING, "Invalid IMPTORS type:\n{0}", input);
176 } else {
177 try {
178 int[] atomClasses = new int[4];
179 atomClasses[0] = parseInt(tokens[1]);
180 atomClasses[1] = parseInt(tokens[2]);
181 atomClasses[2] = parseInt(tokens[3]);
182 atomClasses[3] = parseInt(tokens[4]);
183 double k = parseDouble(tokens[5]);
184 double phase = parseDouble(tokens[6]);
185 int period = parseInt(tokens[7]);
186 return new ImproperTorsionType(atomClasses, k, phase, period);
187 } catch (NumberFormatException e) {
188 String message = "Exception parsing IMPTORS type:\n" + input + "\n";
189 logger.log(Level.SEVERE, message, e);
190 }
191 }
192 return null;
193 }
194
195
196
197
198
199
200
201
202 public static String sortKey(int[] c) {
203 if (c == null || c.length != 4) {
204 return null;
205 }
206 return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
207 }
208
209
210
211
212
213
214
215
216
217
218 public boolean assigned(int[] inputClasses, boolean allowInitialWildCards,
219 boolean allowFinalWildCard) {
220
221 if (inputClasses[2] != atomClasses[2]) {
222 return false;
223 }
224
225
226 if (inputClasses[3] == atomClasses[3] || (atomClasses[3] == 0 && allowFinalWildCard)) {
227
228 } else if (inputClasses[1] == atomClasses[3]) {
229 int temp = inputClasses[3];
230 inputClasses[3] = inputClasses[1];
231 inputClasses[1] = temp;
232 } else if (inputClasses[0] == atomClasses[3]) {
233 int temp = inputClasses[3];
234 inputClasses[3] = inputClasses[0];
235 inputClasses[0] = temp;
236 } else {
237 return false;
238 }
239
240
241 if (inputClasses[1] == atomClasses[1] || (atomClasses[1] == 0 && allowInitialWildCards)) {
242
243 } else if (inputClasses[0] == atomClasses[1]) {
244 int temp = inputClasses[1];
245 inputClasses[1] = inputClasses[0];
246 inputClasses[0] = temp;
247 } else {
248 return false;
249 }
250
251
252 return (inputClasses[0] == atomClasses[0] || (atomClasses[0] == 0 && allowInitialWildCards));
253 }
254
255
256
257
258
259
260
261
262 @Override
263 public int compare(String s1, String s2) {
264 String[] keys1 = s1.split(" ");
265 String[] keys2 = s2.split(" ");
266 int[] c1 = new int[4];
267 int[] c2 = new int[4];
268
269 for (int i = 0; i < 4; i++) {
270 c1[i] = parseInt(keys1[i]);
271 c2[i] = parseInt(keys2[i]);
272 }
273
274 if (c1[2] < c2[2]) {
275 return -1;
276 } else if (c1[2] > c2[2]) {
277 return 1;
278 } else if (c1[0] < c2[0]) {
279 return -1;
280 } else if (c1[0] > c2[0]) {
281 return 1;
282 } else if (c1[1] < c2[1]) {
283 return -1;
284 } else if (c1[1] > c2[1]) {
285 return 1;
286 } else if (c1[3] < c2[3]) {
287 return -1;
288 } else if (c1[3] > c2[3]) {
289 return 1;
290 }
291
292 return 0;
293 }
294
295
296
297
298
299
300
301
302 @Override
303 public boolean equals(Object o) {
304 if (this == o) {
305 return true;
306 }
307 if (o == null || getClass() != o.getClass()) {
308 return false;
309 }
310 ImproperTorsionType improperTorsionType = (ImproperTorsionType) o;
311 return Arrays.equals(atomClasses, improperTorsionType.atomClasses);
312 }
313
314
315
316
317
318
319
320
321 @Override
322 public int hashCode() {
323 return Arrays.hashCode(atomClasses);
324 }
325
326
327
328
329
330
331 public void incrementClasses(int increment) {
332 for (int i = 0; i < atomClasses.length; i++) {
333 if (atomClasses[i] != 0) {
334 atomClasses[i] += increment;
335 }
336 }
337 setKey(sortKey(atomClasses));
338 }
339
340
341
342
343
344
345 public boolean noZeroClasses() {
346 return atomClasses[0] != 0 && atomClasses[1] != 0 && atomClasses[3] != 0;
347 }
348
349
350
351
352
353
354 public void patchClasses(HashMap<AtomType, AtomType> typeMap) {
355 int count = 0;
356 for (AtomType newType : typeMap.keySet()) {
357 for (int atomClass : atomClasses) {
358 if (atomClass == newType.atomClass) {
359 count++;
360 }
361 }
362 }
363 if (count > 0 && count < atomClasses.length) {
364 for (AtomType newType : typeMap.keySet()) {
365 for (int i = 0; i < atomClasses.length; i++) {
366 if (atomClasses[i] == newType.atomClass) {
367 AtomType knownType = typeMap.get(newType);
368 atomClasses[i] = knownType.atomClass;
369 }
370 }
371 }
372 setKey(sortKey(atomClasses));
373 }
374 }
375
376
377
378
379
380
381
382
383 @Override
384 public String toString() {
385 StringBuilder imptorsBuffer = new StringBuilder("imptors");
386 for (int i : atomClasses) {
387 imptorsBuffer.append(String.format(" %5d", i));
388 }
389 imptorsBuffer.append(String.format(" %7.3f %7.3f %1d", k, phase, periodicity));
390
391 return imptorsBuffer.toString();
392 }
393 }