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.BondedTerm;
41 import ffx.potential.bonded.OutOfPlaneBend;
42 import ffx.potential.parameters.OutOfPlaneBendType;
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
50 import static java.lang.String.format;
51 import static org.apache.commons.math3.util.FastMath.PI;
52
53 /**
54 * Out-of-Plane Bend potential energy term using {@link ffx.potential.bonded.OutOfPlaneBend} instances.
55 */
56 public class OutOfPlaneBendPotentialEnergy extends EnergyTerm {
57
58 private static final Logger logger = Logger.getLogger(OutOfPlaneBendPotentialEnergy.class.getName());
59
60
61 /**
62 * Internal list of OutOfPlaneBend instances.
63 */
64 private final List<OutOfPlaneBend> outOfPlaneBends = new ArrayList<>();
65
66 /**
67 * Create an OutOfPlaneBendPotentialEnergy with the provided name.
68 *
69 * @param name Name for this term.
70 */
71 public OutOfPlaneBendPotentialEnergy(String name) {
72 super(name);
73 }
74
75 /**
76 * Create an OutOfPlaneBendPotentialEnergy with the provided name and force group.
77 *
78 * @param name Name for this term.
79 * @param forceGroup Integer force group identifier.
80 */
81 public OutOfPlaneBendPotentialEnergy(String name, int forceGroup) {
82 super(name, forceGroup);
83 }
84
85 /**
86 * Create an OutOfPlaneBendPotentialEnergy initialized with a list of terms and force group.
87 *
88 * @param name Name for this term.
89 * @param forceGroup Force group identifier.
90 * @param outOfPlaneBends List of OutOfPlaneBend instances to add (null-safe).
91 */
92 public OutOfPlaneBendPotentialEnergy(String name, int forceGroup, List<OutOfPlaneBend> outOfPlaneBends) {
93 super(name, forceGroup);
94 if (outOfPlaneBends != null) {
95 Collections.sort(outOfPlaneBends);
96 this.outOfPlaneBends.addAll(outOfPlaneBends);
97 logger.info(format(" Out-of-Plane Bends: %10d", getNumberOfOutOfPlaneBends()));
98 }
99 }
100
101 /**
102 * {@inheritDoc}
103 */
104 @Override
105 public int getNumberOfTerms() {
106 return getNumberOfOutOfPlaneBends();
107 }
108
109 /**
110 * {@inheritDoc}
111 */
112 @Override
113 public BondedTerm[] getBondedTermsArray() {
114 return getOutOfPlaneBendArray();
115 }
116
117 /**
118 * Create an OutOfPlaneBendPotentialEnergy initialized with a collection of terms.
119 *
120 * @param name Name for this term (may be null).
121 * @param outOfPlaneBends Collection of OutOfPlaneBend instances to add (null-safe).
122 */
123 public OutOfPlaneBendPotentialEnergy(String name, Collection<OutOfPlaneBend> outOfPlaneBends) {
124 super(name);
125 if (outOfPlaneBends != null) {
126 this.outOfPlaneBends.addAll(outOfPlaneBends);
127 }
128 }
129
130 /**
131 * Add an OutOfPlaneBend to this term.
132 *
133 * @param outOfPlaneBend OutOfPlaneBend to add (ignored if null).
134 * @return true if it was added.
135 */
136 public boolean addOutOfPlaneBend(OutOfPlaneBend outOfPlaneBend) {
137 if (outOfPlaneBend == null) {
138 return false;
139 }
140 return outOfPlaneBends.add(outOfPlaneBend);
141 }
142
143 /**
144 * Add an array of OutOfPlaneBends to this term.
145 *
146 * @param outOfPlaneBends Array of OutOfPlaneBend instances to add.
147 * @return true if they were added.
148 */
149 public boolean addOutOfPlaneBends(OutOfPlaneBend[] outOfPlaneBends) {
150 if (outOfPlaneBends == null) {
151 return false;
152 }
153 Collections.addAll(this.outOfPlaneBends, outOfPlaneBends);
154 return true;
155 }
156
157 /**
158 * Add a list of OutOfPlaneBends to this term.
159 *
160 * @param outOfPlaneBends List of OutOfPlaneBend instances to add.
161 * @return true if they were added.
162 */
163 public boolean addOutOfPlaneBends(List<OutOfPlaneBend> outOfPlaneBends) {
164 if (outOfPlaneBends == null) {
165 return false;
166 }
167 this.outOfPlaneBends.addAll(outOfPlaneBends);
168 return true;
169 }
170
171 /**
172 * Remove an OutOfPlaneBend from this term.
173 *
174 * @param outOfPlaneBend OutOfPlaneBend to remove (ignored if null).
175 * @return true if it was present and removed.
176 */
177 public boolean removeOutOfPlaneBend(OutOfPlaneBend outOfPlaneBend) {
178 if (outOfPlaneBend == null) {
179 return false;
180 }
181 return outOfPlaneBends.remove(outOfPlaneBend);
182 }
183
184 /**
185 * Get the OutOfPlaneBend at a given index.
186 *
187 * @param index Index in the internal list.
188 * @return OutOfPlaneBend at the specified index.
189 * @throws IndexOutOfBoundsException if index is invalid.
190 */
191 public OutOfPlaneBend getOutOfPlaneBend(int index) {
192 return outOfPlaneBends.get(index);
193 }
194
195 /**
196 * Get an unmodifiable view of the OutOfPlaneBends in this term.
197 *
198 * @return Unmodifiable List of OutOfPlaneBends.
199 */
200 public List<OutOfPlaneBend> getOutOfPlaneBends() {
201 return Collections.unmodifiableList(outOfPlaneBends);
202 }
203
204 /**
205 * Get an array of OutOfPlaneBends in this term.
206 *
207 * @return Array of OutOfPlaneBends.
208 */
209 public OutOfPlaneBend[] getOutOfPlaneBendArray() {
210 return outOfPlaneBends.toArray(new OutOfPlaneBend[0]);
211 }
212
213 /**
214 * Get the number of OutOfPlaneBends in this term.
215 *
216 * @return The number of OutOfPlaneBends.
217 */
218 public int getNumberOfOutOfPlaneBends() {
219 return outOfPlaneBends.size();
220 }
221
222
223 /**
224 * Get a string representation of the Out-of-Plane Bend energy expression.
225 * @return A formatted string representing the energy expression for Out-of-Plane Bends.
226 */
227 public String getOutOfPlaneEnergyString() {
228 OutOfPlaneBendType outOfPlaneBendType = outOfPlaneBends.getFirst().outOfPlaneBendType;
229 String energy = format("""
230 k*(theta^2 + %.15g*theta^3 + %.15g*theta^4 + %.15g*theta^5 + %.15g*theta^6);
231 theta = %.15g*pointangle(x2, y2, z2, x4, y4, z4, projx, projy, projz);
232 projx = x2-nx*dot;
233 projy = y2-ny*dot;
234 projz = z2-nz*dot;
235 dot = nx*(x2-x3) + ny*(y2-y3) + nz*(z2-z3);
236 nx = px/norm;
237 ny = py/norm;
238 nz = pz/norm;
239 norm = sqrt(px*px + py*py + pz*pz);
240 px = (d1y*d2z-d1z*d2y);
241 py = (d1z*d2x-d1x*d2z);
242 pz = (d1x*d2y-d1y*d2x);
243 d1x = x1-x4;
244 d1y = y1-y4;
245 d1z = z1-z4;
246 d2x = x3-x4;
247 d2y = y3-y4;
248 d2z = z3-z4
249 """,
250 outOfPlaneBendType.cubic, outOfPlaneBendType.quartic,
251 outOfPlaneBendType.pentic, outOfPlaneBendType.sextic, 180.0 / PI);
252 return energy;
253 }
254
255 /**
256 * Log the details of Out-of-Plane Bend interactions.
257 */
258 @Override
259 public void log() {
260 if (getNumberOfOutOfPlaneBends() <= 0) {
261 return;
262 }
263 logger.info("\n Out-of-Plane Bend Interactions:");
264 for (OutOfPlaneBend outOfPlaneBend : getOutOfPlaneBends()) {
265 logger.info(" Out-of-Plane Bend \t" + outOfPlaneBend.toString());
266 }
267 }
268
269 @Override
270 public String toPDBString() {
271 if (getNumberOfOutOfPlaneBends() <= 0) {
272 return "";
273 }
274 return format("REMARK 3 %s %g (%d)\n", "OUT-OF-PLANE BEND : ", getEnergy(), getNumberOfOutOfPlaneBends());
275 }
276
277 @Override
278 public String toString() {
279 return format(" %s %20.8f %12d %12.3f\n", "Out-of-Plane Bend ",
280 getEnergy(), getNumberOfOutOfPlaneBends(), getTime());
281 }
282 }