1 // ******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2024.
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 static ffx.crystal.SymOp.applyFracSymOp;
41 import static ffx.potential.bonded.RotamerLibrary.applyRotamer;
42
43 import ffx.crystal.Crystal;
44 import ffx.crystal.SymOp;
45 import ffx.potential.bonded.Atom;
46 import ffx.potential.bonded.Residue;
47 import ffx.potential.bonded.ResidueState;
48 import ffx.potential.bonded.Rotamer;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Comparator;
52 import java.util.List;
53
54 /**
55 * Contains a cell used for box optimization, its residues, the fractional coordinates within a
56 * crystal it takes up, its overall (linear) index, and its indices along the a, b, and c crystal
57 * axes.
58 */
59 public class BoxOptCell {
60
61 // fracCoords indexed by 1-3 min x,y,z, 4-6 are max x,y,z
62 private final double[] fracCoords = new double[6];
63 private final int[] indexXYZ = new int[3];
64 private final int linearIndex;
65 private final ArrayList<Residue> residues = new ArrayList<>();
66
67 /**
68 * Constructs a BoxOptCell object, which takes up a set of fractional coordinates within the
69 * Crystal, the Residues contained within, and the index of the cell along the crystal's a, b, and
70 * c axes.
71 *
72 * @param fractionalCoordinates Fractional coordinates contained, indexed by 1-3 min x,y,z, 4-6
73 * max x,y,z
74 * @param indices Index of cell along a, b, and c (x, y, and z).
75 * @param linearIndex Index of box in linear box array.
76 */
77 public BoxOptCell(double[] fractionalCoordinates, int[] indices, int linearIndex) {
78 System.arraycopy(fractionalCoordinates, 0, fracCoords, 0, fracCoords.length);
79 System.arraycopy(indices, 0, indexXYZ, 0, indexXYZ.length);
80 this.linearIndex = linearIndex;
81 }
82
83 /**
84 * Add a residue to the box.
85 *
86 * @param residue Residue to be added.
87 */
88 public void addResidue(Residue residue) {
89 residues.add(residue);
90 }
91
92 /**
93 * Checks if any rotamer of a Residue is inside this BoxOptCell.
94 *
95 * @param residue Residue to check.
96 * @param crystal A Crystal.
97 * @param symOp A symmetry operator to apply.
98 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone)
99 * atoms.
100 * @return If contained inside this BoxOptCell.
101 */
102 public boolean anyRotamerInsideCell(Residue residue, Crystal crystal, SymOp symOp,
103 boolean variableOnly) {
104 ResidueState incomingState = residue.storeState();
105 Rotamer[] rotamers = residue.getRotamers();
106 boolean inside = Arrays.stream(rotamers).anyMatch((Rotamer r) -> {
107 applyRotamer(residue, r);
108 return residueInsideCell(residue, crystal, symOp, variableOnly);
109 });
110 residue.revertState(incomingState);
111 return inside;
112 }
113
114 /**
115 * Checks if an Atom would be contained inside this cell.
116 *
117 * @param atom Atom to check.
118 * @param crystal A Crystal.
119 * @param symOp A symmetry operator to apply.
120 * @return If contained.
121 */
122 public boolean atomInsideCell(Atom atom, Crystal crystal, SymOp symOp) {
123 double[] atXYZ = new double[3];
124 atXYZ = atom.getXYZ(atXYZ);
125 crystal.toFractionalCoordinates(atXYZ, atXYZ);
126 moveValuesBetweenZeroAndOne(atXYZ);
127 applyFracSymOp(atXYZ, atXYZ, symOp);
128 return checkIfContained(atXYZ);
129 }
130
131 /**
132 * Moves an array of doubles to be within 0.0 and 1.0 by addition or subtraction of a multiple of
133 * 1.0. Typical use is moving an atom placed outside crystal boundaries from the symmetry mate back
134 * into the crystal.
135 *
136 * @param valuesToMove Doubles to be moved between 0 and 1.
137 */
138 public static void moveValuesBetweenZeroAndOne(double[] valuesToMove) {
139 for (int i = 0; i < valuesToMove.length; i++) {
140 valuesToMove[i] = moveBetweenZeroAndOne(valuesToMove[i]);
141 }
142 }
143
144 /**
145 * Moves a double to be within 0.0 and 1.0 by addition or subtraction of a multiple of 1.0. Typical
146 * use is moving an atom place outside crystal boundaries from the symmetry mate back into the
147 * crystal.
148 *
149 * @param value Double to be moved between 0 and 1.
150 * @return Shifted double.
151 */
152 private static double moveBetweenZeroAndOne(double value) {
153 if (value < 0.0) {
154 int belowZero = (int) (value);
155 belowZero = 1 + (-1 * belowZero);
156 value = value + belowZero;
157 } else {
158 value = value % 1.0;
159 }
160 return value;
161 }
162
163 /**
164 * Returns the linear index of this Box.
165 *
166 * @return Linear index.
167 */
168 public int getLinearIndex() {
169 return linearIndex;
170 }
171
172 /**
173 * Returns an array of the Residues contained within the cell.
174 *
175 * @return Array of Residues.
176 */
177 public Residue[] getResidues() {
178 return (Residue[]) residues.toArray();
179 }
180
181 /**
182 * Returns a copy of the ArrayList of residues.
183 *
184 * @return ArrayList of Residues in the cell.
185 */
186 public ArrayList<Residue> getResiduesAsList() {
187 return new ArrayList<>(residues);
188 }
189
190 /**
191 * Returns the x, y, and z indices of this box.
192 *
193 * @return Box indices.
194 */
195 public int[] getXYZIndex() {
196 int[] returnedIndices = new int[3];
197 System.arraycopy(indexXYZ, 0, returnedIndices, 0, returnedIndices.length);
198 return returnedIndices;
199 }
200
201 /**
202 * Checks if a Residue is inside this BoxOptCell.
203 *
204 * @param residue Residue to check.
205 * @param crystal A Crystal.
206 * @param symOp A symmetry operator to apply.
207 * @param variableOnly If using only variable (protein side-chain, nucleic acid backbone)
208 * atoms.
209 * @return If contained inside this BoxOptCell.
210 */
211 public boolean residueInsideCell(Residue residue, Crystal crystal, SymOp symOp,
212 boolean variableOnly) {
213 List<Atom> atoms = variableOnly ? residue.getVariableAtoms() : residue.getAtomList();
214 return atoms.stream().anyMatch(a -> atomInsideCell(a, crystal, symOp));
215 }
216
217 /** Sorts residues in the box. */
218 public void sortBoxResidues() {
219 Comparator<Residue> comparator = Comparator.comparing(Residue::getChainID)
220 .thenComparingInt(Residue::getResidueNumber);
221 residues.sort(comparator);
222 }
223
224 /**
225 * Check if an atom's fractional coordinates would be contained by the box.
226 *
227 * @param atomFracCoords Atomic fractional coordinates
228 * @return If contained.
229 */
230 private boolean checkIfContained(double[] atomFracCoords) {
231 for (int i = 0; i < 3; i++) {
232 if ((fracCoords[i] > atomFracCoords[i]) || (fracCoords[i + 3] < atomFracCoords[i])) {
233 return false;
234 }
235 }
236 return true;
237 }
238 }