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.crystal;
39  
40  import java.util.List;
41  import java.util.Objects;
42  import java.util.logging.Level;
43  import java.util.logging.Logger;
44  
45  import static java.lang.String.format;
46  
47  /**
48   * The ReplicatesCrystal class extends Crystal to generate additional symmetry operators needed to
49   * describe a "replicated" super-cell.
50   * <p>
51   * The replicated crystal cell edges are of length {l*a, m*b, n*c} where l, m and n are integers and
52   * a, b and c are the original unit cell edge lengths.
53   * <br>
54   * The replicates integers l, m and n are chosen large enough for the ReplicatesCrystal to allow
55   * consistent application of the minimum image convention. This is ensured by increasing l, m and/or
56   * n until a sphere of necessary radius fits entirely inside the ReplicatedCrystal.
57   * <br>
58   *
59   * @author Michael J. Schnieders
60   * @see Crystal
61   * @since 1.0
62   */
63  public class ReplicatesCrystal extends Crystal {
64  
65    /**
66     * The logger.
67     */
68    private static final Logger logger = Logger.getLogger(ReplicatesCrystal.class.getName());
69    /**
70     * The base unit cell for the system being simulated.
71     */
72    private final Crystal unitCell;
73    /**
74     * The cut-off distance in Angstroms.
75     */
76    private final double cutOff;
77    /**
78     * The number of replicates along the a-axis.
79     */
80    private int l;
81    /**
82     * The number of replicates along the b-axis.
83     */
84    private int m;
85    /**
86     * The number of replicates along the c-axis.
87     */
88    private int n;
89  
90    /**
91     * Constructor for a ReplicatesCrystal.
92     *
93     * @param unitCell The base unit cell.
94     * @param l        Number of replicates along the a-axis.
95     * @param m        Number of replicates along the b-axis.
96     * @param n        Number of replicates along the c-axis.
97     * @param cutOff2  Twice the cut-off distance.
98     * @since 1.0
99     */
100   public ReplicatesCrystal(Crystal unitCell, int l, int m, int n, double cutOff2) {
101     super(unitCell.a * l, unitCell.b * m, unitCell.c * n, unitCell.alpha, unitCell.beta, unitCell.gamma, unitCell.spaceGroup.shortName);
102     this.unitCell = unitCell;
103 
104     assert (l >= 1);
105     assert (m >= 1);
106     assert (n >= 1);
107     this.l = l;
108     this.m = m;
109     this.n = n;
110     this.cutOff = cutOff2 / 2.0;
111 
112     /*
113      At this point, the ReplicatesCrystal references a SpaceGroup instance
114      whose symmetry operators are inconsistent. This is corrected by
115      generating symmetry operators to fill up the ReplicatesCrystal based
116      on the asymmetric unit.
117     */
118     updateReplicateOperators();
119   }
120 
121   /**
122    * The number of replicates along the a-axis.
123    *
124    * @return The number of replicates along the a-axis.
125    */
126   public int getL() {
127     return l;
128   }
129 
130   /**
131    * The number of replicates along the b-axis.
132    *
133    * @return The number of replicates along the b-axis.
134    */
135   public int getM() {
136     return m;
137   }
138 
139   /**
140    * The number of replicates along the c-axis.
141    *
142    * @return The number of replicates along the c-axis.
143    */
144   public int getN() {
145     return n;
146   }
147 
148   /**
149    * Returns a ReplicatesCrystal large enough to satisfy the minimum image convention for the
150    * specified unit cell and cutoff criteria. If the unit cell is already sufficiently large, then it
151    * is returned.
152    *
153    * @param unitCell The unit cell of the crystal.
154    * @param cutOff2  Two times the cutoff distance.
155    * @return A Crystal or ReplicatesCrystal large enough to satisfy the minimum image convention.
156    */
157   public static Crystal replicatesCrystalFactory(Crystal unitCell, double cutOff2) {
158 
159     return replicatesCrystalFactory(unitCell, cutOff2, new int[3]);
160   }
161 
162   /**
163    * Returns a ReplicatesCrystal large enough to satisfy the minimum image convention for the
164    * specified unit cell and cutoff criteria. If the unit cell is already sufficiently large, then it
165    * is returned.
166    *
167    * @param unitCell         The unit cell of the crystal.
168    * @param cutOff2          Two times the cutoff distance.
169    * @param replicatesVector The number of replicates along the a, b, and c axes.
170    * @return A Crystal or ReplicatesCrystal large enough to satisfy the minimum image convention.
171    */
172   public static Crystal replicatesCrystalFactory(Crystal unitCell, double cutOff2, int[] replicatesVector) {
173 
174     if (unitCell == null || unitCell.aperiodic()) {
175       replicatesVector[0] = 0;
176       replicatesVector[1] = 0;
177       replicatesVector[2] = 0;
178       return unitCell;
179     }
180 
181     int l = 1;
182     int m = 1;
183     int n = 1;
184 
185     double cutOff = cutOff2 / 2.0;
186 
187     while (unitCell.interfacialRadiusA * l < cutOff) {
188       l++;
189     }
190     while (unitCell.interfacialRadiusB * m < cutOff) {
191       m++;
192     }
193     while (unitCell.interfacialRadiusC * n < cutOff) {
194       n++;
195     }
196 
197     replicatesVector[0] = l;
198     replicatesVector[1] = m;
199     replicatesVector[2] = n;
200     return new ReplicatesCrystal(unitCell, l, m, n, cutOff2);
201 
202   }
203 
204   /**
205    * Change the cell vectors for the base unit cell, which is followed by an update of the
206    * ReplicateCrystal parameters and possibly the number of replicated cells.
207    *
208    * @param cellVectors 3x3 matrix of cell vectors.
209    * @return True if the perturbation of cell vectors succeeds.
210    */
211   public boolean setCellVectors(double[][] cellVectors) {
212 
213     // First, update the parameters of the unit cell.
214     if (unitCell.setCellVectors(cellVectors)) {
215 
216       // Then, update the parameters of the ReplicatesCrystal and possibly the number of replicates.
217       return updateReplicatesDimensions();
218     }
219     return false;
220   }
221 
222   /**
223    * Change the cell vectors and volume for the base unit cell, which is followed by an update of the
224    * ReplicateCrystal parameters and possibly the number of replicated cells.
225    *
226    * @param cellVectors    3x3 matrix of cell vectors.
227    * @param targetAUVolume the target volume for the new cell Vectors.
228    * @return True if the perturbation of cell vectors succeeds.
229    */
230   public boolean setCellVectorsAndVolume(double[][] cellVectors, double targetAUVolume) {
231     // First, update the parameters of the unit cell.
232     if (unitCell.setCellVectorsAndVolume(cellVectors, targetAUVolume)) {
233 
234       // Then, update the parameters of the ReplicatesCrystal and possibly the number of replicates.
235       return updateReplicatesDimensions();
236     }
237     return false;
238   }
239 
240   /**
241    * Change the cell parameters for the base unit cell, which is followed by an update of the
242    * ReplicateCrystal parameters and possibly the number of replicated cells.
243    *
244    * @param a     The length of the a-axis for the base unit cell (in Angstroms).
245    * @param b     The length of the b-axis for the base unit cell (in Angstroms).
246    * @param c     The length of the c-axis for the base unit cell (in Angstroms).
247    * @param alpha The angle between the b-axis and c-axis (in Degrees).
248    * @param beta  The angle between the a-axis and c-axis (in Degrees).
249    * @param gamma The angle between the a-axis and b-axis (in Degrees).
250    * @return True is returned if the unit cell and replicates cell are updated successfully.
251    */
252   @Override
253   public boolean changeUnitCellParameters(double a, double b, double c, double alpha, double beta, double gamma) {
254 
255     // First, update the parameters of the unit cell.
256     if (unitCell.changeUnitCellParameters(a, b, c, alpha, beta, gamma)) {
257 
258       // Then, update the parameters of the ReplicatesCrystal and possibly the number of replicates.
259       return updateReplicatesDimensions();
260     }
261     return false;
262   }
263 
264   /**
265    * Change the cell parameters for the base unit cell, which is followed by an update of the
266    * ReplicateCrystal parameters and possibly the number of replicated cells.
267    *
268    * @param a              The length of the a-axis for the base unit cell (in Angstroms).
269    * @param b              The length of the b-axis for the base unit cell (in Angstroms).
270    * @param c              The length of the c-axis for the base unit cell (in Angstroms).
271    * @param alpha          The angle between the b-axis and c-axis (in Degrees).
272    * @param beta           The angle between the a-axis and c-axis (in Degrees).
273    * @param gamma          The angle between the a-axis and b-axis (in Degrees).
274    * @param targetAUVolume the target volume for the new cell Vectors.
275    * @return True is returned if the unit cell and replicates cell are updated successfully.
276    */
277   @Override
278   public boolean changeUnitCellParametersAndVolume(double a, double b, double c, double alpha, double beta, double gamma, double targetAUVolume) {
279 
280     // First, update the parameters of the unit cell.
281     if (unitCell.changeUnitCellParametersAndVolume(a, b, c, alpha, beta, gamma, targetAUVolume)) {
282 
283       // Then, update the parameters of the ReplicatesCrystal and possibly the number of replicates.
284       return updateReplicatesDimensions();
285     }
286     return false;
287   }
288 
289 
290   /**
291    * Two crystals are equal only if all unit cell parameters are exactly the same.
292    */
293   @Override
294   public boolean equals(Object o) {
295     if (this == o) {
296       return true;
297     }
298     if (o == null || getClass() != o.getClass()) {
299       return false;
300     }
301     ReplicatesCrystal replicatesCrystal = (ReplicatesCrystal) o;
302     return (
303         Objects.equals(unitCell, replicatesCrystal.unitCell)
304             && a == replicatesCrystal.a
305             && b == replicatesCrystal.b
306             && c == replicatesCrystal.c
307             && alpha == replicatesCrystal.alpha
308             && beta == replicatesCrystal.beta
309             && gamma == replicatesCrystal.gamma
310             && l == replicatesCrystal.l
311             && m == replicatesCrystal.m
312             && n == replicatesCrystal.n
313             && spaceGroup.number == replicatesCrystal.spaceGroup.number
314             && spaceGroup.symOps.size() == replicatesCrystal.spaceGroup.symOps.size());
315   }
316 
317   /**
318    * {@inheritDoc}
319    */
320   @Override
321   public boolean getCheckRestrictions() {
322     return unitCell.getCheckRestrictions();
323   }
324 
325   /**
326    * {@inheritDoc}
327    */
328   @Override
329   public void setCheckRestrictions(boolean checkRestrictions) {
330     this.checkRestrictions = checkRestrictions;
331     unitCell.setCheckRestrictions(checkRestrictions);
332   }
333 
334   /**
335    * Return the density of the ReplicatesCrystal.
336    *
337    * @param mass The mass of the ReplicatesCrystal.
338    * @return The density.
339    */
340   public double getDensity(double mass) {
341     return unitCell.getDensity(mass);
342   }
343 
344   /**
345    * {@inheritDoc}
346    *
347    * <p>Returns the unit cell for this ReplicatesCrystal. This is useful for the reciprocal space
348    * portion of PME that operates on the unit cell even though the real space cutoff requires a
349    * ReplicatesCrystal.
350    */
351   @Override
352   public Crystal getUnitCell() {
353     return unitCell;
354   }
355 
356   /**
357    * Update the ReplicatesCrystal using random parameters with the target density.
358    *
359    * @param density Target density.
360    * @param mass    Mass of the ReplicatesCrystal.
361    */
362   public boolean randomParameters(double density, double mass) {
363     boolean succeed = unitCell.randomParameters(density, mass);
364     if (succeed) {
365       updateReplicatesDimensions();
366     }
367     return succeed;
368   }
369 
370   /**
371    * Update the ReplicatesCrystal dimensions to the target density.
372    *
373    * @param density Target density.
374    * @param mass    Mass of the ReplicatesCrystal.
375    */
376   public void setDensity(double density, double mass) {
377     unitCell.setDensity(density, mass);
378     updateReplicatesDimensions();
379   }
380 
381   /**
382    * {@inheritDoc}
383    *
384    * <p>Include information about the base unit cell and replicates cell.
385    */
386   @Override
387   public String toString() {
388     StringBuilder sb = new StringBuilder(unitCell.toString());
389     // Only log the replicates cell if there is more than one replicate.
390     if (l * m * n > 1) {
391       sb.append("\n\n Replicates Cell\n");
392       sb.append(format("  Dimension:                    (%3d x%3d x%3d)\n", l, m, n));
393       sb.append(format("  A-axis:                              %8.3f\n", a));
394       sb.append(format("  B-axis:                              %8.3f\n", b));
395       sb.append(format("  C-axis:                              %8.3f\n", c));
396       sb.append(format("  Alpha:                               %8.3f\n", alpha));
397       sb.append(format("  Beta:                                %8.3f\n", beta));
398       sb.append(format("  Gamma:                               %8.3f\n", gamma));
399       sb.append(format("  Total Symmetry Operators:            %8d", spaceGroup.getNumberOfSymOps()));
400     }
401     return sb.toString();
402   }
403 
404   /**
405    * A String containing the replicated unit cell parameters.
406    *
407    * @return A string with the unit cell parameters.
408    */
409   public String toShortString() {
410     return format("%6.2f %6.2f %6.2f %6.2f %6.2f %6.2f (%3d x%3d x%3d) ", a, b, c, alpha, beta, gamma, l, m, n);
411   }
412 
413   private boolean updateReplicatesDimensions() {
414     // Then, update the parameters of the ReplicatesCrystal and possibly the number of replicates.
415     int ll = 1;
416     int mm = 1;
417     int nn = 1;
418 
419     while (unitCell.interfacialRadiusA * ll < cutOff) {
420       ll++;
421     }
422     while (unitCell.interfacialRadiusB * mm < cutOff) {
423       mm++;
424     }
425     while (unitCell.interfacialRadiusC * nn < cutOff) {
426       nn++;
427     }
428     if (super.changeUnitCellParameters(unitCell.a * ll, unitCell.b * mm, unitCell.c * nn, unitCell.alpha, unitCell.beta, unitCell.gamma)) {
429       l = ll;
430       m = mm;
431       n = nn;
432       updateReplicateOperators();
433       return true;
434     }
435 
436     return false;
437   }
438 
439   /**
440    * Update the list of symmetry operators used to generate the replicates super-cell from the
441    * asymmetric unit.
442    */
443   private void updateReplicateOperators() {
444     List<SymOp> symOps = spaceGroup.symOps;
445 
446     // First, we remove the existing symmetry operators.
447     symOps.clear();
448 
449     /*
450      Now create symmetry operators for each replicate. Note that the first
451      symOp is still equivalent to the asymmetric unit and the first set of
452      symOps are still equivalent to the unit cell.
453     */
454     double dX = 1.0 / (double) l;
455     double dY = 1.0 / (double) m;
456     double dZ = 1.0 / (double) n;
457     int symOpCount = 0;
458     List<SymOp> ucSymOps = unitCell.spaceGroup.symOps;
459     for (int i = 0; i < l; i++) {
460       for (int j = 0; j < m; j++) {
461         for (int k = 0; k < n; k++) {
462           int ii = 0;
463           for (SymOp symOp : ucSymOps) {
464             double[] repTrans = new double[3];
465             repTrans[0] = (symOp.tr[0] + i) * dX;
466             repTrans[1] = (symOp.tr[1] + j) * dY;
467             repTrans[2] = (symOp.tr[2] + k) * dZ;
468             SymOp repSymOp = new SymOp(symOp.rot, repTrans, new int[]{i, j, k});
469             symOps.add(repSymOp);
470             if (logger.isLoggable(Level.FINEST)) {
471               logger.finest(format("\n SymOp %d (%2d,%2d,%2d): %d", symOpCount, i, j, k, ii));
472               logger.finest(repSymOp.toString());
473             }
474             ii++;
475             symOpCount++;
476           }
477         }
478       }
479     }
480   }
481 }