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,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrideAlign
|
|
4
|
+
class << self
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def utf8_string(value, name)
|
|
8
|
+
return nil if value.nil?
|
|
9
|
+
raise ArgumentError, "#{name} must be a String or nil" unless value.is_a?(String)
|
|
10
|
+
|
|
11
|
+
value.encode(Encoding::UTF_8)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pairwise(query, target)
|
|
15
|
+
query_is_array = query.is_a?(Array)
|
|
16
|
+
target_is_array = target.is_a?(Array)
|
|
17
|
+
queries = query_is_array ? query : [query]
|
|
18
|
+
targets = target_is_array ? target : [target]
|
|
19
|
+
|
|
20
|
+
output_size = if queries.length == targets.length
|
|
21
|
+
queries.length
|
|
22
|
+
elsif queries.length == 1
|
|
23
|
+
targets.length
|
|
24
|
+
elsif targets.length == 1
|
|
25
|
+
queries.length
|
|
26
|
+
else
|
|
27
|
+
raise ArgumentError,
|
|
28
|
+
"query and target must have equal lengths, or one must have length one"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
output = Array.new(output_size) do |index|
|
|
32
|
+
left = queries.length == 1 ? queries[0] : queries[index]
|
|
33
|
+
right = targets.length == 1 ? targets[0] : targets[index]
|
|
34
|
+
yield(utf8_string(left, "query"), utf8_string(right, "target"))
|
|
35
|
+
end
|
|
36
|
+
query_is_array || target_is_array ? output : output[0]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unary(input)
|
|
40
|
+
is_array = input.is_a?(Array)
|
|
41
|
+
values = is_array ? input : [input]
|
|
42
|
+
output = values.map { |value| yield(utf8_string(value, "input")) }
|
|
43
|
+
is_array ? output : output[0]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scalar_query(query)
|
|
47
|
+
return nil if query.nil?
|
|
48
|
+
raise ArgumentError, "query must be one String or nil" unless query.is_a?(String)
|
|
49
|
+
|
|
50
|
+
query.encode(Encoding::UTF_8)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def target_array(targets)
|
|
54
|
+
raise ArgumentError, "targets must be an Array" unless targets.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
targets.map { |target| utf8_string(target, "target") }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def non_negative_integer(value, name)
|
|
60
|
+
unless value.is_a?(Numeric) && value.finite? && value >= 0 && value == value.to_i
|
|
61
|
+
raise ArgumentError, "#{name} must be one non-negative integer"
|
|
62
|
+
end
|
|
63
|
+
value.to_i
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def finite_number(value, name)
|
|
67
|
+
unless value.is_a?(Numeric) && value.finite?
|
|
68
|
+
raise ArgumentError, "#{name} must be one finite number"
|
|
69
|
+
end
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
public
|
|
74
|
+
|
|
75
|
+
def stride_levenshtein(query, target)
|
|
76
|
+
pairwise(query, target) { |left, right| __native_backend.levenshtein(left, right) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def stride_levenshtein_similarity(query, target)
|
|
80
|
+
pairwise(query, target) do |left, right|
|
|
81
|
+
__native_backend.levenshtein_similarity(left, right)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stride_osa(query, target)
|
|
86
|
+
pairwise(query, target) { |left, right| __native_backend.osa(left, right) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def stride_osa_similarity(query, target)
|
|
90
|
+
pairwise(query, target) { |left, right| __native_backend.osa_similarity(left, right) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def stride_true_damerau_levenshtein(query, target)
|
|
94
|
+
pairwise(query, target) do |left, right|
|
|
95
|
+
__native_backend.true_damerau_levenshtein(left, right)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def stride_true_damerau_levenshtein_similarity(query, target)
|
|
100
|
+
pairwise(query, target) do |left, right|
|
|
101
|
+
__native_backend.true_damerau_levenshtein_similarity(left, right)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def stride_indel(query, target)
|
|
106
|
+
pairwise(query, target) { |left, right| __native_backend.indel(left, right) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def stride_indel_similarity(query, target)
|
|
110
|
+
pairwise(query, target) do |left, right|
|
|
111
|
+
__native_backend.indel_similarity(left, right)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def stride_hamming(query, target)
|
|
116
|
+
pairwise(query, target) { |left, right| __native_backend.hamming(left, right) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def stride_hamming_similarity(query, target)
|
|
120
|
+
pairwise(query, target) do |left, right|
|
|
121
|
+
__native_backend.hamming_similarity(left, right)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def stride_jaro(query, target)
|
|
126
|
+
pairwise(query, target) { |left, right| __native_backend.jaro(left, right) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def stride_jaro_winkler(query, target, prefix_weight: 0.1,
|
|
130
|
+
prefix_threshold: 0.7, prefix_cap: 4)
|
|
131
|
+
pairwise(query, target) do |left, right|
|
|
132
|
+
__native_backend.jaro_winkler(
|
|
133
|
+
left, right, prefix_weight, prefix_threshold, prefix_cap
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def stride_smith_waterman(query, target, match_score: 2,
|
|
139
|
+
mismatch_score: -1, gap_score: -1)
|
|
140
|
+
pairwise(query, target) do |left, right|
|
|
141
|
+
__native_backend.smith_waterman(
|
|
142
|
+
left, right, match_score, mismatch_score, gap_score
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def stride_needleman_wunsch(query, target, match_score: 2,
|
|
148
|
+
mismatch_score: -1, gap_score: -1)
|
|
149
|
+
pairwise(query, target) do |left, right|
|
|
150
|
+
__native_backend.needleman_wunsch(
|
|
151
|
+
left, right, match_score, mismatch_score, gap_score
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def stride_smith_waterman_affine(query, target, match_score: 2,
|
|
157
|
+
mismatch_score: -1,
|
|
158
|
+
gap_open_score: -2,
|
|
159
|
+
gap_extend_score: -1)
|
|
160
|
+
pairwise(query, target) do |left, right|
|
|
161
|
+
__native_backend.smith_waterman_affine(
|
|
162
|
+
left, right, match_score, mismatch_score,
|
|
163
|
+
gap_open_score, gap_extend_score
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def stride_needleman_wunsch_affine(query, target, match_score: 2,
|
|
169
|
+
mismatch_score: -1,
|
|
170
|
+
gap_open_score: -2,
|
|
171
|
+
gap_extend_score: -1)
|
|
172
|
+
pairwise(query, target) do |left, right|
|
|
173
|
+
__native_backend.needleman_wunsch_affine(
|
|
174
|
+
left, right, match_score, mismatch_score,
|
|
175
|
+
gap_open_score, gap_extend_score
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrideAlign
|
|
4
|
+
module Keyboard
|
|
5
|
+
ASCII_CODEPOINTS = (0..127).to_a.freeze
|
|
6
|
+
ASCII_ALPHABET = ASCII_CODEPOINTS.map(&:chr).join.freeze
|
|
7
|
+
ASCII_WILDCARD = 127.chr.freeze
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def from_npy(*args, **kwargs)
|
|
11
|
+
StrideAlign.keyboard_from_npy(*args, **kwargs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def from_confusion_counts(*args, **kwargs)
|
|
15
|
+
StrideAlign.keyboard_from_confusion_counts(*args, **kwargs)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def available
|
|
19
|
+
StrideAlign.keyboard_available
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def keyboard
|
|
26
|
+
Keyboard
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def keyboard_from_npy(path, name: File.basename(path, File.extname(path)),
|
|
30
|
+
alphabet: Keyboard::ASCII_ALPHABET,
|
|
31
|
+
wildcard: Keyboard::ASCII_WILDCARD,
|
|
32
|
+
transpose: false, gap_score: -1,
|
|
33
|
+
gap_open: nil, gap_extend: nil)
|
|
34
|
+
raise ArgumentError, "path must be one filename" unless path.is_a?(String)
|
|
35
|
+
grid = keyboard_grid_for_alphabet(read_npy(path), alphabet)
|
|
36
|
+
grid = grid.transpose if transpose
|
|
37
|
+
SubstitutionMatrix.new(
|
|
38
|
+
name: name, alphabet: alphabet, matrix: grid,
|
|
39
|
+
gap_score: gap_score, wildcard: wildcard,
|
|
40
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def keyboard_from_confusion_counts(counts,
|
|
45
|
+
alphabet: Keyboard::ASCII_ALPHABET,
|
|
46
|
+
name: "KEYBOARD", scale: 2,
|
|
47
|
+
match_margin: 4, floor: nil,
|
|
48
|
+
wildcard: alphabet.each_char.to_a.last,
|
|
49
|
+
gap_score: -1, gap_open: nil,
|
|
50
|
+
gap_extend: nil)
|
|
51
|
+
grid_counts = keyboard_counts_grid(counts, alphabet)
|
|
52
|
+
size = grid_counts.length
|
|
53
|
+
total = grid_counts.sum { |row| row.sum }
|
|
54
|
+
grid = if total.zero?
|
|
55
|
+
Array.new(size) do |row|
|
|
56
|
+
Array.new(size) { |column| row == column ? 1 : -1 }
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
row_totals = grid_counts.map(&:sum)
|
|
60
|
+
column_totals = grid_counts.transpose.map(&:sum)
|
|
61
|
+
scores = Array.new(size) { Array.new(size) }
|
|
62
|
+
finite_scores = []
|
|
63
|
+
size.times do |row|
|
|
64
|
+
size.times do |column|
|
|
65
|
+
denominator = row_totals[row] * column_totals[column]
|
|
66
|
+
value = if grid_counts[row][column].zero? || denominator.zero?
|
|
67
|
+
-Float::INFINITY
|
|
68
|
+
else
|
|
69
|
+
scale * Math.log2(grid_counts[row][column] * total / denominator.to_f)
|
|
70
|
+
end
|
|
71
|
+
scores[row][column] = value
|
|
72
|
+
finite_scores << value if value.finite?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
resolved_floor = floor.nil? ? (finite_scores.min&.floor || -1) : floor
|
|
76
|
+
scores.map! do |row|
|
|
77
|
+
row.map do |value|
|
|
78
|
+
value = resolved_floor unless value.finite?
|
|
79
|
+
[[value.round, -128].max, 127].min
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
size.times { |index| scores[index][index] = -128 }
|
|
83
|
+
best_substitution = scores.flatten.max
|
|
84
|
+
diagonal = [[best_substitution, 0].max + match_margin, 127].min
|
|
85
|
+
size.times { |index| scores[index][index] = diagonal }
|
|
86
|
+
scores
|
|
87
|
+
end
|
|
88
|
+
SubstitutionMatrix.new(
|
|
89
|
+
name: name, alphabet: alphabet, matrix: grid,
|
|
90
|
+
gap_score: gap_score, wildcard: wildcard,
|
|
91
|
+
gap_open: gap_open, gap_extend: gap_extend
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def keyboard_available
|
|
96
|
+
directory = __data_directory("keyboard_data")
|
|
97
|
+
return [] unless File.directory?(directory)
|
|
98
|
+
Dir[File.join(directory, "*.npy")].map do |path|
|
|
99
|
+
File.basename(path, ".npy")
|
|
100
|
+
end.sort
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def read_npy(path)
|
|
106
|
+
data = File.binread(path)
|
|
107
|
+
raise ArgumentError, "not a NumPy .npy file" unless data.byteslice(0, 6) == "\x93NUMPY".b
|
|
108
|
+
major = data.getbyte(6)
|
|
109
|
+
header_size, offset = case major
|
|
110
|
+
when 1 then [data.byteslice(8, 2).unpack1("v"), 10]
|
|
111
|
+
when 2, 3 then [data.byteslice(8, 4).unpack1("V"), 12]
|
|
112
|
+
else
|
|
113
|
+
raise ArgumentError, "unsupported NumPy .npy version"
|
|
114
|
+
end
|
|
115
|
+
header = data.byteslice(offset, header_size)
|
|
116
|
+
unless header.match?(/["']descr["']\s*:\s*["']\|i1["']/)
|
|
117
|
+
raise ArgumentError, "keyboard .npy matrix must use signed int8 values"
|
|
118
|
+
end
|
|
119
|
+
if header.match?(/["']fortran_order["']\s*:\s*True/)
|
|
120
|
+
raise ArgumentError, "Fortran-order .npy matrices are not supported"
|
|
121
|
+
end
|
|
122
|
+
shape_text = header[/["']shape["']\s*:\s*\(([^)]*)\)/, 1]
|
|
123
|
+
raise ArgumentError, "could not read shape from keyboard .npy file" unless shape_text
|
|
124
|
+
dimensions = shape_text.split(",").map(&:strip).reject(&:empty?).map do |value|
|
|
125
|
+
Integer(value, 10)
|
|
126
|
+
end
|
|
127
|
+
unless dimensions.length == 2
|
|
128
|
+
raise ArgumentError, "keyboard .npy value must be a two-dimensional matrix"
|
|
129
|
+
end
|
|
130
|
+
payload = data.byteslice(offset + header_size, dimensions.reduce(:*))
|
|
131
|
+
unless payload && payload.bytesize == dimensions.reduce(:*)
|
|
132
|
+
raise ArgumentError, "truncated keyboard .npy payload"
|
|
133
|
+
end
|
|
134
|
+
values = payload.unpack("c*")
|
|
135
|
+
Array.new(dimensions[0]) do |row|
|
|
136
|
+
values.slice(row * dimensions[1], dimensions[1])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def keyboard_grid_for_alphabet(grid, alphabet)
|
|
141
|
+
size = alphabet.each_char.count
|
|
142
|
+
if grid.length == 128 && grid.all? { |row| row.length == 128 } && size == 127
|
|
143
|
+
grid = grid.drop(1).map { |row| row.drop(1) }
|
|
144
|
+
end
|
|
145
|
+
unless grid.length == size && grid.all? { |row| row.length == size }
|
|
146
|
+
raise ArgumentError, "keyboard matrix shape does not match the alphabet size"
|
|
147
|
+
end
|
|
148
|
+
grid
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def keyboard_counts_grid(counts, alphabet)
|
|
152
|
+
symbols = alphabet.each_char.to_a
|
|
153
|
+
size = symbols.length
|
|
154
|
+
if counts.is_a?(Array) && counts.length == size &&
|
|
155
|
+
counts.all? { |row| row.is_a?(Array) && row.length == size }
|
|
156
|
+
return counts.map do |row|
|
|
157
|
+
row.map do |value|
|
|
158
|
+
unless value.is_a?(Numeric) && value.finite? && value >= 0
|
|
159
|
+
raise ArgumentError, "counts must be finite non-negative numbers"
|
|
160
|
+
end
|
|
161
|
+
value.to_f
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
unless counts.is_a?(Array) && counts.all? { |row| row.is_a?(Hash) }
|
|
166
|
+
raise ArgumentError,
|
|
167
|
+
"counts must be a numeric matrix or an Array of typed/intended/count Hashes"
|
|
168
|
+
end
|
|
169
|
+
indices = symbols.each_with_index.to_h
|
|
170
|
+
grid = Array.new(size) { Array.new(size, 0.0) }
|
|
171
|
+
counts.each do |row|
|
|
172
|
+
typed = row[:typed] || row["typed"]
|
|
173
|
+
intended = row[:intended] || row["intended"]
|
|
174
|
+
count = row.key?(:count) ? row[:count] : row["count"]
|
|
175
|
+
unless typed.is_a?(String) && intended.is_a?(String) &&
|
|
176
|
+
count.is_a?(Numeric) && count.finite? && count >= 0
|
|
177
|
+
raise ArgumentError, "confusion-count rows are invalid"
|
|
178
|
+
end
|
|
179
|
+
next unless indices.key?(typed) && indices.key?(intended)
|
|
180
|
+
grid[indices[typed]][indices[intended]] = count.to_f
|
|
181
|
+
end
|
|
182
|
+
grid
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def load_bundled_keyboards
|
|
186
|
+
directory = __data_directory("keyboard_data")
|
|
187
|
+
keyboard_available.each do |name|
|
|
188
|
+
matrix = keyboard_from_npy(
|
|
189
|
+
File.join(directory, "#{name}.npy"), name: "keyboard:#{name}"
|
|
190
|
+
)
|
|
191
|
+
install_keyboard(name, matrix)
|
|
192
|
+
end
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def install_keyboard(name, matrix)
|
|
197
|
+
Keyboard.define_singleton_method(name) { matrix }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|