]> git.donarmstrong.com Git - biopieces.git/blob - code_ruby/lib/maasha/align/pair.rb
fixed another bug in find orfs
[biopieces.git] / code_ruby / lib / maasha / align / pair.rb
1 # Copyright (C) 2007-2012 Martin A. Hansen.
2
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17 # http://www.gnu.org/copyleft/gpl.html
18
19 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
20
21 # This software is part of the Biopieces framework (www.biopieces.org).
22
23 # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
24
25 # Module with stuff to create a pairwise aligment.
26 module PairAlign
27   # Class for creating a pairwise alignment.
28   class AlignPair
29     # Class method to create a pairwise alignment of two given Seq objects.
30     def self.align(q_entry, s_entry)
31       self.new(q_entry, s_entry)
32     end
33
34     # Method to inialize a pairwise alignment given two Seq objects.
35     def initialize(q_entry, s_entry)
36       @q_entry = q_entry
37       @s_entry = s_entry
38       @matches = []
39
40       @q_entry.seq.downcase!
41       @s_entry.seq.downcase!
42
43       align_recurse(@q_entry.seq, @s_entry.seq, 0, 0, @q_entry.length - 1, @s_entry.length - 1, 16, [])
44       matches_upcase
45       gaps_insert
46     end
47
48     private
49
50     # Method that creates an alignment by chaining matches, which are
51     # subsequences shared between two sequences. This recursive method
52     # functions by considering only matches within a given search space. If no
53     # matches are given these will be located and matches will be included
54     # depending on a calculated score. New search spaces spanning the spaces
55     # between the best scoring matches and the search space boundaries will be
56     # cast and recursed into.
57     def align_recurse(q_seq, s_seq, q_min, s_min, q_max, s_max, kmer, matches)
58       matches = matches_select_by_space(matches, q_min, s_min, q_max, s_max)
59
60       while (matches.size == 0 and kmer > 0)
61         matches = matches_find(q_seq, s_seq, q_min, s_min, q_max, s_max, kmer)
62         kmer /= 2
63       end
64
65       matches_score(matches, q_min, s_min, q_max, s_max)
66
67 #      matches.each { |m| puts m.to_s(q_seq) }
68
69       unless @matches.empty?
70         matches = matches.select { |match| match.score > 0 }
71       end
72
73       if best_match = matches.pop
74         @matches << best_match
75
76         l_q_min = q_min
77         l_s_min = s_min
78         l_q_max = best_match.q_beg - 1
79         l_s_max = best_match.s_beg - 1
80
81         r_q_min = best_match.q_end + 1
82         r_s_min = best_match.s_end + 1
83         r_q_max = q_max
84         r_s_max = s_max
85
86         if l_q_max - l_q_min > 0 and l_s_max - l_s_min > 0
87           align_recurse(q_seq, s_seq, l_q_min, l_s_min, l_q_max, l_s_max, kmer, matches)
88         end
89
90         if r_q_max - r_q_min > 0 and r_s_max - r_s_min > 0
91           align_recurse(q_seq, s_seq, r_q_min, r_s_min, r_q_max, r_s_max, kmer, matches)
92         end
93       end
94     end
95
96     # Method to select matches that lies within the search space.
97     def matches_select_by_space(matches, q_min, s_min, q_max, s_max)
98       new_matches = matches.select do |match|
99         match.q_beg >= q_min and
100         match.s_beg >= s_min and
101         match.q_end <= q_max and
102         match.s_end <= s_max
103       end
104
105       new_matches
106     end
107
108     def matches_score(matches, q_min, s_min, q_max, s_max)
109       matches.each do |match|
110         score_length = match.length
111
112         min = (q_min - s_min).abs
113         max = ((q_max - q_min) - (s_max - s_min)).abs
114         beg = ((match.q_beg - q_min) - (match.s_beg - s_min)).abs
115
116         if beg > (max - min) / 2
117           score_diag = max - min
118         else
119           score_diag = beg
120         end
121
122 #        puts "score_length: #{score_length}   score_diag: #{score_diag}     score: #{score_length - score_diag}"
123
124         match.score = score_length - score_diag
125       end
126
127       matches.sort_by! { |match| match.score }
128     end
129
130
131     # Method that finds all maximally expanded non-redundant matches shared
132     # between two sequences inside a given search space.
133     def matches_find(q_seq, s_seq, q_min, s_min, q_max, s_max, kmer)
134       matches   = []
135       redundant = Hash.new { |h, k| h[k] = [] }
136
137       s_index = index_seq(s_seq, s_min, s_max, kmer)
138
139       q_pos = q_min
140
141       while q_pos <= q_max - kmer + 1
142         q_oligo = q_seq[q_pos ... q_pos + kmer]
143
144         s_index[q_oligo].each do |s_pos|
145           match = Match.new(q_pos, s_pos, kmer)
146
147           unless match_redundant?(redundant, match)
148             match_expand(match, q_seq, s_seq, q_min, s_min, q_max, s_max)
149             matches << match
150
151             match_redundant_add(redundant, match)
152           end
153         end
154
155         q_pos += 1
156       end
157
158       matches
159     end
160
161     # Method that indexes a seuquence within a given interval such that the
162     # index contains all oligos of a given kmer size and the positions where
163     # this oligo was located.
164     def index_seq(seq, min, max, kmer)
165       index_hash = Hash.new { |h, k| h[k] = [] }
166
167       pos = min
168
169       while pos <= max - kmer + 1
170         oligo = seq[pos ... pos + kmer]
171         index_hash[oligo] << pos
172
173         pos += 1
174       end
175
176       index_hash
177     end
178
179     # Method to check if a match is redundant.
180     def match_redundant?(redundant, match)
181       redundant[match.q_beg].each do |s_interval|
182         if s_interval.include? match.s_beg and s_interval.include? match.s_end
183           return true
184         end
185       end
186
187       false
188     end
189
190     # Method that adds a match to the redundancy index.
191     def match_redundant_add(redundant, match)
192       (match.q_beg .. match.q_end).each do |q|
193         redundant[q] << (match.s_beg .. match.s_end)
194       end
195     end
196
197     # Method that expands a match as far as possible to the left and right.
198     def match_expand(match, q_seq, s_seq, q_min, s_min, q_max, s_max)
199       match_expand_left(match, q_seq, s_seq, q_min, s_min)
200       match_expand_right(match, q_seq, s_seq, q_max, s_max)
201
202       match
203     end
204
205     # Method that expands a match as far as possible to the left.
206     def match_expand_left(match, q_seq, s_seq, q_min, s_min)
207       while match.q_beg > q_min and
208             match.s_beg > s_min and 
209             q_seq[match.q_beg - 1] == s_seq[match.s_beg - 1]
210         match.q_beg  -= 1
211         match.s_beg  -= 1
212         match.length += 1
213       end
214
215       match
216     end
217
218     # Method that expands a match as far as possible to the right.
219     def match_expand_right(match, q_seq, s_seq, q_max, s_max)
220       while match.q_end < q_max and
221             match.s_end < s_max and
222             q_seq[match.q_end + 1] == s_seq[match.s_end + 1]
223         match.length += 1
224       end
225
226       match
227     end
228
229     # Method for debugging purposes that upcase matching sequence while non-matches
230     # sequence is kept in lower case.
231     def matches_upcase
232       @matches.each do |match|
233         @q_entry.seq[match.q_beg .. match.q_end] = @q_entry.seq[match.q_beg .. match.q_end].upcase
234         @s_entry.seq[match.s_beg .. match.s_end] = @s_entry.seq[match.s_beg .. match.s_end].upcase
235       end
236     end
237
238     # Method that insert gaps in sequences based on a list of matches and thus
239     # creating an alignment.
240     def gaps_insert
241       @matches.sort_by! { |m| m.q_beg }
242
243       q_gaps = 0
244       s_gaps = 0
245
246       match = @matches.first
247       diff  = (q_gaps + match.q_beg) - (s_gaps + match.s_beg)
248
249       if diff < 0
250         @q_entry.seq.insert(0, "-" * diff.abs)
251         q_gaps += diff.abs
252       elsif diff > 0
253         @s_entry.seq.insert(0, "-" * diff.abs)
254         s_gaps += diff.abs
255       end
256
257       @matches[1 .. -1].each do |match|
258         diff = (q_gaps + match.q_beg) - (s_gaps + match.s_beg)
259
260         if diff < 0
261           @q_entry.seq.insert(match.q_beg + q_gaps, "-" * diff.abs)
262           q_gaps += diff.abs
263         elsif diff > 0
264           @s_entry.seq.insert(match.s_beg + s_gaps, "-" * diff.abs)
265           s_gaps += diff.abs
266         end
267       end
268
269       diff = @q_entry.length - @s_entry.length
270
271       if diff < 0
272         @q_entry.seq << ("-" * diff.abs)
273       else
274         @s_entry.seq << ("-" * diff.abs)
275       end
276     end
277   end
278
279   # Class for containing a match between two sequences q and s.
280   class Match
281     attr_accessor :q_beg, :s_beg, :length, :score
282
283     def initialize(q_beg, s_beg, length, score = 0.0)
284       @q_beg  = q_beg
285       @s_beg  = s_beg
286       @length = length
287       @score  = score
288     end
289
290     def q_end
291       @q_beg + @length - 1
292     end
293
294     def s_end
295       @s_beg + @length - 1
296     end
297
298     def to_s(seq = nil)
299       s = "q: #{@q_beg} #{q_end} s: #{@s_beg} #{s_end} l: #{@length} s: #{@score}"
300       s << " seq: #{seq[@q_beg .. q_end]}" if seq
301       s
302     end
303   end
304 end
305