1 // ****************************************************************************** 2 // 3 // Title: Force Field X. 4 // Description: Force Field X - Software for Molecular Biophysics. 5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025. 6 // 7 // This file is part of Force Field X. 8 // 9 // Force Field X is free software; you can redistribute it and/or modify it 10 // under the terms of the GNU General Public License version 3 as published by 11 // the Free Software Foundation. 12 // 13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT 14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 // details. 17 // 18 // You should have received a copy of the GNU General Public License along with 19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple 20 // Place, Suite 330, Boston, MA 02111-1307 USA 21 // 22 // Linking this library statically or dynamically with other modules is making a 23 // combined work based on this library. Thus, the terms and conditions of the 24 // GNU General Public License cover the whole combination. 25 // 26 // As a special exception, the copyright holders of this library give you 27 // permission to link this library with independent modules to produce an 28 // executable, regardless of the license terms of these independent modules, and 29 // to copy and distribute the resulting executable under terms of your choice, 30 // provided that you also meet, for each linked independent module, the terms 31 // and conditions of the license of that module. An independent module is a 32 // module which is not derived from or based on this library. If you modify this 33 // library, you may extend this exception to your version of the library, but 34 // you are not obligated to do so. If you do not wish to do so, delete this 35 // exception statement from your version. 36 // 37 // ****************************************************************************** 38 package ffx.algorithms.optimize.manybody; 39 40 import ffx.crystal.Crystal; 41 import ffx.crystal.SymOp; 42 import ffx.potential.bonded.Atom; 43 import ffx.potential.bonded.Residue; 44 import ffx.potential.bonded.ResidueState; 45 import ffx.potential.bonded.Rotamer; 46 import ffx.potential.nonbonded.NeighborList; 47 import ffx.utilities.StringOutputStream; 48 49 import java.util.*; 50 51 import static ffx.crystal.SymOp.applyFracSymOp; 52 import static ffx.potential.bonded.RotamerLibrary.applyRotamer; 53 import static java.lang.String.format; 54 import static java.lang.System.arraycopy; 55 56 /** 57 * A cell used for optimization of a subdomain, its residues, its extent in fractional coordinates, 58 * its overall (linear) index, and its indices along the a, b, and c axes. 59 */ 60 public class ManyBodyCell { 61 62 /** 63 * Cell extent in fractional coordinates. 64 * fracCoords[0-2] contains min a, b and c 65 * fracCoords[3-5] contains max a, b and c 66 */ 67 private final double[] fracCoords = new double[6]; 68 /** 69 * The a, b, and c indices of this cell. 70 */ 71 private final int[] indices = new int[3]; 72 /** 73 * The index of this cell. 74 */ 75 private final int cellIndex; 76 /** 77 * The Residues contained within this cell. 78 */ 79 private final ArrayList<Residue> residues = new ArrayList<>(); 80 81 /** 82 * Constructs a ManyBodyCell instance, which takes up a set of fractional coordinates within the 83 * Crystal, the Residues contained within, and the index of the cell along the crystal's a, b, and 84 * c axes. 85 * 86 * @param fractionalCoordinates Extent in fractional coordinates (0-2 are min a,b,c and 3-5 are max). 87 * @param indices Index of the cell along a, b, and c axes. 88 * @param cellIndex Index of the cell in linear array. 89 */ 90 public ManyBodyCell(double[] fractionalCoordinates, int[] indices, int cellIndex) { 91 arraycopy(fractionalCoordinates, 0, fracCoords, 0, fracCoords.length); 92 arraycopy(indices, 0, this.indices, 0, this.indices.length); 93 this.cellIndex = cellIndex; 94 } 95 96 /** 97 * Returns a string representation of this BoxOptCell. 98 * 99 * @return String representation. 100 */ 101 public String toString() { 102 StringBuilder sb = new StringBuilder(); 103 sb.append(format(" Optimization Cell %2d (%2d, %2d, %2d)\n", cellIndex + 1, indices[0] + 1, indices[1] + 1, indices[2] + 1)); 104 int n = residues.size(); 105 if (n == 0) { 106 sb.append(" Cell is Empty\n"); 107 } else if (n == 1) { 108 sb.append(format(" Single Residue: %s\n", residues.getFirst())); 109 } else { 110 Residue firstResidue = residues.getFirst(); 111 Residue lastResidue = residues.getLast(); 112 sb.append(format(" %d Residues: %s ... %s\n", n, firstResidue.toString(), lastResidue.toString())); 113 } 114 sb.append(" Fractional Coordinates:\n"); 115 sb.append(format(" A-axis %5.3f to %5.3f\n", fracCoords[0], fracCoords[3])); 116 sb.append(format(" B-axis %5.3f to %5.3f\n", fracCoords[1], fracCoords[4])); 117 sb.append(format(" C-axis %5.3f to %5.3f", fracCoords[2], fracCoords[5])); 118 return sb.toString(); 119 } 120 121 /** 122 * Add a residue to the box. 123 * 124 * @param residue Residue to be added. 125 */ 126 public void addResidue(Residue residue) { 127 residues.add(residue); 128 } 129 130 /** 131 * Checks if any rotamer of a Residue is inside this BoxOptCell. 132 * 133 * @param residue Residue to check. 134 * @param crystal A Crystal. 135 * @param symOp A symmetry operator to apply. 136 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone) atoms. 137 * @return If contained inside this BoxOptCell. 138 */ 139 public boolean anyRotamerInsideCell(Residue residue, Crystal crystal, SymOp symOp, boolean variableOnly) { 140 ResidueState incomingState = residue.storeState(); 141 Rotamer[] rotamers = residue.getRotamers(); 142 boolean inside = Arrays.stream(rotamers).anyMatch((Rotamer r) -> { 143 applyRotamer(residue, r); 144 return residueInsideCell(residue, crystal, symOp, variableOnly); 145 }); 146 residue.revertState(incomingState); 147 return inside; 148 } 149 150 /** 151 * Checks if an Atom would be contained inside this cell. 152 * 153 * @param atom Atom to check. 154 * @param crystal A Crystal. 155 * @param symOp A symmetry operator to apply. 156 * @return If contained. 157 */ 158 public boolean atomInsideCell(Atom atom, Crystal crystal, SymOp symOp) { 159 double[] atXYZ = new double[3]; 160 atXYZ = atom.getXYZ(atXYZ); 161 crystal.toFractionalCoordinates(atXYZ, atXYZ); 162 applyFracSymOp(atXYZ, atXYZ, symOp); 163 moveValuesBetweenZeroAndOne(atXYZ); 164 return checkIfContained(atXYZ); 165 } 166 167 /** 168 * Moves an array of doubles to be within 0.0 and 1.0 by addition or subtraction of a multiple of 169 * 1.0. Typical use is moving an atom placed outside crystal boundaries from the symmetry mate back 170 * into the crystal. 171 * 172 * @param valuesToMove Doubles to be moved between 0 and 1. 173 */ 174 public static void moveValuesBetweenZeroAndOne(double[] valuesToMove) { 175 for (int i = 0; i < valuesToMove.length; i++) { 176 valuesToMove[i] = moveBetweenZeroAndOne(valuesToMove[i]); 177 } 178 } 179 180 /** 181 * Moves a double to be within 0.0 and 1.0 by addition or subtraction of a multiple of 1.0. Typical 182 * use is moving an atom within unit cell boundaries. 183 * 184 * @param value Double to be moved between 0 and 1. 185 * @return Shifted double. 186 */ 187 private static double moveBetweenZeroAndOne(double value) { 188 if (value < 0.0) { 189 int belowZero = (int) (value); 190 belowZero = 1 + (-1 * belowZero); 191 value = value + belowZero; 192 } else { 193 value = value % 1.0; 194 } 195 return value; 196 } 197 198 /** 199 * Returns the linear index of this Box. 200 * 201 * @return Linear index. 202 */ 203 public int getCellIndex() { 204 return cellIndex; 205 } 206 207 /** 208 * Returns a copy of the ArrayList of residues. 209 * 210 * @return ArrayList of Residues in the cell. 211 */ 212 public ArrayList<Residue> getResiduesAsList() { 213 return new ArrayList<>(residues); 214 } 215 216 /** 217 * Returns the a, b, and c axis indices of this cell. 218 * 219 * @return Cell indices. 220 */ 221 public int[] getABCIndices() { 222 int[] returnedIndices = new int[3]; 223 arraycopy(indices, 0, returnedIndices, 0, returnedIndices.length); 224 return returnedIndices; 225 } 226 227 /** 228 * Checks if a Residue is inside this BoxOptCell. 229 * 230 * @param residue Residue to check. 231 * @param crystal A Crystal. 232 * @param symOp A symmetry operator to apply. 233 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone) atoms. 234 * @return If contained inside this BoxOptCell. 235 */ 236 public boolean residueInsideCell(Residue residue, Crystal crystal, SymOp symOp, boolean variableOnly) { 237 List<Atom> atoms = variableOnly ? residue.getVariableAtoms() : residue.getAtomList(); 238 return atoms.stream().anyMatch(a -> atomInsideCell(a, crystal, symOp)); 239 } 240 241 /** 242 * Sorts residues in the box. 243 */ 244 public void sortCellResidues() { 245 Comparator<Residue> comparator = Comparator.comparing(Residue::getChainID) 246 .thenComparingInt(Residue::getResidueNumber); 247 residues.sort(comparator); 248 } 249 250 /** 251 * Check if an atom's fractional coordinates would be contained by the box. 252 * 253 * @param atomFracCoords Atomic fractional coordinates 254 * @return If contained. 255 */ 256 private boolean checkIfContained(double[] atomFracCoords) { 257 for (int i = 0; i < 3; i++) { 258 if (atomFracCoords[i] < fracCoords[i] || atomFracCoords[i] > fracCoords[i + 3]) { 259 // fracCoords[0-2] contain min a, b, and c-axes. 260 // fracCoords[3-5] contain max a, b, and c-axes. 261 return false; 262 } 263 } 264 return true; 265 } 266 }