ranges)
{
boolean forwardStrand = true;
for (int[] range : ranges)
{
if (range[1] > range[0])
{
break; // forward strand confirmed
}
else if (range[1] < range[0])
{
forwardStrand = false;
break; // reverse strand confirmed
}
}
return forwardStrand;
}
/**
*
* @return true if from, or to is a three to 1 mapping
*/
public boolean isTripletMap()
{
return (toRatio == 3 && fromRatio == 1)
|| (fromRatio == 3 && toRatio == 1);
}
/**
* Returns a map which is the composite of this one and the input map. That
* is, the output map has the fromRanges of this map, and its toRanges are the
* toRanges of this map as transformed by the input map.
*
* Returns null if the mappings cannot be traversed (not all toRanges of this
* map correspond to fromRanges of the input), or if this.toRatio does not
* match map.fromRatio.
*
*
* Example 1:
* this: from [1-100] to [501-600]
* input: from [10-40] to [60-90]
* output: from [10-40] to [560-590]
* Example 2 ('reverse strand exons'):
* this: from [1-100] to [2000-1951], [1000-951] // transcript to loci
* input: from [1-50] to [41-90] // CDS to transcript
* output: from [10-40] to [1960-1951], [1000-971] // CDS to gene loci
*
*
* @param map
* @return
*/
public MapList traverse(MapList map)
{
if (map == null)
{
return null;
}
/*
* compound the ratios by this rule:
* A:B with M:N gives A*M:B*N
* reduced by greatest common divisor
* so 1:3 with 3:3 is 3:9 or 1:3
* 1:3 with 3:1 is 3:3 or 1:1
* 1:3 with 1:3 is 1:9
* 2:5 with 3:7 is 6:35
*/
int outFromRatio = getFromRatio() * map.getFromRatio();
int outToRatio = getToRatio() * map.getToRatio();
int gcd = MathUtils.gcd(outFromRatio, outToRatio);
outFromRatio /= gcd;
outToRatio /= gcd;
List toRanges = new ArrayList<>();
for (int[] range : getToRanges())
{
int[] transferred = map.locateInTo(range[0], range[1]);
if (transferred == null || transferred.length % 2 != 0)
{
return null;
}
/*
* convert [start1, end1, start2, end2, ...]
* to [[start1, end1], [start2, end2], ...]
*/
for (int i = 0; i < transferred.length;)
{
toRanges.add(new int[] { transferred[i], transferred[i + 1] });
i += 2;
}
}
return new MapList(getFromRanges(), toRanges, outFromRatio, outToRatio);
}
/**
* Answers true if the mapping is from one contiguous range to another, else
* false
*
* @return
*/
public boolean isContiguous()
{
return fromShifts.size() == 1 && toShifts.size() == 1;
}
/**
* Returns the [start, end, start, end, ...] ranges in the 'from' range that
* map to the given start-end in the 'to' range. Returns null if either
* {@code start} or {@code end} is not a mapped 'to' range position.
*
* @param start
* @param end
* @return
*/
public int[] locateInFrom2(int start, int end)
{
List ranges = mapBetween(start, end, toShifts, fromShifts,
toRatio, fromRatio);
// TODO: or just return the List and adjust calling code to match
return ranges.isEmpty() ? null : MappingUtils.rangeListToArray(ranges);
}
/**
* Returns the [start, end, start, end, ...] ranges in the 'to' range that map
* to the given start-end in the 'from' range. Returns null if either
* {@code start} or {@code end} is not a mapped 'from' range position.
*
* @param start
* @param end
* @return
*/
public int[] locateInTo2(int start, int end)
{
List ranges = mapBetween(start, end, fromShifts, toShifts,
fromRatio, toRatio);
return ranges.isEmpty() ? null : MappingUtils.rangeListToArray(ranges);
}
/**
* A helper method for navigating the mapping. Returns a (possibly empty) list
* of [start-end] positions in {@code ranges2} that map to positions in
* {@code ranges1} between {@code start} and {@code end}.
*
* @param start
* @param end
* @param ranges1
* @param ranges2
* @param wordLength1
* @param wordLength2
* @return
*/
final static List mapBetween(int start, int end,
List ranges1, List ranges2, int wordLength1,
int wordLength2)
{
/*
* first traverse ranges1 and record count of mapped positions
* to any that overlap start-end
*/
List overlaps = findOverlapPositions(ranges1, start, end);
if (overlaps.isEmpty())
{
return overlaps;
}
/*
* convert positions to equivalent 'word' positions in ranges
*/
mapWords(overlaps, wordLength1, wordLength2);
/*
* walk ranges2 and record the values found at
* the offsets in 'overlaps'
*/
List mapped = new ArrayList<>();
final int s1 = overlaps.size();
final int s2 = ranges2.size();
int rangeIndex = 0;
int rangeOffset = 0;
int mappedCount = 0;
for (int i = 0 ; i < s1 ; i++)
{
/*
* for each range in overlaps, walk ranges2 and record the values
* at the offsets, advancing rangeIndex / Offset
*/
int [] mappedRange = ranges2.get(rangeIndex);
int [] overlap = overlaps.get(s1);
while (mappedCount < overlap[1])
{
}
}
return mapped;
}
/**
* Converts the start-end positions (counted from zero) in the {@code ranges}
* list from one word length to another. Start-end positions are expanded if
* necessary to cover a whole word of length {@code wordLength1}. Positions
* are then divided by {@code wordLength1} and multiplied by
* {@code wordLength2} to give equivalent mapped words.
*
* Put simply, this converts peptide residue positions to the corresponding
* codon ranges, and codons - including partial codons - to the corresponding
* peptide positions; for example
*
*
* [1, 10] with word lengths 3:1 converts (as if bases [0-11]) to [1, 4]
*
*
* @param ranges
* @param wordLength1
* @param wordLength2
* @return
*/
final static void mapWords(List ranges, int wordLength1,
int wordLength2)
{
if (wordLength1 == 1 && wordLength2 == 1)
{
return; // nothing to do here
}
int s = ranges.size();
for (int i = 0; i < s; i++)
{
int[] range = ranges.get(i);
/*
* expand range start to the start of a word,
* and convert to wordLength2
*/
range[0] -= range[0] % wordLength1;
range[0] = range[0] / wordLength1 * wordLength2;
/*
* similar calculation for range end, adding
* (wordLength2 - 1) for end of mapped word
*/
range[1] -= range[1] % wordLength1;
range[1] = range[1] / wordLength1 * wordLength2;
range[1] += wordLength2 - 1;
}
}
/**
* Helper method that returns a (possibly empty) list of offsets in
* {@code ranges} to subranges that overlap {@code start-end}. The list
* returned holds counts of the number of positions traversed (inclusive) to
* reach the overlapping positions, not the overlapping values. Returns null
* if there are no overlaps.
*
* @param ranges
* @param start
* @param end
* @return
*/
final static List findOverlapPositions(List ranges,
int start, int end)
{
List positions = new ArrayList<>();
int pos = 0;
int s = ranges.size();
for (int i = 0; i < s; i++)
{
int[] range = ranges.get(i);
addOverlap(positions, pos, range, start, end);
pos += 1 + Math.abs(range[1] - range[0]);
}
return positions;
}
/**
* A helper method that checks whether {@code range} overlaps
* {@code start-end}, and if so adds the positional offset of the overlap to
* {@code positions}.
*
* @param positions
* a list of map offsets to add to
* @param pos
* the number of mapped positions already visited
* @param range
* a from-to range (may be forward or reverse)
* @param start
* position to test for overlap in range
* @param end
* position to test for overlap in range
* @return
*/
final static void addOverlap(List positions, int pos, int[] range,
int start, int end)
{
if (range[1] >= range[0])
{
/*
* forward direction range
*/
if (start <= range[1] && end >= range[0])
{
/*
* overlap
*/
int overlapStart = Math.max(start, range[0]);
int overlapEnd = Math.min(end, range[1]);
positions
.add(new int[]
{ 1 + overlapStart - range[0], 1 + overlapEnd - range[0] });
}
}
else
{
/*
* reverse direction range
*/
if (start <= range[0] && end >= range[1])
{
/*
* overlap
*/
int overlapStart = Math.max(start, range[1]);
int overlapEnd = Math.min(end, range[0]);
positions
.add(new int[]
{ 1 + range[0] - overlapStart, 1 + range[0] - overlapEnd });
}
}
}
}