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 */
021package org.biojava.nbio.structure.symmetry.internal;
022
023import java.util.ArrayList;
024import java.util.List;
025
026import javax.vecmath.Matrix4d;
027
028import org.biojava.nbio.structure.Atom;
029import org.biojava.nbio.structure.Structure;
030import org.biojava.nbio.structure.StructureException;
031import org.biojava.nbio.structure.StructureIdentifier;
032import org.biojava.nbio.structure.StructureTools;
033import org.biojava.nbio.structure.align.ce.CECalculator;
034import org.biojava.nbio.structure.align.ce.CeCPMain;
035import org.biojava.nbio.structure.align.ce.MatrixListener;
036import org.biojava.nbio.structure.align.model.AFPChain;
037import org.biojava.nbio.structure.align.multiple.MultipleAlignment;
038import org.biojava.nbio.structure.align.util.AFPChainScorer;
039import org.biojava.nbio.structure.jama.Matrix;
040import org.biojava.nbio.structure.secstruc.SecStrucCalc;
041import org.biojava.nbio.structure.secstruc.SecStrucTools;
042import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.SymmetryType;
043import org.biojava.nbio.structure.symmetry.utils.SymmetryTools;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047/**
048 * Identify the symmetries in a structure by running an alignment of the
049 * structure against itself disabling the diagonal of the identity alignment.
050 * <p>
051 * Iterating over previous results and disabling the diagonal of the previous
052 * alignments can also be done with this implementation, which will generate a
053 * set of self-alignments (disabled, because none improvements were shown, but
054 * can be turn on manually).
055 * <p>
056 * Multiple levels of symmetry can be analyzed by finding symmetries in repeats
057 * of previous results. This feature allows to find multiple symmetry axes.
058 * <p>
059 * The alignment is then refined to obtain a consistent alignment among all
060 * residues of the structure and organized into different parts, called
061 * symmetric repeats.
062 * <p>
063 * After refinement of the initial alignment, an optimization step can be used
064 * to improve the overall score of the repeat multiple alignment.
065 *
066 * @author Andreas Prlic
067 * @author Spencer Bliven
068 * @author Aleix Lafita
069 * @since 4.1.1
070 *
071 */
072public class CeSymm {
073
074        /**
075         * Version History:
076         * <p>
077         * <ul>
078         * <li>1.0 - initial implementation of CE-Symm.
079         * <li>1.1 - enable multiple CE-Symm runs to calculate all self-alignments.
080         * <li>2.0 - refine the alignment for consistency of repeat definition.
081         * <li>2.1 - optimize the alignment to improve the score.
082         * <li>2.2 - run multiple symmetry levels recursively to find PG and
083         * hierarchical symmetries.
084         * </ul>
085         */
086        public static final String version = "2.2";
087        public static final String algorithmName = "jCE-symm";
088        private static final Logger logger = LoggerFactory.getLogger(CeSymm.class);
089        private final static boolean multiPass = false; // multiple self-alignments
090
091        /**
092         * Prevent instantiation. Static class.
093         */
094        private CeSymm() {
095        }
096
097        private static Matrix align(AFPChain afpChain, Atom[] ca1, Atom[] ca2,
098                        CESymmParameters params, Matrix origM, CECalculator calculator,
099                        int counter) throws StructureException {
100
101                int fragmentLength = params.getWinSize();
102                Atom[] ca2clone = StructureTools.cloneAtomArray(ca2);
103
104                int rows = ca1.length;
105                int cols = ca2.length;
106
107                // Matrix that tracks similarity of a fragment of length fragmentLength
108                // starting a position i,j.
109
110                int blankWindowSize = fragmentLength;
111                if (origM == null) {
112
113                        // Build alignment ca1 to ca2-ca2
114                        afpChain = calculator.extractFragments(afpChain, ca1, ca2clone);
115
116                        origM = SymmetryTools.blankOutPreviousAlignment(afpChain, ca2,
117                                        rows, cols, calculator, null, blankWindowSize);
118
119                } else {
120                        // we are doing an iteration on a previous alignment
121                        // mask the previous alignment
122                        origM = SymmetryTools.blankOutPreviousAlignment(afpChain, ca2,
123                                        rows, cols, calculator, origM, blankWindowSize);
124                }
125
126                Matrix clone = (Matrix) origM.clone();
127
128                // that's the matrix to run the alignment on..
129                calculator.setMatMatrix(clone.getArray());
130
131                calculator.traceFragmentMatrix(afpChain, ca1, ca2clone);
132
133                final Matrix origMfinal = (Matrix) origM.clone();
134                // Add a matrix listener to keep the blacked zones in max.
135                calculator.addMatrixListener(new MatrixListener() {
136
137                        @Override
138                        public double[][] matrixInOptimizer(double[][] max) {
139
140                                // Check every entry of origM for blacked out regions
141                                for (int i = 0; i < max.length; i++) {
142                                        for (int j = 0; j < max[i].length; j++) {
143                                                if (origMfinal.getArray()[i][j] > 1e9) {
144                                                        max[i][j] = -origMfinal.getArray()[i][j];
145                                                }
146                                        }
147                                }
148                                return max;
149                        }
150
151                        @Override
152                        public boolean[][] initializeBreakFlag(boolean[][] brkFlag) {
153
154                                return brkFlag;
155                        }
156                });
157
158                calculator.nextStep(afpChain, ca1, ca2clone);
159
160                afpChain.setAlgorithmName(algorithmName);
161                afpChain.setVersion(version);
162
163                afpChain.setDistanceMatrix(origM);
164
165                return origMfinal;
166
167        }
168
169        @SuppressWarnings("unused")
170        protected static CeSymmResult align(Atom[] atoms, CESymmParameters params)
171                        throws StructureException {
172
173                CeSymmResult result = new CeSymmResult();
174                result.setParams(params);
175                result.setAtoms(atoms);
176
177                // STEP 1: prepare all the information for the symmetry alignment
178                Atom[] ca2 = StructureTools.duplicateCA2(atoms);
179                int rows = atoms.length;
180                int cols = ca2.length;
181
182                if (rows == 0 || cols == 0) {
183                        throw new StructureException("Aligning empty structure");
184                }
185
186                Matrix origM = null;
187                AFPChain myAFP = new AFPChain(algorithmName);
188                CECalculator calculator = new CECalculator(params);
189                Matrix lastMatrix = null;
190
191                List<AFPChain> selfAlignments = new ArrayList<>();
192                AFPChain optimalAFP = null;
193
194                // STEP 2: perform the self-alignments of the structure
195                int i = 0;
196                do {
197                        if (origM != null)
198                                myAFP.setDistanceMatrix((Matrix) origM.clone());
199
200                        origM = align(myAFP, atoms, ca2, params, origM, calculator, i);
201
202                        double tmScore2 = AFPChainScorer.getTMScore(myAFP, atoms, ca2);
203                        myAFP.setTMScore(tmScore2);
204
205                        AFPChain newAFP = (AFPChain) myAFP.clone();
206                        newAFP = CeCPMain.postProcessAlignment(newAFP, atoms, ca2,
207                                        calculator);
208
209                        // Calculate and set the TM score for the newAFP alignment
210                        double tmScore3 = AFPChainScorer.getTMScore(newAFP, atoms, ca2);
211                        newAFP.setTMScore(tmScore3);
212
213                        // Determine if the alignment is significant, stop if false
214                        if (tmScore3 < params.getUnrefinedScoreThreshold()) {
215                                // If it is the first alignment save it anyway
216                                if (i == 0)
217                                        selfAlignments.add(newAFP);
218                                // store final matrix
219                                lastMatrix = newAFP.getDistanceMatrix().copy();
220                                break;
221                        }
222
223                        // If it is a symmetric alignment add it to the allAlignments list
224                        selfAlignments.add(newAFP);
225
226                        i++;
227
228                } while (i < params.getMaxSymmOrder() && multiPass);
229
230                // We reached the maximum order, so blank out the final alignment
231                if (lastMatrix == null && selfAlignments.size() > 1 && multiPass) {
232                        AFPChain last = selfAlignments.get(selfAlignments.size() - 1);
233                        lastMatrix = SymmetryTools.blankOutPreviousAlignment(last, ca2,
234                                        last.getCa1Length(), last.getCa2Length(), calculator,
235                                        origM, params.getWinSize());
236                        lastMatrix = lastMatrix.getMatrix(0, last.getCa1Length() - 1, 0,
237                                        last.getCa2Length() - 1);
238                }
239
240                // Extract the structure identifier
241                optimalAFP = selfAlignments.get(0);
242                StructureIdentifier id = atoms[0].getGroup().getChain().getStructure()
243                                .getStructureIdentifier();
244                if(id != null) {
245                        optimalAFP.setName1(id.getIdentifier());
246                        optimalAFP.setName2(id.getIdentifier());
247                }
248
249                // Store the optimal self-alignment
250                result.setSelfAlignment(optimalAFP);
251                result.setStructureId(id);
252
253                // Determine the symmetry Type or get the one in params
254                SymmetryType type = params.getSymmType();
255                if (type == SymmetryType.AUTO) {
256                        if (result.getSelfAlignment().getBlockNum() == 1) {
257                                type = SymmetryType.OPEN;
258                                logger.info("Open Symmetry detected");
259                        } else {
260                                type = SymmetryType.CLOSED;
261                                logger.info("Close Symmetry detected");
262                        }
263                }
264
265                // Do not try the refinement if the self-alignment is not significant
266                if (optimalAFP.getTMScore() < params.getUnrefinedScoreThreshold()){
267                        result.setNumRepeats(1);
268                        return result;
269                }
270
271                // STEP 3: order detection & symmetry refinement, apply consistency
272                try {
273                        // ORDER DETECTION
274                        OrderDetector orderDetector = null;
275                        int order = 1;
276                        switch (params.getOrderDetectorMethod()) {
277                        case USER_INPUT:
278                                order = params.getUserOrder();
279                                break;
280                        case SEQUENCE_FUNCTION:
281                                // Does not work for OPEN alignments
282                                if (type == SymmetryType.CLOSED) {
283                                        orderDetector = new SequenceFunctionOrderDetector(
284                                                        params.getMaxSymmOrder(), 0.4f);
285                                        order = orderDetector.calculateOrder(
286                                                        result.getSelfAlignment(), atoms);
287                                        break;
288                                }
289                        case ANGLE:
290                                // Does not work for OPEN alignments
291                                if (type == SymmetryType.CLOSED) {
292                                        orderDetector = new AngleOrderDetectorPlus(
293                                                        params.getMaxSymmOrder());
294                                        order = orderDetector.calculateOrder(
295                                                        result.getSelfAlignment(), atoms);
296                                        break;
297                                }
298                        case GRAPH_COMPONENT:
299                                orderDetector = new GraphComponentOrderDetector();
300                                order = orderDetector.calculateOrder(result.getSelfAlignment(),
301                                                atoms);
302                                break;
303                        }
304                        result.setNumRepeats(order);
305
306                        // REFINEMENT
307                        SymmetryRefiner refiner = null;
308                        switch (params.getRefineMethod()) {
309                        case NOT_REFINED:
310                                return result;
311                        case SEQUENCE_FUNCTION:
312                                // Does not work for OPEN alignments
313                                if (type == SymmetryType.CLOSED) {
314                                        refiner = new SequenceFunctionRefiner();
315                                        break;
316                                }
317                        case GRAPH_COMPONENT:
318                                refiner = new GraphComponentRefiner();
319                                break;
320                        }
321
322                        MultipleAlignment msa = refiner.refine(result.getSelfAlignment(),
323                                        atoms, order);
324
325                        // Refinement succeeded, store results
326                        result.setMultipleAlignment(msa);
327                        result.setNumRepeats(msa.size());
328                        result.setRefined(true);
329
330                } catch (RefinerFailedException e) {
331                        logger.info("Refinement failed: " + e.getMessage());
332                        return result;
333                }
334
335                // STEP 4: symmetry axes
336                SymmetryAxes axes = new SymmetryAxes();
337                int order = result.getMultipleAlignment().size();
338                Matrix4d axis = result.getMultipleAlignment().getBlockSet(0)
339                                .getTransformations().get(1);
340                axes.addAxis(axis, order, type);
341
342                result.setAxes(axes);
343                return result;
344        }
345
346        /**
347         * Analyze the symmetries of the input Atom array using the DEFAULT
348         * parameters.
349         *
350         * @param atoms
351         *            representative Atom array of the Structure
352         * @return CeSymmResult
353         * @throws StructureException
354         */
355        public static CeSymmResult analyze(Atom[] atoms) throws StructureException {
356                CESymmParameters params = new CESymmParameters();
357                return analyze(atoms, params);
358        }
359
360        /**
361         * Analyze the symmetries of the input Atom array using the provided
362         * parameters.
363         *
364         * @param atoms
365         *            representative Atom array of the Structure
366         * @param params
367         *            CeSymmParameters bean
368         * @return CeSymmResult
369         * @throws StructureException
370         */
371        public static CeSymmResult analyze(Atom[] atoms, CESymmParameters params)
372                        throws StructureException {
373
374                if (atoms.length < 1)
375                        throw new IllegalArgumentException("Empty Atom array given.");
376
377                // If the SSE information is needed, we calculate it if the user did not
378                if (params.getSSEThreshold() > 0) {
379                        Structure s = atoms[0].getGroup().getChain().getStructure();
380                        if (SecStrucTools.getSecStrucInfo(s).isEmpty()) {
381                                logger.info("Calculating Secondary Structure...");
382                                SecStrucCalc ssp = new SecStrucCalc();
383                                ssp.calculate(s, true);
384                        }
385                }
386
387                CeSymmIterative iter = new CeSymmIterative(params);
388                CeSymmResult result = iter.execute(atoms);
389
390                if (result.isRefined()) {
391                        // Optimize the global alignment freely once more (final step)
392                        if (params.getOptimization() && result.getSymmLevels() > 1) {
393                                try {
394                                        SymmOptimizer optimizer = new SymmOptimizer(result);
395                                        MultipleAlignment optimized = optimizer.optimize();
396                                        // Set the optimized MultipleAlignment and the axes
397                                        result.setMultipleAlignment(optimized);
398                                } catch (RefinerFailedException e) {
399                                        logger.info("Final optimization failed:" + e.getMessage());
400                                }
401                        }
402                        result.getMultipleAlignment().getEnsemble()
403                                        .setStructureIdentifiers(result.getRepeatsID());
404                }
405                return result;
406        }
407
408        /**
409         * Analyze a single level of symmetry.
410         *
411         * @param atoms
412         *            Atom array of the current level
413         * @return CeSymmResult
414         * @throws StructureException
415         */
416        public static CeSymmResult analyzeLevel(Atom[] atoms,
417                        CESymmParameters params) throws StructureException {
418
419                if (atoms.length < 1)
420                        throw new IllegalArgumentException("Empty Atom array given.");
421
422                CeSymmResult result = align(atoms, params);
423
424                if (result.isRefined()) {
425                        // STEP 5: symmetry alignment optimization
426                        if (result.getParams().getOptimization()) {
427                                try {
428                                        MultipleAlignment msa = result.getMultipleAlignment();
429                                        SymmOptimizer optimizer = new SymmOptimizer(result);
430                                        msa = optimizer.optimize();
431                                        result.setMultipleAlignment(msa);
432                                } catch (RefinerFailedException e) {
433                                        logger.debug("Optimization failed:{}", e.getMessage());
434                                }
435                        }
436                }
437                return result;
438        }
439
440}