001package com.hfg.bio.seq.alignment;
002
003import java.util.*;
004
005import com.hfg.bio.seq.BioSequence;
006import com.hfg.bio.seq.BioSequenceType;
007import com.hfg.bio.seq.NucleicAcid;
008import com.hfg.bio.seq.Protein;
009import com.hfg.bio.seq.alignment.matrix.PSSM;
010import com.hfg.bio.seq.alignment.matrix.SubstitutionMatrix;
011import com.hfg.math.SimpleSampleStats;
012import com.hfg.util.CompareUtil;
013import com.hfg.util.StringBuilderPlus;
014import com.hfg.util.StringUtil;
015
016
017//------------------------------------------------------------------------------
018/**
019 Tool for creating pairwise sequence alignments. Flexible configuration allows
020 the specification of whether the alignments returned should be global or local
021 with respect to each sequence. Gap penalties including whether or not to penalize
022 terminal gaps can also be specified per-sequence. Using a Position-specific score matrix
023 (PSSM) as the subject is also allowed.
024
025 Global alignments use a version of the <a href='http://en.wikipedia.org/wiki/Needleman-Wunsch_algorithm'>Needleman-Wunsch alogrithm</a>.
026 @author J. Alex Taylor, hairyfatguy.com
027 */
028//------------------------------------------------------------------------------
029// com.hfg XML/HTML Coding Library
030//
031// This library is free software; you can redistribute it and/or
032// modify it under the terms of the GNU Lesser General Public
033// License as published by the Free Software Foundation; either
034// version 2.1 of the License, or (at your option) any later version.
035//
036// This library is distributed in the hope that it will be useful,
037// but WITHOUT ANY WARRANTY; without even the implied warranty of
038// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
039// Lesser General Public License for more details.
040//
041// You should have received a copy of the GNU Lesser General Public
042// License along with this library; if not, write to the Free Software
043// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
044//
045// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
046// jataylor@hairyfatguy.com
047//------------------------------------------------------------------------------
048
049
050public class PairwiseSeqAligner
051{
052   private BioSequence mQuery;
053   private BioSequence mSubject;
054   private PSSM mSubjectPSSM;
055   private String mQueryResidues;
056   private String mSubjectResidues;
057   int[] mQuerySubstitutionIndices;
058   int[] mSubjectSubstitutionIndices;
059   private char mGapCharacter = sDefaultGapChar;
060   private int mMinY;
061   private int mMaxY;
062
063   // Cached
064   private int mQueryLength;
065   private int mSubjectLength;
066
067   private PairwiseSettings mSettings;
068
069   // Cached settings values for performance
070   private Integer mMinRawScore;
071   private boolean mQueryLocal;
072   private boolean mSubjectLocal;
073   private boolean mPenalizeQueryLeftTerminalGaps;
074   private boolean mPenalizeQueryRightTerminalGaps;
075   private boolean mPenalizeSubjectLeftTerminalGaps;
076   private boolean mPenalizeSubjectRightTerminalGaps;
077
078   // Matrices
079   private float[][] mScoreMatrix;
080   private byte[][] mTracebackMatrix;
081
082   // Traceback matrix values
083   private byte DIAGONAL = 0;
084   private byte QUERY_GAP = -1;
085   private byte SUBJECT_GAP = 1;
086
087   private AlignmentSeed mNextSeed = new AlignmentSeed();
088   private boolean mHasMoreSeeds;
089   
090   private float mBestScore;
091   private int   mBestScoreX;
092   private int   mBestScoreY;
093
094   private static char sDefaultGapChar = '-';
095
096   //###########################################################################
097   // CONSTRUCTORS
098   //###########################################################################
099
100   //---------------------------------------------------------------------------
101   public PairwiseSeqAligner()
102   {
103      mSettings = generateDefaultSettings();
104   }
105
106   //---------------------------------------------------------------------------
107   public PairwiseSeqAligner(PairwiseSettings inSettings)
108   {
109      setSettings(inSettings != null ? inSettings : generateDefaultSettings());
110   }
111
112   //###########################################################################
113   // PUBLIC METHODS
114   //###########################################################################
115
116   //---------------------------------------------------------------------------
117   public PairwiseSettings getSettings()
118   {
119      return mSettings;
120   }
121
122   //---------------------------------------------------------------------------
123   public PairwiseSeqAligner setSettings(PairwiseSettings inValue)
124   {
125      mSettings = inValue;
126      return this;
127   }
128
129   //---------------------------------------------------------------------------
130   public synchronized List<PairwiseSeqAlignment> align(NucleicAcid inQuery, NucleicAcid inSubject)
131   {
132      fillInMatrix(inQuery, inSubject);
133
134      List<PairwiseSeqAlignment> alignments = generateAlignments();
135
136      if (getSettings().getCalculateStatisticalScores())
137      {
138         calculateStatisticalScores(alignments);
139      }
140
141      return alignments;
142   }
143
144   //---------------------------------------------------------------------------
145   public synchronized List<PairwiseSeqAlignment> align(Protein inQuery, Protein inSubject)
146   {
147      fillInMatrix(inQuery, inSubject);
148
149      List<PairwiseSeqAlignment> alignments = generateAlignments();
150
151      if (getSettings().getCalculateStatisticalScores())
152      {
153         calculateStatisticalScores(alignments);
154      }
155
156      return alignments;
157   }
158
159   //---------------------------------------------------------------------------
160   public synchronized List<PairwiseSeqAlignment> align(BioSequence inQuery, PSSM inSubject)
161   {
162      fillInMatrix(inQuery, inSubject);
163
164      List<PairwiseSeqAlignment> alignments = generateAlignments();
165
166      if (getSettings().getCalculateStatisticalScores())
167      {
168         calculateStatisticalScores(alignments);
169      }
170
171      return alignments;
172   }
173
174   //---------------------------------------------------------------------------
175   public String showScoring(Collection<Protein> inQueries, Protein inSubject)
176   {
177      Map<Protein, Float[]> scoringMap = new HashMap<>(inQueries.size());
178
179      SubstitutionMatrix matrix = getSettings().getSubstitutionMatrix(BioSequenceType.PROTEIN);
180
181      char[] subjectResidues = inSubject.getSequence().toCharArray();
182      int subjectLength = StringUtil.replaceAll(inSubject.getSequence(), "-", "").length();
183
184      for (Protein seq : inQueries)
185      {
186         int queryLength = StringUtil.replaceAll(seq.getSequence(), "-", "").length();
187
188         char[] queryResidues = seq.getSequence().toCharArray();
189
190         boolean nTerminalQueryGap = true;
191         boolean openQueryGap = false;
192
193         boolean nTerminalSubjectGap = true;
194         boolean openSubjectGap = false;
195
196         Float[] positionScores = new Float[seq.length()];
197         int index = 0;
198         int queryResidueCount = 0;
199         int subjectResidueCount = 0;
200         for (char queryResidue : queryResidues)
201         {
202            char subjectResidue = subjectResidues[index];
203
204            if (nTerminalQueryGap
205                && queryResidue != '-')
206            {
207               nTerminalQueryGap = false;
208            }
209
210            if (nTerminalSubjectGap
211                && subjectResidue != '-')
212            {
213               nTerminalSubjectGap = false;
214            }
215
216
217            Float positionScore;
218            if ('-' == queryResidue)
219            {
220               if ((nTerminalQueryGap
221                    && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.LEFT))
222                   || (queryLength == queryResidueCount
223                       && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.RIGHT)))
224               {
225                  positionScore = 0.0f;
226               }
227               else if (openQueryGap)
228               {
229                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.QUERY).getExtensionPenalty();
230               }
231               else
232               {
233                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.QUERY).getOpenPenalty();
234                  openQueryGap = true;
235               }
236
237               subjectResidueCount++;
238            }
239            else if ('-' == subjectResidue)
240            {
241               if ((nTerminalSubjectGap
242                    && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.SUBJECT, PairwiseSeqTerminus.LEFT))
243                   || (subjectLength == subjectResidueCount
244                       && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.SUBJECT, PairwiseSeqTerminus.RIGHT)))
245               {
246                  positionScore = 0.0f;
247               }
248               else if (openSubjectGap)
249               {
250                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.SUBJECT).getExtensionPenalty();
251               }
252               else
253               {
254                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.SUBJECT).getOpenPenalty();
255                  openSubjectGap = true;
256               }
257
258               queryResidueCount++;
259            }
260            else
261            {
262               openQueryGap = false;
263               openSubjectGap = false;
264               queryResidueCount++;
265               subjectResidueCount++;
266               positionScore = (getSettings().getScoreCaseInsensitive() ? matrix.scoreCaseInsensitive(subjectResidue, queryResidue) : matrix.score(subjectResidue, queryResidue));
267            }
268
269            positionScores[index++] = positionScore;
270         }
271
272         scoringMap.put(seq, positionScores);
273      }
274
275      StringBuilderPlus buffer = new StringBuilderPlus();
276
277      for (int position = 0; position < inSubject.length(); position++)
278      {
279         buffer.append(String.format("%3d.  ", position + 1));
280
281         for (Protein seq : inQueries)
282         {
283            char residue = seq.getSequence().charAt(position);
284            Float score = scoringMap.get(seq)[position];
285            buffer.append(String.format("%c %5.2f   ", residue, score));
286         }
287
288         buffer.appendln();
289      }
290
291      buffer.appendln();
292      buffer.append("TOTAL: ");
293      for (Protein seq : inQueries)
294      {
295         Float[] positionScores = scoringMap.get(seq);
296         float total = 0.0f;
297         for (int i = 0; i < positionScores.length; i++)
298         {
299            total += positionScores[i];
300         }
301         buffer.append(String.format("%.2f    ", total));
302      }
303      buffer.appendln();
304
305      return buffer.toString();
306   }
307
308   //---------------------------------------------------------------------------
309   public String showScoring(BioSequence inQuery, PSSM inSubject)
310   {
311      List<BioSequence> queries = new ArrayList<>(1);
312      queries.add(inQuery);
313
314      return showScoring(queries, inSubject);
315   }
316
317   //---------------------------------------------------------------------------
318   public String showScoring(Collection<? extends BioSequence> inQueries, PSSM inSubject)
319   {
320      Map<BioSequence, Float[]> scoringMap = new HashMap<>(inQueries.size());
321
322      for (BioSequence seq : inQueries)
323      {
324         int queryLength = StringUtil.replaceAll(seq.getSequence(), "-", "").length();
325
326         char[] residues = seq.getSequence().toCharArray();
327
328         boolean nTerminalGap = true;
329         boolean openGap = false;
330
331         Float[] positionScores = new Float[seq.length()];
332         int index = 0;
333         int residueCount = 0;
334         for (char residue : residues)
335         {
336            if (nTerminalGap
337                && residue != '-')
338            {
339               nTerminalGap = false;
340            }
341
342            Float positionScore;
343            if ('-' == residue)
344            {
345               if ((nTerminalGap
346                    && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.LEFT))
347                   || (queryLength == residueCount
348                       && ! getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.RIGHT)))
349               {
350                  positionScore = 0.0f;
351               }
352               else if (openGap)
353               {
354                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.QUERY).getExtensionPenalty()
355                                  + ((inSubject.score(index + 1, mGapCharacter) + 0.0001f) / 2);
356               }
357               else
358               {
359                  positionScore = getSettings().getGapPenalties(PairwiseSeqType.QUERY).getOpenPenalty()
360                                  + ((inSubject.score(index + 1, mGapCharacter) + 0.0001f) / 2);
361                  openGap = true;
362               }
363            }
364            else
365            {
366               openGap = false;
367               residueCount++;
368               positionScore = inSubject.score(index + 1, residue);
369            }
370
371            positionScores[index++] = positionScore;
372         }
373
374         scoringMap.put(seq, positionScores);
375      }
376
377      StringBuilderPlus buffer = new StringBuilderPlus();
378
379      for (int position = 0; position < inSubject.length(); position++)
380      {
381         buffer.append(String.format("%3d.  ", position + 1));
382
383         for (BioSequence seq : inQueries)
384         {
385            char residue = seq.getSequence().charAt(position);
386            Float score = scoringMap.get(seq)[position];
387            buffer.append(String.format("%c %5.2f   ", residue, score));
388         }
389
390         buffer.appendln();
391      }
392
393      buffer.appendln();
394      buffer.append("TOTAL: ");
395      for (BioSequence seq : inQueries)
396      {
397         Float[] positionScores = scoringMap.get(seq);
398         float total = 0.0f;
399         for (int i = 0; i < positionScores.length; i++)
400         {
401            total += positionScores[i];
402         }
403         buffer.append(String.format("%.2f    ", total));
404      }
405      buffer.appendln();
406
407      return buffer.toString();
408   }
409
410   //###########################################################################
411   // PRIVATE METHODS
412   //###########################################################################
413
414   //---------------------------------------------------------------------------
415   private PairwiseSettings generateDefaultSettings()
416   {
417      PairwiseSettings settings = new PairwiseSettings();
418
419      return settings;
420   }
421
422   //---------------------------------------------------------------------------
423   private void commonSetup(BioSequence inQuery)
424   {
425      mQuery           = inQuery;
426      mQueryResidues   = inQuery.getSequence();
427      mQueryLength     = mQueryResidues.length();
428
429      mMinRawScore = getSettings().getMinRawScore();
430
431      mQueryLocal   = getSettings().getQueryAlignmentType().equals(PairwiseAlignmentType.LOCAL);
432      mSubjectLocal = getSettings().getSubjectAlignmentType().equals(PairwiseAlignmentType.LOCAL);
433
434      mPenalizeQueryLeftTerminalGaps = (mSubjectLocal ? false : getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.LEFT));
435      mPenalizeQueryRightTerminalGaps = (mSubjectLocal ? false : getSettings().getPenalizeTerminalGaps(PairwiseSeqType.QUERY, PairwiseSeqTerminus.RIGHT));
436      mPenalizeSubjectLeftTerminalGaps = (mQueryLocal ? false : getSettings().getPenalizeTerminalGaps(PairwiseSeqType.SUBJECT, PairwiseSeqTerminus.LEFT));
437      mPenalizeSubjectRightTerminalGaps = (mQueryLocal ? false : getSettings().getPenalizeTerminalGaps(PairwiseSeqType.SUBJECT, PairwiseSeqTerminus.RIGHT));
438   }
439
440   //---------------------------------------------------------------------------
441   private void setup(BioSequence inQuery, BioSequence inSubject)
442   {
443      commonSetup(inQuery);
444
445      mSubject = inSubject;
446      mSubjectResidues = inSubject.getSequence();
447      mSubjectLength = inSubject.length();
448
449
450      mMinY = 1;
451      mMaxY  = mSubjectLength;
452      if (getSettings().getSubjectTemplateMode())
453      {
454         while (mMinY < mSubjectLength
455               && mSubjectResidues.charAt(mMinY - 1) == '-')
456         {
457            mMinY++;
458         }
459
460         while (mMaxY > 1
461               && mSubjectResidues.charAt(mMaxY - 1) == '-')
462         {
463            mMaxY--;
464         }
465      }
466
467      // Do we need to create a new or bigger scoring matrix?
468      if (null == mScoreMatrix
469          || mQueryLength + 1 > mScoreMatrix.length
470          || mSubjectLength + 1 > mScoreMatrix[0].length)
471      {
472         mScoreMatrix = new float[mQueryLength + 1][mSubjectLength + 1];
473         mTracebackMatrix = new byte[mQueryLength + 1][mSubjectLength + 1];
474      }
475
476      mBestScore  = -1;
477      mBestScoreX = -1;
478      mBestScoreY = -1;
479
480      mScoreMatrix[0][0]     = 0;
481      mTracebackMatrix[0][0] = 0;
482
483      GapPenalties queryGapPenalties   = getSettings().getGapPenalties(PairwiseSeqType.QUERY);
484      GapPenalties subjectGapPenalties = getSettings().getGapPenalties(PairwiseSeqType.SUBJECT);
485
486      SubstitutionMatrix substitutionMatrix = getSettings().getSubstitutionMatrix(inQuery.getType());
487      // We will use pre-fetched substitution matrix indices to speed up the score matrix generation.
488      mQuerySubstitutionIndices = substitutionMatrix.getResidueIndicesForSequence(getSettings().getScoreCaseInsensitive() ? mQuery.getSequence().toUpperCase() : mQuery.getSequence());
489      mSubjectSubstitutionIndices = substitutionMatrix.getResidueIndicesForSequence(getSettings().getScoreCaseInsensitive() ? mSubject.getSequence().toUpperCase() : mSubject.getSequence());
490
491      float gapGapScore = 0;
492      if (substitutionMatrix.contains('-'))
493      {
494         gapGapScore = (getSettings().getScoreCaseInsensitive() ? substitutionMatrix.scoreCaseInsensitive('-', '-') : substitutionMatrix.score('-', '-'));
495      }
496
497      // First row
498      boolean openGap = true;
499      for (int x = 1; x <= mQueryLength; x++)
500      {
501         if (mPenalizeSubjectLeftTerminalGaps)
502         {
503            mScoreMatrix[x][0] = mScoreMatrix[x - 1][0]; // Start with the score to the left
504            if (openGap)
505            {
506               if (mQueryResidues.charAt(x - 1) == mGapCharacter)
507               {
508                  mScoreMatrix[x][0] += gapGapScore;
509               }
510               else
511               {
512                  openGap = false;
513                  mScoreMatrix[x][0] += subjectGapPenalties.getOpenPenalty();
514               }
515            }
516            else
517            {
518               mScoreMatrix[x][0] += subjectGapPenalties.getExtensionPenalty();
519            }
520         }
521         else // No subject left terminal gap penalty
522         {
523            mScoreMatrix[x][0] = 0;
524         }
525
526         mTracebackMatrix[x][0] = SUBJECT_GAP;
527      }
528
529      // First column
530      openGap = true;
531      for (int y = 1; y <= mSubjectLength; y++)
532      {
533         if (mPenalizeQueryLeftTerminalGaps)
534         {
535            mScoreMatrix[0][y] = mScoreMatrix[0][y - 1]; // Start with the score above
536
537            if (openGap)
538            {
539               if (mSubjectResidues.charAt(y - 1) == mGapCharacter)
540               {
541                  mScoreMatrix[0][y] += gapGapScore;
542               }
543               else
544               {
545                  openGap = false;
546                  mScoreMatrix[0][y] += queryGapPenalties.getOpenPenalty();
547               }
548            }
549            else
550            {
551               mScoreMatrix[0][y] += queryGapPenalties.getExtensionPenalty();
552            }
553         }
554         else if (mSubjectResidues.charAt(y - 1) == '-')
555         {
556            mScoreMatrix[0][y] = mScoreMatrix[0][y - 1] + gapGapScore;
557         }
558         else // No query left terminal gap penalty
559         {
560            mScoreMatrix[0][y] = mScoreMatrix[0][y - 1];
561         }
562
563         mTracebackMatrix[0][y] = QUERY_GAP;
564      }
565   }
566
567   //---------------------------------------------------------------------------
568   private void setup(BioSequence inQuery, PSSM inSubject)
569   {
570      commonSetup(inQuery);
571
572      mSubjectPSSM = inSubject;
573      mSubjectLength = inSubject.length();
574      mSubjectResidues = StringUtil.polyChar(' ', mSubjectLength);   //TODO: What should be done here?
575
576
577      mMinY = 1;
578      mMaxY  = mSubjectPSSM.length();
579
580      // Do we need to create a new or bigger scoring matrix?
581      if (null == mScoreMatrix
582          || mQueryLength + 1 > mScoreMatrix.length
583          || mSubjectLength + 1 > mScoreMatrix[0].length)
584      {
585         mScoreMatrix = new float[mQueryLength + 1][mSubjectLength + 1];
586         mTracebackMatrix = new byte[mQueryLength + 1][mSubjectLength + 1];
587      }
588
589      mBestScore  = -1;
590      mBestScoreX = -1;
591      mBestScoreY = -1;
592
593      mScoreMatrix[0][0]     = 0;
594      mTracebackMatrix[0][0] = 0;
595
596      GapPenalties queryGapPenalties   = getSettings().getGapPenalties(PairwiseSeqType.QUERY);
597      GapPenalties subjectGapPenalties = getSettings().getGapPenalties(PairwiseSeqType.SUBJECT);
598
599      // First row
600      boolean openGap = true;
601      for (int x = 1; x <= mQueryLength; x++)
602      {
603         if (mPenalizeSubjectLeftTerminalGaps)
604         {
605            mScoreMatrix[x][0] = mScoreMatrix[x - 1][0]; // Start with the score to the left
606            if (openGap)
607            {
608               if (mQueryResidues.charAt(x - 1) == mGapCharacter)
609               {
610                  mScoreMatrix[x][0] += inSubject.score(x, mGapCharacter);
611               }
612               else
613               {
614                  openGap = false;
615                  mScoreMatrix[x][0] += subjectGapPenalties.getOpenPenalty();
616               }
617            }
618            else
619            {
620               mScoreMatrix[x][0] += subjectGapPenalties.getExtensionPenalty();
621            }
622         }
623         else // No subject left terminal gap penalty
624         {
625            mScoreMatrix[x][0] = 0;
626         }
627
628         mTracebackMatrix[x][0] = SUBJECT_GAP;
629      }
630
631      // First column
632      for (int y = 1; y <= mSubjectLength; y++)
633      {
634         if (mPenalizeQueryLeftTerminalGaps)
635         {
636            mScoreMatrix[0][y] = mScoreMatrix[0][y - 1]; // Start with the score above
637
638            mScoreMatrix[0][y] += queryGapPenalties.getExtensionPenalty();
639         }
640         else // No query left terminal gap penalty
641         {
642            mScoreMatrix[0][y] = 0;
643         }
644
645         mTracebackMatrix[0][y] = QUERY_GAP;
646      }
647   }
648
649   //---------------------------------------------------------------------------
650   private void fillInMatrix(BioSequence inQuery, BioSequence inSubject)
651   {
652      setup(inQuery, inSubject);
653
654      GapPenalties queryGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.QUERY);
655      GapPenalties subjectGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.SUBJECT);
656
657      boolean queryLocal   = getSettings().getQueryAlignmentType().equals(PairwiseAlignmentType.LOCAL);
658      boolean subjectLocal = getSettings().getSubjectAlignmentType().equals(PairwiseAlignmentType.LOCAL);
659      boolean fullGlobal = (! queryLocal && ! subjectLocal);
660
661      boolean subjectTemplateMode = getSettings().getSubjectTemplateMode();
662
663      SubstitutionMatrix substitutionMatrix = getSettings().getSubstitutionMatrix(inQuery.getType());
664      // Use the raw matrix for speed so we won't have to repeatedly look up the substitution matrix indices.
665      float[][] rawSubstitutionMatrix = substitutionMatrix.getRawMatrix();
666
667      float gapGapScore = 0;
668      if (substitutionMatrix.contains('-'))
669      {
670         gapGapScore = (getSettings().getScoreCaseInsensitive() ? substitutionMatrix.scoreCaseInsensitive('-', '-') : substitutionMatrix.score('-', '-'));
671      }
672
673      int queryLength = inQuery.length();
674      int subjectLength = inSubject.length();
675
676      for (int y = 1; y <= subjectLength; y++)
677      {
678         for (int x = 1; x <= mQueryLength; x++)
679         {
680            // Calculate a diagonal score, a query gap score, and a subject gap score; then choose the highest score
681            // as this cell's value
682
683            float diagScore = mScoreMatrix[x - 1][y - 1] + rawSubstitutionMatrix[mQuerySubstitutionIndices[x - 1]][mSubjectSubstitutionIndices[y - 1]];
684            byte tracebackDirection = DIAGONAL;
685            float bestScore = diagScore;
686
687            // Don't create right-terminal gaps for the query if the subject is in local mode
688            if (fullGlobal
689                || x < mQueryLength)
690            {
691               float queryGapScore = mScoreMatrix[x][y - 1];
692               if (! mPenalizeQueryRightTerminalGaps
693                   && x == queryLength)
694               {
695                  // Don't add any other penalty
696               }
697               else if (subjectTemplateMode
698                        && mSubjectResidues.charAt(y - 1) == '-')
699               {
700                  // Don't penalize - subject has a gap at this position.
701                  // However, only add the gap score if it is not a terminal gap.
702                  if (y > mMinY
703                      && y < mMaxY)
704                  {
705                     queryGapScore += gapGapScore;
706                  }
707               }
708               else
709               {
710                  // If there was a gap in the query sequence at the preceding position, the gap opening penalty should apply and not the gap extension penalty.
711                  // If we are in subject template mode and there was a subject gap at the preceding position, the gap opening penalty should apply and not the gap extension penalty.
712                  boolean preceedingQueryGap = (mQueryResidues.charAt(x - 1) == '-');
713                  queryGapScore += (preceedingQueryGap
714                                    || (QUERY_GAP == mTracebackMatrix[x][y - 1]
715                                        && (! subjectTemplateMode
716                                            || y < 2
717                                            || mSubjectResidues.charAt(y - 2) != '-')) ? queryGapPenalties.getExtensionPenalty() : queryGapPenalties.getOpenPenalty());
718               }
719
720               if (queryGapScore > bestScore)
721               {
722                  tracebackDirection = QUERY_GAP;
723                  bestScore = queryGapScore;
724               }
725            }
726
727            // Don't create right-terminal gaps for the subject if the query is in local mode
728            if (fullGlobal
729                || y < mSubjectLength)
730            {
731               float subjectGapScore = mScoreMatrix[x - 1][y];
732               if (! mPenalizeSubjectRightTerminalGaps
733                   && y == subjectLength)
734               {
735                  // Full global and no subject right terminal gap penalty.
736                  // Don't add any other penalty
737               }
738               else
739               {
740                  subjectGapScore += (SUBJECT_GAP == mTracebackMatrix[x - 1][y] ? subjectGapPenalties.getExtensionPenalty() : subjectGapPenalties.getOpenPenalty());
741               }
742
743               if (subjectGapScore > bestScore)
744               {
745                  tracebackDirection = SUBJECT_GAP;
746                  bestScore = subjectGapScore;
747               }
748            }
749
750            mTracebackMatrix[x][y] = tracebackDirection;
751            mScoreMatrix[x][y] = bestScore;
752
753            // For performance, keep track of the best score.
754            // If only one alignment is desired, this will keep us from having to examine the whole matrix.
755            if (bestScore >= mBestScore)
756            {
757               mBestScore = bestScore;
758               mBestScoreX = x;
759               mBestScoreY = y;
760            }
761         }
762      }
763
764      if (getSettings().getDumpMatrices())
765      {
766         dumpMatrices();
767      }
768   }
769
770   //---------------------------------------------------------------------------
771   private void fillInMatrix(BioSequence inQuery, PSSM inSubject)
772   {
773      setup(inQuery, inSubject);
774
775      GapPenalties queryGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.QUERY);
776      GapPenalties subjectGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.SUBJECT);
777
778      boolean queryLocal   = getSettings().getQueryAlignmentType().equals(PairwiseAlignmentType.LOCAL);
779      boolean subjectLocal = getSettings().getSubjectAlignmentType().equals(PairwiseAlignmentType.LOCAL);
780      boolean fullGlobal = (! queryLocal && ! subjectLocal);
781
782      int queryLength = inQuery.length();
783      int subjectLength = inSubject.length();
784
785      float[] gapOpenScores = inSubject.getGapOpenScores();
786
787      for (int y = 1; y <= subjectLength; y++)
788      {
789         for (int x = 1; x <= queryLength; x++)
790         {
791            char queryResidue = mQueryResidues.charAt(x - 1);
792
793            // Calculate a diagonal score, a query gap score, and a subject gap score; then choose the highest score
794            // as this cell's value
795
796            float diagScore = mScoreMatrix[x - 1][y - 1] + inSubject.score(y, queryResidue);
797            byte tracebackDirection = DIAGONAL;
798            float bestScore = diagScore;
799
800            // Don't create right-terminal gaps for the query if the subject is in local mode
801            if (fullGlobal
802                || x < queryLength)
803            {
804               float queryGapScore = mScoreMatrix[x][y - 1];
805               if (! mPenalizeQueryRightTerminalGaps
806                   && x == queryLength)
807               {
808                  // Don't add any other penalty
809               }
810               else
811               {
812                  // If there was a gap in the query sequence at the preceding position, the gap opening penalty should apply and not the gap extension penalty.
813                  queryGapScore += (QUERY_GAP == (mTracebackMatrix[x][y - 1]) ? queryGapPenalties.getExtensionPenalty() * inSubject.getGapExtScore(y)
814                                                                              : queryGapPenalties.getOpenPenalty() * gapOpenScores[y - 1] );
815               }
816
817               if (queryGapScore > bestScore)
818               {
819                  tracebackDirection = QUERY_GAP;
820                  bestScore = queryGapScore;
821               }
822            }
823
824            // Don't create right-terminal gaps for the subject if the query is in local mode
825            if (fullGlobal
826                || y < subjectLength)
827            {
828               float subjectGapScore = mScoreMatrix[x - 1][y];
829               if (! mPenalizeSubjectRightTerminalGaps
830                   && y == subjectLength)
831               {
832                  // Full global and no subject right terminal gap penalty.
833                  // Don't add any other penalty
834               }
835               else
836               {
837                  subjectGapScore += (SUBJECT_GAP == (mTracebackMatrix)[x - 1][y] ? subjectGapPenalties.getExtensionPenalty() : subjectGapPenalties.getOpenPenalty());
838               }
839
840               if (subjectGapScore > bestScore)
841               {
842                  tracebackDirection = SUBJECT_GAP;
843                  bestScore = subjectGapScore;
844               }
845            }
846
847            mTracebackMatrix[x][y] = tracebackDirection;
848            mScoreMatrix[x][y] = bestScore;
849
850            // For performance, keep track of the best score.
851            // If only one alignment is desired, this will keep us from having to examine the whole matrix.
852            if (bestScore >= mBestScore)
853            {
854               mBestScore = bestScore;
855               mBestScoreX = x;
856               mBestScoreY = y;
857            }
858         }
859      }
860
861      if (getSettings().getDumpMatrices())
862      {
863         dumpMatrices();
864      }
865   }
866
867   //---------------------------------------------------------------------------
868   private void dumpMatrices()
869   {
870      StringBuilderPlus buffer = new StringBuilderPlus();
871      buffer.appendln("SCORE MATRIX:");
872
873      buffer.append("               ");
874      for (int x = 0; x < mQueryLength; x++)
875      {
876         buffer.append(String.format("   %c     ", mQueryResidues.charAt(x)));
877      }
878      buffer.appendln("");
879
880      for (int y = 0; y <= mSubjectLength; y++)
881      {
882         if (0 == y)
883         {
884            buffer.append("     ");
885         }
886         else
887         {
888            if (mSubject != null)
889            {
890               buffer.append(String.format("  %c  ", mSubjectResidues.charAt(y - 1)));
891            }
892            else
893            {
894               buffer.append(String.format("%3d. ", y));
895            }
896         }
897
898         for (int x = 0; x <= mQueryLength; x++)
899         {
900            buffer.append(String.format("%7.1f  ", mScoreMatrix[x][y]));
901         }
902         buffer.appendln("");
903      }
904
905      buffer.appendln("\n\nTRACEBACK MATRIX:");
906      buffer.append("               ");
907      for (int x = 0; x < mQueryLength; x++)
908      {
909         buffer.append(String.format("   %c     ", mQueryResidues.charAt(x)));
910      }
911      buffer.appendln("");
912
913      for (int y = 0; y <= mSubjectLength; y++)
914      {
915         if (0 == y)
916         {
917            buffer.append("    ");
918         }
919         else
920         {
921            if (mSubject != null)
922            {
923               buffer.append(String.format("  %c ", mSubjectResidues.charAt(y - 1)));
924            }
925            else
926            {
927               buffer.append(String.format("%3d. ", y));
928            }
929         }
930
931         for (int x = 0; x <= mQueryLength; x++)
932         {
933            buffer.append(String.format("%7d  ", mTracebackMatrix[x][y]));
934         }
935         buffer.appendln("");
936      }
937
938      System.out.println(buffer);
939   }
940
941   //---------------------------------------------------------------------------
942   // Tried another implementation keeping a queue of seeds but it didn't perform as well
943   private boolean hasMoreSeeds()
944   {
945      mHasMoreSeeds = false;
946
947      // Full global?
948      if (! mQueryLocal
949          && ! mSubjectLocal)
950      {
951         // Start from the bottom right cell in the matrix
952         float score = mScoreMatrix[mQueryLength][mSubjectLength];
953         if (score != Integer.MIN_VALUE)
954         {
955            mNextSeed.update(mQueryLength, mSubjectLength, score);
956            mHasMoreSeeds = true;
957         }
958      }
959      else if (mBestScore > 0)
960      {
961         mNextSeed.update(mBestScoreX, mBestScoreY, mBestScore);
962         mBestScore  = -1;
963         mBestScoreX = -1;
964         mBestScoreY = -1;
965         mHasMoreSeeds = true;
966      }
967      else
968      {
969         int minX = 0;
970         int maxX = mQueryLength;
971
972         float largestScore = 0;
973         int seedX = 0;
974         int seedY = 0;
975
976         if (! mSubjectLocal // Global subject alignment
977             && mPenalizeQueryRightTerminalGaps)
978         {
979            // Only search the bottom
980            for (int x = minX; x <= maxX; x++)
981            {
982               float score = mScoreMatrix[x][mMaxY];
983               if (score >= largestScore)
984               {
985                  seedX = x;
986                  seedY = mMaxY;
987                  largestScore = score;
988               }
989            }
990         }
991         else if (! mQueryLocal // Global query alignment
992                  && mPenalizeSubjectRightTerminalGaps)
993         {
994            // Only search the side
995            for (int y = mMinY - 1; y < mMaxY; y++)
996            {
997               float score = mScoreMatrix[maxX][y];
998               if (score >= largestScore)
999               {
1000                  seedX = maxX;
1001                  seedY = y;
1002                  largestScore = score;
1003               }
1004            }
1005         }
1006         else
1007         {
1008            for (int x = minX; x <= maxX; x++)
1009            {
1010               for (int y = mMinY - 1; y <= mMaxY; y++)
1011               {
1012                  float score = mScoreMatrix[x][y];
1013                  if (score >= largestScore)
1014                  {
1015                     seedX = x;
1016                     seedY = y;
1017                     largestScore = score;
1018                  }
1019               }
1020            }
1021         }
1022
1023         mNextSeed.update(seedX, seedY, largestScore);
1024         mHasMoreSeeds = true;
1025      }
1026
1027      if (mMinRawScore != null
1028          && mMinRawScore > mNextSeed.getScore())
1029      {
1030         mHasMoreSeeds = false;
1031      }
1032
1033      return mHasMoreSeeds;
1034   }
1035
1036   //---------------------------------------------------------------------------
1037   private AlignmentSeed nextSeed()
1038   {
1039      return mNextSeed;
1040   }
1041
1042   //---------------------------------------------------------------------------
1043   private List<PairwiseSeqAlignment> generateAlignments()
1044   {
1045      List<PairwiseSeqAlignment> alignments = new ArrayList<>(mSettings.getMaxLocalAlignmentResults());
1046
1047      boolean queryGlobal = ! mQueryLocal;
1048      boolean subjectGlobal = ! mSubjectLocal;
1049      boolean fullGlobal = queryGlobal && subjectGlobal;
1050      boolean fullLocal = ! queryGlobal && ! subjectGlobal;
1051
1052      int maxAlignments = (fullGlobal ? 1 : getSettings().getMaxLocalAlignmentResults());
1053
1054      while (hasMoreSeeds()
1055             && alignments.size() < maxAlignments)
1056      {
1057         AlignmentSeed seed = nextSeed();
1058
1059         // Note that these aligned sequences will be built up in the reverse order and then reversed at the end for performance
1060         StringBuilder alignedQuery   = new StringBuilder();
1061         StringBuilder alignedSubject = new StringBuilder();
1062
1063         int x = seed.getX();
1064         int y = seed.getY();
1065         float score = seed.getScore();
1066
1067         byte tracebackDirection = mTracebackMatrix[x][y];
1068
1069         // Query global / Subject local or Subject global / Query local alignment.
1070         // Don't align past the end of the global seq.
1071         while ((x > mQueryLength
1072                 && queryGlobal
1073                 && mSubjectLocal)
1074                || (y > mSubjectLength
1075                    && subjectGlobal
1076                    && mQueryLocal))
1077         {
1078            if (DIAGONAL == tracebackDirection)
1079            {
1080               x--;
1081               y--;
1082            }
1083            else if (QUERY_GAP == tracebackDirection)
1084            {
1085               y--;
1086            }
1087            else if (SUBJECT_GAP == tracebackDirection)
1088            {
1089               x--;
1090            }
1091
1092            tracebackDirection = mTracebackMatrix[x][y];
1093         }
1094
1095//         int xLeftLimit = (queryGlobal && subjectLocal ? 1 : 0);
1096//         int yLeftLimit = (subjectGlobal && queryLocal ? 1 : 0);
1097         int xLeftLimit = (fullGlobal ? 0 : 1);
1098         int yLeftLimit = (fullGlobal ? 0 : 1);
1099
1100         while ((x > 0
1101                 || y > 0)
1102                && (fullLocal ? mScoreMatrix[x][y] > 0 : true))
1103         {
1104            float matrixScore = mScoreMatrix[x][y];
1105
1106            // Did we overlap a previous alignment?
1107            if (Integer.MIN_VALUE == matrixScore)
1108            {
1109               break; // Skip
1110            }
1111            else if (x < xLeftLimit)
1112            {
1113               break;  // Query global / Subject local alignment. Don't align past the beginning of the Query
1114            }
1115            else if (y < yLeftLimit)
1116            {
1117               break;  // Subject global / Query local alignment. Don't align past the beginning of the Subject
1118            }
1119
1120            // Deface this position so we won't get overlapping subsequences
1121            mScoreMatrix[x][y] = Integer.MIN_VALUE;
1122
1123            if (DIAGONAL == tracebackDirection)
1124            {
1125               alignedQuery.append(mQueryResidues.charAt(x - 1));
1126               alignedSubject.append(mSubjectResidues.charAt(y - 1));
1127               x--;
1128               y--;
1129            }
1130            else if (QUERY_GAP == tracebackDirection)
1131            {
1132               alignedQuery.append(mGapCharacter);
1133               alignedSubject.append(mSubjectResidues.charAt(y - 1));
1134               y--;
1135            }
1136            else if (SUBJECT_GAP == tracebackDirection)
1137            {
1138               alignedQuery.append(mQueryResidues.charAt(x - 1));
1139               alignedSubject.append(mGapCharacter);
1140               x--;
1141            }
1142
1143            tracebackDirection = mTracebackMatrix[x][y];
1144         }
1145
1146         // Did we overlap a previous alignment?
1147         if (Integer.MIN_VALUE == mScoreMatrix[x][y])
1148         {
1149            continue; // Skip
1150         }
1151         else
1152         {
1153            // Deface this position so we won't get overlapping subsequences
1154            mScoreMatrix[x][y] = Integer.MIN_VALUE;
1155         }
1156
1157         if (queryGlobal
1158             || (alignedQuery.length() >= mSettings.getMinLocalAlignmentLength()  // Length of aligned query meets minimum?
1159                 && StringUtil.replaceAll(alignedQuery, "-", "").length() >= mSettings.getMinQueryMatchLength()))
1160         {
1161            int initialQueryPosition   = x + 1;
1162            int initialSubjectPosition = y + 1;
1163
1164            if (mQueryLocal && subjectGlobal)
1165            {
1166               if (y > 0)
1167               {
1168                  // Add a left terminal query gap
1169                  alignedQuery.append(StringUtil.polyChar('-', y));
1170
1171                  alignedSubject = alignedSubject.reverse();
1172                  alignedSubject.insert(0, mSubjectResidues, 0, y);
1173                  initialSubjectPosition = 1;
1174
1175                  if (mPenalizeQueryLeftTerminalGaps)
1176                  {
1177                     GapPenalties queryGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.QUERY);
1178
1179                     score += queryGapPenalties.getOpenPenalty()
1180                              + (queryGapPenalties.getExtensionPenalty() * (y - 1));
1181                  }
1182               }
1183               else
1184               {
1185                  alignedSubject = alignedSubject.reverse();
1186               }
1187
1188               alignedQuery = alignedQuery.reverse();
1189
1190               if (seed.getY() < mSubjectLength)
1191               {
1192                  // Add a right terminal query gap
1193                  int gapLength = mSubjectLength - seed.getY();
1194
1195                  alignedQuery.append(StringUtil.polyChar('-', gapLength));
1196                  alignedSubject.append(mSubjectResidues, seed.getY(), mSubjectLength);
1197
1198                  if (mPenalizeQueryRightTerminalGaps)
1199                  {
1200                     GapPenalties queryGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.QUERY);
1201
1202                     score += queryGapPenalties.getOpenPenalty()
1203                              + (queryGapPenalties.getExtensionPenalty() * (gapLength - 1));
1204                  }
1205               }
1206            }
1207            else if (mSubjectLocal && queryGlobal)
1208            {
1209               if (x > 0)
1210               {
1211                  // Add a left terminal subject gap
1212                  alignedQuery = alignedQuery.reverse();
1213                  alignedQuery.insert(0, mQueryResidues, 0, x);
1214
1215                  alignedSubject.append(StringUtil.polyChar('-', x));
1216                  initialSubjectPosition = 1;
1217
1218                  if (mPenalizeSubjectLeftTerminalGaps)
1219                  {
1220                     GapPenalties subjectGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.SUBJECT);
1221
1222                     score += subjectGapPenalties.getOpenPenalty()
1223                              + (subjectGapPenalties.getExtensionPenalty() * (x - 1));
1224                  }
1225               }
1226               else
1227               {
1228                  alignedQuery = alignedQuery.reverse();
1229               }
1230
1231               alignedSubject = alignedSubject.reverse();
1232
1233               if (seed.getX() < mQueryLength)
1234               {
1235                  // Add a right terminal subject gap
1236                  int gapLength = mQueryLength - seed.getX();
1237
1238                  alignedQuery.append(mQueryResidues, seed.getX(), mQueryLength);
1239                  alignedSubject.append(StringUtil.polyChar('-', gapLength));
1240
1241                  if (mPenalizeSubjectRightTerminalGaps)
1242                  {
1243                     GapPenalties subjectGapPenalties = mSettings.getGapPenalties(PairwiseSeqType.SUBJECT);
1244
1245                     score += subjectGapPenalties.getOpenPenalty()
1246                              + (subjectGapPenalties.getExtensionPenalty() * (gapLength - 1));
1247                  }
1248               }
1249            }
1250            else
1251            {
1252               alignedQuery = alignedQuery.reverse();
1253               alignedSubject = alignedSubject.reverse();
1254            }
1255
1256            if (score >= mMinRawScore)
1257            {
1258               AlignedQuery query = new AlignedQuery(mQuery, alignedQuery, initialQueryPosition);
1259               AlignedSubject subject;
1260               if (mSubject != null)
1261               {
1262                  subject = new AlignedSubject(mSubject, alignedSubject, initialSubjectPosition);
1263               }
1264               else
1265               {
1266                  subject = new AlignedSubject(mSubjectPSSM, alignedSubject, initialSubjectPosition);
1267               }
1268
1269               PairwiseSeqAlignment alignment = new PairwiseSeqAlignment(query, subject).setScore(score);
1270               alignment.setSettings(getSettings().clone());
1271
1272               // TODO: force full local alignment if specified
1273
1274               alignments.add(alignment);
1275            }
1276         }
1277
1278         if (getSettings().getDumpMatrices())
1279         {
1280            dumpMatrices();
1281         }
1282      }
1283
1284      return alignments;
1285   }
1286
1287   //---------------------------------------------------------------------------
1288   private void calculateStatisticalScores(List<PairwiseSeqAlignment> inAlignments)
1289   {
1290      String originalQuerySeq = mQuery.getSequence();
1291
1292      // Calculate the background
1293      double prevMean = 0;
1294      SimpleSampleStats stats = new SimpleSampleStats();
1295
1296      do
1297      {
1298         if (stats.getSampleSize() > 0)
1299         {
1300            prevMean = stats.getMean();
1301         }
1302         
1303         // Scramble the query
1304         String querySeq = mQuery.getSequence();
1305         mQuery.setSequence(StringUtil.scramble(querySeq));
1306
1307         if (mSubject != null)
1308         {
1309            fillInMatrix(mQuery, mSubject);
1310         }
1311         else // Working w/ a PSSM
1312         {
1313            fillInMatrix(mQuery, mSubjectPSSM);
1314         }
1315
1316         stats.add(hasMoreSeeds() ? nextSeed().getScore() : 0);
1317
1318         // Repeat until the mean background value isn't changing significantly
1319      }
1320      while (stats.getSampleSize() < getSettings().getMinNumOfBackgroundCycles()
1321             || Math.abs(stats.getMean() - prevMean) > 0.000001 * stats.getMean());
1322
1323
1324      // Calculate the decay constant
1325      double lambda = Math.PI / (stats.getSampleStandardDeviation() * Math.sqrt(6));
1326
1327      // Estimate the extreme value distribution's location constant
1328      double mu = stats.getMean() - 0.5772 / lambda;
1329
1330      for (PairwiseSeqAlignment alignment : inAlignments)
1331      {
1332         double eValue = Math.exp(-lambda * (alignment.getScore() - mu));
1333         alignment.setEValue((float) eValue);
1334
1335         alignment.setPValue((float) (1 - Math.exp(-eValue)));
1336
1337         alignment.setZScore((float) ((alignment.getScore() - stats.getMean()) / stats.getSampleStandardDeviation()));
1338      }
1339
1340
1341      // Replace the original query seq
1342      if (originalQuerySeq != null)
1343      {
1344         mQuery.setSequence(originalQuerySeq);
1345      }
1346   }
1347
1348
1349   //###########################################################################
1350   // INNER CLASS
1351   //###########################################################################
1352
1353   private class AlignmentSeed implements Comparable<AlignmentSeed>
1354   {
1355      private int   mX;
1356      private int   mY;
1357      private float mCumulativeScore;
1358
1359      //###########################################################################
1360      // CONSTRUCTORS
1361      //###########################################################################
1362
1363      //---------------------------------------------------------------------------
1364      public AlignmentSeed()
1365      {
1366      }
1367
1368      //---------------------------------------------------------------------------
1369      public AlignmentSeed(int inX, int inY, float inScore)
1370      {
1371         mX = inX;
1372         mY = inY;
1373         mCumulativeScore = inScore;
1374      }
1375
1376      //###########################################################################
1377      // PUBLIC METHODS
1378      //###########################################################################
1379
1380      //---------------------------------------------------------------------------
1381      public void update(int inX, int inY, float inScore)
1382      {
1383         mX = inX;
1384         mY = inY;
1385         mCumulativeScore = inScore;
1386      }
1387
1388      //---------------------------------------------------------------------------
1389      public int getX()
1390      {
1391         return mX;
1392      }
1393
1394      //---------------------------------------------------------------------------
1395      public int getY()
1396      {
1397         return mY;
1398      }
1399
1400      //---------------------------------------------------------------------------
1401      public float getScore()
1402      {
1403         return mCumulativeScore;
1404      }
1405
1406      //---------------------------------------------------------------------------
1407      @Override
1408      public String toString()
1409      {
1410         return String.format("[%d,%d]: %.2f", mX, mY, mCumulativeScore);
1411      }
1412
1413      //---------------------------------------------------------------------------
1414      public int compareTo(AlignmentSeed inObj2)
1415      {
1416         int result = 1;
1417
1418         if (inObj2 != null)
1419         {
1420            // Sort by score high to low
1421            result = - CompareUtil.compare(getScore(), inObj2.getScore());
1422
1423            if (0 == result)
1424            {
1425               result = CompareUtil.compare(getX(), inObj2.getX());
1426
1427               if (0 == result)
1428               {
1429                  result = CompareUtil.compare(getY(), inObj2.getY());
1430               }
1431            }
1432         }
1433
1434         return result;
1435      }
1436
1437      //---------------------------------------------------------------------------
1438      @Override
1439      public boolean equals(Object inObj2)
1440      {
1441         boolean result = false;
1442
1443         if (inObj2 != null
1444               && inObj2 instanceof AlignmentSeed)
1445         {
1446            AlignmentSeed seed2 = (AlignmentSeed) inObj2;
1447            result = (0 == compareTo(seed2));
1448         }
1449
1450         return result;
1451      }
1452
1453      //---------------------------------------------------------------------------
1454      @Override
1455      public int hashCode()
1456      {
1457         return (31 * mX) + (31 * mY) + (31 * Float.floatToIntBits(mCumulativeScore));
1458      }
1459   }
1460
1461}