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.potential.bonded;
39
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.logging.Level;
44 import java.util.logging.Logger;
45
46 import static java.lang.String.format;
47
48 /**
49 * The ResidueState class encodes the current chemical and coordinate state of a Residue,
50 * particularly a MultiResidue, for ease of reverting coordinates. Should not be too difficult to get
51 * it to also store velocities, etc.
52 *
53 * @author Michael J. Schnieders
54 * @author Jacob M. Litman
55 * @since 1.0
56 */
57 public class ResidueState {
58
59 private static final Logger logger = Logger.getLogger(ResidueState.class.getName());
60
61 /**
62 * For a MultiResidue, parent is the MultiResidue, while res is the chemical state to which this
63 * ResidueState belongs. For a regular Residue, both are just the Residue. In general, use res to
64 * store a chemical or other state, and parent to store the Residue to which this belongs.
65 */
66 private final Residue parent;
67
68 private final Residue residue;
69 private final HashMap<Atom, double[]> atomMap;
70 private final Atom[] atoms;
71
72 /**
73 * Constructor for ResidueState.
74 *
75 * @param parent a {@link ffx.potential.bonded.Residue} object.
76 * @param residue a {@link ffx.potential.bonded.Residue} object.
77 */
78 public ResidueState(Residue parent, Residue residue) {
79 this.parent = parent;
80 this.residue = residue;
81
82 List<Atom> atomList = residue.getAtomList();
83 int nAtoms = atomList.size();
84 atomMap = new HashMap<>(nAtoms);
85 atoms = new Atom[nAtoms];
86
87 atomList.toArray(atoms);
88 for (Atom atom : atoms) {
89 double[] atXYZ = new double[3];
90 atom.getXYZ(atXYZ);
91 atomMap.put(atom, atXYZ);
92 }
93 }
94
95 /**
96 * Constructor for ResidueState.
97 *
98 * @param residue a {@link ffx.potential.bonded.Residue} object.
99 */
100 public ResidueState(Residue residue) {
101 this(residue, residue);
102 if (residue instanceof MultiResidue) {
103 throw new IllegalArgumentException(format(
104 " Residue %s is a MultiResidue: this ResidueState has been incorrectly constructed!",
105 residue));
106 }
107 }
108
109 /**
110 * revertAllCoordinates.
111 *
112 * @param residueList a {@link java.util.List} object.
113 * @param states an array of {@link ffx.potential.bonded.ResidueState} objects.
114 */
115 public static void revertAllCoordinates(List<Residue> residueList, ResidueState[] states) {
116 revertAllCoordinates(residueList.toArray(new Residue[0]), states);
117 }
118
119 /**
120 * Uses a double[nAtoms][3] to revert the coordinates of an array of atoms. Does not check to see
121 * if the arrays are properly ordered; make sure you are using the correct arrays.
122 *
123 * @param atoms To revert coordinates of
124 * @param coords Coordinates
125 */
126 public static void revertAtomicCoordinates(Atom[] atoms, double[][] coords) {
127 int nAtoms = atoms.length;
128 if (coords.length != nAtoms) {
129 throw new IllegalArgumentException(
130 format(" Length %d of atoms " + "array does not match length %d of coordinates array",
131 nAtoms, coords.length));
132 }
133 for (int i = 0; i < nAtoms; i++) {
134 atoms[i].setXYZ(coords[i]);
135 }
136 }
137
138 /**
139 * storeAllCoordinates.
140 *
141 * @param residueList a {@link java.util.List} object.
142 * @return an array of {@link ffx.potential.bonded.ResidueState} objects.
143 */
144 public static ResidueState[] storeAllCoordinates(List<Residue> residueList) {
145 return storeAllCoordinates(residueList.toArray(new Residue[0]));
146 }
147
148 /**
149 * storeAllCoordinates.
150 *
151 * @param residues an array of {@link ffx.potential.bonded.Residue} objects.
152 * @return an array of {@link ffx.potential.bonded.ResidueState} objects.
153 */
154 public static ResidueState[] storeAllCoordinates(Residue[] residues) {
155 int nResidues = residues.length;
156 ResidueState[] states = new ResidueState[nResidues];
157 for (int i = 0; i < nResidues; i++) {
158 states[i] = residues[i].storeState();
159 }
160 return states;
161 }
162
163 /**
164 * Returns a new double[nAtoms][3] with the coordinates of an array of atoms.
165 *
166 * @param atoms To store coordinates of
167 * @return Coordinates
168 */
169 public static double[][] storeAtomicCoordinates(Atom[] atoms) {
170 int nAtoms = atoms.length;
171 double[][] coords = new double[nAtoms][3];
172 for (int i = 0; i < nAtoms; i++) {
173 atoms[i].getXYZ(coords[i]);
174 }
175 return coords;
176 }
177
178 /**
179 * revertAllCoordinates.
180 *
181 * @param residueArray an array of {@link ffx.potential.bonded.Residue} objects.
182 * @param states an array of {@link ffx.potential.bonded.ResidueState} objects.
183 */
184 private static void revertAllCoordinates(Residue[] residueArray, ResidueState[] states) {
185 int nResidues = residueArray.length;
186 if (nResidues != states.length) {
187 throw new IllegalArgumentException(
188 format("Length of residue " + "array %d and residue state array %d do not match.",
189 nResidues, states.length));
190 }
191
192 for (int i = 0; i < nResidues; i++) {
193 Residue resi = residueArray[i];
194 // If indexing did not get thrown off:
195 if (resi.equals(states[i].getParent())) {
196 resi.revertState(states[i]);
197 } else {
198 // Else must search states[]
199 boolean matchFound = false;
200 for (int j = 0; j < nResidues; j++) {
201 if (resi.equals(states[j].getParent())) {
202 matchFound = true;
203 resi.revertState(states[j]);
204 break;
205 }
206 }
207
208 if (!matchFound) {
209 throw new IllegalArgumentException(
210 format("Could not " + "find match for residue %s among residue states array.", resi));
211 }
212 }
213 }
214 }
215
216 public double compareTo(ResidueState residueState) {
217 logger.info("Comparing rotamers using the compareTo method in ResidueState");
218 double[][] tempx1 = new double[this.atoms.length][3];
219 double[][] tempx2 = new double[residueState.atoms.length][3];
220 double[] x1 = new double[this.atoms.length * 3];
221 double[] x2 = new double[residueState.atoms.length * 3];
222 // Create arrays of atoms for keptRotamer[k] ResidueState and newResState
223 // Here, each residue state is the same residue with a different rotamer applied.
224 Atom[] x1atoms = this.atoms;
225 Atom[] x2atoms = residueState.atoms;
226 // logger.info("Total number of atoms for current residue state: " + x1atoms.length);
227 // logger.info("Total number of atoms for previously kept residue state to be tested against: "+
228 // x2atoms.length);
229
230 // Get the coordinate arrays for each atom in the current residue state using the atomMap
231 for (int atomCount = 0; atomCount < x1atoms.length; atomCount++) {
232 Atom exampleAtom = x1atoms[atomCount];
233 tempx1[atomCount] = this.atomMap.get(exampleAtom);
234 }
235
236 // Add coordinates from tempx1[][] to x1[] for RMSD calculations
237 int x1count = 0;
238 for (double[] coordSet : tempx1) {
239 for (int coordCount = 0; coordCount < 3; coordCount++) {
240 x1[x1count] = coordSet[coordCount];
241 x1count++;
242 }
243 }
244
245 // Get the coorinate arrays for each atom in the previously kept residue state to be
246 // tested against using the atomMap for that residue state
247 for (int atomCount = 0; atomCount < x2atoms.length; atomCount++) {
248 Atom exampleAtom = x2atoms[atomCount];
249 tempx2[atomCount] = residueState.atomMap.get(exampleAtom);
250 }
251
252 // Add coordinate from tempx2[][] to x2[] for RMSD calculations
253 int x2count = 0;
254 for (double[] coordSet : tempx2) {
255 for (int coordCount = 0; coordCount < 3; coordCount++) {
256 x2[x2count] = coordSet[coordCount];
257 x2count++;
258 }
259 }
260
261 // Create a mass array of ones so the RMSD isn't mass weighted
262 double[] mass = new double[x1.length];
263 Arrays.fill(mass, 1);
264 return ffx.potential.utils.Superpose.rmsd(x1, x2, mass);
265 }
266
267 /**
268 * Getter for the field <code>atoms</code>.
269 *
270 * @return an array of {@link ffx.potential.bonded.Atom} objects.
271 */
272 public Atom[] getAtoms() {
273 return atoms;
274 }
275
276 /**
277 * Getter for the field <code>parent</code>.
278 *
279 * @return a {@link ffx.potential.bonded.Residue} object.
280 */
281 public Residue getParent() {
282 return parent;
283 }
284
285 /** {@inheritDoc} */
286 @Override
287 public String toString() {
288 return format(" ResidueState with parent residue %s, state residue " + "%s, number of atoms %d",
289 parent, residue, atoms.length);
290 }
291
292 /**
293 * getStateResidue.
294 *
295 * @return a {@link ffx.potential.bonded.Residue} object.
296 */
297 Residue getStateResidue() {
298 return residue;
299 }
300
301 /**
302 * Returns the coordinates of the selected atom; or null if atom not contained.
303 *
304 * @param atom An Atom.
305 * @return xyz coordinates.
306 */
307 double[] getAtomCoords(Atom atom) {
308 double[] xyz = new double[3];
309 if (!atomMap.containsKey(atom)) {
310 logger.info(
311 format(" Illegal call to ResidueState.getAtomCoords: atom %s not found: hashcode %d", atom,
312 atom.hashCode()));
313 for (Atom ratom : residue.getAtomList()) {
314 logger.info(format(" Atoms in residue: %s hashcode: %d", ratom, ratom.hashCode()));
315 }
316 for (Atom matom : atomMap.keySet()) {
317 logger.info(
318 format(" Atoms in ResidueState atom cache: %s hashcode: %d", matom, matom.hashCode()));
319 }
320 logger.log(Level.SEVERE, " Error in ResidueState.getAtomCoords.", new IllegalStateException());
321 }
322 System.arraycopy(atomMap.get(atom), 0, xyz, 0, 3);
323 return xyz;
324 }
325 }