001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 * Created on Dec 4, 2005 021 * 022 */ 023package org.biojava.nbio.structure; 024 025import javax.vecmath.Matrix4d; 026 027import org.biojava.nbio.structure.jama.Matrix; 028import org.biojava.nbio.structure.jama.SingularValueDecomposition; 029 030 031/** A class that calculates the superimposition between two sets of atoms 032 * inspired by the biopython SVDSuperimposer class... 033 * 034 * 035 * example usage: 036 * <pre> 037 // get some arbitrary amino acids from somewhere 038 String filename = "/Users/ap3/WORK/PDB/5pti.pdb" ; 039 040 PDBFileReader pdbreader = new PDBFileReader(); 041 Structure struc = pdbreader.getStructure(filename); 042 Group g1 = (Group)struc.getChain(0).getGroup(21).clone(); 043 Group g2 = (Group)struc.getChain(0).getGroup(53).clone(); 044 045 if ( g1.getPDBName().equals("GLY")){ 046 if ( g1 instanceof AminoAcid){ 047 Atom cb = Calc.createVirtualCBAtom((AminoAcid)g1); 048 g1.addAtom(cb); 049 } 050 } 051 052 if ( g2.getPDBName().equals("GLY")){ 053 if ( g2 instanceof AminoAcid){ 054 Atom cb = Calc.createVirtualCBAtom((AminoAcid)g2); 055 g2.addAtom(cb); 056 } 057 } 058 059 Structure struc2 = new StructureImpl((Group)g2.clone()); 060 061 System.out.println(g1); 062 System.out.println(g2); 063 064 065 Atom[] atoms1 = new Atom[3]; 066 Atom[] atoms2 = new Atom[3]; 067 068 atoms1[0] = g1.getAtom("N"); 069 atoms1[1] = g1.getAtom("CA"); 070 atoms1[2] = g1.getAtom("CB"); 071 072 073 atoms2[0] = g2.getAtom("N"); 074 atoms2[1] = g2.getAtom("CA"); 075 atoms2[2] = g2.getAtom("CB"); 076 077 078 SVDSuperimposer svds = new SVDSuperimposer(atoms1,atoms2); 079 080 081 Matrix rotMatrix = svds.getRotation(); 082 Atom tranMatrix = svds.getTranslation(); 083 084 085 // now we have all the info to perform the rotations ... 086 087 Calc.rotate(struc2,rotMatrix); 088 089 // shift structure 2 onto structure one ... 090 Calc.shift(struc2,tranMatrix); 091 092 // 093 // write the whole thing to a file to view in a viewer 094 095 String outputfile = "/Users/ap3/WORK/PDB/rotated.pdb"; 096 097 FileOutputStream out= new FileOutputStream(outputfile); 098 PrintStream p = new PrintStream( out ); 099 100 Structure newstruc = new StructureImpl(); 101 102 Chain c1 = new ChainImpl(); 103 c1.setName("A"); 104 c1.addGroup(g1); 105 newstruc.addChain(c1); 106 107 Chain c2 = struc2.getChain(0); 108 c2.setName("B"); 109 newstruc.addChain(c2); 110 111 // show where the group was originally ... 112 Chain c3 = new ChainImpl(); 113 c3.setName("C"); 114 //c3.addGroup(g1); 115 c3.addGroup(g2); 116 117 newstruc.addChain(c3); 118 p.println(newstruc.toPDB()); 119 120 p.close(); 121 122 System.out.println("wrote to file " + outputfile); 123 124 </pre> 125 * 126 * 127 * @author Andreas Prlic 128 * @since 1.5 129 * @version %I% %G% 130 131 */ 132public class SVDSuperimposer { 133 134 Matrix rot; 135 Matrix tran; 136 137 Matrix centroidA; 138 Matrix centroidB; 139 140 /** Create a SVDSuperimposer object and calculate a SVD superimposition of two sets of atoms. 141 * 142 * @param atomSet1 Atom array 1 143 * @param atomSet2 Atom array 2 144 * @throws StructureException 145 */ 146 public SVDSuperimposer(Atom[] atomSet1,Atom[]atomSet2) 147 throws StructureException{ 148 149 if ( atomSet1.length != atomSet2.length ){ 150 throw new StructureException("The two atom sets are not of same length!"); 151 } 152 153 Atom cena = Calc.getCentroid(atomSet1); 154 Atom cenb = Calc.getCentroid(atomSet2); 155 156 double[][] centAcoords = new double[][]{{cena.getX(),cena.getY(),cena.getZ()}}; 157 centroidA = new Matrix(centAcoords); 158 159 double[][] centBcoords = new double[][]{{cenb.getX(),cenb.getY(),cenb.getZ()}}; 160 centroidB = new Matrix(centBcoords); 161 162 // center at centroid 163 164 Atom[] ats1 = Calc.centerAtoms(atomSet1,cena); 165 Atom[] ats2 = Calc.centerAtoms(atomSet2,cenb); 166 167 double[][] coordSet1 = new double[ats1.length][3]; 168 double[][] coordSet2 = new double[ats2.length][3]; 169 170 // copy the atoms into the internal coords; 171 for (int i =0 ; i< ats1.length;i++) { 172 coordSet1[i] = ats1[i].getCoords(); 173 coordSet2[i] = ats2[i].getCoords(); 174 } 175 176 calculate(coordSet1,coordSet2); 177 178 179 } 180 181 182 /** Do the actual calculation. 183 * 184 * @param coordSet1 coordinates for atom array 1 185 * @param coordSet2 coordiantes for atom array 2 186 */ 187 private void calculate(double[][] coordSet1, double[][]coordSet2){ 188 // now this is the bridge to the Jama package: 189 Matrix a = new Matrix(coordSet1); 190 Matrix b = new Matrix(coordSet2); 191 192 193// # correlation matrix 194 195 Matrix b_trans = b.transpose(); 196 Matrix corr = b_trans.times(a); 197 198 199 SingularValueDecomposition svd = corr.svd(); 200 201 Matrix u = svd.getU(); 202 // v is alreaady transposed ! difference to numermic python ... 203 Matrix vt =svd.getV(); 204 205 Matrix vt_orig = (Matrix) vt.clone(); 206 Matrix u_transp = u.transpose(); 207 208 Matrix rot_nottrans = vt.times(u_transp); 209 rot = rot_nottrans.transpose(); 210 211 // check if we have found a reflection 212 213 double det = rot.det(); 214 215 if (det<0) { 216 vt = vt_orig.transpose(); 217 vt.set(2,0,(0 - vt.get(2,0))); 218 vt.set(2,1,(0 - vt.get(2,1))); 219 vt.set(2,2,(0 - vt.get(2,2))); 220 221 Matrix nv_transp = vt.transpose(); 222 rot_nottrans = nv_transp.times(u_transp); 223 rot = rot_nottrans.transpose(); 224 225 } 226 227 Matrix cb_tmp = centroidB.times(rot); 228 tran = centroidA.minus(cb_tmp); 229 230 231 } 232 233 /** Calculate the RMS (root mean square) deviation of two sets of atoms. 234 * 235 * Atom sets must be pre-rotated. 236 * 237 * @param atomSet1 atom array 1 238 * @param atomSet2 atom array 2 239 * @return the RMS of two atom sets 240 * @throws StructureException 241 */ 242 public static double getRMS(Atom[] atomSet1, Atom[] atomSet2) throws StructureException { 243 if ( atomSet1.length != atomSet2.length ){ 244 throw new StructureException("The two atom sets are not of same length!"); 245 } 246 247 double sum = 0.0; 248 for ( int i =0 ; i < atomSet1.length;i++){ 249 double d = Calc.getDistance(atomSet1[i],atomSet2[i]); 250 sum += (d*d); 251 252 } 253 254 double avd = ( sum/ atomSet1.length); 255 //System.out.println("av dist" + avd); 256 return Math.sqrt(avd); 257 258 } 259 260 /** 261 * Calculate the TM-Score for the superposition. 262 * 263 * <em>Normalizes by the <strong>minimum</strong>-length structure (that is, {@code min\{len1,len2\}}).</em> 264 * 265 * Atom sets must be pre-rotated. 266 * 267 * <p>Citation:<br/> 268 * <i>Zhang Y and Skolnick J (2004). "Scoring function for automated assessment 269 * of protein structure template quality". Proteins 57: 702 - 710.</i> 270 * 271 * @param atomSet1 atom array 1 272 * @param atomSet2 atom array 2 273 * @param len1 The full length of the protein supplying atomSet1 274 * @param len2 The full length of the protein supplying atomSet2 275 * @return The TM-Score 276 * @throws StructureException 277 */ 278 public static double getTMScore(Atom[] atomSet1, Atom[] atomSet2, int len1, int len2) throws StructureException { 279 if ( atomSet1.length != atomSet2.length ){ 280 throw new StructureException("The two atom sets are not of same length!"); 281 } 282 if ( atomSet1.length > len1 ){ 283 throw new StructureException("len1 must be greater or equal to the alignment length!"); 284 } 285 if ( atomSet2.length > len2 ){ 286 throw new StructureException("len2 must be greater or equal to the alignment length!"); 287 } 288 289 int Lmin = Math.min(len1,len2); 290 int Laln = atomSet1.length; 291 292 double d0 = 1.24 * Math.cbrt(Lmin - 15.) - 1.8; 293 double d0sq = d0*d0; 294 295 double sum = 0; 296 for(int i=0;i<Laln;i++) { 297 double d = Calc.getDistance(atomSet1[i],atomSet2[i]); 298 sum+= 1./(1+d*d/d0sq); 299 } 300 301 return sum/Lmin; 302 } 303 304 /** 305 * Calculate the TM-Score for the superposition. 306 * 307 * <em>Normalizes by the <strong>maximum</strong>-length structure (that is, {@code max\{len1,len2\}}) rather than the minimum.</em> 308 * 309 * Atom sets must be pre-rotated. 310 * 311 * <p>Citation:<br/> 312 * <i>Zhang Y and Skolnick J (2004). "Scoring function for automated assessment 313 * of protein structure template quality". Proteins 57: 702 - 710.</i> 314 * 315 * @param atomSet1 atom array 1 316 * @param atomSet2 atom array 2 317 * @param len1 The full length of the protein supplying atomSet1 318 * @param len2 The full length of the protein supplying atomSet2 319 * @return The TM-Score 320 * @throws StructureException 321 * @see {@link #getTMScore(Atom[], Atom[], int, int)}, which normalizes by the minimum length 322 */ 323 public static double getTMScoreAlternate(Atom[] atomSet1, Atom[] atomSet2, int len1, int len2) throws StructureException { 324 if ( atomSet1.length != atomSet2.length ){ 325 throw new StructureException("The two atom sets are not of same length!"); 326 } 327 if ( atomSet1.length > len1 ){ 328 throw new StructureException("len1 must be greater or equal to the alignment length!"); 329 } 330 if ( atomSet2.length > len2 ){ 331 throw new StructureException("len2 must be greater or equal to the alignment length!"); 332 } 333 334 int Lmax = Math.max(len1,len2); 335 int Laln = atomSet1.length; 336 337 double d0 = 1.24 * Math.cbrt(Lmax - 15.) - 1.8; 338 double d0sq = d0*d0; 339 340 double sum = 0; 341 for(int i=0;i<Laln;i++) { 342 double d = Calc.getDistance(atomSet1[i],atomSet2[i]); 343 sum+= 1./(1+d*d/d0sq); 344 } 345 346 return sum/Lmax; 347 } 348 349 /** Get the Rotation matrix that is required to superimpose the two atom sets. 350 * 351 * @return a rotation matrix. 352 */ 353 public Matrix getRotation(){ 354 return rot; 355 } 356 357 /** Get the shift vector. 358 * 359 * @return the shift vector 360 */ 361 public Atom getTranslation(){ 362 363 Atom a = new AtomImpl(); 364 a.setX(tran.get(0,0)); 365 a.setY(tran.get(0,1)); 366 a.setZ(tran.get(0,2)); 367 return a; 368 } 369 370 public Matrix4d getTransformation() { 371 return Calc.getTransformation(rot, tran); 372 } 373 374}