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.nonbonded;
39  
40  import ffx.crystal.Crystal;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.bonded.Atom;
43  import org.apache.commons.configuration2.CompositeConfiguration;
44  
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.HashMap;
48  import java.util.logging.Logger;
49  
50  import static java.lang.Double.parseDouble;
51  import static java.lang.Integer.parseInt;
52  import static java.lang.String.format;
53  import static org.apache.commons.math3.util.FastMath.max;
54  import static org.apache.commons.math3.util.FastMath.sqrt;
55  
56  /**
57   * Apply a restraint between groups.
58   *
59   * @author Michael J. Schnieders
60   * @since 1.0
61   */
62  public class RestrainGroups {
63  
64    private static final Logger logger = Logger.getLogger(RestrainGroups.class.getName());
65  
66    /** The MolecularAssembly to operate on. */
67    final MolecularAssembly molecularAssembly;
68    /** Atom array. */
69    final Atom[] atoms;
70    /** Number of atoms. */
71    final int nAtoms;
72    /** Number of groups. */
73    final int nGroups;
74    /** The atoms in each group. */
75    final int[][] groupMembers;
76    /** The molecule number of each atom group (or -1 if the atoms are in different molecules */
77    final int[] groupMolecule;
78    /** The mass of each group. */
79    final double[] groupMass;
80    /** Number of group restraints. */
81    final int nRestraints;
82    /** Index of group 1 for each restraint. */
83    final int[] group1;
84    /** Index of group 2 for each restraint. */
85    final int[] group2;
86    /** Force constant each restraint. */
87    final double[] forceConstants;
88    /** Distance 1 for each restraint. */
89    final double[] distance1;
90    /** Distance 2 for each restraint. */
91    final double[] distance2;
92    /** Flag to indicate of all atoms for both groups are part of the same molecule. */
93    final boolean[] sameMolecule;
94  
95    /**
96     * Group Restraint Constructor.
97     *
98     * @param molecularAssembly The molecularAssembly to operate on.
99     */
100   public RestrainGroups(MolecularAssembly molecularAssembly) {
101     this.molecularAssembly = molecularAssembly;
102     this.atoms = molecularAssembly.getAtomArray();
103     nAtoms = atoms.length;
104 
105     CompositeConfiguration compositeConfiguration = molecularAssembly.getProperties();
106 
107     String[] groups = compositeConfiguration.getStringArray("group");
108     if (groups == null || groups.length < 2) {
109       logger.severe(" restrain-groups requires two groups to be defined.");
110     }
111 
112     String[] restrainGroups = compositeConfiguration.getStringArray("restrain-groups");
113     if (restrainGroups == null || restrainGroups.length < 1) {
114       logger.severe(" No restrain-groups property found.");
115     }
116 
117     logger.fine("\n Initializing Groups");
118 
119     HashMap<Integer, ArrayList<Integer>> groupMap = new HashMap<>();
120     // Loop over groups.
121     for (String g : groups) {
122       // Split group lines on 1 or mores spaces.
123       String[] gs = g.trim().split(" +");
124       // First entry is the group number.
125       int groupNumber = parseInt(gs[0]) - 1;
126       ArrayList<Integer> groupAtomIDs = new ArrayList<>();
127       // Loop over group ranges.
128       for (int i = 1; i < gs.length; i++) {
129         int start = parseInt(gs[i]);
130         if (start < 0) {
131           // This is a range.
132           start = -start - 1;
133           i++;
134           int end = parseInt(gs[i]) - 1;
135           if (start >= nAtoms || end < start || end >= nAtoms) {
136             logger.severe(format(" Property group could not be parsed:\n %s.", g));
137             continue;
138           }
139           for (int j = start; j <= end; j++) {
140             groupAtomIDs.add(j);
141           }
142           logger.fine(format(" Group %d added atoms %d to %d.", groupNumber + 1, start + 1, end + 1));
143         } else {
144           int atomID = start - 1;
145           groupAtomIDs.add(atomID);
146           logger.fine(format(" Group %d added atom %d.", groupNumber + 1, atomID + 1));
147         }
148         groupMap.put(groupNumber, groupAtomIDs);
149       }
150     }
151 
152     // Pack groups into arrays.
153     int[] molecule = molecularAssembly.getMoleculeNumbers();
154     nGroups = groupMap.size();
155     groupMembers = new int[nGroups][];
156     groupMass = new double[nGroups];
157     groupMolecule = new int[nGroups];
158     sameMolecule = new boolean[nGroups];
159     for (Integer groupNumber : groupMap.keySet()) {
160       ArrayList<Integer> members = groupMap.get(groupNumber);
161       if (groupNumber >= nGroups) {
162         logger.severe(" Please label groups from 1 to the number of groups.");
163       }
164       int groupID = groupNumber;
165       groupMembers[groupID] = new int[members.size()];
166       int mem = 0;
167       for (int member : members) {
168         groupMembers[groupID][mem++] = member;
169       }
170       logger.finer(format(" Group %d members %s.", groupID, Arrays.toString(groupMembers[groupID])));
171       int k = groupMembers[groupID][0];
172       Atom atom = atoms[k];
173       groupMass[groupID] = atom.getMass();
174       groupMolecule[groupID] = molecule[k];
175       for (int i = 1; i < groupMembers[groupID].length; i++) {
176         k = groupMembers[groupID][i];
177         atom = atoms[k];
178         groupMass[groupID] += atom.getMass();
179         if (groupMolecule[groupID] != molecule[k]) {
180           groupMolecule[groupID] = -1;
181         }
182       }
183       groupMass[groupID] = max(groupMass[groupID], 1.0);
184     }
185 
186     // Parse restrain-groups properties.
187     logger.fine("\n Initializing Restrain-Groups");
188     nRestraints = restrainGroups.length;
189     group1 = new int[nRestraints];
190     group2 = new int[nRestraints];
191     forceConstants = new double[nRestraints];
192     distance1 = new double[nRestraints];
193     distance2 = new double[nRestraints];
194     int iRestraint = 0;
195     for (String restraint : restrainGroups) {
196       String[] values = restraint.trim().split(" +");
197       if (values.length < 3) {
198         logger.severe(format(" Property restrain-groups could not be parsed:\n %s.", restraint));
199       }
200       group1[iRestraint] = parseInt(values[0]) - 1;
201       group2[iRestraint] = parseInt(values[1]) - 1;
202       int i1 = group1[iRestraint];
203       int i2 = group2[iRestraint];
204       if (i1 >= nGroups || i2 >= nGroups) {
205         logger.severe(format(" Property restrain-groups has invalid groups:\n %s.", restraint));
206       }
207       forceConstants[iRestraint] = parseDouble(values[2]);
208       distance1[iRestraint] = 0.0;
209       distance2[iRestraint] = 0.0;
210       if (values.length > 3) {
211         distance1[iRestraint] = parseDouble(values[3]);
212         if (values.length > 4) {
213           distance2[iRestraint] = parseDouble(values[4]);
214         }
215       }
216       sameMolecule[iRestraint] = false;
217       if (groupMolecule[i1] > -1 && groupMolecule[i2] > -1) {
218         if (groupMolecule[i1] == groupMolecule[i2]) {
219           sameMolecule[iRestraint] = true;
220         }
221       }
222       logger.fine(format(" Restrain-Groups %2d %2d %8.3f %8.3f %8.3f",
223           i1 + 1, i2 + 1, forceConstants[iRestraint], distance1[iRestraint], distance2[iRestraint]));
224 
225       iRestraint++;
226     }
227     logger.fine("\n");
228   }
229 
230   /**
231    * Get the number of groups.
232    *
233    * @return Returns the number of groups.
234    */
235   public int getNumberOfGroups() {
236     return nGroups;
237   }
238 
239   /**
240    * Get group members.
241    *
242    * @param group Group index.
243    * @return Returns group members.
244    */
245   public int[] getGroupMembers(int group) {
246     return groupMembers[group];
247   }
248 
249   /**
250    * Group 1 for each restraint.
251    *
252    * @return Returns group 1 for each restraint.
253    */
254   public int[] getGroup1() {
255     return group1;
256   }
257 
258   /**
259    * Group 2 for each restraint.
260    *
261    * @return Returns group 2 for each restraint.
262    */
263   public int[] getGroup2() {
264     return group2;
265   }
266 
267   /**
268    * Force constant for each restraint.
269    *
270    * @return Returns the force constant for each restraint.
271    */
272   public double[] getForceConstants() {
273     return forceConstants;
274   }
275 
276   /**
277    * Smaller distance for each restraint.
278    *
279    * @return Returns the smaller distance for each restraint.
280    */
281   public double[] getSmallerDistance() {
282     return distance1;
283   }
284 
285   /**
286    * Larger distance for each restraint.
287    *
288    * @return Returns the force constant for each restraint.
289    */
290   public double[] getLargerDistance() {
291     return distance2;
292   }
293 
294   /**
295    * Compute energy and derivatives for group distance restraint terms.
296    *
297    * @param gradient If true, compute the derivative.
298    * @return The potential energy.
299    */
300   public double energy(boolean gradient) {
301     double energy = 0;
302     double[] xyz = new double[3];
303     double[] dr = new double[3];
304     Crystal crystal = molecularAssembly.getCrystal();
305     for (int i = 0; i < nRestraints; i++) {
306       int i1 = group1[i];
307       int i2 = group2[i];
308       // Loop over the first group members.
309       double xcm = 0.0;
310       double ycm = 0.0;
311       double zcm = 0.0;
312       for (int j = 0; j < groupMembers[i1].length; j++) {
313         int k = groupMembers[i1][j];
314         Atom atom = atoms[k];
315         double mass = atom.getMass();
316         atom.getXYZ(xyz);
317         xcm = xcm + xyz[0] * mass;
318         ycm = ycm + xyz[1] * mass;
319         zcm = zcm + xyz[2] * mass;
320       }
321       double mass1 = groupMass[i1];
322       double xr = xcm / mass1;
323       double yr = ycm / mass1;
324       double zr = zcm / mass1;
325       // Loop over the second group members.
326       xcm = 0.0;
327       ycm = 0.0;
328       zcm = 0.0;
329       for (int j = 0; j < groupMembers[i2].length; j++) {
330         int k = groupMembers[i2][j];
331         Atom atom = atoms[k];
332         double mass = atom.getMass();
333         atom.getXYZ(xyz);
334         xcm = xcm + xyz[0] * mass;
335         ycm = ycm + xyz[1] * mass;
336         zcm = zcm + xyz[2] * mass;
337       }
338       double mass2 = groupMass[i2];
339       xr = xr - xcm / mass2;
340       yr = yr - ycm / mass2;
341       zr = zr - zcm / mass2;
342       double r2;
343       if (sameMolecule[i]) {
344         r2 = xr * xr + yr * yr + zr * zr;
345       } else {
346         dr[0] = xr;
347         dr[1] = yr;
348         dr[2] = zr;
349         r2 = crystal.image(dr);
350       }
351       double r = sqrt(r2);
352       double force = forceConstants[i];
353       double gf1 = distance1[i];
354       double gf2 = distance2[i];
355       double target = r;
356       if (r < gf1) {
357         target = gf1;
358       }
359       if (r > gf2) {
360         target = gf2;
361       }
362       double dt = r - target;
363       double dt2 = dt * dt;
364       double e = force * dt2;
365       energy = energy + e;
366       // logger.fine(format(" Restrain-Groups %d %d %8.3f - %8.3f %8.3f %16.8f", i1 + 1, i2+1, gf1, gf2, r, e));
367       if (gradient) {
368         // Compute chain rule terms needed for derivatives.
369         if (r == 0.0) {
370           r = 1.0;
371         }
372         double de = 2.0 * force * dt / r;
373         double dedx = de * xr;
374         double dedy = de * yr;
375         double dedz = de * zr;
376         // Increment the total energy and first derivatives for group 1.
377         for (int j = 0; j < groupMembers[i1].length; j++) {
378           int k = groupMembers[i1][j];
379           Atom atom = atoms[k];
380           double mass = atom.getMass();
381           double ratio = mass / mass1;
382           atom.addToXYZGradient(dedx * ratio, dedy * ratio, dedz * ratio);
383         }
384         // Increment the total energy and first derivatives for group 2.
385         for (int j = 0; j < groupMembers[i2].length; j++) {
386           int k = groupMembers[i2][j];
387           Atom atom = atoms[k];
388           double mass = atom.getMass();
389           double ratio = mass / mass2;
390           atom.addToXYZGradient(-dedx * ratio, -dedy * ratio, -dedz * ratio);
391         }
392       }
393     }
394     return energy;
395   }
396 
397   /**
398    * Get the number of group restraints.
399    *
400    * @return The number of group restraints.
401    */
402   public int getNumberOfRestraints() {
403     return nRestraints;
404   }
405 }