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.commands.test;
39
40 import ffx.numerics.Potential;
41 import ffx.potential.ForceFieldEnergy;
42 import ffx.potential.bonded.Atom;
43 import ffx.potential.bonded.Residue;
44 import ffx.potential.cli.AtomSelectionOptions;
45 import ffx.potential.cli.GradientOptions;
46 import ffx.potential.cli.PotentialCommand;
47 import ffx.potential.extended.ExtendedSystem;
48 import ffx.potential.terms.AnglePotentialEnergy;
49 import ffx.potential.terms.BondPotentialEnergy;
50 import ffx.potential.terms.ImproperTorsionPotentialEnergy;
51 import ffx.potential.terms.OutOfPlaneBendPotentialEnergy;
52 import ffx.potential.terms.PiOrbitalTorsionPotentialEnergy;
53 import ffx.potential.terms.StretchBendPotentialEnergy;
54 import ffx.potential.terms.TorsionPotentialEnergy;
55 import ffx.potential.terms.TorsionTorsionPotentialEnergy;
56 import ffx.potential.terms.UreyBradleyPotentialEnergy;
57 import ffx.utilities.FFXBinding;
58 import picocli.CommandLine.Command;
59 import picocli.CommandLine.Mixin;
60 import picocli.CommandLine.Option;
61 import picocli.CommandLine.Parameters;
62
63 import java.util.ArrayList;
64 import java.util.Collections;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.stream.IntStream;
69
70 import static ffx.utilities.StringUtils.parseAtomRanges;
71 import static java.lang.String.format;
72 import static org.apache.commons.math3.util.FastMath.abs;
73 import static org.apache.commons.math3.util.FastMath.pow;
74 import static org.apache.commons.math3.util.FastMath.sqrt;
75
76
77
78
79
80
81
82
83 @Command(description = " Test the potential energy gradient for CpHMD.", name = "test.PhGradient")
84 public class PhGradient extends PotentialCommand {
85
86 @Mixin
87 GradientOptions gradientOptions;
88
89 @Mixin
90 AtomSelectionOptions atomSelectionOptions;
91
92
93 @Option(names = {"--pH", "--constantPH"}, paramLabel = "7.4",
94 description = "Constant pH value for the test.")
95 double pH = 7.4;
96
97
98 @Option(names = {"--esvLambda"}, paramLabel = "0.5",
99 description = "ESV Lambda at which to test gradient.")
100 double esvLambda = 0.5;
101
102
103 @Option(names = {"--scanLambdas"}, paramLabel = "false",
104 description = "Scan titration and tautomer lambda landscape.")
105 boolean scan = false;
106
107
108 @Option(names = {"--testEndStateEnergies"}, paramLabel = "false",
109 description = "Test both ESV energy end states as if the polarization damping factor is initialized from the respective protonated or deprotonated state")
110 boolean testEndstateEnergies = false;
111
112
113 @Parameters(arity = "1", paramLabel = "file", description = "The atomic coordinate file in PDB format.")
114 String filename = null;
115
116 private ForceFieldEnergy energy;
117
118 public HashMap<String, double[]> endstateEnergyMap = new HashMap<>();
119 public int nFailures = 0;
120 public int nESVFailures = 0;
121 public double minEnergy = 0.0;
122 public String minLambdaList = "";
123
124
125 public PhGradient() {
126 super();
127 }
128
129
130
131
132
133 public PhGradient(FFXBinding binding) {
134 super(binding);
135 }
136
137
138
139
140
141 public PhGradient(String[] args) {
142 super(args);
143 }
144
145
146 @Override
147 public PhGradient run() {
148
149 if (!init()) {
150 return this;
151 }
152 activeAssembly = getActiveAssembly(filename);
153 if (activeAssembly == null) {
154 logger.info(helpString());
155 return this;
156 }
157
158
159 filename = activeAssembly.getFile().getAbsolutePath();
160
161 logger.info("\n Testing the atomic coordinate gradient of " + filename + "\n");
162
163
164 energy = activeAssembly.getPotentialEnergy();
165
166 ExtendedSystem esvSystem = new ExtendedSystem(activeAssembly, pH, null);
167 esvSystem.setConstantPh(pH);
168 List<Residue> extendedResidues = esvSystem.getExtendedResidueList();
169 List<Residue> titratingResidues = esvSystem.getTitratingResidueList();
170 List<Residue> tautomerResidues = esvSystem.getTautomerizingResidueList();
171
172 int numESVs = esvSystem.getExtendedResidueList().size();
173 energy.attachExtendedSystem(esvSystem);
174 logger.info(format(" Attached extended system with %d residues.", numESVs));
175
176
177 for (Residue residue : extendedResidues) {
178 esvSystem.setTitrationLambda(residue, esvLambda);
179 esvSystem.setTautomerLambda(residue, esvLambda);
180 }
181
182 Atom[] atoms = activeAssembly.getAtomArray();
183 int nAtoms = atoms.length;
184
185
186 atomSelectionOptions.setActiveAtoms(activeAssembly);
187
188
189 double step = gradientOptions.getDx();
190 logger.info(" Finite-difference step size:\t" + step);
191
192
193 boolean print = gradientOptions.getVerbose();
194 logger.info(" Verbose printing:\t\t" + print);
195
196
197 List<Integer> atomsToTest;
198 if (gradientOptions.getGradientAtoms().equalsIgnoreCase("NONE")) {
199 logger.info(" The gradient of no atoms will be evaluated.");
200 return this;
201 } else if (gradientOptions.getGradientAtoms().equalsIgnoreCase("ALL")) {
202 logger.info(" Checking gradient for all active atoms.\n");
203 atomsToTest = new ArrayList<>();
204 IntStream.range(0, nAtoms).forEach(val -> atomsToTest.add(val));
205 } else {
206 atomsToTest = parseAtomRanges(" Gradient atoms", gradientOptions.getGradientAtoms(), nAtoms);
207 logger.info(
208 " Checking gradient for active atoms in the range: " + gradientOptions.getGradientAtoms() +
209 "\n");
210 }
211
212
213 Map<Integer, Integer> allToActive = new HashMap<>();
214 int nActive = 0;
215 for (int i = 0; i < nAtoms; i++) {
216 Atom atom = atoms[i];
217 if (atom.isActive()) {
218 allToActive.put(i, nActive);
219 nActive++;
220 }
221 }
222
223
224 int n = energy.getNumberOfVariables();
225 double[] x = new double[n];
226 double[] g = new double[n];
227 energy.getCoordinates(x);
228 energy.energyAndGradient(x, g);
229 int index = 0;
230 double[][] allAnalytic = new double[nAtoms][3];
231 for (Atom a : atoms) {
232 a.getXYZGradient(allAnalytic[index++]);
233 }
234
235
236 double expGrad = 1000.0;
237 double gradientTolerance = gradientOptions.getTolerance();
238 double width = 2.0 * step;
239 double avLen = 0.0;
240 double avGrad = 0.0;
241 double expGrad2 = expGrad * expGrad;
242
243 int nTested = 0;
244 for (int k : atomsToTest) {
245 Atom a0 = atoms[k];
246 if (!a0.isActive()) {
247 continue;
248 }
249
250 nTested++;
251 double[] analytic = allAnalytic[k];
252
253
254 int ia = allToActive.get(k);
255 int i3 = ia * 3;
256 int i0 = i3 + 0;
257 int i1 = i3 + 1;
258 int i2 = i3 + 2;
259 double[] numeric = new double[3];
260
261
262 double orig = x[i0];
263 x[i0] = x[i0] + step;
264 double e = energy.energy(x);
265 x[i0] = orig - step;
266 e -= energy.energy(x);
267 x[i0] = orig;
268 numeric[0] = e / width;
269
270
271 orig = x[i1];
272 x[i1] = x[i1] + step;
273 e = energy.energy(x);
274 x[i1] = orig - step;
275 e -= energy.energy(x);
276 x[i1] = orig;
277 numeric[1] = e / width;
278
279
280 orig = x[i2];
281 x[i2] = x[i2] + step;
282 e = energy.energy(x);
283 x[i2] = orig - step;
284 e -= energy.energy(x);
285 x[i2] = orig;
286 numeric[2] = e / width;
287
288 double dx = analytic[0] - numeric[0];
289 double dy = analytic[1] - numeric[1];
290 double dz = analytic[2] - numeric[2];
291 double len = dx * dx + dy * dy + dz * dz;
292 avLen += len;
293 len = sqrt(len);
294
295 double grad2 = analytic[0] * analytic[0] + analytic[1] * analytic[1] + analytic[2] * analytic[2];
296 avGrad += grad2;
297
298 if (len > gradientTolerance) {
299 logger.info(format(" %s\n Failed: %10.6f\n", a0, len) +
300 format(" Analytic: (%12.4f, %12.4f, %12.4f)\n", analytic[0], analytic[1], analytic[2]) +
301 format(" Numeric: (%12.4f, %12.4f, %12.4f)\n", numeric[0], numeric[1], numeric[2]));
302 ++nFailures;
303 } else {
304 logger.info(format(" %s\n Passed: %10.6f\n", a0, len) +
305 format(" Analytic: (%12.4f, %12.4f, %12.4f)\n", analytic[0], analytic[1], analytic[2]) +
306 format(" Numeric: (%12.4f, %12.4f, %12.4f)", numeric[0], numeric[1], numeric[2]));
307 }
308
309 if (grad2 > expGrad2) {
310 logger.info(format(" Atom %d has an unusually large gradient: %10.6f", ia + 1, Math.sqrt(
311 grad2)));
312 }
313 logger.info("\n");
314 }
315
316 avLen = avLen / nTested;
317 avLen = sqrt(avLen);
318 if (avLen > gradientTolerance) {
319 logger.info(format(" Test failure: RMSD from analytic solution is %10.6f > %10.6f", avLen,
320 gradientTolerance));
321 } else {
322 logger.info(format(" Test success: RMSD from analytic solution is %10.6f < %10.6f", avLen,
323 gradientTolerance));
324 }
325 logger.info(format(" Number of atoms failing analytic test: %d", nFailures));
326
327 avGrad = avGrad / nTested;
328 avGrad = sqrt(avGrad);
329 if (avGrad > expGrad) {
330 logger.info(format(" Unusually large RMS gradient: %10.6f > %10.6f", avGrad, expGrad));
331 } else {
332 logger.info(format(" RMS gradient: %10.6f", avGrad));
333 }
334 energy.setCoordinates(x);
335 energy.getCoordinates(x);
336 energy.energyAndGradient(x, g);
337 double[] esvDerivs = esvSystem.getDerivatives();
338
339
340
341 for (int i = 0; i < titratingResidues.size(); i++) {
342 double eMinusTitr = 0.0;
343 double ePlusTitr = 0.0;
344 double eMinusTaut = 0.0;
345 double ePlusTaut = 0.0;
346 Residue residue = titratingResidues.get(i);
347 int tautomerIndex = tautomerResidues.indexOf(residue) + titratingResidues.size();
348
349 if (esvLambda + step > 1.0) {
350 logger.info("Backward finite difference being applied. Consider using a smaller step size than the default in this case.\n");
351 esvSystem.setTitrationLambda(residue, esvLambda - 2 * step);
352 eMinusTitr = energy.energy(x);
353 esvSystem.setTitrationLambda(residue, esvLambda);
354 ePlusTitr = energy.energy(x);
355
356 if (esvSystem.isTautomer(residue)) {
357 esvSystem.setTautomerLambda(residue, esvLambda - 2 * step);
358 eMinusTaut = energy.energy(x);
359 esvSystem.setTautomerLambda(residue, esvLambda);
360 ePlusTaut = energy.energy(x);
361 }
362 }
363
364
365 else if (esvLambda - step < 0.0) {
366 logger.info("Forward finite difference being applied. Consider using a smaller step size than the default in this case.\n");
367 esvSystem.setTitrationLambda(residue, esvLambda + 2 * step);
368 ePlusTitr = energy.energy(x);
369 esvSystem.setTitrationLambda(residue, esvLambda);
370 eMinusTitr = energy.energy(x);
371
372 if (esvSystem.isTautomer(residue)) {
373 esvSystem.setTautomerLambda(residue, esvLambda + 2 * step);
374 ePlusTaut = energy.energy(x);
375 esvSystem.setTautomerLambda(residue, esvLambda);
376 eMinusTaut = energy.energy(x);
377 }
378 }
379
380
381 else {
382 esvSystem.setTitrationLambda(residue, esvLambda + step);
383 ePlusTitr = energy.energy(x);
384 esvSystem.setTitrationLambda(residue, esvLambda - step);
385 eMinusTitr = energy.energy(x);
386 esvSystem.setTitrationLambda(residue, esvLambda);
387
388 if (esvSystem.isTautomer(residue)) {
389 esvSystem.setTautomerLambda(residue, esvLambda + step);
390 ePlusTaut = energy.energy(x);
391 esvSystem.setTautomerLambda(residue, esvLambda - step);
392 eMinusTaut = energy.energy(x);
393 esvSystem.setTautomerLambda(residue, esvLambda);
394 }
395 }
396
397 double fdDerivTitr = (ePlusTitr - eMinusTitr) / width;
398 double errorTitr = abs(fdDerivTitr - esvDerivs[i]);
399
400 if (errorTitr > gradientTolerance) {
401 logger.info(format(" Residue: %s Chain: %s ESV %d\n Failed: %10.6f\n", residue.toString(), residue.getChainID(), i, errorTitr) +
402 format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[i], fdDerivTitr));
403 ++nESVFailures;
404 } else {
405 logger.info(format(" Residue: %s Chain: %s ESV %d\n Passed: %10.6f\n", residue.toString(), residue.getChainID(), i, errorTitr) +
406 format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[i], fdDerivTitr));
407 }
408
409 if (esvSystem.isTautomer(residue)) {
410 double fdDerivTaut = (ePlusTaut - eMinusTaut) / width;
411 int ti = tautomerIndex;
412 double errorTaut = abs(fdDerivTaut - esvDerivs[ti]);
413 if (errorTaut > gradientTolerance) {
414 logger.info(format(" Residue: %s (Tautomer) Chain: %s ESV %d\n Failed: %10.6f\n", residue.toString(), residue.getChainID(), ti, errorTaut) +
415 format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[ti], fdDerivTaut));
416 ++nESVFailures;
417 } else {
418 logger.info(format(" Residue: %s (Tautomer) Chain: %s ESV %d\n Passed: %10.6f\n", residue.toString(), residue.getChainID(), ti, errorTaut) +
419 format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[ti], fdDerivTaut));
420 }
421 }
422 if (nESVFailures > 0) {
423 logger.info(format(" %d ESVs failed the gradient test.\n", nESVFailures));
424 }
425 }
426 if (nESVFailures == 0) {
427 logger.info(" All ESVs passed the gradient test.\n");
428 }
429
430 if (scan) {
431 for (Residue residue : esvSystem.getTitratingResidueList()) {
432 esvSystem.setTitrationLambda(residue, 0.0);
433 esvSystem.setTautomerLambda(residue, 0.0);
434 }
435 scanLambdas(esvSystem, energy, x);
436 printPermutations(esvSystem, titratingResidues.size(), energy, x);
437 }
438
439 if (testEndstateEnergies) {
440 testEndState(x, esvSystem, 0.0, 0.0);
441 testEndState(x, esvSystem, 1.0, 0.0);
442 }
443
444 return this;
445 }
446
447 private void printPermutations(ExtendedSystem esvSystem, int numESVs, ForceFieldEnergy energy, double[] x) {
448 for (Residue residue : esvSystem.getTitratingResidueList()) {
449 esvSystem.setTitrationLambda(residue, 0.0);
450 }
451 energy.getCoordinates(x);
452 printPermutationsR(esvSystem, numESVs - 1, energy, x);
453 logger.info("Minimum Energy:" + minEnergy + " acheived with lambdas: " + minLambdaList);
454 }
455
456 private void printPermutationsR(ExtendedSystem esvSystem, int esvID, ForceFieldEnergy energy, double[] x) {
457 for (int i = 0; i <= 1; i++) {
458 Residue residue = esvSystem.getTitratingResidueList().get(esvID);
459 esvSystem.setTitrationLambda(residue, i);
460 if (esvID != 0) {
461 printPermutationsR(esvSystem, esvID - 1, energy, x);
462 } else {
463 String lambdaList = esvSystem.getLambdaList();
464 logger.info(format("Lambda List: %s", lambdaList));
465 double stateEnergy = energy.energy(x, true);
466 if (stateEnergy < minEnergy) {
467 minEnergy = stateEnergy;
468 minLambdaList = lambdaList;
469 }
470 logger.info("\n");
471 }
472 }
473 }
474
475 private void scanLambdas(ExtendedSystem esvSystem, ForceFieldEnergy energy, double[] x) {
476 int nTitrESVs = esvSystem.getTitratingResidueList().size();
477 double[][][] peLandscape = new double[nTitrESVs][11][11];
478 for (int i = 0; i < nTitrESVs; i++) {
479 Residue residue = esvSystem.getTitratingResidueList().get(i);
480 for (int j = 0; j < 11; j++) {
481 esvSystem.setTitrationLambda(residue, (double) j / 10.0);
482 for (int k = 0; k < 11; k++) {
483 esvSystem.setTautomerLambda(residue, (double) k / 10.0);
484 peLandscape[i][j][k] = energy.energy(x, false);
485 }
486 }
487 esvSystem.setTitrationLambda(residue, 0.0);
488 esvSystem.setTautomerLambda(residue, 0.0);
489 }
490 StringBuilder tautomerHeader = new StringBuilder(" X→ ");
491 for (int k = 0; k < 11; k++) {
492 double lb = (double) k / 10;
493 tautomerHeader.append(String.format("%1$12s", "[" + lb + "]"));
494 }
495 tautomerHeader.append("\nλ↓");
496 for (int i = 0; i < nTitrESVs; i++) {
497 logger.info(format("ESV: %d \n", i));
498 logger.info(tautomerHeader.toString());
499 for (int j = 0; j < 11; j++) {
500 double lb = (double) j / 10;
501 StringBuilder histogram = new StringBuilder();
502 for (int k = 0; k < 11; k++) {
503 StringBuilder hisvalue = new StringBuilder();
504 String value = String.format("%5.4f", peLandscape[i][j][k]);
505 hisvalue.append(String.format("%1$12s", value));
506 histogram.append(hisvalue);
507 }
508 logger.info("[" + lb + "] " + histogram);
509 }
510 logger.info("\n");
511 }
512 }
513
514 private void testEndState(double[] x, ExtendedSystem esvSystem, double titrLambda, double tautLambda) {
515 for (Residue residue : esvSystem.getTitratingResidueList()) {
516 esvSystem.setTitrationLambda(residue, titrLambda);
517 esvSystem.setTautomerLambda(residue, tautLambda);
518 }
519
520
521
522
523 for (Atom atom : esvSystem.getExtendedAtoms()) {
524 int atomIndex = atom.getArrayIndex();
525 if (esvSystem.isTitratingHeavy(atomIndex)) {
526
527 if (atom.getPolarizeType() != null) {
528 double endstatePolar = esvSystem.getTitrationUtils().getPolarizability(atom, titrLambda, tautLambda, atom.getPolarizeType().polarizability);
529 double sixth = 1.0 / 6.0;
530 atom.getPolarizeType().pdamp = pow(endstatePolar, sixth);
531 }
532 }
533 }
534
535
536 String lambdaList = esvSystem.getLambdaList();
537 energy.setCoordinates(x);
538 energy.getCoordinates(x);
539 double stateEnergy = energy.energy(x, true);
540 double[] energyAndInteractionList = new double[26];
541
542 BondPotentialEnergy bondPotentialEnergy = energy.getBondPotentialEnergy();
543 if (bondPotentialEnergy != null) {
544 energyAndInteractionList[0] = bondPotentialEnergy.getEnergy();
545 energyAndInteractionList[1] = bondPotentialEnergy.getNumberOfBonds();
546 }
547
548 AnglePotentialEnergy anglePotentialEnergy = energy.getAnglePotentialEnergy();
549 if (anglePotentialEnergy != null) {
550 energyAndInteractionList[2] = anglePotentialEnergy.getEnergy();
551 energyAndInteractionList[3] = anglePotentialEnergy.getNumberOfAngles();
552 }
553
554 StretchBendPotentialEnergy stretchBendPotentialEnergy = energy.getStretchBendPotentialEnergy();
555 if (stretchBendPotentialEnergy != null) {
556 energyAndInteractionList[4] = stretchBendPotentialEnergy.getEnergy();
557 energyAndInteractionList[5] = stretchBendPotentialEnergy.getNumberOfStretchBends();
558 }
559
560 UreyBradleyPotentialEnergy ureyBradleyPotentialEnergy = energy.getUreyBradleyPotentialEnergy();
561 if (ureyBradleyPotentialEnergy != null) {
562 energyAndInteractionList[6] = ureyBradleyPotentialEnergy.getEnergy();
563 energyAndInteractionList[7] = ureyBradleyPotentialEnergy.getNumberOfUreyBradleys();
564 }
565
566 OutOfPlaneBendPotentialEnergy ofPlaneBendPotentialEnergy = energy.getOutOfPlaneBendPotentialEnergy();
567 if (ofPlaneBendPotentialEnergy != null) {
568 energyAndInteractionList[8] = ofPlaneBendPotentialEnergy.getEnergy();
569 energyAndInteractionList[9] = ofPlaneBendPotentialEnergy.getNumberOfOutOfPlaneBends();
570 }
571
572 TorsionPotentialEnergy torsionPotentialEnergy = energy.getTorsionPotentialEnergy();
573 if (torsionPotentialEnergy != null) {
574 energyAndInteractionList[10] = torsionPotentialEnergy.getEnergy();
575 energyAndInteractionList[11] = torsionPotentialEnergy.getNumberOfTorsions();
576 }
577
578 ImproperTorsionPotentialEnergy improperTorsionPotentialEnergy = energy.getImproperTorsionPotentialEnergy();
579 if (improperTorsionPotentialEnergy != null) {
580 energyAndInteractionList[12] = improperTorsionPotentialEnergy.getEnergy();
581 energyAndInteractionList[13] = improperTorsionPotentialEnergy.getNumberOfImproperTorsions();
582 }
583
584 PiOrbitalTorsionPotentialEnergy piOrbitalTorsionPotentialEnergy = energy.getPiOrbitalTorsionPotentialEnergy();
585 if (piOrbitalTorsionPotentialEnergy != null) {
586 energyAndInteractionList[14] = piOrbitalTorsionPotentialEnergy.getEnergy();
587 energyAndInteractionList[15] = piOrbitalTorsionPotentialEnergy.getNumberOfPiOrbitalTorsions();
588 }
589
590 TorsionTorsionPotentialEnergy torsionTorsionPotentialEnergy = energy.getTorsionTorsionPotentialEnergy();
591 if (torsionTorsionPotentialEnergy != null) {
592 energyAndInteractionList[16] = torsionTorsionPotentialEnergy.getEnergy();
593 energyAndInteractionList[17] = torsionTorsionPotentialEnergy.getNumberOfTorsionTorsions();
594 }
595
596 energyAndInteractionList[18] = energy.getVanDerWaalsEnergy();
597 energyAndInteractionList[19] = energy.getVanDerWaalsInteractions();
598
599 energyAndInteractionList[20] = energy.getPermanentMultipoleEnergy();
600 energyAndInteractionList[21] = energy.getPermanentInteractions();
601
602 energyAndInteractionList[22] = energy.getPolarizationEnergy();
603 energyAndInteractionList[23] = energy.getPermanentInteractions();
604
605 energyAndInteractionList[24] = energy.getEsvBiasEnergy();
606
607 energyAndInteractionList[25] = energy.getTotalEnergy();
608 endstateEnergyMap.put(lambdaList, energyAndInteractionList);
609 }
610
611 @Override
612 public List<Potential> getPotentials() {
613 List<Potential> potentials;
614 if (energy == null) {
615 potentials = Collections.emptyList();
616 } else {
617 potentials = Collections.singletonList(energy);
618 }
619 return potentials;
620 }
621 }