stride-align 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/NOTICE +255 -0
- data/README.md +107 -0
- data/data/bmpm_data/gen_approx_any.txt +131 -0
- data/data/bmpm_data/gen_approx_arabic.txt +26 -0
- data/data/bmpm_data/gen_approx_common.txt +233 -0
- data/data/bmpm_data/gen_approx_cyrillic.txt +18 -0
- data/data/bmpm_data/gen_approx_czech.txt +18 -0
- data/data/bmpm_data/gen_approx_dutch.txt +18 -0
- data/data/bmpm_data/gen_approx_english.txt +47 -0
- data/data/bmpm_data/gen_approx_french.txt +25 -0
- data/data/bmpm_data/gen_approx_german.txt +73 -0
- data/data/bmpm_data/gen_approx_greek.txt +18 -0
- data/data/bmpm_data/gen_approx_greeklatin.txt +20 -0
- data/data/bmpm_data/gen_approx_hebrew.txt +18 -0
- data/data/bmpm_data/gen_approx_hungarian.txt +18 -0
- data/data/bmpm_data/gen_approx_italian.txt +18 -0
- data/data/bmpm_data/gen_approx_polish.txt +84 -0
- data/data/bmpm_data/gen_approx_portuguese.txt +18 -0
- data/data/bmpm_data/gen_approx_romanian.txt +18 -0
- data/data/bmpm_data/gen_approx_russian.txt +48 -0
- data/data/bmpm_data/gen_approx_spanish.txt +21 -0
- data/data/bmpm_data/gen_approx_turkish.txt +18 -0
- data/data/bmpm_data/gen_exact_any.txt +40 -0
- data/data/bmpm_data/gen_exact_approx_common.txt +79 -0
- data/data/bmpm_data/gen_exact_arabic.txt +18 -0
- data/data/bmpm_data/gen_exact_common.txt +32 -0
- data/data/bmpm_data/gen_exact_cyrillic.txt +18 -0
- data/data/bmpm_data/gen_exact_czech.txt +18 -0
- data/data/bmpm_data/gen_exact_dutch.txt +18 -0
- data/data/bmpm_data/gen_exact_english.txt +18 -0
- data/data/bmpm_data/gen_exact_french.txt +18 -0
- data/data/bmpm_data/gen_exact_german.txt +18 -0
- data/data/bmpm_data/gen_exact_greek.txt +18 -0
- data/data/bmpm_data/gen_exact_greeklatin.txt +18 -0
- data/data/bmpm_data/gen_exact_hebrew.txt +18 -0
- data/data/bmpm_data/gen_exact_hungarian.txt +18 -0
- data/data/bmpm_data/gen_exact_italian.txt +18 -0
- data/data/bmpm_data/gen_exact_polish.txt +23 -0
- data/data/bmpm_data/gen_exact_portuguese.txt +18 -0
- data/data/bmpm_data/gen_exact_romanian.txt +18 -0
- data/data/bmpm_data/gen_exact_russian.txt +19 -0
- data/data/bmpm_data/gen_exact_spanish.txt +19 -0
- data/data/bmpm_data/gen_exact_turkish.txt +18 -0
- data/data/bmpm_data/gen_hebrew_common.txt +113 -0
- data/data/bmpm_data/gen_lang.txt +295 -0
- data/data/bmpm_data/gen_languages.txt +36 -0
- data/data/bmpm_data/gen_rules_any.txt +367 -0
- data/data/bmpm_data/gen_rules_arabic.txt +76 -0
- data/data/bmpm_data/gen_rules_cyrillic.txt +99 -0
- data/data/bmpm_data/gen_rules_czech.txt +67 -0
- data/data/bmpm_data/gen_rules_dutch.txt +78 -0
- data/data/bmpm_data/gen_rules_english.txt +113 -0
- data/data/bmpm_data/gen_rules_french.txt +114 -0
- data/data/bmpm_data/gen_rules_german.txt +129 -0
- data/data/bmpm_data/gen_rules_greek.txt +97 -0
- data/data/bmpm_data/gen_rules_greeklatin.txt +118 -0
- data/data/bmpm_data/gen_rules_hebrew.txt +62 -0
- data/data/bmpm_data/gen_rules_hungarian.txt +83 -0
- data/data/bmpm_data/gen_rules_italian.txt +77 -0
- data/data/bmpm_data/gen_rules_polish.txt +185 -0
- data/data/bmpm_data/gen_rules_portuguese.txt +105 -0
- data/data/bmpm_data/gen_rules_romanian.txt +64 -0
- data/data/bmpm_data/gen_rules_russian.txt +142 -0
- data/data/bmpm_data/gen_rules_spanish.txt +85 -0
- data/data/bmpm_data/gen_rules_turkish.txt +50 -0
- data/data/keyboard_data/qwerty.npy +0 -0
- data/data/matrix_data/BLOSUM100 +31 -0
- data/data/matrix_data/BLOSUM30 +31 -0
- data/data/matrix_data/BLOSUM35 +31 -0
- data/data/matrix_data/BLOSUM40 +31 -0
- data/data/matrix_data/BLOSUM45 +25 -0
- data/data/matrix_data/BLOSUM50 +25 -0
- data/data/matrix_data/BLOSUM55 +31 -0
- data/data/matrix_data/BLOSUM60 +31 -0
- data/data/matrix_data/BLOSUM62 +25 -0
- data/data/matrix_data/BLOSUM65 +31 -0
- data/data/matrix_data/BLOSUM70 +31 -0
- data/data/matrix_data/BLOSUM75 +31 -0
- data/data/matrix_data/BLOSUM80 +25 -0
- data/data/matrix_data/BLOSUM85 +31 -0
- data/data/matrix_data/BLOSUM90 +25 -0
- data/data/matrix_data/NUC.4.4 +25 -0
- data/data/matrix_data/PAM10 +34 -0
- data/data/matrix_data/PAM100 +34 -0
- data/data/matrix_data/PAM110 +34 -0
- data/data/matrix_data/PAM120 +34 -0
- data/data/matrix_data/PAM130 +34 -0
- data/data/matrix_data/PAM140 +34 -0
- data/data/matrix_data/PAM150 +34 -0
- data/data/matrix_data/PAM160 +34 -0
- data/data/matrix_data/PAM170 +34 -0
- data/data/matrix_data/PAM180 +34 -0
- data/data/matrix_data/PAM190 +34 -0
- data/data/matrix_data/PAM20 +34 -0
- data/data/matrix_data/PAM200 +34 -0
- data/data/matrix_data/PAM210 +34 -0
- data/data/matrix_data/PAM220 +34 -0
- data/data/matrix_data/PAM230 +34 -0
- data/data/matrix_data/PAM240 +34 -0
- data/data/matrix_data/PAM250 +25 -0
- data/data/matrix_data/PAM260 +34 -0
- data/data/matrix_data/PAM270 +34 -0
- data/data/matrix_data/PAM280 +34 -0
- data/data/matrix_data/PAM290 +34 -0
- data/data/matrix_data/PAM30 +25 -0
- data/data/matrix_data/PAM300 +34 -0
- data/data/matrix_data/PAM310 +34 -0
- data/data/matrix_data/PAM320 +34 -0
- data/data/matrix_data/PAM330 +34 -0
- data/data/matrix_data/PAM340 +34 -0
- data/data/matrix_data/PAM350 +34 -0
- data/data/matrix_data/PAM360 +34 -0
- data/data/matrix_data/PAM370 +34 -0
- data/data/matrix_data/PAM380 +34 -0
- data/data/matrix_data/PAM390 +34 -0
- data/data/matrix_data/PAM40 +34 -0
- data/data/matrix_data/PAM400 +34 -0
- data/data/matrix_data/PAM410 +34 -0
- data/data/matrix_data/PAM420 +34 -0
- data/data/matrix_data/PAM430 +34 -0
- data/data/matrix_data/PAM440 +34 -0
- data/data/matrix_data/PAM450 +34 -0
- data/data/matrix_data/PAM460 +34 -0
- data/data/matrix_data/PAM470 +34 -0
- data/data/matrix_data/PAM480 +34 -0
- data/data/matrix_data/PAM490 +34 -0
- data/data/matrix_data/PAM50 +34 -0
- data/data/matrix_data/PAM500 +34 -0
- data/data/matrix_data/PAM60 +34 -0
- data/data/matrix_data/PAM70 +25 -0
- data/data/matrix_data/PAM80 +34 -0
- data/data/matrix_data/PAM90 +34 -0
- data/ext/stride_align/backend_avx2.cpp +2 -0
- data/ext/stride_align/backend_avx512bwvl.cpp +2 -0
- data/ext/stride_align/backend_generic.cpp +3 -0
- data/ext/stride_align/backend_impl.hpp +983 -0
- data/ext/stride_align/backend_lasx.cpp +2 -0
- data/ext/stride_align/backend_lsx.cpp +2 -0
- data/ext/stride_align/backend_neon.cpp +2 -0
- data/ext/stride_align/backend_rvv.cpp +2 -0
- data/ext/stride_align/backend_sse41.cpp +2 -0
- data/ext/stride_align/backend_sve.cpp +2 -0
- data/ext/stride_align/backend_sve2.cpp +2 -0
- data/ext/stride_align/backend_vsx.cpp +2 -0
- data/ext/stride_align/beider_morse_impl.cpp +5 -0
- data/ext/stride_align/cpu_detect.cpp +172 -0
- data/ext/stride_align/cpu_detect.hpp +6 -0
- data/ext/stride_align/extconf.rb +114 -0
- data/ext/stride_align/target_profile.hpp +82 -0
- data/ext/stride_align/vendor/beider_morse_impl.cpp +1467 -0
- data/ext/stride_align/vendor/stride_align/alignment.hpp +199 -0
- data/ext/stride_align/vendor/stride_align/batch.hpp +812 -0
- data/ext/stride_align/vendor/stride_align/beider_morse.hpp +121 -0
- data/ext/stride_align/vendor/stride_align/caverphone.hpp +222 -0
- data/ext/stride_align/vendor/stride_align/cologne_phonetic.hpp +202 -0
- data/ext/stride_align/vendor/stride_align/core.hpp +731 -0
- data/ext/stride_align/vendor/stride_align/daitch_mokotoff.hpp +631 -0
- data/ext/stride_align/vendor/stride_align/double_metaphone.hpp +796 -0
- data/ext/stride_align/vendor/stride_align/dtw.hpp +300 -0
- data/ext/stride_align/vendor/stride_align/encoded.hpp +235 -0
- data/ext/stride_align/vendor/stride_align/hamming.hpp +55 -0
- data/ext/stride_align/vendor/stride_align/indel.hpp +1200 -0
- data/ext/stride_align/vendor/stride_align/jaro.hpp +517 -0
- data/ext/stride_align/vendor/stride_align/lcs.hpp +159 -0
- data/ext/stride_align/vendor/stride_align/levenshtein.hpp +1247 -0
- data/ext/stride_align/vendor/stride_align/levenshtein_prepared.hpp +193 -0
- data/ext/stride_align/vendor/stride_align/match_rating.hpp +168 -0
- data/ext/stride_align/vendor/stride_align/metaphone.hpp +291 -0
- data/ext/stride_align/vendor/stride_align/ngram.hpp +176 -0
- data/ext/stride_align/vendor/stride_align/nysiis.hpp +199 -0
- data/ext/stride_align/vendor/stride_align/pairwise_alignment.hpp +465 -0
- data/ext/stride_align/vendor/stride_align/partial_ratio.hpp +486 -0
- data/ext/stride_align/vendor/stride_align/ratcliff_obershelp.hpp +101 -0
- data/ext/stride_align/vendor/stride_align/soundex.hpp +108 -0
- data/ext/stride_align/vendor/stride_align/token_ratios.hpp +445 -0
- data/ext/stride_align/vendor/stride_align/types.hpp +16 -0
- data/ext/stride_align/vendor/stride_align/utf8.hpp +512 -0
- data/ext/stride_align/vendor/stride_align/wratio.hpp +363 -0
- data/lib/stride_align/algorithms.rb +296 -0
- data/lib/stride_align/alignment_path.rb +217 -0
- data/lib/stride_align/backend.rb +47 -0
- data/lib/stride_align/batch.rb +705 -0
- data/lib/stride_align/core.rb +180 -0
- data/lib/stride_align/keyboard.rb +200 -0
- data/lib/stride_align/matrices.rb +403 -0
- data/lib/stride_align/version.rb +5 -0
- data/lib/stride_align.rb +87 -0
- metadata +231 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrideAlign
|
|
4
|
+
class SubstitutionMatrix
|
|
5
|
+
attr_reader :name, :alphabet, :matrix, :gap_score, :wildcard,
|
|
6
|
+
:gap_open, :gap_extend, :stride, :max_abs, :matrix_bytes
|
|
7
|
+
|
|
8
|
+
def initialize(name:, alphabet:, matrix:, gap_score: -4, wildcard: "X",
|
|
9
|
+
gap_open: nil, gap_extend: nil)
|
|
10
|
+
raise ArgumentError, "name must be one String" unless name.is_a?(String)
|
|
11
|
+
raise ArgumentError, "alphabet must be one String" unless alphabet.is_a?(String)
|
|
12
|
+
|
|
13
|
+
symbols = alphabet.each_char.to_a
|
|
14
|
+
raise ArgumentError, "alphabet has duplicate symbols" unless symbols.uniq.length == symbols.length
|
|
15
|
+
unless matrix.is_a?(Array) && matrix.length == symbols.length &&
|
|
16
|
+
matrix.all? { |row| row.is_a?(Array) && row.length == symbols.length }
|
|
17
|
+
raise ArgumentError, "matrix dimensions must match the alphabet"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
values = matrix.map do |row|
|
|
21
|
+
row.map do |score|
|
|
22
|
+
unless score.is_a?(Numeric) && score.finite? && score == score.to_i
|
|
23
|
+
raise ArgumentError, "matrix must contain finite integer scores"
|
|
24
|
+
end
|
|
25
|
+
score = score.to_i
|
|
26
|
+
raise ArgumentError, "matrix scores must fit in signed 8-bit integers" unless (-128..127).cover?(score)
|
|
27
|
+
score
|
|
28
|
+
end.freeze
|
|
29
|
+
end.freeze
|
|
30
|
+
unless wildcard.is_a?(String) && wildcard.each_char.count == 1 && symbols.include?(wildcard)
|
|
31
|
+
raise ArgumentError, "wildcard must be one symbol in alphabet"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@name = name.dup.freeze
|
|
35
|
+
@alphabet = alphabet.dup.freeze
|
|
36
|
+
@matrix = values
|
|
37
|
+
@gap_score = gap_score
|
|
38
|
+
@wildcard = wildcard.dup.freeze
|
|
39
|
+
@gap_open = gap_open
|
|
40
|
+
@gap_extend = gap_extend
|
|
41
|
+
@stride = symbols.length
|
|
42
|
+
@max_abs = values.flatten.map(&:abs).max || 0
|
|
43
|
+
@matrix_bytes = values.flatten.pack("c*").freeze
|
|
44
|
+
@symbol_indices = symbols.each_with_index.to_h.freeze
|
|
45
|
+
@wildcard_index = @symbol_indices.fetch(@wildcard)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def encode(sequence)
|
|
49
|
+
StrideAlign.substitution_matrix_encode(self, sequence)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def score(query, target)
|
|
53
|
+
StrideAlign.substitution_matrix_score(self, query, target)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def score_step_limit(gap_score: nil, gap_open: nil, gap_extend: nil)
|
|
57
|
+
StrideAlign.substitution_matrix_score_step_limit(
|
|
58
|
+
self, gap_score: gap_score, gap_open: gap_open, gap_extend: gap_extend
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def transpose(name: "#{@name}.T")
|
|
63
|
+
StrideAlign.substitution_matrix_transpose(self, name: name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def encode_one(sequence)
|
|
67
|
+
raise ArgumentError, "sequence must be a String" unless sequence.is_a?(String)
|
|
68
|
+
|
|
69
|
+
sequence.encode(Encoding::UTF_8).each_char.map do |symbol|
|
|
70
|
+
@symbol_indices.fetch(symbol, @wildcard_index)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
def SubstitutionMatrix(name:, alphabet:, matrix:, gap_score: -4,
|
|
77
|
+
wildcard: "X", gap_open: nil, gap_extend: nil)
|
|
78
|
+
SubstitutionMatrix.new(
|
|
79
|
+
name: name, alphabet: alphabet, matrix: matrix,
|
|
80
|
+
gap_score: gap_score, wildcard: wildcard,
|
|
81
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def substitution_matrix_encode(substitution_matrix, sequence)
|
|
86
|
+
validate_substitution_matrix(substitution_matrix)
|
|
87
|
+
if sequence.is_a?(Array)
|
|
88
|
+
sequence.map { |value| value.nil? ? nil : substitution_matrix.encode_one(value) }
|
|
89
|
+
elsif sequence.nil?
|
|
90
|
+
nil
|
|
91
|
+
else
|
|
92
|
+
substitution_matrix.encode_one(sequence)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def substitution_matrix_score(substitution_matrix, query, target)
|
|
97
|
+
validate_substitution_matrix(substitution_matrix)
|
|
98
|
+
smith_waterman_score(
|
|
99
|
+
query, target, matrix: substitution_matrix,
|
|
100
|
+
gap_score: substitution_matrix.gap_score
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def substitution_matrix_score_step_limit(substitution_matrix,
|
|
105
|
+
gap_score: nil,
|
|
106
|
+
gap_open: nil,
|
|
107
|
+
gap_extend: nil)
|
|
108
|
+
validate_substitution_matrix(substitution_matrix)
|
|
109
|
+
if gap_score.nil? && gap_open.nil? && gap_extend.nil?
|
|
110
|
+
gap_score = substitution_matrix.gap_score
|
|
111
|
+
gap_open = substitution_matrix.gap_open
|
|
112
|
+
gap_extend = substitution_matrix.gap_extend
|
|
113
|
+
end
|
|
114
|
+
[substitution_matrix.max_abs, gap_score, gap_open, gap_extend]
|
|
115
|
+
.compact.map(&:abs).max
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def substitution_matrix_transpose(substitution_matrix,
|
|
119
|
+
name: "#{substitution_matrix.name}.T")
|
|
120
|
+
validate_substitution_matrix(substitution_matrix)
|
|
121
|
+
SubstitutionMatrix.new(
|
|
122
|
+
name: name,
|
|
123
|
+
alphabet: substitution_matrix.alphabet,
|
|
124
|
+
matrix: substitution_matrix.matrix.transpose,
|
|
125
|
+
gap_score: substitution_matrix.gap_score,
|
|
126
|
+
wildcard: substitution_matrix.wildcard,
|
|
127
|
+
gap_open: substitution_matrix.gap_open,
|
|
128
|
+
gap_extend: substitution_matrix.gap_extend
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def substitution_matrix_from_ncbi_text(text, name: "<unnamed>",
|
|
133
|
+
gap_score: -4, wildcard: "X",
|
|
134
|
+
gap_open: nil, gap_extend: nil)
|
|
135
|
+
raise ArgumentError, "text must be one String" unless text.is_a?(String)
|
|
136
|
+
|
|
137
|
+
lines = text.lines.map(&:strip).reject { |line| line.empty? || line.start_with?("#") }
|
|
138
|
+
raise ArgumentError, "NCBI matrix text has no header or data rows" if lines.length < 2
|
|
139
|
+
|
|
140
|
+
header = lines.shift.split
|
|
141
|
+
rows = lines.map(&:split)
|
|
142
|
+
labels = rows.map(&:first)
|
|
143
|
+
raise ArgumentError, "NCBI matrix row labels do not match column header" unless labels == header
|
|
144
|
+
|
|
145
|
+
values = rows.map do |row|
|
|
146
|
+
raise ArgumentError, "NCBI matrix row has the wrong number of values" unless row.length == header.length + 1
|
|
147
|
+
row.drop(1).map do |token|
|
|
148
|
+
Integer(token, 10)
|
|
149
|
+
rescue ArgumentError
|
|
150
|
+
raise ArgumentError, "non-integer score in NCBI matrix row"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
SubstitutionMatrix.new(
|
|
154
|
+
name: name, alphabet: header.join, matrix: values,
|
|
155
|
+
gap_score: gap_score, wildcard: wildcard,
|
|
156
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def identity_matrix(alphabet, match: 1, mismatch: 0, wildcard: "*",
|
|
161
|
+
name: "IDENTITY", gap_score: -1,
|
|
162
|
+
gap_open: nil, gap_extend: nil)
|
|
163
|
+
raise ArgumentError, "alphabet must be a String" unless alphabet.is_a?(String)
|
|
164
|
+
|
|
165
|
+
symbols = alphabet.each_char.to_a
|
|
166
|
+
symbols << wildcard unless symbols.include?(wildcard)
|
|
167
|
+
values = Array.new(symbols.length) { Array.new(symbols.length, mismatch) }
|
|
168
|
+
symbols.length.times { |index| values[index][index] = match }
|
|
169
|
+
wildcard_index = symbols.index(wildcard)
|
|
170
|
+
symbols.length.times do |index|
|
|
171
|
+
values[wildcard_index][index] = mismatch
|
|
172
|
+
values[index][wildcard_index] = mismatch
|
|
173
|
+
end
|
|
174
|
+
SubstitutionMatrix.new(
|
|
175
|
+
name: name, alphabet: symbols.join, matrix: values,
|
|
176
|
+
gap_score: gap_score, wildcard: wildcard,
|
|
177
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def ascii_matrix(match: 1, mismatch: -1, name: "ASCII", gap_score: -1,
|
|
182
|
+
gap_open: nil, gap_extend: nil)
|
|
183
|
+
symbols = (0..127).map(&:chr)
|
|
184
|
+
values = Array.new(128) { Array.new(128, mismatch) }
|
|
185
|
+
128.times { |index| values[index][index] = match }
|
|
186
|
+
128.times do |index|
|
|
187
|
+
values[127][index] = mismatch
|
|
188
|
+
values[index][127] = mismatch
|
|
189
|
+
end
|
|
190
|
+
SubstitutionMatrix.new(
|
|
191
|
+
name: name, alphabet: symbols.join, matrix: values,
|
|
192
|
+
gap_score: gap_score, wildcard: symbols[127],
|
|
193
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def validate_substitution_matrix(matrix)
|
|
200
|
+
raise ArgumentError, "matrix must be a SubstitutionMatrix" unless matrix.is_a?(SubstitutionMatrix)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def matrix_defaults(name)
|
|
204
|
+
if name.start_with?("BLOSUM")
|
|
205
|
+
level = name.delete_prefix("BLOSUM").to_i
|
|
206
|
+
return [-5, -10, -1] if level >= 80
|
|
207
|
+
return [-5, -11, -1] if level >= 62
|
|
208
|
+
return [-6, -13, -2] if level >= 50
|
|
209
|
+
return [-6, -14, -2]
|
|
210
|
+
end
|
|
211
|
+
if name.start_with?("PAM")
|
|
212
|
+
level = name.delete_prefix("PAM").to_i
|
|
213
|
+
return [-5, -9, -1] if level <= 30
|
|
214
|
+
return [-6, -10, -1] if level <= 120
|
|
215
|
+
return [-7, -12, -1] if level <= 200
|
|
216
|
+
return [-8, -14, -2]
|
|
217
|
+
end
|
|
218
|
+
[-5, -10, -2]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def read_ncbi_matrix(filename, name = filename, wildcard = "X")
|
|
222
|
+
defaults = matrix_defaults(name)
|
|
223
|
+
path = File.join(__data_directory("matrix_data"), filename)
|
|
224
|
+
substitution_matrix_from_ncbi_text(
|
|
225
|
+
File.read(path, mode: "r:BOM|UTF-8"), name: name,
|
|
226
|
+
gap_score: defaults[0], wildcard: wildcard,
|
|
227
|
+
gap_open: defaults[1], gap_extend: defaults[2]
|
|
228
|
+
)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def matrix_catalog_load(name)
|
|
232
|
+
@matrix_catalog ||= {}
|
|
233
|
+
return @matrix_catalog[name] if @matrix_catalog.key?(name)
|
|
234
|
+
|
|
235
|
+
value = case name
|
|
236
|
+
when "nuc44"
|
|
237
|
+
read_ncbi_matrix("NUC.4.4", "NUC.4.4", "N")
|
|
238
|
+
when "dna_match"
|
|
239
|
+
identity_matrix("ACGT", match: 5, mismatch: -4,
|
|
240
|
+
wildcard: "N", name: "DNA_MATCH", gap_score: -5)
|
|
241
|
+
when "ascii_text"
|
|
242
|
+
ascii_matrix(name: "ASCII")
|
|
243
|
+
when /\Ablosum\d+\z/
|
|
244
|
+
label = name.upcase
|
|
245
|
+
read_ncbi_matrix(label, label)
|
|
246
|
+
when /\Apam\d+\z/
|
|
247
|
+
label = name.upcase
|
|
248
|
+
read_ncbi_matrix(label, label)
|
|
249
|
+
else
|
|
250
|
+
raise ArgumentError, "unknown substitution matrix: #{name}"
|
|
251
|
+
end
|
|
252
|
+
@matrix_catalog[name] = value
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def matrix_pairwise(query, target, substitution_matrix, local,
|
|
256
|
+
gap_score: nil, gap_open_score: nil,
|
|
257
|
+
gap_extend_score: nil)
|
|
258
|
+
validate_substitution_matrix(substitution_matrix)
|
|
259
|
+
pairwise(query, target) do |left, right|
|
|
260
|
+
if left.nil? || right.nil?
|
|
261
|
+
nil
|
|
262
|
+
else
|
|
263
|
+
matrix_alignment(
|
|
264
|
+
left, right, substitution_matrix, local,
|
|
265
|
+
gap_score: gap_score, gap_open_score: gap_open_score,
|
|
266
|
+
gap_extend_score: gap_extend_score
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def matrix_alignment(query, target, substitution_matrix, local,
|
|
273
|
+
gap_score: nil, gap_open_score: nil,
|
|
274
|
+
gap_extend_score: nil, traceback: false)
|
|
275
|
+
validate_substitution_matrix(substitution_matrix)
|
|
276
|
+
query = scalar_query(query)
|
|
277
|
+
target = scalar_query(target)
|
|
278
|
+
q = substitution_matrix.encode_one(query)
|
|
279
|
+
t = substitution_matrix.encode_one(target)
|
|
280
|
+
linear_gap = gap_score.nil? ? substitution_matrix.gap_score : gap_score
|
|
281
|
+
open_score = gap_open_score.nil? ? linear_gap : gap_open_score
|
|
282
|
+
extend_score = gap_extend_score.nil? ? open_score : gap_extend_score
|
|
283
|
+
rows = q.length + 1
|
|
284
|
+
columns = t.length + 1
|
|
285
|
+
negative_infinity = -Float::INFINITY
|
|
286
|
+
h = Array.new(rows) { Array.new(columns, local ? 0 : negative_infinity) }
|
|
287
|
+
vertical = Array.new(rows) { Array.new(columns, negative_infinity) }
|
|
288
|
+
horizontal = Array.new(rows) { Array.new(columns, negative_infinity) }
|
|
289
|
+
h[0][0] = 0
|
|
290
|
+
gap_cost = lambda { |length| length.zero? ? 0 : open_score + (length - 1) * extend_score }
|
|
291
|
+
unless local
|
|
292
|
+
1.upto(rows - 1) do |row|
|
|
293
|
+
h[row][0] = gap_cost.call(row)
|
|
294
|
+
vertical[row][0] = h[row][0]
|
|
295
|
+
end
|
|
296
|
+
1.upto(columns - 1) do |column|
|
|
297
|
+
h[0][column] = gap_cost.call(column)
|
|
298
|
+
horizontal[0][column] = h[0][column]
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
best_score = 0
|
|
303
|
+
best_row = 0
|
|
304
|
+
best_column = 0
|
|
305
|
+
1.upto(rows - 1) do |row|
|
|
306
|
+
1.upto(columns - 1) do |column|
|
|
307
|
+
vertical[row][column] = [
|
|
308
|
+
h[row - 1][column] + open_score,
|
|
309
|
+
vertical[row - 1][column] + extend_score
|
|
310
|
+
].max
|
|
311
|
+
horizontal[row][column] = [
|
|
312
|
+
h[row][column - 1] + open_score,
|
|
313
|
+
horizontal[row][column - 1] + extend_score
|
|
314
|
+
].max
|
|
315
|
+
diagonal = h[row - 1][column - 1] +
|
|
316
|
+
substitution_matrix.matrix[q[row - 1]][t[column - 1]]
|
|
317
|
+
cell = [diagonal, vertical[row][column], horizontal[row][column]].max
|
|
318
|
+
cell = [0, cell].max if local
|
|
319
|
+
h[row][column] = cell
|
|
320
|
+
if local && cell > best_score
|
|
321
|
+
best_score = cell
|
|
322
|
+
best_row = row
|
|
323
|
+
best_column = column
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
unless local
|
|
328
|
+
best_score = h[rows - 1][columns - 1]
|
|
329
|
+
best_row = rows - 1
|
|
330
|
+
best_column = columns - 1
|
|
331
|
+
end
|
|
332
|
+
return best_score unless traceback
|
|
333
|
+
|
|
334
|
+
row = best_row
|
|
335
|
+
column = best_column
|
|
336
|
+
query_end = row
|
|
337
|
+
target_end = column
|
|
338
|
+
operations = []
|
|
339
|
+
state = :h
|
|
340
|
+
while row.positive? || column.positive?
|
|
341
|
+
break if local && state == :h && h[row][column] <= 0
|
|
342
|
+
|
|
343
|
+
if state == :h
|
|
344
|
+
if row.positive? && column.positive?
|
|
345
|
+
diagonal = h[row - 1][column - 1] +
|
|
346
|
+
substitution_matrix.matrix[q[row - 1]][t[column - 1]]
|
|
347
|
+
if h[row][column] == diagonal
|
|
348
|
+
operations.unshift(q[row - 1] == t[column - 1] ? "=" : "X")
|
|
349
|
+
row -= 1
|
|
350
|
+
column -= 1
|
|
351
|
+
next
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
if row.positive? && h[row][column] == vertical[row][column]
|
|
355
|
+
state = :vertical
|
|
356
|
+
next
|
|
357
|
+
end
|
|
358
|
+
if column.positive? && h[row][column] == horizontal[row][column]
|
|
359
|
+
state = :horizontal
|
|
360
|
+
next
|
|
361
|
+
end
|
|
362
|
+
break
|
|
363
|
+
elsif state == :vertical
|
|
364
|
+
operations.unshift("D")
|
|
365
|
+
continuing = row > 1 &&
|
|
366
|
+
vertical[row][column] == vertical[row - 1][column] + extend_score
|
|
367
|
+
row -= 1
|
|
368
|
+
state = continuing ? :vertical : :h
|
|
369
|
+
else
|
|
370
|
+
operations.unshift("I")
|
|
371
|
+
continuing = column > 1 &&
|
|
372
|
+
horizontal[row][column] == horizontal[row][column - 1] + extend_score
|
|
373
|
+
column -= 1
|
|
374
|
+
state = continuing ? :horizontal : :h
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
operation_string = operations.join
|
|
378
|
+
cigar = operations.chunk_while { |left, right| left == right }
|
|
379
|
+
.map { |run| "#{run.length}#{run[0]}" }.join
|
|
380
|
+
AlignmentPath.new(
|
|
381
|
+
score: best_score, query_start: row, query_end: query_end,
|
|
382
|
+
target_start: column, target_end: target_end,
|
|
383
|
+
operations: operation_string, cigar: cigar,
|
|
384
|
+
matches: operations.count("="), mismatches: operations.count("X"),
|
|
385
|
+
insertions: operations.count("I"), deletions: operations.count("D"),
|
|
386
|
+
aligned_length: operations.length
|
|
387
|
+
)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
MATRIX_NAMES = (
|
|
392
|
+
[30, 35, 40, 45, 50, 55, 60, 62, 65, 70, 75, 80, 85, 90, 100]
|
|
393
|
+
.map { |level| "blosum#{level}" } +
|
|
394
|
+
(10..500).step(10).map { |level| "pam#{level}" } +
|
|
395
|
+
%w[nuc44 dna_match ascii_text]
|
|
396
|
+
).freeze
|
|
397
|
+
|
|
398
|
+
class << self
|
|
399
|
+
MATRIX_NAMES.each do |matrix_name|
|
|
400
|
+
define_method(matrix_name) { matrix_catalog_load(matrix_name) }
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
data/lib/stride_align.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "stride_align/version"
|
|
4
|
+
require "stride_align/_generic"
|
|
5
|
+
|
|
6
|
+
module StrideAlign
|
|
7
|
+
NATIVE_MODULES = {
|
|
8
|
+
"generic" => "Generic",
|
|
9
|
+
"sse41" => "Sse41",
|
|
10
|
+
"avx2" => "Avx2",
|
|
11
|
+
"avx512bwvl" => "Avx512bwvl",
|
|
12
|
+
"neon" => "Neon",
|
|
13
|
+
"sve" => "Sve",
|
|
14
|
+
"sve2" => "Sve2",
|
|
15
|
+
"lsx" => "Lsx",
|
|
16
|
+
"lasx" => "Lasx",
|
|
17
|
+
"vsx" => "Vsx",
|
|
18
|
+
"rvv" => "Rvv"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
@primary_backend = Native::Generic
|
|
22
|
+
@available_backend_names = @primary_backend.backend_candidates
|
|
23
|
+
.map { |name| name.freeze }
|
|
24
|
+
.freeze
|
|
25
|
+
requested_backend = ENV.fetch("STRIDE_ALIGN_RUBY_BACKEND", "").strip
|
|
26
|
+
|
|
27
|
+
if !requested_backend.empty? && !@available_backend_names.include?(requested_backend)
|
|
28
|
+
raise LoadError,
|
|
29
|
+
"requested stride-align backend is unavailable or CPU-incompatible: " \
|
|
30
|
+
"#{requested_backend}; compatible packaged backends: " \
|
|
31
|
+
"#{@available_backend_names.join(', ')}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
dispatch_order = requested_backend.empty? ? @available_backend_names : [requested_backend]
|
|
35
|
+
@backend_name = "generic"
|
|
36
|
+
@native_backend = @primary_backend
|
|
37
|
+
|
|
38
|
+
dispatch_order.each do |candidate|
|
|
39
|
+
break if candidate == "generic"
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
require "stride_align/_#{candidate}"
|
|
43
|
+
@backend_name = candidate
|
|
44
|
+
@native_backend = Native.const_get(NATIVE_MODULES.fetch(candidate), false)
|
|
45
|
+
break
|
|
46
|
+
rescue LoadError
|
|
47
|
+
raise if !requested_backend.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
def __native_backend
|
|
53
|
+
@native_backend
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def __primary_backend
|
|
57
|
+
@primary_backend
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def __available_backend_names
|
|
61
|
+
@available_backend_names
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def __data_directory(kind)
|
|
65
|
+
installed = File.expand_path("../data/#{kind}", __dir__)
|
|
66
|
+
return installed if File.directory?(installed)
|
|
67
|
+
|
|
68
|
+
repository_relative = case kind
|
|
69
|
+
when "keyboard_data"
|
|
70
|
+
"../../../src/stride_align/matrices/keyboard_data"
|
|
71
|
+
else
|
|
72
|
+
"../../../src/stride_align/#{kind}"
|
|
73
|
+
end
|
|
74
|
+
File.expand_path(repository_relative, __dir__)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
require_relative "stride_align/backend"
|
|
80
|
+
require_relative "stride_align/core"
|
|
81
|
+
require_relative "stride_align/matrices"
|
|
82
|
+
require_relative "stride_align/alignment_path"
|
|
83
|
+
require_relative "stride_align/batch"
|
|
84
|
+
require_relative "stride_align/algorithms"
|
|
85
|
+
require_relative "stride_align/keyboard"
|
|
86
|
+
|
|
87
|
+
StrideAlign.__send__(:load_bundled_keyboards)
|