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
27
28 private static final Logger logger = Logger.getLogger(RotamerOptimization.class.getName());
29
30
31
32
33 public final int[] numXYZCells = {3, 3, 3};
34
35
36
37 public double cellBorderSize = 0;
38
39
40
41 public double approxBoxLength = 0;
42
43
44
45 public int boxInclusionCriterion = 1;
46
47
48
49 public int cellStart = 0;
50
51
52
53 public int cellEnd = -1;
54
55
56
57 public boolean manualSuperbox = false;
58
59
60
61 public double[] boxDimensions;
62
63
64
65 public double superboxBuffer = 8.0;
66
67
68
69 public int boxLoadIndex = -1;
70
71
72
73 public int[] boxLoadCellIndices;
74
75
76
77 public boolean titrationBoxes;
78
79
80
81 public double titrationBoxSize;
82
83 public BoxOptimization(RotamerOptimization rotamerOptimization) {
84 this.rotamerOptimization = rotamerOptimization;
85 }
86
87
88
89
90
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
98
99
100
101
102
103 Crystal crystal = generateSuperbox(residueList);
104
105
106 int totalCells = getTotalCellCount(crystal);
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
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
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
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
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
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 {
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
343
344
345
346
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
371
372
373
374
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;
390
391
392
393
394
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
440 case FORWARD:
441 default:
442 return cells;
443 }
444 }
445
446
447
448
449
450
451
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;
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
500 case FORWARD:
501 default:
502 return cells;
503 }
504 }
505
506
507
508
509
510
511
512
513
514
515
516 private void assignResiduesToCells(Crystal crystal, Residue[] residues, ManyBodyCell[] cells) {
517
518
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
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 }