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