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.potential.terms;
39  
40  import ffx.potential.bonded.Angle;
41  import ffx.potential.bonded.BondedTerm;
42  import ffx.potential.parameters.AngleType;
43  
44  import java.util.ArrayList;
45  import java.util.Collection;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.logging.Logger;
49  import static java.lang.String.format;
50  import static org.apache.commons.math3.util.FastMath.PI;
51  
52  /**
53   * Angle potential energy term using {@link ffx.potential.bonded.Angle} instances.
54   *
55   * @author Michael J. Schnieders
56   * @since 1.0
57   */
58  public class AnglePotentialEnergy extends EnergyTerm {
59  
60    private static final Logger logger = Logger.getLogger(AnglePotentialEnergy.class.getName());
61  
62    /**
63     * Internal list of Angle instances.
64     */
65    private final List<Angle> angles = new ArrayList<>();
66  
67    /**
68     * Create an AnglePotentialEnergy with the provided name.
69     *
70     * @param name Name for this term.
71     */
72    public AnglePotentialEnergy(String name) {
73      super(name);
74    }
75  
76    /**
77     * Create an AnglePotentialEnergy with the provided name and force group.
78     *
79     * @param name       Name for this term.
80     * @param forceGroup Integer force group identifier.
81     */
82    public AnglePotentialEnergy(String name, int forceGroup) {
83      super(name, forceGroup);
84    }
85  
86    /**
87     * Create an AnglePotentialEnergy initialized with a list of angles and force group.
88     *
89     * @param name       Name for this term.
90     * @param forceGroup Force group identifier.
91     * @param angles     List of Angle instances to add (null-safe).
92     */
93    public AnglePotentialEnergy(String name, int forceGroup, List<Angle> angles) {
94      super(name, forceGroup);
95      if (angles != null) {
96        Collections.sort(angles);
97        this.angles.addAll(angles);
98        logger.info(format("  Angles:                            %10d", getNumberOfAngles()));
99      }
100   }
101 
102   /**
103    * Get the number of terms in this potential energy term.
104    *
105    * @return The number of terms, which is the same as the number of angles.
106    */
107   @Override
108   public int getNumberOfTerms() {
109     return getNumberOfAngles();
110   }
111 
112   /**
113    * Get an array of BondedTerms in this term.
114    *
115    * @return Array of BondedTerms, which are actually Angles in this case.
116    */
117   @Override
118   public BondedTerm[] getBondedTermsArray() {
119     return getAngleArray();
120   }
121 
122   /**
123    * Create an AnglePotentialEnergy initialized with a collection of angles.
124    *
125    * @param name   Name for this term (may be null).
126    * @param angles Collection of Angle instances to add (null-safe).
127    */
128   public AnglePotentialEnergy(String name, Collection<Angle> angles) {
129     super(name);
130     if (angles != null) {
131       this.angles.addAll(angles);
132     }
133   }
134 
135   /**
136    * Add an Angle to this term.
137    *
138    * @param angle Angle to add (ignored if null).
139    * @return true if the angle was added.
140    */
141   public boolean addAngle(Angle angle) {
142     if (angle == null) {
143       return false;
144     }
145     return angles.add(angle);
146   }
147 
148   /**
149    * Add an array of Angles to this term.
150    *
151    * @param angles Array of Angle instances to add.
152    * @return true if the angles were added.
153    */
154   public boolean addAngles(Angle[] angles) {
155     if (angles == null) {
156       return false;
157     }
158     Collections.addAll(this.angles, angles);
159     return true;
160   }
161 
162   /**
163    * Add a list of Angles to this term.
164    *
165    * @param angles List of Angle instances to add.
166    * @return true if the angles were added.
167    */
168   public boolean addAngles(List<Angle> angles) {
169     if (angles == null) {
170       return false;
171     }
172     this.angles.addAll(angles);
173     return true;
174   }
175 
176   /**
177    * Remove an Angle from this term.
178    *
179    * @param angle Angle to remove (ignored if null).
180    * @return true if the angle was present and removed.
181    */
182   public boolean removeAngle(Angle angle) {
183     if (angle == null) {
184       return false;
185     }
186     return angles.remove(angle);
187   }
188 
189   /**
190    * Get the Angle at a given index.
191    *
192    * @param index Index in the internal list.
193    * @return Angle at the specified index.
194    * @throws IndexOutOfBoundsException if index is invalid.
195    */
196   public Angle getAngle(int index) {
197     return angles.get(index);
198   }
199 
200   /**
201    * Get an unmodifiable view of the Angles in this term.
202    *
203    * @return Unmodifiable List of Angles.
204    */
205   public List<Angle> getAngles() {
206     return Collections.unmodifiableList(angles);
207   }
208 
209   /**
210    * Get an array of Angles in this term.
211    *
212    * @return Array of Angles.
213    */
214   public Angle[] getAngleArray() {
215     return angles.toArray(new Angle[0]);
216   }
217 
218   /**
219    * Get the number of Angles in this term.
220    *
221    * @return The number of Angles.
222    */
223   public int getNumberOfAngles() {
224     return angles.size();
225   }
226 
227   /**
228    * Get the String used for OpenMM angle energy expressions.
229    * @return String representing the angle energy expression.
230    */
231   public String getAngleEnergyString() {
232     AngleType angleType = angles.getFirst().angleType;
233     String energy;
234     if (angleType.angleFunction == AngleType.AngleFunction.SEXTIC) {
235       energy = format("""
236               k*(d^2 + %.15g*d^3 + %.15g*d^4 + %.15g*d^5 + %.15g*d^6);
237               d=%.15g*theta-theta0;
238               """,
239           angleType.cubic, angleType.quartic, angleType.pentic, angleType.sextic, 180.0 / PI);
240     } else {
241       energy = format("""
242               k*(d^2);
243               d=%.15g*theta-theta0;
244               """,
245           180.0 / PI);
246     }
247     return energy;
248   }
249 
250   public String getInPlaneAngleEnergyString() {
251     AngleType angleType = angles.getFirst().angleType;
252     String energy = format("""
253             k*(d^2 + %.15g*d^3 + %.15g*d^4 + %.15g*d^5 + %.15g*d^6);
254             d=theta-theta0;
255             theta = %.15g*pointangle(x1, y1, z1, projx, projy, projz, x3, y3, z3);
256             projx = x2-nx*dot;
257             projy = y2-ny*dot;
258             projz = z2-nz*dot;
259             dot = nx*(x2-x3) + ny*(y2-y3) + nz*(z2-z3);
260             nx = px/norm;
261             ny = py/norm;
262             nz = pz/norm;
263             norm = sqrt(px*px + py*py + pz*pz);
264             px = (d1y*d2z-d1z*d2y);
265             py = (d1z*d2x-d1x*d2z);
266             pz = (d1x*d2y-d1y*d2x);
267             d1x = x1-x4;
268             d1y = y1-y4;
269             d1z = z1-z4;
270             d2x = x3-x4;
271             d2y = y3-y4;
272             d2z = z3-z4;
273             """,
274         angleType.cubic, angleType.quartic, angleType.pentic, angleType.sextic, 180.0 / PI);
275     return energy;
276   }
277 
278   /**
279    * Log the details of Angle interactions.
280    */
281   @Override
282   public void log() {
283     if (getNumberOfAngles() <= 0) {
284       return;
285     }
286     logger.info("\n Angle Bending Interactions:");
287     for (Angle angle : getAngles()) {
288       logger.info(" Angle \t" + angle.toString());
289     }
290   }
291 
292   @Override
293   public String toPDBString() {
294     if (getNumberOfAngles() <= 0) {
295       return "";
296     }
297     StringBuilder sb = new StringBuilder();
298     sb.append(format("REMARK   3   %s %g (%d)\n", "ANGLE BENDING              : ", getEnergy(), getNumberOfAngles()));
299     sb.append(format("REMARK   3   %s %g\n", "ANGLE RMSD                 : ", getRMSD()));
300     return sb.toString();
301   }
302 
303   @Override
304   public String toString() {
305     return format("  %s %20.8f %12d %12.3f (%8.5f)\n", "Angle Bending     ",
306         getEnergy(), getNumberOfAngles(), getTime(), getRMSD());
307   }
308 
309 }