ae8638d2914ed7e22d7a329ce335b44379c99c7f
[jalview.git] / src / jalview / analysis / AlignSeq.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.analysis;
22
23 import jalview.analysis.scoremodels.PIDModel;
24 import jalview.analysis.scoremodels.ScoreMatrix;
25 import jalview.analysis.scoremodels.ScoreModels;
26 import jalview.analysis.scoremodels.SimilarityParams;
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.Mapping;
30 import jalview.datamodel.Sequence;
31 import jalview.datamodel.SequenceI;
32 import jalview.math.MiscMath;
33 import jalview.util.Comparison;
34 import jalview.util.Format;
35 import jalview.util.MapList;
36 import jalview.util.MessageManager;
37
38 import java.awt.Color;
39 import java.awt.Graphics;
40 import java.io.PrintStream;
41 import java.lang.IllegalArgumentException;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.StringTokenizer;
47 import java.util.Locale;
48
49 /**
50  * @author $author$
51  * @version $Revision$
52  */
53 public class AlignSeq
54 {
55   private static final int MAX_NAME_LENGTH = 30;
56
57   private static final int DEFAULT_OPENCOST = 120;
58
59   private static final int DEFAULT_EXTENDCOST = 20;
60   
61   private int GAP_OPEN_COST=DEFAULT_OPENCOST;
62
63   private int GAP_EXTEND_COST=DEFAULT_EXTENDCOST;
64
65   private static final int GAP_INDEX = -1;
66
67   public static final String PEP = "pep";
68
69   public static final String DNA = "dna";
70
71   private static final String NEWLINE = System.lineSeparator();
72
73   float[][] score;
74
75   float alignmentScore;
76
77   float[][] E;
78
79   float[][] F;
80
81   int[][] traceback; // todo is this actually used?
82
83   int[] seq1;
84
85   int[] seq2;
86
87   SequenceI s1;
88
89   SequenceI s2;
90
91   public String s1str;
92
93   public String s2str;
94
95   int maxi;
96
97   int maxj;
98
99   int[] aseq1;
100
101   int[] aseq2;
102
103   public String astr1 = "";
104
105   public String astr2 = "";
106
107   public String indelfreeAstr1 = "";
108
109   public String indelfreeAstr2 = "";
110
111   /** DOCUMENT ME!! */
112   public int seq1start;
113
114   /** DOCUMENT ME!! */
115   public int seq1end;
116
117   /** DOCUMENT ME!! */
118   public int seq2start;
119
120   public int seq2end;
121
122   int count;
123
124   public float maxscore;
125
126   public float meanScore;       //needed for PaSiMap
127
128   public int hypotheticMaxScore;        // needed for PaSiMap
129
130   int prev = 0;
131
132   StringBuffer output = new StringBuffer();
133
134   String type; // AlignSeq.PEP or AlignSeq.DNA
135
136   private ScoreMatrix scoreMatrix;
137
138   /**
139    * Creates a new AlignSeq object.
140    * 
141    * @param s1
142    *          first sequence for alignment
143    * @param s2
144    *          second sequence for alignment
145    * @param type
146    *          molecule type, either AlignSeq.PEP or AlignSeq.DNA
147    */
148   public AlignSeq(int opencost, int extcost)
149   {
150     GAP_OPEN_COST = opencost;
151     GAP_EXTEND_COST = extcost;
152   }
153   public AlignSeq(SequenceI s1, SequenceI s2, String type)
154   {
155     seqInit(s1, s1.getSequenceAsString(), s2, s2.getSequenceAsString(),
156             type);
157   }
158
159   /**
160    * Creates a new AlignSeq object.
161    * 
162    * @param s1,string1
163    *          s1 reference sequence for string1
164    * @param s2,string2
165    *          s2 reference sequence for string2
166    * @param type
167    *          molecule type, either AlignSeq.PEP or AlignSeq.DNA
168    */
169   public AlignSeq(SequenceI s1, String string1, SequenceI s2,
170           String string2, String type)
171   {
172     seqInit(s1, string1.toUpperCase(Locale.ROOT), s2,
173             string2.toUpperCase(Locale.ROOT), type);
174   }
175
176   public AlignSeq(SequenceI s1, SequenceI s2, String type, int opencost,
177           int extcost)
178   {
179     this(s1,s2,type);
180     GAP_OPEN_COST=opencost;
181     GAP_EXTEND_COST=extcost;
182   }
183
184   public AlignSeq(SequenceI s12, String string1, SequenceI s22,
185           String string2, String type2, int defaultOpencost,
186           int defaultExtendcost)
187   {
188     this(s12,string1,s22,string2,type2);
189     GAP_OPEN_COST=defaultOpencost;
190     GAP_EXTEND_COST=defaultExtendcost;
191   }
192
193   /**
194    * DOCUMENT ME!
195    * 
196    * @return DOCUMENT ME!
197    */
198   public float getMaxScore()
199   {
200     return maxscore;
201   }
202
203   /**
204   * returns the overall score of the alignment
205   *
206   * @return
207   */
208   public float getAlignmentScore()
209   {
210     return alignmentScore;
211   }
212
213   /**
214    * DOCUMENT ME!
215    * 
216    * @return DOCUMENT ME!
217    */
218   public int getSeq2Start()
219   {
220     return seq2start;
221   }
222
223   /**
224    * DOCUMENT ME!
225    * 
226    * @return DOCUMENT ME!
227    */
228   public int getSeq2End()
229   {
230     return seq2end;
231   }
232
233   /**
234    * DOCUMENT ME!
235    * 
236    * @return DOCUMENT ME!
237    */
238   public int getSeq1Start()
239   {
240     return seq1start;
241   }
242
243   /**
244    * DOCUMENT ME!
245    * 
246    * @return DOCUMENT ME!
247    */
248   public int getSeq1End()
249   {
250     return seq1end;
251   }
252
253   /**
254    * DOCUMENT ME!
255    * 
256    * @return DOCUMENT ME!
257    */
258   public String getOutput()
259   {
260     return output.toString();
261   }
262
263   /**
264    * DOCUMENT ME!
265    * 
266    * @return DOCUMENT ME!
267    */
268   public String getAStr1()
269   {
270     return astr1;
271   }
272
273   /**
274    * DOCUMENT ME!
275    * 
276    * @return DOCUMENT ME!
277    */
278   public String getAStr2()
279   {
280     return astr2;
281   }
282
283   /**
284    * DOCUMENT ME!
285    * 
286    * @return DOCUMENT ME!
287    */
288   public int[] getASeq1()
289   {
290     return aseq1;
291   }
292
293   /**
294    * DOCUMENT ME!
295    * 
296    * @return DOCUMENT ME!
297    */
298   public int[] getASeq2()
299   {
300     return aseq2;
301   }
302
303   /**
304    * 
305    * @return aligned instance of Seq 1
306    */
307   public SequenceI getAlignedSeq1()
308   {
309     SequenceI alSeq1 = new Sequence(s1.getName(), getAStr1());
310     alSeq1.setStart(s1.getStart() + getSeq1Start() - 1);
311     alSeq1.setEnd(s1.getStart() + getSeq1End() - 1);
312     alSeq1.setDatasetSequence(
313             s1.getDatasetSequence() == null ? s1 : s1.getDatasetSequence());
314     return alSeq1;
315   }
316
317   /**
318    * 
319    * @return aligned instance of Seq 2
320    */
321   public SequenceI getAlignedSeq2()
322   {
323     SequenceI alSeq2 = new Sequence(s2.getName(), getAStr2());
324     alSeq2.setStart(s2.getStart() + getSeq2Start() - 1);
325     alSeq2.setEnd(s2.getStart() + getSeq2End() - 1);
326     alSeq2.setDatasetSequence(
327             s2.getDatasetSequence() == null ? s2 : s2.getDatasetSequence());
328     return alSeq2;
329   }
330
331   /**
332    * Construct score matrix for sequences with standard DNA or PEPTIDE matrix
333    * 
334    * @param s1
335    *          - sequence 1
336    * @param string1
337    *          - string to use for s1
338    * @param s2
339    *          - sequence 2
340    * @param string2
341    *          - string to use for s2
342    * @param type
343    *          DNA or PEPTIDE
344    */
345   public void seqInit(SequenceI s1, String string1, SequenceI s2,
346           String string2, String type)
347   {
348     seqInit(s1,string1,s2,string2,type,GAP_OPEN_COST,GAP_EXTEND_COST);
349   }
350   public void seqInit(SequenceI s1, String string1, SequenceI s2,
351           String string2, String type, int opening,int extension)
352   {
353     GAP_OPEN_COST=opening;
354     GAP_EXTEND_COST=extension;
355     this.s1 = s1;
356     this.s2 = s2;
357     setDefaultParams(type);
358     seqInit(string1, string2);
359   }
360
361   /**
362    * construct score matrix for string1 and string2 (after removing any existing
363    * gaps
364    * 
365    * @param string1
366    * @param string2
367    */
368   private void seqInit(String string1, String string2)
369   {
370     s1str = extractGaps(jalview.util.Comparison.GapChars, string1);
371     s2str = extractGaps(jalview.util.Comparison.GapChars, string2);
372
373     if (s1str.length() == 0 || s2str.length() == 0)
374     {
375       output.append(
376               "ALL GAPS: " + (s1str.length() == 0 ? s1.getName() : " ")
377                       + (s2str.length() == 0 ? s2.getName() : ""));
378       return;
379     }
380
381     score = new float[s1str.length()][s2str.length()];
382
383     E = new float[s1str.length()][s2str.length()];
384
385     F = new float[s1str.length()][s2str.length()];
386     traceback = new int[s1str.length()][s2str.length()];
387
388     seq1 = indexEncode(s1str);
389
390     seq2 = indexEncode(s2str);
391   }
392
393   private void setDefaultParams(String moleculeType)
394   {
395     if (!PEP.equals(moleculeType) && !DNA.equals(moleculeType))
396     {
397       output.append("Wrong type = dna or pep only");
398       throw new Error(MessageManager
399               .formatMessage("error.unknown_type_dna_or_pep", new String[]
400               { moleculeType }));
401     }
402
403     type = moleculeType;
404     scoreMatrix = ScoreModels.getInstance()
405             .getDefaultModel(PEP.equals(type));
406   }
407
408   /**
409    * DOCUMENT ME!
410    */
411   public void traceAlignment()
412   {
413     // Find the maximum score along the rhs or bottom row
414     float max = -Float.MAX_VALUE;
415
416     for (int i = 0; i < seq1.length; i++)
417     {
418       if (score[i][seq2.length - 1] > max)
419       {
420         max = score[i][seq2.length - 1];
421         maxi = i;
422         maxj = seq2.length - 1;
423       }
424     }
425
426     for (int j = 0; j < seq2.length; j++)
427     {
428       if (score[seq1.length - 1][j] > max)
429       {
430         max = score[seq1.length - 1][j];
431         maxi = seq1.length - 1;
432         maxj = j;
433       }
434     }
435
436     int i = maxi;
437     int j = maxj;
438     int trace;
439     maxscore = score[i][j] / 10f;
440
441
442     aseq1 = new int[seq1.length + seq2.length];
443     aseq2 = new int[seq1.length + seq2.length];
444
445     StringBuilder sb1 = new StringBuilder(aseq1.length);
446     StringBuilder sb2 = new StringBuilder(aseq2.length);
447
448     count = (seq1.length + seq2.length) - 1;
449
450
451     while (i > 0 && j > 0)
452     {
453       aseq1[count] = seq1[i];
454       sb1.append(s1str.charAt(i));
455       aseq2[count] = seq2[j];
456       sb2.append(s2str.charAt(j));
457
458       trace = findTrace(i, j);
459
460       if (trace == 0)
461       {
462         i--;
463         j--;
464       }
465       else if (trace == 1)
466       {
467         j--;
468         aseq1[count] = GAP_INDEX;
469         sb1.replace(sb1.length() - 1, sb1.length(), "-");
470       }
471       else if (trace == -1)
472       {
473         i--;
474         aseq2[count] = GAP_INDEX;
475         sb2.replace(sb2.length() - 1, sb2.length(), "-");
476       }
477
478       count--;
479     }
480
481     seq1start = i + 1;
482     seq2start = j + 1;
483
484     if (aseq1[count] != GAP_INDEX)
485     {
486       aseq1[count] = seq1[i];
487       sb1.append(s1str.charAt(i));
488     }
489
490     if (aseq2[count] != GAP_INDEX)
491     {
492       aseq2[count] = seq2[j];
493       sb2.append(s2str.charAt(j));
494     }
495
496
497     /*
498      * we built the character strings backwards, so now
499      * reverse them to convert to sequence strings
500      */
501     astr1 = sb1.reverse().toString();
502     astr2 = sb2.reverse().toString();
503   }
504
505   /**
506    * DOCUMENT ME!
507    */
508   public void traceAlignmentWithEndGaps()
509   {
510     // Find the maximum score along the rhs or bottom row
511     float max = -Float.MAX_VALUE;
512
513     for (int i = 0; i < seq1.length; i++)
514     {
515       if (score[i][seq2.length - 1] > max)
516       {
517         max = score[i][seq2.length - 1];
518         maxi = i;
519         maxj = seq2.length - 1;
520       }
521     }
522
523     for (int j = 0; j < seq2.length; j++)
524     {
525       if (score[seq1.length - 1][j] > max)
526       {
527         max = score[seq1.length - 1][j];
528         maxi = seq1.length - 1;
529         maxj = j;
530       }
531     }
532
533     int i = maxi;
534     int j = maxj;
535     int trace;
536     maxscore = score[i][j] / 10f;
537
538     //prepare trailing gaps
539     while ((i < seq1.length - 1) || (j < seq2.length - 1))
540     {
541       i++;
542       j++;
543     }
544     seq1end = i + 1;
545     seq2end = j + 1;
546
547     aseq1 = new int[seq1.length + seq2.length];
548     aseq2 = new int[seq1.length + seq2.length];
549
550     StringBuilder sb1 = new StringBuilder(aseq1.length);
551     StringBuilder sb2 = new StringBuilder(aseq2.length);
552
553     count = (seq1.length + seq2.length) - 1;
554
555     //get trailing gaps
556     while ((i >= seq1.length) || (j >= seq2.length))
557     {
558       if (i >= seq1.length)
559       {
560         aseq1[count] = GAP_INDEX;
561         sb1.append("-");
562         aseq2[count] = seq2[j];
563         sb2.append(s2str.charAt(j));
564       } else if (j >= seq2.length) {
565         aseq1[count] = seq1[i];
566         sb1.append(s1str.charAt(i));
567         aseq2[count] = GAP_INDEX;
568         sb2.append("-");
569       }
570       i--;
571       j--;
572     }
573
574     while (i > 0 && j > 0)
575     {
576       aseq1[count] = seq1[i];
577       sb1.append(s1str.charAt(i));
578       aseq2[count] = seq2[j];
579       sb2.append(s2str.charAt(j));
580
581       trace = findTrace(i, j);
582
583       if (trace == 0)
584       {
585         i--;
586         j--;
587       }
588       else if (trace == 1)
589       {
590         j--;
591         aseq1[count] = GAP_INDEX;
592         sb1.replace(sb1.length() - 1, sb1.length(), "-");
593       }
594       else if (trace == -1)
595       {
596         i--;
597         aseq2[count] = GAP_INDEX;
598         sb2.replace(sb2.length() - 1, sb2.length(), "-");
599       }
600
601       count--;
602     }
603
604     seq1start = i + 1;
605     seq2start = j + 1;
606
607     aseq1[count] = seq1[i];
608     sb1.append(s1str.charAt(i));
609     aseq2[count] = seq2[j];
610     sb2.append(s2str.charAt(j));
611
612     //get initial gaps
613     while (j > 0 || i > 0)
614     {
615       if (j > 0)
616       {
617         j--;
618         sb1.append("-");
619         sb2.append(s2str.charAt(j));
620       } else if (i > 0) {
621         i--;
622         sb1.append(s1str.charAt(i));
623         sb2.append("-");
624       }
625     }
626
627     /*
628      * we built the character strings backwards, so now
629      * reverse them to convert to sequence strings
630      */
631     astr1 = sb1.reverse().toString();
632     astr2 = sb2.reverse().toString();
633   }
634
635   /**
636    * DOCUMENT ME!
637    */
638   public void printAlignment(PrintStream os)
639   {
640     // TODO: Use original sequence characters rather than re-translated
641     // characters in output
642     // Find the biggest id length for formatting purposes
643     String s1id = getAlignedSeq1().getDisplayId(true);
644     String s2id = getAlignedSeq2().getDisplayId(true);
645     int nameLength = Math.max(s1id.length(), s2id.length());
646     if (nameLength > MAX_NAME_LENGTH)
647     {
648       int truncateBy = nameLength - MAX_NAME_LENGTH;
649       nameLength = MAX_NAME_LENGTH;
650       // JAL-527 - truncate the sequence ids
651       if (s1id.length() > nameLength)
652       {
653         int slashPos = s1id.lastIndexOf('/');
654         s1id = s1id.substring(0, slashPos - truncateBy)
655                 + s1id.substring(slashPos);
656       }
657       if (s2id.length() > nameLength)
658       {
659         int slashPos = s2id.lastIndexOf('/');
660         s2id = s2id.substring(0, slashPos - truncateBy)
661                 + s2id.substring(slashPos);
662       }
663     }
664     int len = 72 - nameLength - 1;
665     int nochunks = ((aseq1.length - count) / len)
666             + ((aseq1.length - count) % len > 0 ? 1 : 0);
667     float pid = 0f;
668
669     output.append("Score = ").append(score[maxi][maxj]).append(NEWLINE);
670     output.append("Length of alignment = ")
671             .append(String.valueOf(aseq1.length - count)).append(NEWLINE);
672     output.append("Sequence ");
673     Format nameFormat = new Format("%" + nameLength + "s");
674     output.append(nameFormat.form(s1id));
675     output.append(" (Sequence length = ")
676             .append(String.valueOf(s1str.length())).append(")")
677             .append(NEWLINE);
678     output.append("Sequence ");
679     output.append(nameFormat.form(s2id));
680     output.append(" (Sequence length = ")
681             .append(String.valueOf(s2str.length())).append(")")
682             .append(NEWLINE).append(NEWLINE);
683
684     ScoreMatrix pam250 = ScoreModels.getInstance().getPam250();
685
686     for (int j = 0; j < nochunks; j++)
687     {
688       // Print the first aligned sequence
689       output.append(nameFormat.form(s1id)).append(" ");
690
691       for (int i = 0; i < len; i++)
692       {
693         if ((i + (j * len)) < astr1.length())
694         {
695           output.append(astr1.charAt(i + (j * len)));
696         }
697       }
698
699       output.append(NEWLINE);
700       output.append(nameFormat.form(" ")).append(" ");
701
702       /*
703        * Print out the match symbols:
704        * | for exact match (ignoring case)
705        * . if PAM250 score is positive
706        * else a space
707        */
708       for (int i = 0; i < len; i++)
709       {
710         if ((i + (j * len)) < astr1.length())
711         {
712           char c1 = astr1.charAt(i + (j * len));
713           char c2 = astr2.charAt(i + (j * len));
714           boolean sameChar = Comparison.isSameResidue(c1, c2, false);
715           if (sameChar && !Comparison.isGap(c1))
716           {
717             pid++;
718             output.append("|");
719           }
720           else if (PEP.equals(type))
721           {
722             if (pam250.getPairwiseScore(c1, c2) > 0)
723             {
724               output.append(".");
725             }
726             else
727             {
728               output.append(" ");
729             }
730           }
731           else
732           {
733             output.append(" ");
734           }
735         }
736       }
737
738       // Now print the second aligned sequence
739       output = output.append(NEWLINE);
740       output = output.append(nameFormat.form(s2id)).append(" ");
741
742       for (int i = 0; i < len; i++)
743       {
744         if ((i + (j * len)) < astr2.length())
745         {
746           output.append(astr2.charAt(i + (j * len)));
747         }
748       }
749
750       output.append(NEWLINE).append(NEWLINE);
751     }
752
753     pid = pid / (aseq1.length - count) * 100;
754     output.append(new Format("Percentage ID = %3.2f\n").form(pid));
755     output.append(NEWLINE);
756     try
757     {
758       os.print(output.toString());
759     } catch (Exception ex)
760     {
761     }
762   }
763
764   /**
765    * DOCUMENT ME!
766    * 
767    * @param i
768    *          DOCUMENT ME!
769    * @param j
770    *          DOCUMENT ME!
771    * 
772    * @return DOCUMENT ME!
773    */
774   public int findTrace(int i, int j)
775   {
776     int t = 0;
777     float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
778             s2str.charAt(j));
779     float max = score[i - 1][j - 1] + (pairwiseScore * 10);
780
781     if (F[i][j] > max)
782     {
783       max = F[i][j];
784       t = -1;
785     }
786     else if (F[i][j] == max)
787     {
788       if (prev == -1)
789       {
790         max = F[i][j];
791         t = -1;
792       }
793     }
794
795     if (E[i][j] >= max)
796     {
797       max = E[i][j];
798       t = 1;
799     }
800     else if (E[i][j] == max)
801     {
802       if (prev == 1)
803       {
804         max = E[i][j];
805         t = 1;
806       }
807     }
808
809     prev = t;
810
811     return t;
812   }
813
814   /**
815    * DOCUMENT ME!
816    */
817   public void calcScoreMatrix()
818   {
819     int n = seq1.length;
820     int m = seq2.length;
821     final int GAP_EX_COST=GAP_EXTEND_COST;
822     final int GAP_OP_COST = GAP_OPEN_COST;
823     // top left hand element
824     score[0][0] = scoreMatrix.getPairwiseScore(s1str.charAt(0),
825             s2str.charAt(0)) * 10;
826     E[0][0] = -GAP_EX_COST;
827     F[0][0] = 0;
828
829     // Calculate the top row first
830     for (int j = 1; j < m; j++)
831     {
832       // What should these values be? 0 maybe
833       E[0][j] = max(score[0][j - 1] - GAP_OP_COST,
834               E[0][j - 1] - GAP_EX_COST);
835       F[0][j] = -GAP_EX_COST;
836
837       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(0),
838               s2str.charAt(j));
839       score[0][j] = max(pairwiseScore * 10, -GAP_OP_COST,
840               -GAP_EX_COST);
841
842       traceback[0][j] = 1;
843     }
844
845     // Now do the left hand column
846     for (int i = 1; i < n; i++)
847     {
848       E[i][0] = -GAP_OP_COST;
849       F[i][0] = max(score[i - 1][0] - GAP_OP_COST,
850               F[i - 1][0] - GAP_EX_COST);
851
852       float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
853               s2str.charAt(0));
854       score[i][0] = max(pairwiseScore * 10, E[i][0], F[i][0]);
855       traceback[i][0] = -1;
856     }
857
858     // Now do all the other rows
859     for (int i = 1; i < n; i++)
860     {
861       for (int j = 1; j < m; j++)
862       {
863         E[i][j] = max(score[i][j - 1] - GAP_OP_COST,
864                 E[i][j - 1] - GAP_EX_COST);
865         F[i][j] = max(score[i - 1][j] - GAP_OP_COST,
866                 F[i - 1][j] - GAP_EX_COST);
867
868         float pairwiseScore = scoreMatrix.getPairwiseScore(s1str.charAt(i),
869                 s2str.charAt(j));
870         score[i][j] = max(score[i - 1][j - 1] + (pairwiseScore * 10),
871                 E[i][j], F[i][j]);
872         traceback[i][j] = findTrace(i, j);
873       }
874     }
875   }
876
877   /**
878    * Returns the given sequence with all of the given gap characters removed.
879    * 
880    * @param gapChars
881    *          a string of characters to be treated as gaps
882    * @param seq
883    *          the input sequence
884    * 
885    * @return
886    */
887   public static String extractGaps(String gapChars, String seq)
888   {
889     if (gapChars == null || seq == null)
890     {
891       return null;
892     }
893     StringTokenizer str = new StringTokenizer(seq, gapChars);
894     StringBuilder newString = new StringBuilder(seq.length());
895
896     while (str.hasMoreTokens())
897     {
898       newString.append(str.nextToken());
899     }
900
901     return newString.toString();
902   }
903
904   /**
905    * DOCUMENT ME!
906    * 
907    * @param f1
908    *          DOCUMENT ME!
909    * @param f2
910    *          DOCUMENT ME!
911    * @param f3
912    *          DOCUMENT ME!
913    * 
914    * @return DOCUMENT ME!
915    */
916   private static float max(float f1, float f2, float f3)
917   {
918     float max = f1;
919
920     if (f2 > f1)
921     {
922       max = f2;
923     }
924
925     if (f3 > max)
926     {
927       max = f3;
928     }
929
930     return max;
931   }
932
933   /**
934    * DOCUMENT ME!
935    * 
936    * @param f1
937    *          DOCUMENT ME!
938    * @param f2
939    *          DOCUMENT ME!
940    * 
941    * @return DOCUMENT ME!
942    */
943   private static float max(float f1, float f2)
944   {
945     float max = f1;
946
947     if (f2 > f1)
948     {
949       max = f2;
950     }
951
952     return max;
953   }
954
955   /**
956    * Converts the character string to an array of integers which are the
957    * corresponding indices to the characters in the score matrix
958    * 
959    * @param s
960    * 
961    * @return
962    */
963   int[] indexEncode(String s)
964   {
965     int[] encoded = new int[s.length()];
966
967     for (int i = 0; i < s.length(); i++)
968     {
969       char c = s.charAt(i);
970       encoded[i] = scoreMatrix.getMatrixIndex(c);
971     }
972
973     return encoded;
974   }
975
976   /**
977    * DOCUMENT ME!
978    * 
979    * @param g
980    *          DOCUMENT ME!
981    * @param mat
982    *          DOCUMENT ME!
983    * @param n
984    *          DOCUMENT ME!
985    * @param m
986    *          DOCUMENT ME!
987    * @param psize
988    *          DOCUMENT ME!
989    */
990   public static void displayMatrix(Graphics g, int[][] mat, int n, int m,
991           int psize)
992   {
993     // TODO method doesn't seem to be referenced anywhere delete??
994     int max = -1000;
995     int min = 1000;
996
997     for (int i = 0; i < n; i++)
998     {
999       for (int j = 0; j < m; j++)
1000       {
1001         if (mat[i][j] >= max)
1002         {
1003           max = mat[i][j];
1004         }
1005
1006         if (mat[i][j] <= min)
1007         {
1008           min = mat[i][j];
1009         }
1010       }
1011     }
1012
1013     jalview.bin.Console.outPrintln(max + " " + min);
1014
1015     for (int i = 0; i < n; i++)
1016     {
1017       for (int j = 0; j < m; j++)
1018       {
1019         int x = psize * i;
1020         int y = psize * j;
1021
1022         // jalview.bin.Console.outPrintln(mat[i][j]);
1023         float score = (float) (mat[i][j] - min) / (float) (max - min);
1024         g.setColor(new Color(score, 0, 0));
1025         g.fillRect(x, y, psize, psize);
1026
1027         // jalview.bin.Console.outPrintln(x + " " + y + " " + score);
1028       }
1029     }
1030   }
1031
1032   /**
1033    * Compute a globally optimal needleman and wunsch alignment between two
1034    * sequences
1035    * 
1036    * @param s1
1037    * @param s2
1038    * @param type
1039    *          AlignSeq.DNA or AlignSeq.PEP
1040    */
1041   public static AlignSeq doGlobalNWAlignment(SequenceI s1, SequenceI s2,
1042           String type)
1043   {
1044     return doGlobalNWAlignment(s1, s2, type, DEFAULT_OPENCOST,DEFAULT_EXTENDCOST);
1045   }
1046   public static AlignSeq doGlobalNWAlignment(SequenceI s1, SequenceI s2,
1047           String type, int opencost,int extcost)
1048   {
1049   
1050     AlignSeq as = new AlignSeq(s1, s2, type,opencost,extcost);
1051
1052     as.calcScoreMatrix();
1053     as.traceAlignment();
1054     return as;
1055   }
1056
1057   /**
1058    * 
1059    * @return mapping from positions in S1 to corresponding positions in S2
1060    */
1061   public jalview.datamodel.Mapping getMappingFromS1(boolean allowmismatch)
1062   {
1063     ArrayList<Integer> as1 = new ArrayList<Integer>(),
1064             as2 = new ArrayList<Integer>();
1065     int pdbpos = s2.getStart() + getSeq2Start() - 2;
1066     int alignpos = s1.getStart() + getSeq1Start() - 2;
1067     int lp2 = pdbpos - 3, lp1 = alignpos - 3;
1068     boolean lastmatch = false;
1069     // and now trace the alignment onto the atom set.
1070     for (int i = 0; i < astr1.length(); i++)
1071     {
1072       char c1 = astr1.charAt(i), c2 = astr2.charAt(i);
1073       if (c1 != '-')
1074       {
1075         alignpos++;
1076       }
1077
1078       if (c2 != '-')
1079       {
1080         pdbpos++;
1081       }
1082
1083       // ignore case differences
1084       if (allowmismatch || (c1 == c2) || (Math.abs(c2 - c1) == ('a' - 'A')))
1085       {
1086         // extend mapping interval
1087         if (lp1 + 1 != alignpos || lp2 + 1 != pdbpos)
1088         {
1089           as1.add(Integer.valueOf(alignpos));
1090           as2.add(Integer.valueOf(pdbpos));
1091         }
1092         lastmatch = true;
1093         lp1 = alignpos;
1094         lp2 = pdbpos;
1095       }
1096       else
1097       {
1098         // extend mapping interval
1099         if (lastmatch)
1100         {
1101           as1.add(Integer.valueOf(lp1));
1102           as2.add(Integer.valueOf(lp2));
1103         }
1104         lastmatch = false;
1105       }
1106     }
1107     // construct range pairs
1108
1109     int[] mapseq1 = new int[as1.size() + (lastmatch ? 1 : 0)],
1110             mapseq2 = new int[as2.size() + (lastmatch ? 1 : 0)];
1111     int i = 0;
1112     for (Integer ip : as1)
1113     {
1114       mapseq1[i++] = ip;
1115     }
1116     ;
1117     i = 0;
1118     for (Integer ip : as2)
1119     {
1120       mapseq2[i++] = ip;
1121     }
1122     ;
1123     if (lastmatch)
1124     {
1125       mapseq1[mapseq1.length - 1] = alignpos;
1126       mapseq2[mapseq2.length - 1] = pdbpos;
1127     }
1128     MapList map = new MapList(mapseq1, mapseq2, 1, 1);
1129
1130     jalview.datamodel.Mapping mapping = new Mapping(map);
1131     mapping.setTo(s2);
1132     return mapping;
1133   }
1134
1135   /**
1136    * matches ochains against al and populates seqs with the best match between
1137    * each ochain and the set in al
1138    * 
1139    * @param ochains
1140    * @param al
1141    * @param dnaOrProtein
1142    * @param removeOldAnnots
1143    *          when true, old annotation is cleared before new annotation
1144    *          transferred
1145    * @return List<List<SequenceI> originals, List<SequenceI> replacement,
1146    *         List<AlignSeq> alignment between each>
1147    */
1148   public static List<List<? extends Object>> replaceMatchingSeqsWith(
1149           List<SequenceI> seqs, List<AlignmentAnnotation> annotations,
1150           List<SequenceI> ochains, AlignmentI al, String dnaOrProtein,
1151           boolean removeOldAnnots)
1152   {
1153     List<SequenceI> orig = new ArrayList<SequenceI>(),
1154             repl = new ArrayList<SequenceI>();
1155     List<AlignSeq> aligs = new ArrayList<AlignSeq>();
1156     if (al != null && al.getHeight() > 0)
1157     {
1158       ArrayList<SequenceI> matches = new ArrayList<SequenceI>();
1159       ArrayList<AlignSeq> aligns = new ArrayList<AlignSeq>();
1160
1161       for (SequenceI sq : ochains)
1162       {
1163         SequenceI bestm = null;
1164         AlignSeq bestaseq = null;
1165         float bestscore = 0;
1166         for (SequenceI msq : al.getSequences())
1167         {
1168           AlignSeq aseq = doGlobalNWAlignment(msq, sq, dnaOrProtein);
1169           if (bestm == null || aseq.getMaxScore() > bestscore)
1170           {
1171             bestscore = aseq.getMaxScore();
1172             bestaseq = aseq;
1173             bestm = msq;
1174           }
1175         }
1176         // jalview.bin.Console.outPrintln("Best Score for " + (matches.size() +
1177         // 1) + " :"
1178         // + bestscore);
1179         matches.add(bestm);
1180         aligns.add(bestaseq);
1181         al.deleteSequence(bestm);
1182       }
1183       for (int p = 0, pSize = seqs.size(); p < pSize; p++)
1184       {
1185         SequenceI sq, sp = seqs.get(p);
1186         int q;
1187         if ((q = ochains.indexOf(sp)) > -1)
1188         {
1189           seqs.set(p, sq = matches.get(q));
1190           orig.add(sp);
1191           repl.add(sq);
1192           sq.setName(sp.getName());
1193           sq.setDescription(sp.getDescription());
1194           Mapping sp2sq;
1195           sq.transferAnnotation(sp,
1196                   sp2sq = aligns.get(q).getMappingFromS1(false));
1197           aligs.add(aligns.get(q));
1198           int inspos = -1;
1199           for (int ap = 0; ap < annotations.size();)
1200           {
1201             if (annotations.get(ap).sequenceRef == sp)
1202             {
1203               if (inspos == -1)
1204               {
1205                 inspos = ap;
1206               }
1207               if (removeOldAnnots)
1208               {
1209                 annotations.remove(ap);
1210               }
1211               else
1212               {
1213                 AlignmentAnnotation alan = annotations.remove(ap);
1214                 alan.liftOver(sq, sp2sq);
1215                 alan.setSequenceRef(sq);
1216                 sq.addAlignmentAnnotation(alan);
1217               }
1218             }
1219             else
1220             {
1221               ap++;
1222             }
1223           }
1224           if (sq.getAnnotation() != null && sq.getAnnotation().length > 0)
1225           {
1226             annotations.addAll(inspos == -1 ? annotations.size() : inspos,
1227                     Arrays.asList(sq.getAnnotation()));
1228           }
1229         }
1230       }
1231     }
1232     return Arrays.asList(orig, repl, aligs);
1233   }
1234
1235   /**
1236    * compute the PID vector used by the redundancy filter.
1237    * 
1238    * @param originalSequences
1239    *          - sequences in alignment that are to filtered
1240    * @param omitHidden
1241    *          - null or strings to be analysed (typically, visible portion of
1242    *          each sequence in alignment)
1243    * @param start
1244    *          - first column in window for calculation
1245    * @param end
1246    *          - last column in window for calculation
1247    * @param ungapped
1248    *          - if true then use ungapped sequence to compute PID
1249    * @return vector containing maximum PID for i-th sequence and any sequences
1250    *         longer than that seuqence
1251    */
1252   public static float[] computeRedundancyMatrix(
1253           SequenceI[] originalSequences, String[] omitHidden, int start,
1254           int end, boolean ungapped)
1255   {
1256     int height = originalSequences.length;
1257     float[] redundancy = new float[height];
1258     int[] lngth = new int[height];
1259     for (int i = 0; i < height; i++)
1260     {
1261       redundancy[i] = 0f;
1262       lngth[i] = -1;
1263     }
1264
1265     // long start = System.currentTimeMillis();
1266
1267     SimilarityParams pidParams = new SimilarityParams(true, true, true,
1268             true);
1269     float pid;
1270     String seqi, seqj;
1271     for (int i = 0; i < height; i++)
1272     {
1273
1274       for (int j = 0; j < i; j++)
1275       {
1276         if (i == j)
1277         {
1278           continue;
1279         }
1280
1281         if (omitHidden == null)
1282         {
1283           seqi = originalSequences[i].getSequenceAsString(start, end);
1284           seqj = originalSequences[j].getSequenceAsString(start, end);
1285         }
1286         else
1287         {
1288           seqi = omitHidden[i];
1289           seqj = omitHidden[j];
1290         }
1291         if (lngth[i] == -1)
1292         {
1293           String ug = AlignSeq.extractGaps(Comparison.GapChars, seqi);
1294           lngth[i] = ug.length();
1295           if (ungapped)
1296           {
1297             seqi = ug;
1298           }
1299         }
1300         if (lngth[j] == -1)
1301         {
1302           String ug = AlignSeq.extractGaps(Comparison.GapChars, seqj);
1303           lngth[j] = ug.length();
1304           if (ungapped)
1305           {
1306             seqj = ug;
1307           }
1308         }
1309         pid = (float) PIDModel.computePID(seqi, seqj, pidParams);
1310
1311         // use real sequence length rather than string length
1312         if (lngth[j] < lngth[i])
1313         {
1314           redundancy[j] = Math.max(pid, redundancy[j]);
1315         }
1316         else
1317         {
1318           redundancy[i] = Math.max(pid, redundancy[i]);
1319         }
1320
1321       }
1322     }
1323     return redundancy;
1324   }
1325
1326   /**
1327   * calculate the mean score of the alignment
1328   * mean score is equal to the score of an alignmenet of two sequences with randomly shuffled AA sequence composited of the same AA as the two original sequences
1329   *
1330   */
1331   public void meanScore()
1332   {
1333     int length = indelfreeAstr1.length();       //both have the same length
1334     //create HashMap for counting residues in each sequence
1335     HashMap<Character, Integer> seq1ResCount = new HashMap<Character, Integer>();
1336     HashMap<Character, Integer> seq2ResCount = new HashMap<Character, Integer>();
1337
1338     // for both sequences (String indelfreeAstr1 or 2) create a key for the residue and add 1 each time its encountered
1339     for (char residue: indelfreeAstr1.toCharArray())
1340     {
1341       seq1ResCount.putIfAbsent(residue, 0);
1342       seq1ResCount.replace(residue, seq1ResCount.get(residue) + 1);
1343     }
1344     for (char residue: indelfreeAstr2.toCharArray())
1345     {
1346       seq2ResCount.putIfAbsent(residue, 0);
1347       seq2ResCount.replace(residue, seq2ResCount.get(residue) + 1);
1348     }
1349
1350     // meanscore = for each residue pair get the number of appearance and add (countA * countB * pairwiseScore(AB))
1351     // divide the meanscore by the sequence length afterwards
1352     float _meanscore = 0;
1353     for (char resA : seq1ResCount.keySet())
1354     {
1355       for (char resB : seq2ResCount.keySet())
1356       {
1357         int countA = seq1ResCount.get(resA);
1358         int countB = seq2ResCount.get(resB);
1359
1360         float scoreAB = scoreMatrix.getPairwiseScore(resA, resB);
1361
1362         _meanscore += countA *  countB * scoreAB;
1363       }
1364     }
1365     _meanscore /= length;
1366     this.meanScore = _meanscore;
1367   }
1368
1369   public float getMeanScore()
1370   {
1371     return this.meanScore;
1372   }
1373
1374   /**
1375   * calculate the hypothetic max score using the self-alignment of the sequences
1376   */
1377   public void hypotheticMaxScore()
1378   {
1379     int _hmsA = 0;
1380     int _hmsB = 0;
1381     for (char residue: indelfreeAstr1.toCharArray())
1382     {
1383       _hmsA += scoreMatrix.getPairwiseScore(residue, residue);
1384     }
1385     for (char residue: indelfreeAstr2.toCharArray())
1386     {
1387       _hmsB += scoreMatrix.getPairwiseScore(residue, residue);
1388     }
1389     this.hypotheticMaxScore = (_hmsA < _hmsB) ? _hmsA : _hmsB;  // take the lower self alignment
1390
1391   }
1392
1393   public int getHypotheticMaxScore()
1394   {
1395     return this.hypotheticMaxScore;
1396   }
1397
1398   /**
1399   * create strings based of astr1 and astr2 but without gaps
1400   */
1401   public void getIndelfreeAstr()
1402   {
1403     int n = astr1.length();     // both have the same length
1404     for (int i = 0; i < n; i++)
1405     {
1406       if (Character.isLetter(astr1.charAt(i)) && Character.isLetter(astr2.charAt(i)))   // if both sequences dont have a gap -> add to indelfreeAstr
1407       {
1408         this.indelfreeAstr1 += astr1.charAt(i);
1409         this.indelfreeAstr2 += astr2.charAt(i);
1410       }
1411     }
1412   }
1413
1414   /**
1415   * calculates the overall score of the alignment
1416   * preprescore = sum of all scores - all penalties
1417   * if preprescore < 1 ~ alignmentScore = Float.NaN     >
1418   * alignmentScore = ((preprescore - meanScore) / (hypotheticMaxScore - meanScore)) * coverage
1419   */
1420   public void scoreAlignment()
1421   {
1422
1423     getIndelfreeAstr();
1424     meanScore();
1425     hypotheticMaxScore();
1426     // cannot calculate score because denominator would be zero
1427     if (this.hypotheticMaxScore == this.meanScore)
1428     {
1429       this.alignmentScore = Float.NaN;
1430       return;
1431     }
1432     int n = indelfreeAstr1.length();
1433
1434     float score = 0;
1435     boolean aGapOpen = false;
1436     boolean bGapOpen = false;
1437     for (int i = 0; i < n; i++)
1438     {
1439       char char1 = indelfreeAstr1.charAt(i);
1440       char char2 = indelfreeAstr2.charAt(i);
1441       boolean aIsLetter = Character.isLetter(char1);
1442       boolean bIsLetter = Character.isLetter(char2);
1443       if (aIsLetter && bIsLetter)       // if pair -> get score
1444       {
1445         score += scoreMatrix.getPairwiseScore(char1, char2);
1446       } else if (!aIsLetter && !bIsLetter) {    // both are gap -> skip
1447       } else if ((!aIsLetter && aGapOpen) || (!bIsLetter && bGapOpen)) {        // one side gapopen -> score - gap_extend
1448         score -= GAP_EXTEND_COST;
1449       } else {          // no gap open -> score - gap_open
1450         score -= GAP_OPEN_COST;
1451       }
1452       // adjust GapOpen status in both sequences
1453       aGapOpen = (!aIsLetter) ? true : false;
1454       bGapOpen = (!bIsLetter) ? true : false;
1455     }
1456
1457     float preprescore = score;  // if this score < 1 --> alignment score = Float.NaN
1458     score = (score - this.meanScore) / (this.hypotheticMaxScore - this.meanScore);
1459     int[] _max = MiscMath.findMax(new int[]{astr1.replace("-","").length(), astr2.replace("-","").length()});   // {index of max, max}
1460     float coverage = (float) n / (float) _max[1];       // indelfreeAstr length / longest sequence length
1461     float prescore = score;     // only debug
1462     score *= coverage;
1463
1464     //System.out.println(String.format("prepre-score: %f, pre-score: %f, longlength: %d\nscore: %1.16f, mean: %f, max: %d", preprescore, prescore, _max[1], score, this.meanScore, this.hypotheticMaxScore));
1465     float minScore = 0f;
1466     this.alignmentScore = (score <= minScore) ? Float.NaN : score;
1467   }
1468
1469   public void setScoreMatrix(ScoreMatrix sm)
1470   {
1471     this.scoreMatrix = sm;
1472   }
1473 }