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-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 }