View Javadoc
1   package ffx.algorithms.optimize;
2   
3   import ffx.algorithms.optimize.manybody.ManyBodyCell;
4   import ffx.crystal.Crystal;
5   import ffx.crystal.SymOp;
6   import ffx.potential.bonded.Atom;
7   import ffx.potential.bonded.Residue;
8   import ffx.potential.bonded.ResidueState;
9   
10  import java.io.IOException;
11  import java.util.ArrayList;
12  import java.util.Arrays;
13  import java.util.HashSet;
14  import java.util.List;
15  import java.util.Set;
16  import java.util.logging.Level;
17  import java.util.logging.Logger;
18  
19  import static java.lang.Double.parseDouble;
20  import static java.lang.String.format;
21  
22  class BoxOptimization {
23  
24      private final RotamerOptimization rotamerOptimization;
25      /**
26       * Logger for this class.
27       */
28      private static final Logger logger = Logger.getLogger(RotamerOptimization.class.getName());
29      /**
30       * Number of boxes for box optimization in X, Y, Z.
31       */
32  
33      public final int[] numXYZCells = {3, 3, 3};
34      /**
35       * Box border size.
36       */
37      public double cellBorderSize = 0;
38      /**
39       * Approximate box size.
40       */
41      public double approxBoxLength = 0;
42      /**
43       * Box optimization inclusion criteria.
44       */
45      public int boxInclusionCriterion = 1;
46      /**
47       * Index of the first box to optimize.
48       */
49      public int cellStart = 0;
50      /**
51       * Index of the last box to optimize.
52       */
53      public int cellEnd = -1;
54      /**
55       * Flag to indicate manual definition of a super box.
56       */
57      public boolean manualSuperbox = false;
58      /**
59       * Dimensions of the box.
60       */
61      public double[] boxDimensions;
62      /**
63       * Buffer size for the super box.
64       */
65      public double superboxBuffer = 8.0;
66      /**
67       * Box index loaded during a restart.
68       */
69      public int boxLoadIndex = -1;
70      /**
71       * Box indeces loaded during a restart.
72       */
73      public int[] boxLoadCellIndices;
74      /**
75       * Set center of boxes to titratable residues
76       */
77      public boolean titrationBoxes;
78      /**
79       * Box size around a titratable residue
80       */
81      public double titrationBoxSize;
82  
83      public BoxOptimization(RotamerOptimization rotamerOptimization) {
84          this.rotamerOptimization = rotamerOptimization;
85      }
86  
87      /**
88       * Breaks down a structure into a number of overlapping boxes for optimization.
89       *
90       * @return Potential energy of final structure.
91       */
92      public double boxOptimization(List<Residue> residueList) throws Exception {
93          rotamerOptimization.usingBoxOptimization = true;
94          long beginTime = -System.nanoTime();
95          Residue[] residues = residueList.toArray(new Residue[0]);
96          /*
97           * A new dummy Crystal will be constructed for an aperiodic system. The
98           * purpose is to avoid using the overly large dummy Crystal used for
99           * Ewald purposes. Atoms are not and should not be moved into the dummy
100          * Crystal boundaries; to check if an Atom is inside a cell, use an
101          * array of coordinates adjusted to be 0 < coordinate < 1.0.
102          */
103         Crystal crystal = generateSuperbox(residueList);
104 
105         // Cells indexed by x*(YZ divisions) + y*(Z divisions) + z.
106         int totalCells = getTotalCellCount(crystal); // Also initializes cell count if using -bB
107         if (cellStart > totalCells - 1) {
108             rotamerOptimization.logIfRank0(format(" First cell out of range (%d) -- reset to first cell.", cellStart + 1));
109             cellStart = 0;
110         }
111         if (cellEnd > totalCells - 1) {
112             // Warn the user if the box end was explicitly set incorrectly.
113             if (cellEnd != -1 && cellEnd != Integer.MAX_VALUE) {
114                 rotamerOptimization.logIfRank0(format(" Final cell out of range (%d) -- reset to last cell.", cellEnd + 1));
115             }
116             cellEnd = totalCells - 1;
117         } else if (cellEnd < 0) {
118             cellEnd = totalCells - 1;
119         } else if(!crystal.aperiodic()){
120             cellEnd = totalCells - 1;
121         }
122         ManyBodyCell[] cells;
123         if(titrationBoxes){
124              cells = loadTitrationCells(crystal, residues);
125         } else {
126              cells = loadCells(crystal, residues);
127         }
128         int numCells = cells.length;
129         rotamerOptimization.logIfRank0(format(" Optimizing cells %d to %d", (cellStart + 1), (cellEnd + 1)));
130         for (int i = 0; i < numCells; i++) {
131             ManyBodyCell manyBodyCell = cells[i];
132             List<Residue> residueSubsetList = manyBodyCell.getResiduesAsList();
133             int[] cellIndices = manyBodyCell.getABCIndices();
134             rotamerOptimization.logIfRank0(format("\n Iteration %d of cell based optimization.", (i + 1)));
135             rotamerOptimization.logIfRank0(manyBodyCell.toString());
136             int nResidueSubset = residueSubsetList.size();
137             if (nResidueSubset > 0) {
138                 if (rotamerOptimization.rank0 && rotamerOptimization.writeEnergyRestart && rotamerOptimization.printFiles) {
139                     String boxHeader = format(" Box %d: %d,%d,%d", i + 1, cellIndices[0], cellIndices[1], cellIndices[2]);
140                     try {
141                         rotamerOptimization.energyWriter.append(boxHeader);
142                         rotamerOptimization.energyWriter.newLine();
143                     } catch (IOException ex) {
144                         logger.log(Level.SEVERE, " Exception writing box header to energy restart file.", ex);
145                     }
146                 }
147                 if (rotamerOptimization.loadEnergyRestart) {
148                     boxLoadIndex = i + 1;
149                     boxLoadCellIndices = new int[3];
150                     boxLoadCellIndices[0] = cellIndices[0];
151                     boxLoadCellIndices[1] = cellIndices[1];
152                     boxLoadCellIndices[2] = cellIndices[2];
153                 }
154 
155                 long boxTime = -System.nanoTime();
156                 Residue firstResidue = residueSubsetList.getFirst();
157                 Residue lastResidue = residueSubsetList.get(nResidueSubset - 1);
158                 Residue[] residueSubsetArray = new Residue[residueSubsetList.size()];
159                 residueSubsetList.toArray(residueSubsetArray);
160                 if (rotamerOptimization.revert) {
161                     ResidueState[] coordinates = ResidueState.storeAllCoordinates(residueSubsetList);
162                     double startingEnergy = 0;
163                     double finalEnergy = 0;
164                     try {
165                         startingEnergy = rotamerOptimization.currentEnergy(residueSubsetArray);
166                     } catch (ArithmeticException ex) {
167                         logger.severe(format(" Exception %s in calculating starting energy of a box; FFX shutting down", ex));
168                     }
169                     rotamerOptimization.globalOptimization(residueSubsetList);
170                     try {
171                         finalEnergy = rotamerOptimization.currentEnergy(residueSubsetArray);
172                     } catch (ArithmeticException ex) {
173                         logger.severe(format(" Exception %s in calculating starting energy of a box; FFX shutting down", ex));
174                     }
175                     if (startingEnergy <= finalEnergy) {
176                         logger.info(
177                                 "Optimization did not yield a better energy. Reverting to original coordinates.");
178                         ResidueState.revertAllCoordinates(residueSubsetList, coordinates);
179                     } else {
180                         // Copy sliding window optimal rotamers into the overall optimum array.
181                         int r = 0;
182                         for (Residue residue : residueSubsetList) {
183                             int index = residueList.indexOf(residue);
184                             rotamerOptimization.optimum[index] = rotamerOptimization.optimumSubset[r++];
185                         }
186                     }
187                     long currentTime = System.nanoTime();
188                     boxTime += currentTime;
189                     if(rotamerOptimization.genZ){
190                         int[] currentRotamers = new int[rotamerOptimization.optimumSubset.length];
191                         rotamerOptimization.getFractions(residueSubsetArray,0,currentRotamers, true);
192                         if(titrationBoxes){
193                             int currentResidueNum = manyBodyCell.getABCIndices()[0] + 1;
194                             for(Residue residue: residueSubsetList){
195                                 if(residue.getResidueNumber() == currentResidueNum){
196                                     Residue[] titratationResidue = new Residue[]{residue};
197                                     rotamerOptimization.getProtonationPopulations(titratationResidue);
198                                 }
199                             }
200                         } else {
201                             rotamerOptimization.getProtonationPopulations(residueSubsetArray);
202                         }
203                     }
204                     rotamerOptimization.logIfRank0(format(" Time elapsed for this iteration: %11.3f sec", boxTime * 1.0E-9));
205                     rotamerOptimization.logIfRank0(format(" Overall time elapsed: %11.3f sec", (currentTime + beginTime) * 1.0E-9));
206                 } else {
207                     rotamerOptimization.globalOptimization(residueSubsetList);
208                     // Copy sliding window optimal rotamers into the overall optimum array.
209                     int r = 0;
210                     for (Residue residue : residueSubsetList) {
211                         int index = residueList.indexOf(residue);
212                         rotamerOptimization.optimum[index] = rotamerOptimization.optimumSubset[r++];
213                     }
214                     long currentTime = System.nanoTime();
215                     boxTime += currentTime;
216                     if(rotamerOptimization.genZ){
217                         int[] currentRotamers = new int[rotamerOptimization.optimumSubset.length];
218                         rotamerOptimization.getFractions(residueSubsetArray,0,currentRotamers, true);
219                         if(titrationBoxes){
220                            int currentResidueNum = manyBodyCell.getABCIndices()[0] + 1;
221                            for(Residue residue: residueSubsetList){
222                                if(residue.getResidueNumber() == currentResidueNum){
223                                   Residue[] titratationResidue = new Residue[]{residue};
224                                   rotamerOptimization.getProtonationPopulations(titratationResidue);
225                                }
226                            }
227                         } else {
228                            rotamerOptimization.getProtonationPopulations(residueSubsetArray);
229                         }
230 
231                     }
232                     rotamerOptimization.logIfRank0(format(" Time elapsed for this iteration: %11.3f sec", boxTime * 1.0E-9));
233                     rotamerOptimization.logIfRank0(format(" Overall time elapsed: %11.3f sec", (currentTime + beginTime) * 1.0E-9));
234                 }
235                 if (rotamerOptimization.rank0 && rotamerOptimization.printFiles) {
236                     // Don't write a file if it's the final iteration.
237                     if (i == (numCells - 1)) {
238                         continue;
239                     }
240                     try {
241                         if (firstResidue != lastResidue) {
242                             rotamerOptimization.logIfRank0(format(" File with residues %s ... %s in window written.", firstResidue, lastResidue));
243                         } else {
244                             rotamerOptimization.logIfRank0(format(" File with residue %s in window written.", firstResidue));
245                         }
246                     } catch (Exception e) {
247                         logger.warning("Exception writing to file.");
248                     }
249                 }
250             } else {
251                 rotamerOptimization.logIfRank0(" Empty box: no residues found.");
252             }
253         }
254         return 0.0;
255     }
256 
257     public void update(String boxDim) {
258         // String should be in format (buffer,xmin,xmax,ymin,ymax,zmin,zmax)
259         try {
260             String[] bdTokens = boxDim.split(",+");
261             boxDimensions = new double[6];
262             if (bdTokens.length != 7) {
263                 logger.warning(" Improper number of arguments to boxDimensions; default settings used.");
264             } else {
265                 for (int i = 1; i < 7; i += 2) {
266                     boxDimensions[i - 1] = parseDouble(bdTokens[i]);
267                     boxDimensions[i] = parseDouble(bdTokens[i + 1]);
268                     if (boxDimensions[i] < boxDimensions[i - 1]) {
269                         logger.info(format(" Improper dimension min %8.5f > max %8.5f; max/min reversed.",
270                                 boxDimensions[i - 1], boxDimensions[i]));
271                         double temp = boxDimensions[i];
272                         boxDimensions[i] = boxDimensions[i - 1];
273                         boxDimensions[i - 1] = temp;
274                     }
275                 }
276                 superboxBuffer = parseDouble(bdTokens[0]);
277                 manualSuperbox = true;
278             }
279         } catch (Exception ex) {
280             logger.warning(
281                     format(" Error in parsing box dimensions: input discarded and defaults used: %s.", ex));
282             manualSuperbox = false;
283         }
284     }
285 
286     private Crystal generateSuperbox(List<Residue> residueList) {
287         double[] maxXYZ = new double[3];
288         double[] minXYZ = new double[3];
289         Crystal originalCrystal = rotamerOptimization.molecularAssembly.getCrystal();
290         if (manualSuperbox) {
291             for (int i = 0; i < maxXYZ.length; i++) {
292                 int ii = 2 * i;
293                 minXYZ[i] = boxDimensions[ii] - superboxBuffer;
294                 maxXYZ[i] = boxDimensions[ii + 1] + superboxBuffer;
295             }
296         } else if (originalCrystal.aperiodic()) {
297             if (residueList == null || residueList.isEmpty()) {
298                 throw new IllegalArgumentException(
299                         " Null or empty residue list when generating superbox.");
300             }
301             Atom initializerAtom = residueList.get(0).getReferenceAtom();
302             initializerAtom.getXYZ(minXYZ);
303             initializerAtom.getXYZ(maxXYZ);
304             for (Residue residue : residueList) {
305                 Atom refAtom = residue.getReferenceAtom();
306                 double[] refAtomCoords = new double[3];
307                 refAtom.getXYZ(refAtomCoords);
308                 for (int i = 0; i < 3; i++) {
309                     maxXYZ[i] = Math.max(refAtomCoords[i], maxXYZ[i]);
310                     minXYZ[i] = Math.min(refAtomCoords[i], minXYZ[i]);
311                 }
312             }
313             for (int i = 0; i < 3; i++) {
314                 minXYZ[i] -= superboxBuffer;
315                 maxXYZ[i] += superboxBuffer;
316             }
317         } else {
318             return originalCrystal.getUnitCell();
319         }
320         double newA = maxXYZ[0] - minXYZ[0];
321         double newB = maxXYZ[1] - minXYZ[1];
322         double newC = maxXYZ[2] - minXYZ[2];
323         if (manualSuperbox) {
324             logger.info(format(" Manual superbox set over (minX, maxX, minY, "
325                             + "maxY, minZ, maxZ): %f, %f, %f, %f, %f, %f", minXYZ[0], maxXYZ[0], minXYZ[1],
326                     maxXYZ[1], minXYZ[2], maxXYZ[2]));
327         } else { // Crystal systems will have already returned.
328             logger.info(
329                     " System is aperiodic: protein box generated over these coordinates (minX, maxX, minY, maxY, minZ, maxZ):");
330             String message = " Aperiodic box dimensions: ";
331             for (int i = 0; i < minXYZ.length; i++) {
332                 message = message.concat(format("%f,%f,", minXYZ[i], maxXYZ[i]));
333             }
334             message = message.substring(0, message.length() - 1);
335             logger.info(message);
336         }
337         logger.info(format(" Buffer size (included in dimensions): %f\n", superboxBuffer));
338         return new Crystal(newA, newB, newC, 90.0, 90.0, 90.0, "P1");
339     }
340 
341     /**
342      * Returns the number of cells (boxes) for box optimization; if the -bB flag is set, sets the
343      * final number of cells.
344      *
345      * @param crystal Crystal or dummy crystal being used to define boxes.
346      * @return Total number of cells.
347      */
348     private int getTotalCellCount(Crystal crystal) {
349         int numCells = 1;
350         if (approxBoxLength > 0) {
351             double[] boxes = new double[3];
352             boxes[0] = crystal.a / approxBoxLength;
353             boxes[1] = crystal.b / approxBoxLength;
354             boxes[2] = crystal.c / approxBoxLength;
355             for (int i = 0; i < boxes.length; i++) {
356                 if (boxes[i] < 1) {
357                     numXYZCells[i] = 1;
358                 } else {
359                     numXYZCells[i] = (int) boxes[i];
360                 }
361             }
362         }
363         for (int numXYZBox : numXYZCells) {
364             numCells *= numXYZBox;
365         }
366         return numCells;
367     }
368 
369     /**
370      * Creates and fills cells (boxes) for box optimization.
371      *
372      * @param crystal  Polymer crystal or dummy crystal
373      * @param residues All residues to be optimized
374      * @return Filled cells.
375      */
376     @SuppressWarnings("fallthrough")
377     private ManyBodyCell[] loadCells(Crystal crystal, Residue[] residues) {
378         double aCellBorderFracSize = (cellBorderSize / crystal.a);
379         double bCellBorderFracSize = (cellBorderSize / crystal.b);
380         double cCellBorderFracSize = (cellBorderSize / crystal.c);
381         int numCells = cellEnd - cellStart + 1;
382         rotamerOptimization.logIfRank0(format(" Number of fractional cells: %d = %d x %d x %d",
383                 numCells, numXYZCells[0], numXYZCells[1], numXYZCells[2]));
384 
385         ManyBodyCell[] cells = new ManyBodyCell[numCells];
386         int currentIndex = 0;
387         int filledCells = 0;
388         int[] xyzIndices = new int[3];
389         boolean doBreak = false; // Breaks the ijk loop if the last box passed.
390         /*
391          * Initializes coordinates for all the boxes, indexed linearly along z,
392          * then y, then x (so the box with xyz indices 2,3,2 in a crystal with
393          * 4, 5, and 3 boxes along xyz would be indexed 2*5*3 + 3*3 + 2 = 41).
394          * The int[] indices stores separate x, y, and z indices.
395          */
396         for (int i = 0; i < numXYZCells[0]; i++) {
397             if (doBreak) {
398                 break;
399             }
400             xyzIndices[0] = i;
401             for (int j = 0; j < numXYZCells[1]; j++) {
402                 if (doBreak) {
403                     break;
404                 }
405                 xyzIndices[1] = j;
406                 for (int k = 0; k < numXYZCells[2]; k++) {
407                     if (currentIndex < cellStart) {
408                         ++currentIndex;
409                         continue;
410                     } else if (currentIndex > cellEnd) {
411                         doBreak = true;
412                         break;
413                     }
414                     xyzIndices[2] = k;
415                     double[] fracCoords = new double[6];
416                     fracCoords[0] = (((1.0 * i) / numXYZCells[0]) - aCellBorderFracSize);
417                     fracCoords[1] = (((1.0 * j) / numXYZCells[1]) - bCellBorderFracSize);
418                     fracCoords[2] = (((1.0 * k) / numXYZCells[2]) - cCellBorderFracSize);
419                     fracCoords[3] = (((1.0 + i) / numXYZCells[0]) + aCellBorderFracSize);
420                     fracCoords[4] = (((1.0 + j) / numXYZCells[1]) + bCellBorderFracSize);
421                     fracCoords[5] = (((1.0 + k) / numXYZCells[2]) + cCellBorderFracSize);
422                     cells[filledCells++] = new ManyBodyCell(fracCoords, xyzIndices, currentIndex);
423                     ++currentIndex;
424 
425                 }
426             }
427         }
428         assignResiduesToCells(crystal, residues, cells);
429         for (ManyBodyCell cell : cells) {
430             cell.sortCellResidues();
431         }
432         switch (rotamerOptimization.direction) {
433             case BACKWARD:
434                 ManyBodyCell[] tempCells = new ManyBodyCell[numCells];
435                 for (int i = 0; i < numCells; i++) {
436                     tempCells[i] = cells[numCells - (i + 1)];
437                 }
438                 cells = tempCells;
439                 // Fall through into forward case (for now).
440             case FORWARD:
441             default:
442                 return cells;
443         }
444     }
445 
446     /**
447      * Creates and fills cells (boxes) for box optimization.
448      *
449      * @param crystal  Polymer crystal or dummy crystal
450      * @param residues All residues to be optimized
451      * @return Filled cells.
452      */
453     @SuppressWarnings("fallthrough")
454     private ManyBodyCell[] loadTitrationCells(Crystal crystal, Residue[] residues) {
455         String[] titratableResidues = new String[]{"HIS", "HIE", "HID", "GLU", "GLH", "ASP", "ASH", "LYS", "LYD", "CYS", "CYD"};
456         List<String> titratableResiduesList = Arrays.asList(titratableResidues);
457         List<Residue> centerResidues = new ArrayList<>();
458         for(Residue residue : residues){
459             if (titratableResiduesList.contains(residue.getName())){
460                 logger.info(" Adding residue: " + residue.getName() + residue.getResidueNumber() + " to center residue list");
461                 centerResidues.add(residue);
462             }
463         }
464 
465         int numCells = centerResidues.size();
466         ManyBodyCell[] cells = new ManyBodyCell[numCells];
467         int currentIndex = 0;
468         int filledCells = 0;
469         int[] xyzIndices;
470 
471         boolean doBreak = false; // Breaks the ijk loop if the last box passed.
472         for (Residue centerResidue : centerResidues) {
473             double[] center = new double[3];
474             center = centerResidue.getAtomByName("CA", true).getXYZ(center);
475             double[] fracCenter = new double[3];
476             crystal.toFractionalCoordinates(center, fracCenter);
477             double[] fracCoords = new double[6];
478             fracCoords[0] = (fracCenter[0]) - (titrationBoxSize)/crystal.a;
479             fracCoords[1] = (fracCenter[1]) - (titrationBoxSize)/crystal.b;
480             fracCoords[2] = (fracCenter[2]) - (titrationBoxSize)/crystal.c;
481             fracCoords[3] = (fracCenter[0]) + (titrationBoxSize)/crystal.a;
482             fracCoords[4] = (fracCenter[1]) + (titrationBoxSize)/crystal.b;
483             fracCoords[5] = (fracCenter[2]) + (titrationBoxSize)/crystal.c;
484             currentIndex = centerResidues.indexOf(centerResidue);
485             xyzIndices = new int[]{centerResidue.getResidueNumber()-1, centerResidue.getResidueNumber()-1, centerResidue.getResidueNumber()-1};
486             cells[filledCells++] = new ManyBodyCell(fracCoords, xyzIndices, currentIndex);
487         }
488         assignResiduesToCells(crystal, residues, cells);
489         for (ManyBodyCell cell : cells) {
490             cell.sortCellResidues();
491         }
492         switch (rotamerOptimization.direction) {
493             case BACKWARD:
494                 ManyBodyCell[] tempCells = new ManyBodyCell[numCells];
495                 for (int i = 0; i < numCells; i++) {
496                     tempCells[i] = cells[numCells - (i + 1)];
497                 }
498                 cells = tempCells;
499                 // Fall through into forward case (for now).
500             case FORWARD:
501             default:
502                 return cells;
503         }
504     }
505 
506     /**
507      * Constructs the cells for box optimization and assigns them residues, presently based on C
508      * alpha fractional coordinates; by default, cells are sorted by global index. Presently,
509      * specifying approxBoxLength over-rides numXYZBoxes, and always rounds the number of boxes down
510      * (to ensure boxes are always at least the specified size).
511      *
512      * @param crystal  Crystal group.
513      * @param residues List of residues to be optimized.
514      * @param cells    BoxOptCell instance.
515      */
516     private void assignResiduesToCells(Crystal crystal, Residue[] residues, ManyBodyCell[] cells) {
517         // Search through residues, add them to all boxes containing their
518         // fractional coordinates.
519         int nSymm = crystal.spaceGroup.getNumberOfSymOps();
520 
521         for (ManyBodyCell cell : cells) {
522             Set<Residue> toAdd = new HashSet<>();
523             for (int iSymm = 0; iSymm < nSymm; iSymm++) {
524                 SymOp symOp = crystal.spaceGroup.getSymOp(iSymm);
525                 for (Residue residue : residues) {
526                     boolean contained;
527                     switch (boxInclusionCriterion) {
528                         default -> contained = cell.atomInsideCell(residue.getReferenceAtom(), crystal, symOp);
529                         case 2 -> contained = cell.residueInsideCell(residue, crystal, symOp, true);
530                         case 3 -> contained = cell.anyRotamerInsideCell(residue, crystal, symOp, true);
531                     }
532                     if (contained) {
533                         toAdd.add(residue);
534                     }
535                 }
536                 // If the identity symop produces nothing, skip checking other symops.
537                 if (toAdd.isEmpty()) {
538                     break;
539                 }
540             }
541             toAdd.forEach(cell::addResidue);
542         }
543     }
544 
545     public void setTitrationBoxSize(double titrationBoxSize){this.titrationBoxSize = titrationBoxSize;}
546 }