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}