/** * Fundamentally, an alignment is just a list of aligned residues in each protein. This method * converts two lists of ResidueNumbers into an AFPChain. * * <p>Parameters are filled with defaults (often null) or sometimes calculated. * * <p>For a way to modify the alignment of an existing AFPChain, see {@link * AlignmentTools#replaceOptAln(AFPChain, Atom[], Atom[], Map)} * * @param ca1 CA atoms of the first protein * @param ca2 CA atoms of the second protein * @param aligned1 A list of aligned residues from the first protein * @param aligned2 A list of aligned residues from the second protein. Must be the same length as * aligned1. * @return An AFPChain representing the alignment. Many properties may be null or another default. * @throws StructureException if an error occured during superposition * @throws IllegalArgumentException if aligned1 and aligned2 have different lengths * @see AlignmentTools#replaceOptAln(AFPChain, Atom[], Atom[], Map) */ public static AFPChain createAFPChain( Atom[] ca1, Atom[] ca2, ResidueNumber[] aligned1, ResidueNumber[] aligned2) throws StructureException { // input validation int alnLen = aligned1.length; if (alnLen != aligned2.length) { throw new IllegalArgumentException("Alignment lengths are not equal"); } AFPChain a = new AFPChain(AFPChain.UNKNOWN_ALGORITHM); try { a.setName1(ca1[0].getGroup().getChain().getStructure().getName()); if (ca2[0].getGroup().getChain().getStructure() != null) { // common case for cloned ca2 a.setName2(ca2[0].getGroup().getChain().getStructure().getName()); } } catch (Exception e) { // One of the structures wasn't fully created. Ignore } a.setBlockNum(1); a.setCa1Length(ca1.length); a.setCa2Length(ca2.length); a.setOptLength(alnLen); a.setOptLen(new int[] {alnLen}); Matrix[] ms = new Matrix[a.getBlockNum()]; a.setBlockRotationMatrix(ms); Atom[] blockShiftVector = new Atom[a.getBlockNum()]; a.setBlockShiftVector(blockShiftVector); String[][][] pdbAln = new String[1][2][alnLen]; for (int i = 0; i < alnLen; i++) { pdbAln[0][0][i] = aligned1[i].getChainId() + ":" + aligned1[i]; pdbAln[0][1][i] = aligned2[i].getChainId() + ":" + aligned2[i]; } a.setPdbAln(pdbAln); // convert pdbAln to optAln, and fill in some other basic parameters AFPChainXMLParser.rebuildAFPChain(a, ca1, ca2); return a; // Currently a single block. Split into several blocks by sequence if needed // return AlignmentTools.splitBlocksByTopology(a,ca1,ca2); }
/** * After the alignment changes (optAln, optLen, blockNum, at a minimum), many other properties * which depend on the superposition will be invalid. * * <p>This method re-runs a rigid superposition over the whole alignment and repopulates the * required properties, including RMSD (TotalRMSD) and TM-Score. * * @param afpChain * @param ca1 * @param ca2 Second set of ca atoms. Will be modified based on the superposition * @throws StructureException * @see {@link CECalculator#calc_rmsd(Atom[], Atom[], int, boolean)} contains much of the same * code, but stores results in a CECalculator instance rather than an AFPChain */ public static void updateSuperposition(AFPChain afpChain, Atom[] ca1, Atom[] ca2) throws StructureException { // Update ca information, because the atom array might also be changed afpChain.setCa1Length(ca1.length); afpChain.setCa2Length(ca2.length); // We need this to get the correct superposition int[] focusRes1 = afpChain.getFocusRes1(); int[] focusRes2 = afpChain.getFocusRes2(); if (focusRes1 == null) { focusRes1 = new int[afpChain.getCa1Length()]; afpChain.setFocusRes1(focusRes1); } if (focusRes2 == null) { focusRes2 = new int[afpChain.getCa2Length()]; afpChain.setFocusRes2(focusRes2); } if (afpChain.getNrEQR() == 0) return; // create new arrays for the subset of atoms in the alignment. Atom[] ca1aligned = new Atom[afpChain.getOptLength()]; Atom[] ca2aligned = new Atom[afpChain.getOptLength()]; int pos = 0; int[] blockLens = afpChain.getOptLen(); int[][][] optAln = afpChain.getOptAln(); assert (afpChain.getBlockNum() <= optAln.length); for (int block = 0; block < afpChain.getBlockNum(); block++) { for (int i = 0; i < blockLens[block]; i++) { int pos1 = optAln[block][0][i]; int pos2 = optAln[block][1][i]; Atom a1 = ca1[pos1]; Atom a2 = (Atom) ca2[pos2].clone(); ca1aligned[pos] = a1; ca2aligned[pos] = a2; pos++; } } // this can happen when we load an old XML serialization which did not support modern ChemComp // representation of modified residues. if (pos != afpChain.getOptLength()) { logger.warn( "AFPChainScorer getTMScore: Problems reconstructing alignment! nr of loaded atoms is " + pos + " but should be " + afpChain.getOptLength()); // we need to resize the array, because we allocated too many atoms earlier on. ca1aligned = (Atom[]) resizeArray(ca1aligned, pos); ca2aligned = (Atom[]) resizeArray(ca2aligned, pos); } // Superimpose the two structures in correspondance to the new alignment SVDSuperimposer svd = new SVDSuperimposer(ca1aligned, ca2aligned); Matrix matrix = svd.getRotation(); Atom shift = svd.getTranslation(); Matrix[] blockMxs = new Matrix[afpChain.getBlockNum()]; Arrays.fill(blockMxs, matrix); afpChain.setBlockRotationMatrix(blockMxs); Atom[] blockShifts = new Atom[afpChain.getBlockNum()]; Arrays.fill(blockShifts, shift); afpChain.setBlockShiftVector(blockShifts); for (Atom a : ca2aligned) { Calc.rotate(a, matrix); Calc.shift(a, shift); } // Calculate the RMSD and TM score for the new alignment double rmsd = SVDSuperimposer.getRMS(ca1aligned, ca2aligned); double tmScore = SVDSuperimposer.getTMScore(ca1aligned, ca2aligned, ca1.length, ca2.length); afpChain.setTotalRmsdOpt(rmsd); afpChain.setTMScore(tmScore); // Calculate the RMSD and TM score for every block of the new alignment double[] blockRMSD = new double[afpChain.getBlockNum()]; double[] blockScore = new double[afpChain.getBlockNum()]; for (int k = 0; k < afpChain.getBlockNum(); k++) { // Create the atom arrays corresponding to the aligned residues in the block Atom[] ca1block = new Atom[afpChain.getOptLen()[k]]; Atom[] ca2block = new Atom[afpChain.getOptLen()[k]]; int position = 0; for (int i = 0; i < blockLens[k]; i++) { int pos1 = optAln[k][0][i]; int pos2 = optAln[k][1][i]; Atom a1 = ca1[pos1]; Atom a2 = (Atom) ca2[pos2].clone(); ca1block[position] = a1; ca2block[position] = a2; position++; } if (position != afpChain.getOptLen()[k]) { logger.warn( "AFPChainScorer getTMScore: Problems reconstructing block alignment! nr of loaded atoms is " + pos + " but should be " + afpChain.getOptLen()[k]); // we need to resize the array, because we allocated too many atoms earlier on. ca1block = (Atom[]) resizeArray(ca1block, position); ca2block = (Atom[]) resizeArray(ca2block, position); } // Superimpose the two block structures SVDSuperimposer svdb = new SVDSuperimposer(ca1block, ca2block); Matrix matrixb = svdb.getRotation(); Atom shiftb = svdb.getTranslation(); for (Atom a : ca2block) { Calc.rotate(a, matrixb); Calc.shift(a, shiftb); } // Calculate the RMSD and TM score for the block double rmsdb = SVDSuperimposer.getRMS(ca1block, ca2block); double tmScoreb = SVDSuperimposer.getTMScore(ca1block, ca2block, ca1.length, ca2.length); blockRMSD[k] = rmsdb; blockScore[k] = tmScoreb; } afpChain.setOptRmsd(blockRMSD); afpChain.setBlockRmsd(blockRMSD); afpChain.setBlockScore(blockScore); }