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.crystal;
39  
40  import static java.lang.String.format;
41  
42  import java.util.List;
43  import java.util.Objects;
44  import java.util.logging.Level;
45  import java.util.logging.Logger;
46  import org.apache.commons.math3.linear.MatrixUtils;
47  import org.apache.commons.math3.linear.RealMatrix;
48  
49  /**
50   * The NCSCrystal class extends Crystal to support non-crystallographic symmetry (NCS). The NCS
51   * operators can be obtained from the BIOMT records of a PDB file, and are permuted with the space
52   * group symmetry operators.
53   *
54   * @author Aaron J. Nessler
55   * @author Michael J. Schnieders
56   * @since 1.0
57   */
58  public class NCSCrystal extends Crystal {
59  
60    /** The logger. */
61    private static final Logger logger = Logger.getLogger(NCSCrystal.class.getName());
62    /** The base unit cell for the system being simulated. */
63    private final Crystal unitCell;
64    /** The non-crystallographic symmetry operators needed to be applied to the unit cell. */
65    private final List<SymOp> NCSsymOps;
66  
67    /**
68     * Constructor for a NCSCrystal.
69     *
70     * @param unitCell The base unit cell.
71     * @param NCSsymOps Non-crystallographic symmetry operators applied to the unit cell.
72     * @since 1.0
73     */
74    private NCSCrystal(Crystal unitCell, List<SymOp> NCSsymOps) {
75      super(
76          unitCell.a,
77          unitCell.b,
78          unitCell.c,
79          unitCell.alpha,
80          unitCell.beta,
81          unitCell.gamma,
82          unitCell.spaceGroup.shortName);
83      this.unitCell = unitCell;
84      this.NCSsymOps = NCSsymOps;
85  
86      /*
87       At this point, the NCSCrystal references a SpaceGroup instance
88       that is lacking symmetry operators. This is corrected by generating symmetry
89       operators to fill up the non-crystallographic, which is added to the unit
90       cell based on the asymmetric unit.
91      */
92      updateNCSOperators();
93    }
94  
95    /**
96     * Returns an NCSCrystal by expanding the orignal unit cell with the symmetry operators provided
97     * by the BIOMT records in the PDB files. See REMARK 350.
98     *
99     * @param unitCell The unit cell of the crystal.
100    * @param symOps Symmetry operators for non-crystallographic symmetry
101    * @return A Crystal or NCSCrystal
102    */
103   public static Crystal NCSCrystalFactory(Crystal unitCell, List<SymOp> symOps) {
104 
105     if (unitCell == null || unitCell.aperiodic()) {
106       return unitCell;
107     } else {
108       return new NCSCrystal(unitCell, symOps);
109     }
110   }
111 
112   /**
113    * Change the cell parameters for the base unit cell, which is followed by an update of the
114    * ReplicateCrystal parameters and possibly the number of replicated cells.
115    *
116    * @param a The length of the a-axis for the base unit cell (in Angstroms).
117    * @param b The length of the b-axis for the base unit cell (in Angstroms).
118    * @param c The length of the c-axis for the base unit cell (in Angstroms).
119    * @param alpha The angle between the b-axis and c-axis (in Degrees).
120    * @param beta The angle between the a-axis and c-axis (in Degrees).
121    * @param gamma The angle between the a-axis and b-axis (in Degrees).
122    * @return True is returned if the unit cell and replicates cell are updated successfully.
123    */
124   @Override
125   public boolean changeUnitCellParameters(
126       double a, double b, double c, double alpha, double beta, double gamma) {
127     // First, update the parameters of the unit cell.
128     if (unitCell.changeUnitCellParameters(a, b, c, alpha, beta, gamma)) {
129 
130       // Then, update the parameters of the NCSCrystal.
131       if (super.changeUnitCellParameters(a, b, c, alpha, beta, gamma)) {
132         // Finally, update the NCS operators.
133         updateNCSOperators();
134         return true;
135       }
136     }
137     return false;
138   }
139 
140   /** Two crystals are equal only if all unit cell parameters are exactly the same. */
141   @Override
142   public boolean equals(Object o) {
143     if (this == o) return true;
144     if (o == null || getClass() != o.getClass()) return false;
145     NCSCrystal ncsCrystal = (NCSCrystal) o;
146     return (Objects.equals(unitCell, ncsCrystal.unitCell)
147         && a == ncsCrystal.a
148         && b == ncsCrystal.b
149         && c == ncsCrystal.c
150         && alpha == ncsCrystal.alpha
151         && beta == ncsCrystal.beta
152         && gamma == ncsCrystal.gamma
153         && spaceGroup.number == ncsCrystal.spaceGroup.number
154         && spaceGroup.symOps.size() == ncsCrystal.spaceGroup.symOps.size());
155   }
156 
157   /**
158    * {@inheritDoc}
159    *
160    * <p>Returns the unit cell for this NCSCrystal. This is useful for the reciprocal space portion
161    * of PME that operates on the unit cell.
162    */
163   @Override
164   public Crystal getUnitCell() {
165     return unitCell;
166   }
167 
168   /**
169    * {@inheritDoc}
170    *
171    * <p>Include information about the base unit cell and NCS cell.
172    */
173   @Override
174   public String toString() {
175     StringBuilder sb = new StringBuilder(unitCell.toString());
176     sb.append("\n\n Non-Crystallographic Cell\n");
177     sb.append(format("  A-axis:                              %8.3f\n", a));
178     sb.append(format("  B-axis:                              %8.3f\n", b));
179     sb.append(format("  C-axis:                              %8.3f\n", c));
180     sb.append(format("  Alpha:                               %8.3f\n", alpha));
181     sb.append(format("  Beta:                                %8.3f\n", beta));
182     sb.append(format("  Gamma:                               %8.3f\n", gamma));
183     sb.append(
184         format("  UnitCell Symmetry Operators:         %8d\n", unitCell.spaceGroup.symOps.size()));
185     sb.append(format("  NCS Symmetry Operators:              %8d\n", NCSsymOps.size()));
186     sb.append(format("  Total Symmetry Operators:            %8d", spaceGroup.getNumberOfSymOps()));
187     return sb.toString();
188   }
189 
190   /**
191    * Update the list of symmetry operators to contain the non-crystallographic operations as well.
192    */
193   private void updateNCSOperators() {
194     List<SymOp> symOps = spaceGroup.symOps;
195 
196     // First, we remove the existing symmetry operators.
197     symOps.clear();
198 
199     /*
200      Symmetry operators are produced that are the combination of the non-crystallographic and the unit cell symmetry operations.
201      Note that the first symOp is still equivalent to the asymmetric unit and the first set of
202      symOps are still equivalent to the unit cell.
203     */
204     int ii = 0;
205     int soRemoved = 0;
206     for (SymOp NCSsymOp : NCSsymOps) {
207       // All UC symOps seem to have collision issues... Use 1st (identity) for now...
208       for (SymOp symOp : unitCell.spaceGroup.symOps) {
209         //            SymOp symOp=unitCell.spaceGroup.symOps.get(0); //Identity matrix is standard
210         // first matrix. (P1)
211         double[] NCSTrans = new double[3];
212         RealMatrix NCS = MatrixUtils.createRealMatrix(NCSsymOp.rot);
213         RealMatrix UC = MatrixUtils.createRealMatrix(symOp.rot);
214         RealMatrix result = NCS.multiply(UC); // Abelian groups order doesnt matter...
215         double[][] NCSRot = result.getData();
216         NCSTrans[0] = symOp.tr[0] + NCSsymOp.tr[0];
217         NCSTrans[1] = symOp.tr[1] + NCSsymOp.tr[1];
218         NCSTrans[2] = symOp.tr[2] + NCSsymOp.tr[2];
219         SymOp NCSSymOp = new SymOp(NCSRot, NCSTrans);
220         if (!symOps.contains(NCSSymOp)) {
221           symOps.add(NCSSymOp);
222         } else {
223           soRemoved++;
224         }
225         if (logger.isLoggable(Level.FINEST)) {
226           logger.finest(format("\n SymOp: %d", ii));
227           logger.finest(NCSSymOp.toString());
228         }
229         ii++;
230       }
231     }
232     if (soRemoved != 0) {
233       logger.warning(format("\n NCS Replicated SymOps Removed: %d", soRemoved));
234     }
235   }
236 }