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.potential.utils;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.potential.bonded.Atom;
42  import ffx.potential.bonded.Polymer;
43  import ffx.potential.bonded.Residue;
44  
45  import java.util.ArrayList;
46  import java.util.List;
47  import java.util.Random;
48  import java.util.logging.Logger;
49  
50  import static java.lang.String.format;
51  import static java.lang.System.arraycopy;
52  
53  /**
54   * Loop class.
55   *
56   * @author Mallory R. Tollefson
57   * @since 1.0
58   */
59  public class Loop {
60  
61    private static final Logger logger = Logger.getLogger(Loop.class.getName());
62    private final LoopClosure loopClosure;
63    private final MolecularAssembly molecularAssembly;
64    private final Random random = new Random();
65    private static final int MAX_SOLUTIONS = 16;
66    private final double[][] rN = new double[3][3];
67    private final double[][] rA = new double[3][3];
68    private final double[][] rC = new double[3][3];
69    private double[][] altCoords;
70    private boolean useAltCoords = false;
71  
72    /**
73     * Constructor for Loop.
74     *
75     * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
76     * @param firstResidue a int.
77     * @param endResidue a int.
78     */
79    public Loop(MolecularAssembly molecularAssembly, int firstResidue, int endResidue) {
80      loopClosure = new LoopClosure();
81      this.molecularAssembly = molecularAssembly;
82      generateLoops(firstResidue, endResidue);
83    }
84  
85    /**
86     * Constructor for Loop.
87     *
88     * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
89     */
90    public Loop(MolecularAssembly molecularAssembly) {
91      loopClosure = new LoopClosure();
92      this.molecularAssembly = molecularAssembly;
93      this.altCoords = new double[molecularAssembly.getAtomArray().length][3];
94    }
95  
96    /**
97     * generateLoops.
98     *
99     * @param firstResidue a int.
100    * @param endResidue a int.
101    * @param coordinates an array of {@link double} objects.
102    * @return a {@link java.util.List} object.
103    */
104   public List<double[]> generateLoops(int firstResidue, int endResidue, double[] coordinates) {
105     setAltCoordinates(coordinates);
106     return generateLoops(firstResidue, endResidue);
107   }
108 
109   /**
110    * generateLoops.
111    *
112    * @param firstResidue a int.
113    * @param endResidue a int.
114    * @return a {@link java.util.List} object.
115    */
116   public List<double[]> generateLoops(int firstResidue, int endResidue) {
117     List<Atom> backBoneAtoms = molecularAssembly.getBackBoneAtoms();
118 
119     List<double[]> solutions = new ArrayList<>();
120     logger.info(format(" First residue:   %d\n", firstResidue));
121     logger.info(format(" Ending residue:  %d\n", endResidue));
122     for (Atom atom : backBoneAtoms) {
123       int resID = atom.getResidueNumber();
124       if (resID > endResidue) {
125         // terminates the collection of atom coordinates
126         break;
127       }
128 
129       if (resID >= firstResidue) {
130         int ir = resID - firstResidue;
131         double[] initArray;
132         if (useAltCoords) {
133           initArray = altCoords[atom.getIndex() - 1];
134         } else {
135           initArray = atom.getXYZ(null);
136         }
137 
138         switch (atom.getAtomType().name) {
139           case "C" -> {
140             // Backbone carbon coordinates are stored in rC[]
141             rC[ir][0] = initArray[0];
142             rC[ir][1] = initArray[1];
143             rC[ir][2] = initArray[2];
144           }
145           case "CA" -> {
146             // Backbone alpha carbon coordinates are stored in rA[]
147             rA[ir][0] = initArray[0];
148             rA[ir][1] = initArray[1];
149             rA[ir][2] = initArray[2];
150           }
151           case "N" -> {
152             // Backbone nitrogen coordinates are stored in rN[]
153             rN[ir][0] = initArray[0];
154             rN[ir][1] = initArray[1];
155             rN[ir][2] = initArray[2];
156           }
157         }
158       }
159     }
160 
161     // Method that solves the 16th degree, 3 peptide polynomial.
162     var rSolnN = new double[MAX_SOLUTIONS][3][3];
163     var rSolnA = new double[MAX_SOLUTIONS][3][3];
164     var rSolnC = new double[MAX_SOLUTIONS][3][3];
165 
166     var numSolutions = loopClosure.solve3PepPoly(rN[0], rA[0], rA[2], rC[2], rSolnN, rSolnA, rSolnC);
167 
168     StringBuilder sb = new StringBuilder();
169     sb.append(format(" First residue:                %d\n", firstResidue));
170     sb.append(format(" Ending residue:               %d\n", endResidue));
171     sb.append(format(" Number of solutions:          %d\n", numSolutions));
172     logger.info(sb.toString());
173 
174     for (int k = 0; k < numSolutions; k++) {
175       solutions.add(getSolutionCoordinates(k, rSolnN, rSolnA, rSolnC, firstResidue, endResidue));
176     }
177 
178     return solutions;
179   }
180 
181   /**
182    * getRA.
183    *
184    * @return an array of {@link double} objects.
185    */
186   public double[][] getRA() {
187     return rA;
188   }
189 
190   /**
191    * getRC.
192    *
193    * @return an array of {@link double} objects.
194    */
195   public double[][] getRC() {
196     return rC;
197   }
198 
199   /**
200    * getRN.
201    *
202    * @return an array of {@link double} objects.
203    */
204   public double[][] getRN() {
205     return rN;
206   }
207 
208   private double[] getSolutionCoordinates(int k, double[][][] rSolnN, double[][][] rSolnA,
209       double[][][] rSolnC, int startResidue, int endResidue) {
210     for (int i = 0; i < 3; i++) {
211       for (int j = 0; j < 3; j++) {
212         rN[i][j] = rSolnN[k][i][j];
213         rA[i][j] = rSolnA[k][i][j];
214         rC[i][j] = rSolnC[k][i][j];
215       }
216     }
217 
218     Polymer[] newChain = molecularAssembly.getChains();
219 
220     Atom[] atomArray = molecularAssembly.getAtomArray();
221     double[][] coordsArray = new double[atomArray.length][3];
222     if (useAltCoords) {
223       arraycopy(this.altCoords, 0, coordsArray, 0, coordsArray.length);
224     } else {
225       for (int i = 0; i < atomArray.length; i++) {
226         Atom a = atomArray[i];
227         coordsArray[i][0] = a.getX();
228         coordsArray[i][1] = a.getY();
229         coordsArray[i][2] = a.getZ();
230       }
231     }
232 
233     // Update coordinates of solved loop.
234     for (int i = startResidue; i <= endResidue; i++) {
235       Residue newResidue = newChain[0].getResidue(i);
236 
237       // Determine the new position of backbone atoms and get an offset to apply
238       // for other atoms.
239       var backBoneAtoms = newResidue.getBackboneAtoms();
240       var cOffset = new double[3];
241       var nOffset = new double[3];
242       var aOffset = new double[3];
243       for (Atom backBoneAtom : backBoneAtoms) {
244         int index = backBoneAtom.getIndex() - 1;
245         switch (backBoneAtom.getAtomType().name) {
246           case "C" -> {
247             cOffset[0] = rC[i - startResidue][0] - coordsArray[index][0];
248             cOffset[1] = rC[i - startResidue][1] - coordsArray[index][1];
249             cOffset[2] = rC[i - startResidue][2] - coordsArray[index][2];
250             arraycopy(rC[i - startResidue], 0, coordsArray[index], 0, 3);
251           }
252           case "N" -> {
253             nOffset[0] = rN[i - startResidue][0] - coordsArray[index][0];
254             nOffset[1] = rN[i - startResidue][1] - coordsArray[index][1];
255             nOffset[2] = rN[i - startResidue][2] - coordsArray[index][2];
256             arraycopy(rN[i - startResidue], 0, coordsArray[index], 0, 3);
257           }
258           case "CA" -> {
259             aOffset[0] = rA[i - startResidue][0] - coordsArray[index][0];
260             aOffset[1] = rA[i - startResidue][1] - coordsArray[index][1];
261             aOffset[2] = rA[i - startResidue][2] - coordsArray[index][2];
262             arraycopy(rA[i - startResidue], 0, coordsArray[index], 0, 3);
263           }
264           default -> {
265           }
266         }
267       }
268 
269       // Loop through all other backbone atoms and apply the above offsets.
270       for (Atom backBoneAtom : backBoneAtoms) {
271         int index = backBoneAtom.getIndex() - 1;
272         switch (backBoneAtom.getAtomType().name) {
273           case "C", "N", "CA" -> {
274             // Do nothing. The previous loop already set these values.
275           }
276           case "O" -> {
277             coordsArray[index][0] += cOffset[0];
278             coordsArray[index][1] += cOffset[1];
279             coordsArray[index][2] += cOffset[2];
280           }
281           case "H" -> {
282             coordsArray[index][0] += nOffset[0];
283             coordsArray[index][1] += nOffset[1];
284             coordsArray[index][2] += nOffset[2];
285           }
286           default -> {
287             coordsArray[index][0] += aOffset[0];
288             coordsArray[index][1] += aOffset[1];
289             coordsArray[index][2] += aOffset[2];
290           }
291         }
292       }
293 
294       // Update sidechain atoms based on the alpha carbon.
295       for (Atom sideChainAtom : newResidue.getSideChainAtoms()) {
296         int index = sideChainAtom.getIndex() - 1;
297         coordsArray[index][0] += aOffset[0];
298         coordsArray[index][1] += aOffset[1];
299         coordsArray[index][2] += aOffset[2];
300       }
301     }
302 
303     double[] coordsArray1D = new double[atomArray.length * 3];
304     for (int i = 0; i < atomArray.length; i++) {
305       arraycopy(coordsArray[i], 0, coordsArray1D, i * 3, 3);
306     }
307     return coordsArray1D;
308   }
309 
310   private void setAltCoordinates(double[] coordinates) {
311     for (int i = 0; i < coordinates.length / 3; i++) {
312       arraycopy(coordinates, i * 3, this.altCoords[i], 0, 3);
313     }
314 
315     useAltCoords = true;
316   }
317 
318   private double[][] fillCoordsArray(Atom atom, double[][] coordsArray, double[] determinedXYZ) {
319     int XYZIndex = atom.getIndex() - 1;
320     coordsArray[XYZIndex][0] = determinedXYZ[0];
321     coordsArray[XYZIndex][1] = determinedXYZ[1];
322     coordsArray[XYZIndex][2] = determinedXYZ[2];
323     return coordsArray;
324   }
325 }