View Javadoc
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 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     moveValuesBetweenZeroAndOne(atXYZ);
164     applyFracSymOp(atXYZ, atXYZ, symOp);
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 }