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 }