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-2026.
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.crystal.Crystal;
41  import ffx.numerics.switching.ConstantSwitch;
42  import ffx.numerics.switching.UnivariateFunctionFactory;
43  import ffx.numerics.switching.UnivariateSwitchingFunction;
44  import ffx.potential.bonded.Atom;
45  import ffx.potential.bonded.BondedTerm;
46  import ffx.potential.bonded.RestrainDistance;
47  import ffx.potential.parameters.BondType;
48  import org.apache.commons.configuration2.CompositeConfiguration;
49  
50  import javax.annotation.Nullable;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.Collections;
54  import java.util.List;
55  import java.util.logging.Logger;
56  
57  import static java.lang.String.format;
58  
59  /**
60   * Restrain-Distance potential energy term using {@link ffx.potential.bonded.RestrainDistance} instances.
61   *
62   * @author Michael J. Schnieders
63   * @since 1.0
64   */
65  public class RestrainDistancePotentialEnergy extends EnergyTerm {
66  
67    private static final Logger logger = Logger.getLogger(RestrainDistancePotentialEnergy.class.getName());
68  
69    /**
70     * Log the details of Restrain Distance interactions.
71     */
72    @Override
73    public void log() {
74      if (getNumberOfRestrainDistances() <= 0) {
75        return;
76      }
77      logger.info("\n Restrain Distance Interactions:");
78      for (RestrainDistance restrainDistance : getRestrainDistances()) {
79        logger.info(" Restrain Distance \t" + restrainDistance.toString());
80      }
81    }
82  
83    /**
84     * Internal list of RestrainDistance instances.
85     */
86    private final List<RestrainDistance> restrainDistances = new ArrayList<>();
87  
88    /**
89     * Create a RestrainDistancePotentialEnergy with the provided name.
90     *
91     * @param name Name for this term.
92     */
93    public RestrainDistancePotentialEnergy(String name) {
94      super(name);
95    }
96  
97    /**
98     * Create a RestrainDistancePotentialEnergy with the provided name and force group.
99     *
100    * @param name       Name for this term.
101    * @param forceGroup Integer force group identifier.
102    */
103   public RestrainDistancePotentialEnergy(String name, int forceGroup) {
104     super(name, forceGroup);
105   }
106 
107   /**
108    * Create a RestrainDistancePotentialEnergy initialized with a list of terms and force group.
109    *
110    * @param name              Name for this term.
111    * @param forceGroup        Force group identifier.
112    * @param restrainDistances List of RestrainDistance instances (null-safe).
113    */
114   public RestrainDistancePotentialEnergy(String name, int forceGroup, List<RestrainDistance> restrainDistances) {
115     super(name, forceGroup);
116     if (restrainDistances != null) {
117       Collections.sort(restrainDistances);
118       this.restrainDistances.addAll(restrainDistances);
119       logger.info(String.format("  Restrain Distances:                 %10d", getNumberOfRestrainDistances()));
120     }
121   }
122 
123   /**
124    * {@inheritDoc}
125    */
126   @Override
127   public int getNumberOfTerms() {
128     return getNumberOfRestrainDistances();
129   }
130 
131   /**
132    * {@inheritDoc}
133    */
134   @Override
135   public BondedTerm[] getBondedTermsArray() {
136     return getRestrainDistanceArray();
137   }
138 
139   /**
140    * Create a RestrainDistancePotentialEnergy initialized with a collection of terms.
141    *
142    * @param name              Name for this term (may be null).
143    * @param restrainDistances Collection of RestrainDistance instances to add (null-safe).
144    */
145   public RestrainDistancePotentialEnergy(String name, Collection<RestrainDistance> restrainDistances) {
146     super(name);
147     if (restrainDistances != null) {
148       this.restrainDistances.addAll(restrainDistances);
149     }
150   }
151 
152   /**
153    * Add a RestrainDistance to this term.
154    *
155    * @param restrainDistance RestrainDistance to add (ignored if null).
156    * @return true if it was added.
157    */
158   public boolean addRestrainDistance(RestrainDistance restrainDistance) {
159     if (restrainDistance == null) {
160       return false;
161     }
162     return restrainDistances.add(restrainDistance);
163   }
164 
165   /**
166    * Add an array of RestrainDistances to this term.
167    *
168    * @param restrainDistances Array of RestrainDistance instances to add.
169    * @return true if they were added.
170    */
171   public boolean addRestrainDistances(RestrainDistance[] restrainDistances) {
172     if (restrainDistances == null) {
173       return false;
174     }
175     Collections.addAll(this.restrainDistances, restrainDistances);
176     return true;
177   }
178 
179   /**
180    * Add a list of RestrainDistances to this term.
181    *
182    * @param restrainDistances List of RestrainDistance instances to add.
183    * @return true if they were added.
184    */
185   public boolean addRestrainDistances(List<RestrainDistance> restrainDistances) {
186     if (restrainDistances == null) {
187       return false;
188     }
189     this.restrainDistances.addAll(restrainDistances);
190     return true;
191   }
192 
193   /**
194    * Remove a RestrainDistance from this term.
195    *
196    * @param restrainDistance RestrainDistance to remove (ignored if null).
197    * @return true if it was present and removed.
198    */
199   public boolean removeRestrainDistance(RestrainDistance restrainDistance) {
200     if (restrainDistance == null) {
201       return false;
202     }
203     return restrainDistances.remove(restrainDistance);
204   }
205 
206   /**
207    * Get the RestrainDistance at a given index.
208    *
209    * @param index Index in the internal list.
210    * @return RestrainDistance at the specified index.
211    * @throws IndexOutOfBoundsException if index is invalid.
212    */
213   public RestrainDistance getRestrainDistance(int index) {
214     return restrainDistances.get(index);
215   }
216 
217   /**
218    * Get an unmodifiable view of the RestrainDistances in this term.
219    *
220    * @return Unmodifiable List of RestrainDistances.
221    */
222   public List<RestrainDistance> getRestrainDistances() {
223     return Collections.unmodifiableList(restrainDistances);
224   }
225 
226   /**
227    * Get an array of RestrainDistances in this term.
228    *
229    * @return Array of RestrainDistances.
230    */
231   public RestrainDistance[] getRestrainDistanceArray() {
232     return restrainDistances.toArray(new RestrainDistance[0]);
233   }
234 
235   /**
236    * Get the number of RestrainDistances in this term.
237    *
238    * @return The number of RestrainDistances.
239    */
240   public int getNumberOfRestrainDistances() {
241     return restrainDistances.size();
242   }
243 
244   /**
245    * Returns a list of restraint distances filtered by the specified bond function. If the bondFunction
246    * is null, it returns all restrained bonds.
247    *
248    * @param bondFunction the type of bond function.
249    * @return a {@link java.util.List} object.
250    */
251   public List<RestrainDistance> getRestrainDistances(@Nullable BondType.BondFunction bondFunction) {
252     // If the bondFunction is null, return all restrained bonds.
253     if (bondFunction == null) {
254       return restrainDistances;
255     }
256     // Otherwise, return only the restraint bonds with the specified bond function.
257     List<RestrainDistance> list = new ArrayList<>();
258     for (RestrainDistance restrainDistance : restrainDistances) {
259       if (restrainDistance.getBondType().bondFunction == bondFunction) {
260         list.add(restrainDistance);
261       }
262     }
263     if (!list.isEmpty()) {
264       return list;
265     }
266     return null;
267   }
268 
269   @Override
270   public String toPDBString() {
271     if (getNumberOfRestrainDistances() <= 0) {
272       return "";
273     }
274     return String.format("REMARK   3   %s %g (%d)\n", "RESTRAIN DISTANCE          : ", getEnergy(), getNumberOfRestrainDistances());
275   }
276 
277   @Override
278   public String toString() {
279     return String.format("  %s %20.8f %12d %12.3f\n", "Restrain Distance ",
280         getEnergy(), getNumberOfRestrainDistances(), getTime());
281   }
282 
283   /**
284    * Method to parse the restrain-distance records.
285    *
286    * @param properties Configuration properties.
287    */
288   public static RestrainDistance[] configureRestrainDistances(
289       CompositeConfiguration properties,
290       Atom[] atoms, Crystal crystal, boolean lambdaTerm) {
291     List<RestrainDistance> restrainDistanceList = new ArrayList<>();
292     String[] bondRestraints = properties.getStringArray("restrain-distance");
293     for (String bondRest : bondRestraints) {
294       try {
295         String[] toks = bondRest.split("\\s+");
296         if (toks.length < 2) {
297           throw new IllegalArgumentException(
298               format(" restrain-distance value %s could not be parsed!", bondRest));
299         }
300         // Internally, everything starts with 0, but restrain distance starts at 1, so that 1 has to
301         // be subtracted
302         int at1 = Integer.parseInt(toks[0]) - 1;
303         int at2 = Integer.parseInt(toks[1]) - 1;
304 
305         double forceConst = 100.0;
306         double flatBottomRadius = 0;
307         Atom a1 = atoms[at1];
308         Atom a2 = atoms[at2];
309 
310         if (toks.length > 2) {
311           forceConst = Double.parseDouble(toks[2]);
312         }
313         double dist;
314         switch (toks.length) {
315           case 2:
316           case 3:
317             double[] xyz1 = new double[3];
318             xyz1 = a1.getXYZ(xyz1);
319             double[] xyz2 = new double[3];
320             xyz2 = a2.getXYZ(xyz2);
321             // Current distance between restrained atoms
322             dist = crystal.minDistOverSymOps(xyz1, xyz2);
323             break;
324           case 4:
325             dist = Double.parseDouble(toks[3]);
326             break;
327           case 5:
328           default:
329             double minDist = Double.parseDouble(toks[3]);
330             double maxDist = Double.parseDouble(toks[4]);
331             dist = 0.5 * (minDist + maxDist);
332             flatBottomRadius = 0.5 * Math.abs(maxDist - minDist);
333             break;
334         }
335 
336         UnivariateSwitchingFunction switchF;
337         double lamStart = RestrainDistance.DEFAULT_RB_LAM_START;
338         double lamEnd = RestrainDistance.DEFAULT_RB_LAM_END;
339         if (toks.length > 5) {
340           int offset = 5;
341           if (toks[5].matches("^[01](?:\\.[0-9]*)?")) {
342             offset = 6;
343             lamStart = Double.parseDouble(toks[5]);
344             if (toks[6].matches("^[01](?:\\.[0-9]*)?")) {
345               offset = 7;
346               lamEnd = Double.parseDouble(toks[6]);
347             }
348           }
349           switchF = UnivariateFunctionFactory.parseUSF(toks, offset);
350         } else {
351           switchF = new ConstantSwitch();
352         }
353 
354         RestrainDistance restrainDistance = createRestrainDistance(a1, a2, dist,
355             forceConst, flatBottomRadius, lamStart, lamEnd, switchF, lambdaTerm, crystal);
356         restrainDistanceList.add(restrainDistance);
357       } catch (Exception ex) {
358         logger.info(format(" Exception in parsing restrain-distance: %s", ex));
359       }
360     }
361     if (!restrainDistanceList.isEmpty()) {
362       return restrainDistanceList.toArray(new RestrainDistance[0]);
363     } else {
364       return null;
365     }
366   }
367 
368   /**
369    * setRestrainDistance
370    *
371    * @param a1                a {@link ffx.potential.bonded.Atom} object.
372    * @param a2                a {@link ffx.potential.bonded.Atom} object.
373    * @param distance          a double.
374    * @param forceConstant     the force constant in kcal/mole.
375    * @param flatBottom        Radius of a flat-bottom potential in Angstroms.
376    * @param lamStart          At what lambda does the restraint begin to take effect?
377    * @param lamEnd            At what lambda does the restraint hit full strength?
378    * @param switchingFunction Switching function to use as a lambda dependence.
379    * @param lambdaTerm        Restrain distance is a function of lambda.
380    * @param crystal           The unit cel and space group information.
381    */
382   private static RestrainDistance createRestrainDistance(Atom a1, Atom a2, double distance, double forceConstant,
383                                                          double flatBottom, double lamStart, double lamEnd,
384                                                          UnivariateSwitchingFunction switchingFunction,
385                                                          boolean lambdaTerm, Crystal crystal) {
386     boolean rbLambda = !(switchingFunction instanceof ConstantSwitch) && lambdaTerm;
387     RestrainDistance restrainDistance = new RestrainDistance(a1, a2, crystal, rbLambda, lamStart, lamEnd, switchingFunction);
388     int[] classes = {a1.getAtomType().atomClass, a2.getAtomType().atomClass};
389     if (flatBottom != 0) {
390       BondType bondType = new BondType(classes, forceConstant, distance,
391           BondType.BondFunction.FLAT_BOTTOM_HARMONIC, flatBottom);
392       restrainDistance.setBondType(bondType);
393     } else {
394       BondType bondType = new BondType(classes, forceConstant, distance,
395           BondType.BondFunction.HARMONIC);
396       restrainDistance.setBondType(bondType);
397     }
398     return restrainDistance;
399   }
400 }