1use crate::decoder::TagDecoder;
9use multiversion::multiversion;
10
11pub trait DecodingStrategy: Send + Sync + 'static {
13 type Code: Clone + std::fmt::Debug + Send + Sync;
15
16 fn from_intensities(intensities: &[f64], thresholds: &[f64]) -> Self::Code;
18
19 fn distance(code: &Self::Code, target: u64) -> u32;
24
25 fn decode(
27 code: &Self::Code,
28 decoder: &(impl TagDecoder + ?Sized),
29 max_error: u32,
30 ) -> Option<(u32, u32, u8)>;
31
32 fn to_debug_bits(code: &Self::Code) -> u64;
34}
35
36pub struct HardStrategy;
38
39impl DecodingStrategy for HardStrategy {
40 type Code = u64;
41
42 fn from_intensities(intensities: &[f64], thresholds: &[f64]) -> Self::Code {
43 let mut bits = 0u64;
44 for (i, (&val, &thresh)) in intensities.iter().zip(thresholds.iter()).enumerate() {
45 if val > thresh {
46 bits |= 1 << i;
47 }
48 }
49 bits
50 }
51
52 fn distance(code: &Self::Code, target: u64) -> u32 {
53 (*code ^ target).count_ones()
54 }
55
56 fn decode(
57 code: &Self::Code,
58 decoder: &(impl TagDecoder + ?Sized),
59 max_error: u32,
60 ) -> Option<(u32, u32, u8)> {
61 decoder.decode_full(*code, max_error)
62 }
63
64 fn to_debug_bits(code: &Self::Code) -> u64 {
65 *code
66 }
67}
68
69pub struct SoftStrategy;
71
72#[derive(Clone, Debug)]
74pub struct SoftCode {
75 pub llrs: [i16; 64],
77 pub len: usize,
79}
80
81impl SoftStrategy {
82 #[multiversion(targets(
83 "x86_64+avx2+bmi1+bmi2+popcnt+lzcnt",
84 "x86_64+avx512f+avx512bw+avx512dq+avx512vl",
85 "aarch64+neon"
86 ))]
87 fn distance_with_limit(code: &SoftCode, target: u64, limit: u32) -> u32 {
88 let mut penalty = 0u32;
89 let n = code.len;
90
91 let chunks = code.llrs[..n].chunks_exact(16);
93 let processed = chunks.len() * 16;
94
95 for (chunk_idx, llr_chunk) in chunks.enumerate() {
96 let target_chunk = (target >> (chunk_idx * 16)) as u16;
97 for (i, &llr) in llr_chunk.iter().enumerate() {
98 let target_bit = (target_chunk >> i) & 1;
99 if target_bit == 1 {
100 if llr < 0 {
101 penalty += u32::from(llr.unsigned_abs());
102 }
103 } else if llr > 0 {
104 penalty += u32::from(llr.unsigned_abs());
105 }
106 }
107 if penalty >= limit {
108 return limit;
109 }
110 }
111
112 for i in processed..n {
114 let target_bit = (target >> i) & 1;
115 let llr = code.llrs[i];
116
117 if target_bit == 1 {
118 if llr < 0 {
119 penalty += u32::from(llr.unsigned_abs());
120 }
121 } else if llr > 0 {
122 penalty += u32::from(llr.unsigned_abs());
123 }
124
125 if penalty >= limit {
126 return limit;
127 }
128 }
129 penalty
130 }
131}
132
133impl DecodingStrategy for SoftStrategy {
134 type Code = SoftCode;
135
136 fn from_intensities(intensities: &[f64], thresholds: &[f64]) -> Self::Code {
137 let n = intensities.len().min(64);
138 let mut llrs = [0i16; 64];
139 for i in 0..n {
140 llrs[i] = (intensities[i] - thresholds[i]) as i16;
141 }
142 SoftCode { llrs, len: n }
143 }
144
145 fn distance(code: &Self::Code, target: u64) -> u32 {
146 Self::distance_with_limit(code, target, u32::MAX)
147 }
148
149 fn decode(
150 code: &Self::Code,
151 decoder: &(impl TagDecoder + ?Sized),
152 max_error: u32,
153 ) -> Option<(u32, u32, u8)> {
154 let bits = Self::to_debug_bits(code);
156 if let Some((id, hamming, rot)) = decoder.decode_full(bits, 0) {
157 return Some((id, hamming, rot));
158 }
159
160 let _codes_count = decoder.num_codes();
161
162 let llr_per_hamming_bit = 60_u32;
166
167 let soft_threshold = max_error.max(1) * llr_per_hamming_bit;
168
169 let coarse_rejection_threshold = max_error * 2;
172 let mut best_id = None;
173 let mut best_dist = soft_threshold;
174 let mut best_rot = 0;
175
176 decoder.for_each_candidate_within_hamming(
177 bits,
178 coarse_rejection_threshold,
179 &mut |target_code, id, rot| {
180 let dist = Self::distance_with_limit(code, target_code, best_dist);
181 if dist < best_dist {
182 best_dist = dist;
183 best_id = Some(u32::from(id));
184 best_rot = rot;
185 }
186 },
187 );
188
189 if best_dist < soft_threshold {
190 return best_id.map(|id| {
191 let equiv_hamming = best_dist / llr_per_hamming_bit;
192 (id, equiv_hamming, best_rot)
193 });
194 }
195
196 None
197 }
198
199 fn to_debug_bits(code: &Self::Code) -> u64 {
200 let mut bits = 0u64;
201 for i in 0..code.len {
202 if code.llrs[i] > 0 {
203 bits |= 1 << i;
204 }
205 }
206 bits
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_hard_distance() {
216 let code = 0b1010;
217 let target = 0b1100;
218 assert_eq!(HardStrategy::distance(&code, target), 2);
220 }
221
222 #[test]
223 fn test_soft_from_intensities() {
224 let intensities = vec![100.0, 50.0, 200.0];
225 let thresholds = vec![80.0, 60.0, 150.0];
226 let code = SoftStrategy::from_intensities(&intensities, &thresholds);
228 assert_eq!(code.llrs[0], 20);
229 assert_eq!(code.llrs[1], -10);
230 assert_eq!(code.llrs[2], 50);
231 assert_eq!(code.len, 3);
232 }
233
234 #[test]
235 fn test_soft_distance() {
236 let code = SoftCode {
238 llrs: {
239 let mut l = [0i16; 64];
240 l[0] = 20;
241 l[1] = -10;
242 l[2] = 50;
243 l
244 },
245 len: 3,
246 };
247
248 assert_eq!(SoftStrategy::distance(&code, 0b101), 0);
253
254 assert_eq!(SoftStrategy::distance(&code, 0b010), 80);
260 }
261
262 #[test]
263 fn test_to_debug_bits() {
264 let code = SoftCode {
265 llrs: {
266 let mut l = [0i16; 64];
267 l[0] = 20;
268 l[1] = -10;
269 l[2] = 50;
270 l
271 },
272 len: 3,
273 };
274 assert_eq!(SoftStrategy::to_debug_bits(&code), 5);
277 }
278}