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.Arrays;
025import java.util.List;
026
027import org.biojava.nbio.structure.Atom;
028import org.biojava.nbio.structure.ResidueNumber;
029import org.biojava.nbio.structure.ResidueRange;
030import org.biojava.nbio.structure.StructureException;
031import org.biojava.nbio.structure.StructureIdentifier;
032import org.biojava.nbio.structure.SubstructureIdentifier;
033import org.biojava.nbio.structure.align.model.AFPChain;
034import org.biojava.nbio.structure.align.multiple.Block;
035import org.biojava.nbio.structure.align.multiple.MultipleAlignment;
036import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentScorer;
037import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.RefineMethod;
038import org.biojava.nbio.structure.symmetry.internal.CESymmParameters.SymmetryType;
039import org.biojava.nbio.structure.symmetry.utils.SymmetryTools;
040
041/**
042 * This Class stores all the relevant information of an internal symmetry result
043 * obtained with CeSymm. The purpose is to carry all the information packed
044 * during the calculations and return a single object.
045 *
046 * @author Aleix Lafita
047 * @since 4.2.0
048 *
049 */
050public class CeSymmResult {
051
052        private MultipleAlignment multipleAlignment;
053        private AFPChain selfAlignment;
054
055        private StructureIdentifier structureId;
056        private Atom[] atoms;
057
058        private CESymmParameters params;
059        private SymmetryAxes axes = new SymmetryAxes();
060        private String symmGroup;
061
062        private int numRepeats;
063        private boolean refined;
064
065        /**
066         * Conditions checked are: score above the threshold, number of repeats
067         * higher than 1 and refinement succeeded.
068         *
069         * @return true if significant, false otherwise
070         */
071        public boolean isSignificant() {
072
073                // In any case if the order is 1 it is asymmetric
074                if (numRepeats < 2)
075                        return false;
076
077                // If the TM-Score before refinement is below the threshold
078                if (selfAlignment.getTMScore() < params.getUnrefinedScoreThreshold())
079                        return false;
080
081                // If the refinement was attempted
082                if (params.getRefineMethod() != RefineMethod.NOT_REFINED) {
083                        // Check for minimum length
084                        if (multipleAlignment.getCoreLength() < params.getMinCoreLength())
085                                return false;
086                        // Allow 90% of original TM-Score theshold
087                        if (multipleAlignment.getScore(MultipleAlignmentScorer.AVGTM_SCORE) < params
088                                        .getRefinedScoreThreshold())
089                                return false;
090                        return true;
091                }
092
093                return true;
094        }
095
096        /**
097         * Return the symmetric repeats as structure identifiers, if the result is
098         * symmetric and it was refined, return null otherwise.
099         *
100         * @return List of StructureIdentifiers or null if not defined
101         * @throws StructureException
102         */
103        public List<StructureIdentifier> getRepeatsID() throws StructureException {
104
105                if (!isRefined())
106                        return null;
107
108                List<StructureIdentifier> repeats = new ArrayList<StructureIdentifier>(
109                                numRepeats);
110
111                String pdbId = structureId.toCanonical().getPdbId();
112                Block align = multipleAlignment.getBlocks().get(0);
113
114                for (int su = 0; su < numRepeats; su++) {
115                        // Get the start and end residues of the repeat
116                        ResidueNumber res1 = atoms[align.getStartResidue(su)].getGroup()
117                                        .getResidueNumber();
118                        ResidueNumber res2 = atoms[align.getFinalResidue(su)].getGroup()
119                                        .getResidueNumber();
120                        ResidueRange range = new ResidueRange(res1.getChainId(), res1, res2);
121
122                        StructureIdentifier id = new SubstructureIdentifier(pdbId,
123                                        Arrays.asList(range));
124
125                        repeats.add(id);
126                }
127                return repeats;
128        }
129
130        @Override
131        public String toString() {
132                return structureId + ", symmGroup=" + getSymmGroup() + ", numRepeats="
133                                + numRepeats + ", symmLevels=" + axes.getNumLevels()
134                                + ", refined=" + refined + " | " + params;
135        }
136
137        public MultipleAlignment getMultipleAlignment() {
138                return multipleAlignment;
139        }
140
141        public void setMultipleAlignment(MultipleAlignment multipleAlignment) {
142                this.multipleAlignment = multipleAlignment;
143        }
144
145        public AFPChain getSelfAlignment() {
146                return selfAlignment;
147        }
148
149        public void setSelfAlignment(AFPChain selfAlignment) {
150                this.selfAlignment = selfAlignment;
151        }
152
153        public CESymmParameters getParams() {
154                return params;
155        }
156
157        public void setParams(CESymmParameters params) {
158                this.params = params.clone();
159        }
160
161        public SymmetryAxes getAxes() {
162                return axes;
163        }
164
165        public void setAxes(SymmetryAxes axes) {
166                this.axes = axes;
167        }
168
169        /**
170         * Return the symmetry order determined by the order detector if the
171         * symmetry is significant. Return 1 otherwise.
172         *
173         * @return the order of symmetry if the result is significant
174         */
175        public int getNumRepeats() {
176                if (isSignificant())
177                        return numRepeats;
178                else
179                        return 1;
180        }
181
182        public void setNumRepeats(int symmOrder) {
183                this.numRepeats = symmOrder;
184        }
185
186        public boolean isRefined() {
187                return refined;
188        }
189
190        public void setRefined(boolean refined) {
191                this.refined = refined;
192        }
193
194        public String getSymmGroup() {
195                // Lazily calculate the symmetry group if significant
196                if (symmGroup == null) {
197                        if (isSignificant()) {
198                                if (isRefined()) {
199                                        try {
200                                                symmGroup = SymmetryTools.getQuaternarySymmetry(this)
201                                                                .getSymmetry();
202                                        } catch (StructureException e) {
203                                                symmGroup = "C1";
204                                        }
205                                        if (symmGroup.equals("C1"))
206                                                symmGroup = "R"; // could not find group
207                                } else {
208                                        // in case significant but not refined
209                                        if (axes.getElementaryAxis(0).getSymmType()
210                                                        .equals(SymmetryType.CLOSED))
211                                                symmGroup = "C" + numRepeats;
212                                        else
213                                                symmGroup = "R";
214                                }
215                        } else
216                                // case asymmetric
217                                symmGroup = "C1";
218                }
219                return symmGroup;
220        }
221
222        public void setSymmGroup(String symmGroup) {
223                this.symmGroup = symmGroup;
224        }
225
226        public Atom[] getAtoms() {
227                return atoms;
228        }
229
230        public void setAtoms(Atom[] atoms) {
231                this.atoms = atoms;
232        }
233
234        public int getSymmLevels() {
235                return axes.getNumLevels();
236        }
237
238        public StructureIdentifier getStructureId() {
239                return structureId;
240        }
241
242        public void setStructureId(StructureIdentifier structureId) {
243                this.structureId = structureId;
244        }
245
246        /**
247         * Return a String describing the reasons for the CE-Symm final decision in
248         * this particular result.
249         * 
250         * @return String decision reason
251         */
252        public String getReason() {
253                // Cases:
254                // 1. Asymmetric because insignificant self-alignment (1itb.A_1-100)
255                double tm = selfAlignment.getTMScore();
256                if (tm < params.getUnrefinedScoreThreshold()) {
257                        return String.format("Insignificant self-alignment (TM=%.2f)", tm);
258                }
259                // 2. Asymmetric because order detector returned 1
260                if (numRepeats == 1) {
261                        return String.format(
262                                        "Order detector found asymmetric alignment (TM=%.2f)", tm);
263                }
264
265                // Check that the user requested refinement
266                if (params.getRefineMethod() != RefineMethod.NOT_REFINED) {
267                        // 3. Asymmetric because refinement failed
268                        if (!refined) {
269                                return "Refinement failed";
270                        }
271                        tm = multipleAlignment
272                                        .getScore(MultipleAlignmentScorer.AVGTM_SCORE);
273                        // 4. Asymmetric because refinement & optimization were not
274                        // significant
275                        if (!isSignificant()) {
276                                return String.format(
277                                                "Refinement was not significant (TM=%.2f)", tm);
278                        }
279                } else {
280                        // 4. Not refined, but result was not significant
281                        if (!isSignificant()) {
282                                return String
283                                                .format("Result was not significant (TM=%.2f)", tm);
284                        }
285                }
286
287                String hierarchical = "";
288                if (axes.getNumLevels() > 1) {
289                        hierarchical = String.format("; Contains %d levels of symmetry",
290                                        axes.getNumLevels());
291                }
292                // 5. Symmetric.
293                // a. Open. Give # repeats (1n0r.A)
294                if (axes.getElementaryAxis(0).getSymmType() == SymmetryType.OPEN) {
295                        return String.format("Contains %d open repeats (TM=%.2f)%s",
296                                        getNumRepeats(), tm, hierarchical);
297                }
298                // b. Closed, non-hierarchical (1itb.A)
299                // c. Closed, heirarchical (4gcr)
300                return String.format("Significant (TM=%.2f)%s", tm, hierarchical);
301        }
302
303}