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