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
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Comparator;
50 import java.util.List;
51
52 import static ffx.crystal.SymOp.applyFracSymOp;
53 import static ffx.potential.bonded.RotamerLibrary.applyRotamer;
54 import static java.lang.String.format;
55 import static java.lang.System.arraycopy;
56
57 /**
58 * A cell used for optimization of a subdomain, its residues, its extent in fractional coordinates,
59 * its overall (linear) index, and its indices along the a, b, and c axes.
60 */
61 public class ManyBodyCell {
62
63 /**
64 * Cell extent in fractional coordinates.
65 * fracCoords[0-2] contains min a, b and c
66 * fracCoords[3-5] contains max a, b and c
67 */
68 private final double[] fracCoords = new double[6];
69 /**
70 * The a, b, and c indices of this cell.
71 */
72 private final int[] indices = new int[3];
73 /**
74 * The index of this cell.
75 */
76 private final int cellIndex;
77 /**
78 * The Residues contained within this cell.
79 */
80 private final ArrayList<Residue> residues = new ArrayList<>();
81
82 /**
83 * Constructs a ManyBodyCell instance, which takes up a set of fractional coordinates within the
84 * Crystal, the Residues contained within, and the index of the cell along the crystal's a, b, and
85 * c axes.
86 *
87 * @param fractionalCoordinates Extent in fractional coordinates (0-2 are min a,b,c and 3-5 are max).
88 * @param indices Index of the cell along a, b, and c axes.
89 * @param cellIndex Index of the cell in linear array.
90 */
91 public ManyBodyCell(double[] fractionalCoordinates, int[] indices, int cellIndex) {
92 arraycopy(fractionalCoordinates, 0, fracCoords, 0, fracCoords.length);
93 arraycopy(indices, 0, this.indices, 0, this.indices.length);
94 this.cellIndex = cellIndex;
95 }
96
97 /**
98 * Returns a string representation of this BoxOptCell.
99 *
100 * @return String representation.
101 */
102 public String toString() {
103 StringBuilder sb = new StringBuilder();
104 sb.append(format(" Optimization Cell %2d (%2d, %2d, %2d)\n", cellIndex + 1, indices[0] + 1, indices[1] + 1, indices[2] + 1));
105 int n = residues.size();
106 if (n == 0) {
107 sb.append(" Cell is Empty\n");
108 } else if (n == 1) {
109 sb.append(format(" Single Residue: %s\n", residues.getFirst()));
110 } else {
111 Residue firstResidue = residues.getFirst();
112 Residue lastResidue = residues.getLast();
113 sb.append(format(" %d Residues: %s ... %s\n", n, firstResidue.toString(), lastResidue.toString()));
114 }
115 sb.append(" Fractional Coordinates:\n");
116 sb.append(format(" A-axis %5.3f to %5.3f\n", fracCoords[0], fracCoords[3]));
117 sb.append(format(" B-axis %5.3f to %5.3f\n", fracCoords[1], fracCoords[4]));
118 sb.append(format(" C-axis %5.3f to %5.3f", fracCoords[2], fracCoords[5]));
119 return sb.toString();
120 }
121
122 /**
123 * Add a residue to the box.
124 *
125 * @param residue Residue to be added.
126 */
127 public void addResidue(Residue residue) {
128 residues.add(residue);
129 }
130
131 /**
132 * Checks if any rotamer of a Residue is inside this BoxOptCell.
133 *
134 * @param residue Residue to check.
135 * @param crystal A Crystal.
136 * @param symOp A symmetry operator to apply.
137 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone) atoms.
138 * @return If contained inside this BoxOptCell.
139 */
140 public boolean anyRotamerInsideCell(Residue residue, Crystal crystal, SymOp symOp, boolean variableOnly) {
141 ResidueState incomingState = residue.storeState();
142 Rotamer[] rotamers = residue.getRotamers();
143 boolean inside = Arrays.stream(rotamers).anyMatch((Rotamer r) -> {
144 applyRotamer(residue, r);
145 return residueInsideCell(residue, crystal, symOp, variableOnly);
146 });
147 residue.revertState(incomingState);
148 return inside;
149 }
150
151 /**
152 * Checks if an Atom would be contained inside this cell.
153 *
154 * @param atom Atom to check.
155 * @param crystal A Crystal.
156 * @param symOp A symmetry operator to apply.
157 * @return If contained.
158 */
159 public boolean atomInsideCell(Atom atom, Crystal crystal, SymOp symOp) {
160 double[] atXYZ = new double[3];
161 atXYZ = atom.getXYZ(atXYZ);
162 crystal.toFractionalCoordinates(atXYZ, atXYZ);
163 applyFracSymOp(atXYZ, atXYZ, symOp);
164 moveValuesBetweenZeroAndOne(atXYZ);
165 return checkIfContained(atXYZ);
166 }
167
168 /**
169 * Moves an array of doubles to be within 0.0 and 1.0 by addition or subtraction of a multiple of
170 * 1.0. Typical use is moving an atom placed outside crystal boundaries from the symmetry mate back
171 * into the crystal.
172 *
173 * @param valuesToMove Doubles to be moved between 0 and 1.
174 */
175 public static void moveValuesBetweenZeroAndOne(double[] valuesToMove) {
176 for (int i = 0; i < valuesToMove.length; i++) {
177 valuesToMove[i] = moveBetweenZeroAndOne(valuesToMove[i]);
178 }
179 }
180
181 /**
182 * Moves a double to be within 0.0 and 1.0 by addition or subtraction of a multiple of 1.0. Typical
183 * use is moving an atom within unit cell boundaries.
184 *
185 * @param value Double to be moved between 0 and 1.
186 * @return Shifted double.
187 */
188 private static double moveBetweenZeroAndOne(double value) {
189 if (value < 0.0) {
190 int belowZero = (int) (value);
191 belowZero = 1 + (-1 * belowZero);
192 value = value + belowZero;
193 } else {
194 value = value % 1.0;
195 }
196 return value;
197 }
198
199 /**
200 * Returns the linear index of this Box.
201 *
202 * @return Linear index.
203 */
204 public int getCellIndex() {
205 return cellIndex;
206 }
207
208 /**
209 * Returns a copy of the ArrayList of residues.
210 *
211 * @return ArrayList of Residues in the cell.
212 */
213 public ArrayList<Residue> getResiduesAsList() {
214 return new ArrayList<>(residues);
215 }
216
217 /**
218 * Returns the a, b, and c axis indices of this cell.
219 *
220 * @return Cell indices.
221 */
222 public int[] getABCIndices() {
223 int[] returnedIndices = new int[3];
224 arraycopy(indices, 0, returnedIndices, 0, returnedIndices.length);
225 return returnedIndices;
226 }
227
228 /**
229 * Checks if a Residue is inside this BoxOptCell.
230 *
231 * @param residue Residue to check.
232 * @param crystal A Crystal.
233 * @param symOp A symmetry operator to apply.
234 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone) atoms.
235 * @return If contained inside this BoxOptCell.
236 */
237 public boolean residueInsideCell(Residue residue, Crystal crystal, SymOp symOp, boolean variableOnly) {
238 List<Atom> atoms = variableOnly ? residue.getVariableAtoms() : residue.getAtomList();
239 return atoms.stream().anyMatch(a -> atomInsideCell(a, crystal, symOp));
240 }
241
242 /**
243 * Sorts residues in the box.
244 */
245 public void sortCellResidues() {
246 Comparator<Residue> comparator = Comparator.comparing(Residue::getChainID)
247 .thenComparingInt(Residue::getResidueNumber);
248 residues.sort(comparator);
249 }
250
251 /**
252 * Check if an atom's fractional coordinates would be contained by the box.
253 *
254 * @param atomFracCoords Atomic fractional coordinates
255 * @return If contained.
256 */
257 private boolean checkIfContained(double[] atomFracCoords) {
258 for (int i = 0; i < 3; i++) {
259 if (atomFracCoords[i] < fracCoords[i] || atomFracCoords[i] > fracCoords[i + 3]) {
260 // fracCoords[0-2] contain min a, b, and c-axes.
261 // fracCoords[3-5] contain max a, b, and c-axes.
262 return false;
263 }
264 }
265 return true;
266 }
267 }