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
23
24 private static final Logger logger = Logger.getLogger(RotamerOptimization.class.getName());
25
26
27
28
29 public final int[] numXYZCells = {3, 3, 3};
30
31
32
33 public double cellBorderSize = 0;
34
35
36
37 public double approxBoxLength = 0;
38
39
40
41 public int boxInclusionCriterion = 1;
42
43
44
45 public int cellStart = 0;
46
47
48
49 public int cellEnd = -1;
50
51
52
53 public boolean manualSuperbox = false;
54
55
56
57 public double[] boxDimensions;
58
59
60
61 public double superboxBuffer = 8.0;
62
63
64
65 public int boxLoadIndex = -1;
66
67
68
69 public int[] boxLoadCellIndices;
70
71
72
73 public boolean titrationBoxes;
74
75
76
77 public double titrationBoxSize;
78
79 public BoxOptimization(RotamerOptimization rotamerOptimization) {
80 this.rotamerOptimization = rotamerOptimization;
81 }
82
83
84
85
86
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
94
95
96
97
98
99 Crystal crystal = generateSuperbox(residueList);
100
101
102 int totalCells = getTotalCellCount(crystal);
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
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
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
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
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
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 {
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
339
340
341
342
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
367
368
369
370
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;
386
387
388
389
390
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
436 case FORWARD:
437 default:
438 return cells;
439 }
440 }
441
442
443
444
445
446
447
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;
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
496 case FORWARD:
497 default:
498 return cells;
499 }
500 }
501
502
503
504
505
506
507
508
509
510
511
512 private void assignResiduesToCells(Crystal crystal, Residue[] residues, ManyBodyCell[] cells) {
513
514
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
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 }