All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.citec.tcs.alignment.StrictAllOptimalAlgorithm Maven / Gradle / Ivy

/* 
 * TCS Alignment Toolbox
 * 
 * Copyright (C) 2013-2015
 * Benjamin Paaßen, Georg Zentgraf
 * AG Theoretical Computer Science
 * Centre of Excellence Cognitive Interaction Technology (CITEC)
 * University of Bielefeld
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 */
package de.citec.tcs.alignment;

import de.citec.tcs.alignment.comparators.OperationType;
import de.citec.tcs.alignment.sequence.Node;
import de.citec.tcs.alignment.sequence.Sequence;
import java.util.Random;
import java.util.Stack;

/**
 * This is an implementation of the Needleman-Wunsch-Algorithm for sequence
 * alignment. It returns all optimal paths until a limit of k paths, where k is
 * given per default by DEFAULTPATHLIMIT. The scoring scheme is equivalent to
 * the StrictAlignmentScoreAlgorithm.
 *
 * @author Benjamin Paassen - bpaassen(at)techfak.uni-bielefeld.de
 */
public class StrictAllOptimalAlgorithm
		extends AbstractGapAlignmentAlgorithm {

	public static final int DEFAULTPATHLIMIT = 100;
	private int pathLimit = DEFAULTPATHLIMIT;

	/**
	 * This sets up an AlignmentAlgorithm instance according to the given
	 * specification.
	 *
	 * @param alignmentSpecification an AlignmentSpecification.
	 */
	public StrictAllOptimalAlgorithm(AlignmentSpecification alignmentSpecification) {
		super(alignmentSpecification, FullMatrixEntry.class, PathList.class);
	}

	/**
	 * Please refer to the corresponding setter-method for more information.
	 *
	 * @return a maximum number of co-optimal paths that shall be calculated.
	 */
	public int getPathLimit() {
		return pathLimit;
	}

	/**
	 * The total number of co-optimal alignments can be very huge as multiple
	 * co-optimal operations lead to a combinatorial explosion of possible
	 * alignments. Thus this option enables you to limit the number of paths
	 * that are actually calculated to keep the calculation time controlled.
	 *
	 * As soon as the limit is reached random choices of operations are used
	 * instead of exploring all co-optimal solutions.
	 *
	 * The default for this is DEFAULTPATHLIMIT.
	 *
	 * @param pathLimit a maximum number of co-optimal paths that shall be
	 * calculated.
	 */
	public void setPathLimit(int pathLimit) {
		if (pathLimit < 1) {
			throw new IllegalArgumentException("You have to allow at least one path to be calculated!");
		}
		this.pathLimit = pathLimit;
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public FullMatrixEntry createInitial() {
		return new FullMatrixEntry(0, 0, 0, 0, 0, 0);
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public FullMatrixEntry createDelInitial(FullMatrixEntry delOld, int i, double delLocal) {
		return new FullMatrixEntry(delOld.getScore() + delLocal, delLocal, 0, 0, i, 0);
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public FullMatrixEntry createInsInitial(FullMatrixEntry insOld, int j, double insLocal) {
		return new FullMatrixEntry(insOld.getScore() + insLocal, 0, insLocal, 0, 0, j);
	}

	/**
	 * {@inheritDoc }
	 */
	@Override
	public FullMatrixEntry createNewEntry(FullMatrixEntry delOld,
			FullMatrixEntry insOld,
			FullMatrixEntry repOld,
			int i, int j,
			double delLocal, double insLocal, double repLocal) {
		final double delTotal = delOld.getScore() + delLocal;
		final double insTotal = insOld.getScore() + insLocal;
		final double repTotal = repOld.getScore() + repLocal;
		if (delTotal < insTotal) {
			if (delTotal < repTotal) {
				return new FullMatrixEntry(delTotal, delLocal, insLocal, repLocal, i, j);
			} else {
				return new FullMatrixEntry(repTotal, delLocal, insLocal, repLocal, i, j);
			}
		} else {
			if (insTotal < repTotal) {
				return new FullMatrixEntry(insTotal, delLocal, insLocal, repLocal, i, j);
			} else {
				return new FullMatrixEntry(repTotal, delLocal, insLocal, repLocal, i, j);
			}
		}
	}

	/**
	 * {@inheritDoc }
	 */
	public PathList transformToResult(FullMatrixEntry[][] alignmentMatrix, Sequence a, Sequence b) {
		final int M = a.getNodes().size();
		final int N = b.getNodes().size();

		final FullMatrixEntry last = alignmentMatrix[M][N];
		/*
		 * In each step of the alignment we had a distance of 1 at most. Thus we
		 * can normalize the score to the range [0,1] if we just divide the raw
		 * score by the worst case, which would be to delete a entirely and
		 * insert b entirely => worst case score = m+n.
		 */
		final int normalization = Math.max(1, M + N);
		final double normalizedScore = last.getScore() / normalization;
		//Now backtrace to reconstruct the paths.
		final Stack stack = new Stack();
		final PathList pathlist = new PathList();
		stack.push(new TmpPath(M, N, new AlignmentPath(getSpecification(), a, b, normalizedScore)));
		TmpPath current;
		int pathCounter = 1;
		while (!stack.empty()) {
			current = stack.pop();
			//we are finished if we are at the start.
			if (current.i == 0 && current.j == 0) {
				//in that case we transform the temporary path to an actual one.
				while (!current.opStack.empty()) {
					current.rawPath.getOperations().add(current.opStack.pop());
				}
				pathlist.add(current.rawPath);
				continue;
			}
			//otherwise we still have work to do.
			Node left, right;
			OperationType type;
			double[] local;
			//If we are in the first column we can only delete.
			if (current.j == 0) {
				left = a.getNodes().get(current.i - 1);
				right = null;
				local = getSpecification().calculateDeletionCosts(left);
				type = OperationType.DELETION;
				final Operation del = new Operation(left, right, type, current.rawPath);
				del.setComparatorDistances(local);
				current.opStack.push(del);
				current.i--;
				stack.push(current);
				continue;
			}
			//if we are in the first row we can only insert.
			if (current.i == 0) {
				left = null;
				right = b.getNodes().get(current.j - 1);
				local = getSpecification().calculateInsertionCosts(right);
				type = OperationType.INSERTION;
				final Operation ins = new Operation(left, right, type, current.rawPath);
				ins.setComparatorDistances(local);
				current.opStack.push(ins);
				current.j--;
				stack.push(current);
				continue;
			}
			//if we are inside the matrix we consider all operations that are co-optimal, until we
			//reached the limit.
			final FullMatrixEntry delOld = alignmentMatrix[current.i - 1][current.j];
			final FullMatrixEntry insOld = alignmentMatrix[current.i][current.j - 1];
			final FullMatrixEntry repOld = alignmentMatrix[current.i - 1][current.j - 1];
			final double delTotal = delOld.getScore()
					+ alignmentMatrix[current.i][current.j].getDelLocal();
			final double insTotal = insOld.getScore()
					+ alignmentMatrix[current.i][current.j].getInsLocal();
			final double repTotal = repOld.getScore()
					+ alignmentMatrix[current.i][current.j].getRepLocal();
			final double optimum = alignmentMatrix[current.i][current.j].getScore();

			boolean[] cooptimal = {delTotal == optimum, insTotal == optimum, repTotal == optimum};
			if (pathCounter == pathLimit) {
				//if we have reached the limit we choose one continuation of the path at random.
				int counter = 0;
				for (final boolean coop : cooptimal) {
					if (coop) {
						counter++;
					}
				}
				if (counter > 1) {
					final Random random = new Random();
					int winnerIdx = random.nextInt(counter);
					int i = 0;
					for (int j = 0; j < cooptimal.length; j++) {
						if (cooptimal[j]) {
							if (i != winnerIdx) {
								cooptimal[j] = false;
							}
							i++;
						}
					}
				}
			}

			//continue the backtracing with a deletion if that is co-optimal.
			if (cooptimal[0]) {
				left = a.getNodes().get(current.i - 1);
				right = null;
				local = getSpecification().calculateDeletionCosts(left);
				type = OperationType.DELETION;
				final Operation del = new Operation(left, right, type, current.rawPath);
				del.setComparatorDistances(local);
				current.opStack.push(del);
				current.i--;
				stack.push(current);
			}
			//continue the backtracing with an insertion if that is co-optimal.
			if (cooptimal[1]) {
				//if we already did continue the path using a deletion, we make a clone before we proceed.
				if (cooptimal[0]) {
					try {
						//reverse the changes made by a deletion
						current = (TmpPath) current.clone();
						current.i++;
						current.opStack.pop();
						pathCounter++;
					} catch (CloneNotSupportedException ex) {
						throw new RuntimeException(ex);
					}
				}
				left = null;
				right = b.getNodes().get(current.j - 1);
				local = getSpecification().calculateInsertionCosts(right);
				type = OperationType.INSERTION;
				final Operation ins = new Operation(left, right, type, current.rawPath);
				ins.setComparatorDistances(local);
				current.opStack.push(ins);
				current.j--;
				stack.push(current);
			}
			//continue the backtracing with a replacement if that is co-optimal.
			if (cooptimal[2]) {
				//if we already did continue the path, we make a clone before we proceed.
				if (cooptimal[0] || cooptimal[1]) {
					try {
						current = (TmpPath) current.clone();
						pathCounter++;
						//if we continued with an insertion, reverse the changes made by the insertion
						if (cooptimal[1]) {
							current.j++;
						} else {
							//if we used a deletion, reverse those changes.
							current.i++;
						}
						current.opStack.pop();
					} catch (CloneNotSupportedException ex) {
						throw new RuntimeException(ex);
					}
				}
				left = a.getNodes().get(current.i - 1);
				right = b.getNodes().get(current.j - 1);
				local = getSpecification().calculateReplacementCosts(left, right);
				type = OperationType.REPLACEMENT;
				final Operation rep = new Operation(left, right, type, current.rawPath);
				rep.setComparatorDistances(local);
				current.opStack.push(rep);
				current.i--;
				current.j--;
				stack.push(current);
			}
		}
		return pathlist;
	}

	private static class TmpPath {

		private Stack opStack = new Stack<>();
		private int i;
		private int j;
		private final AlignmentPath rawPath;

		public TmpPath(int i, int j, AlignmentPath rawPath) {
			this.i = i;
			this.j = j;
			this.rawPath = rawPath;
		}

		@Override
		protected Object clone() throws CloneNotSupportedException {
			final TmpPath clone = new TmpPath(i, j, (AlignmentPath) rawPath.clone());
			for (final Operation op : opStack) {
				clone.opStack.push(op);
			}
			return clone;
		}
	}

	public static class FullMatrixEntry implements AlignmentMatrixEntry {

		private final double score;
		private final double delLocal;
		private final double insLocal;
		private final double repLocal;
		private final int i;
		private final int j;

		public FullMatrixEntry(double score, double delLocal, double insLocal, double repLocal,
				int i, int j) {
			this.score = score;
			this.delLocal = delLocal;
			this.insLocal = insLocal;
			this.repLocal = repLocal;
			this.i = i;
			this.j = j;
		}

		public double getScore() {
			return score;
		}

		public double getDelLocal() {
			return delLocal;
		}

		public double getInsLocal() {
			return insLocal;
		}

		public double getRepLocal() {
			return repLocal;
		}

		public int getI() {
			return i;
		}

		public int getJ() {
			return j;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy