LDPC codes with belief propagation decoding
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ldpc: rename get_bit/make_data, fix _hz, Alcotest.failf (merlint)

+31 -31
+9 -9
lib/ldpc.ml
··· 149 149 Reference: CCSDS 131.0-B-4, TM Synchronization and Channel Coding *) 150 150 151 151 (* Block types in the prototype matrix *) 152 - let _hz = 0 (* zero block *) 152 + let hz = 0 (* zero block *) 153 153 let hi = 1 (* identity block (possibly shifted) *) 154 154 let hp = 2 (* permutation block using theta/phi tables *) 155 155 ··· 163 163 [| 164 164 (* row 0 *) 165 165 [| 166 - (_hz, 0); 167 - (_hz, 0); 166 + (hz, 0); 167 + (hz, 0); 168 168 (hi, 0); 169 - (_hz, 0); 169 + (hz, 0); 170 170 (hi, 0); 171 171 (0, 0); 172 172 (0, 0); ··· 179 179 [| 180 180 (hi, 0); 181 181 (hi, 0); 182 - (_hz, 0); 182 + (hz, 0); 183 183 (hi, 0); 184 184 (hp, 1); 185 185 (0, 0); ··· 193 193 [| 194 194 (hi, 0); 195 195 (hp, 4); 196 - (_hz, 0); 196 + (hz, 0); 197 197 (hp, 6); 198 198 (hi, 0); 199 199 (0, 0); ··· 553 553 554 554 (* --- Bit manipulation helpers --- *) 555 555 556 - let get_bit (buf : bytes) i = 556 + let bit (buf : bytes) i = 557 557 let byte_idx = i / 8 in 558 558 let bit_pos = 7 - (i mod 8) in 559 559 (Char.code (Bytes.get buf byte_idx) lsr bit_pos) land 1 ··· 577 577 ((code.k + 7) / 8) 578 578 data_bits); 579 579 (* Read k information bits from data *) 580 - let info = Array.init code.k (fun i -> get_bit data i) in 580 + let info = Array.init code.k (fun i -> bit data i) in 581 581 (* Compute m parity bits: parity_j = XOR of info_i for i in gen_p.(j) *) 582 582 let parity = 583 583 Array.init code.m (fun j -> ··· 636 636 Array.init code.n_full (fun i -> 637 637 if i < code.n then 638 638 (* Transmitted bit: read from codeword *) 639 - begin if get_bit codeword i = 0 then hard_decision_llr 639 + begin if bit codeword i = 0 then hard_decision_llr 640 640 else Float.neg hard_decision_llr 641 641 end 642 642 else
+22 -22
test/test_ldpc.ml
··· 30 30 Alcotest.(check string) 31 31 "LDPC roundtrip" (Bytes.to_string data) 32 32 (Bytes.to_string recovered) 33 - | Error e -> Alcotest.fail (Fmt.str "LDPC decode failed: %s" e) 33 + | Error e -> Alcotest.failf "LDPC decode failed: %s" e 34 34 35 35 (** LDPC syndrome check: decoder converges immediately on an uncorrupted 36 36 codeword. *) ··· 42 42 Alcotest.(check string) 43 43 "LDPC syndrome valid (1 iteration enough)" (Bytes.to_string data) 44 44 (Bytes.to_string recovered) 45 - | Error e -> Alcotest.fail (Fmt.str "LDPC syndrome check failed: %s" e) 45 + | Error e -> Alcotest.failf "LDPC syndrome check failed: %s" e 46 46 47 47 (** LDPC error correction: flip 5 bits and verify the decoder corrects them. *) 48 48 let test_ldpc_error_correction () = ··· 57 57 Alcotest.(check string) 58 58 "LDPC corrects 5 bit errors" (Bytes.to_string data) 59 59 (Bytes.to_string recovered) 60 - | Error e -> Alcotest.fail (Fmt.str "LDPC error correction failed: %s" e) 60 + | Error e -> Alcotest.failf "LDPC error correction failed: %s" e 61 61 62 62 (** LDPC roundtrip with all-zeros data. *) 63 63 let test_ldpc_all_zeros () = ··· 72 72 Alcotest.(check string) 73 73 "LDPC all-zeros roundtrip" (Bytes.to_string data) 74 74 (Bytes.to_string recovered) 75 - | Error e -> Alcotest.fail (Fmt.str "LDPC all-zeros failed: %s" e) 75 + | Error e -> Alcotest.failf "LDPC all-zeros failed: %s" e 76 76 77 77 (** LDPC roundtrip with all-ones data. *) 78 78 let test_ldpc_all_ones () = ··· 85 85 Alcotest.(check string) 86 86 "LDPC all-ones roundtrip" (Bytes.to_string data) 87 87 (Bytes.to_string recovered) 88 - | Error e -> Alcotest.fail (Fmt.str "LDPC all-ones failed: %s" e) 88 + | Error e -> Alcotest.failf "LDPC all-ones failed: %s" e 89 89 90 90 (** LDPC codeword length check. *) 91 91 let test_ldpc_codeword_length () = ··· 103 103 (* --- Error correction stress tests --- *) 104 104 105 105 (** Make deterministic test data from a seed byte. *) 106 - let make_data seed = 106 + let data seed = 107 107 Bytes.init 128 (fun i -> Char.chr (((((i * 37) + seed) * 53) + 7) land 0xFF)) 108 108 109 109 (** Flip [n] bits at pseudo-random positions in a copy of [buf], using a simple ··· 133 133 (** Test error correction with exactly [n_errors] bit flips. Returns true if the 134 134 decoder successfully recovered the original data. *) 135 135 let try_correction n_errors seed = 136 - let data = make_data seed in 136 + let data = data seed in 137 137 let codeword = Ldpc.encode ldpc data in 138 138 let corrupted = flip_n_bits codeword n_errors ((seed * 1000) + n_errors) in 139 139 match Ldpc.decode ~max_iter:50 ldpc corrupted with ··· 224 224 Alcotest.(check string) 225 225 "large data roundtrip" (Bytes.to_string data) 226 226 (Bytes.to_string recovered) 227 - | Error e -> Alcotest.fail (Fmt.str "large data decode failed: %s" e) 227 + | Error e -> Alcotest.failf "large data decode failed: %s" e 228 228 229 229 (* --- Syndrome check: zero syndrome for valid codewords --- *) 230 230 ··· 233 233 let seeds = [ 0; 1; 42; 128; 255 ] in 234 234 List.iter 235 235 (fun seed -> 236 - let data = make_data seed in 236 + let data = data seed in 237 237 let codeword = Ldpc.encode ldpc data in 238 238 (* Decode with max_iter=1 should succeed immediately if syndrome is 0 *) 239 239 match Ldpc.decode ~max_iter:1 ldpc codeword with ··· 243 243 (Bytes.to_string data) 244 244 (Bytes.to_string recovered) 245 245 | Error e -> 246 - Alcotest.fail (Fmt.str "syndrome check failed for seed %d: %s" seed e)) 246 + Alcotest.failf "syndrome check failed for seed %d: %s" seed e) 247 247 seeds 248 248 249 249 (* --- BP convergence: test max_iter=1,5,50 on same corrupted codeword --- *) 250 250 251 251 (** Test that increasing max_iter improves or maintains decoding quality. *) 252 252 let test_bp_convergence () = 253 - let data = make_data 77 in 253 + let data = data 77 in 254 254 let codeword = Ldpc.encode ldpc data in 255 255 (* Flip 3 bits: should be correctable with enough iterations *) 256 256 let corrupted = flip_n_bits codeword 3 9999 in ··· 274 274 (** Test that max_iter=1 is insufficient for larger errors but max_iter=50 275 275 succeeds, demonstrating that iteration count matters. *) 276 276 let test_bp_insufficient_iterations () = 277 - let data = make_data 33 in 277 + let data = data 33 in 278 278 let codeword = Ldpc.encode ldpc data in 279 279 let corrupted = flip_n_bits codeword 8 5555 in 280 280 let ok_1 = ··· 305 305 Alcotest.(check string) 306 306 "alternating 01 roundtrip" (Bytes.to_string data) 307 307 (Bytes.to_string recovered) 308 - | Error e -> Alcotest.fail (Fmt.str "alternating 01 failed: %s" e) 308 + | Error e -> Alcotest.failf "alternating 01 failed: %s" e 309 309 310 310 (** Alternating 1010... pattern across all 128 bytes. *) 311 311 let test_pattern_alternating_10 () = ··· 317 317 Alcotest.(check string) 318 318 "alternating 10 roundtrip" (Bytes.to_string data) 319 319 (Bytes.to_string recovered) 320 - | Error e -> Alcotest.fail (Fmt.str "alternating 10 failed: %s" e) 320 + | Error e -> Alcotest.failf "alternating 10 failed: %s" e 321 321 322 322 (** Running ones: 0xFF bytes (same as all-ones but explicitly checked). *) 323 323 let test_pattern_running_ones () = ··· 328 328 Alcotest.(check string) 329 329 "running ones roundtrip" (Bytes.to_string data) 330 330 (Bytes.to_string recovered) 331 - | Error e -> Alcotest.fail (Fmt.str "running ones failed: %s" e) 331 + | Error e -> Alcotest.failf "running ones failed: %s" e 332 332 333 333 (** Single bit set: only bit 0 is 1, everything else is 0. *) 334 334 let test_pattern_single_bit_0 () = ··· 341 341 Alcotest.(check string) 342 342 "single bit 0 roundtrip" (Bytes.to_string data) 343 343 (Bytes.to_string recovered) 344 - | Error e -> Alcotest.fail (Fmt.str "single bit 0 failed: %s" e) 344 + | Error e -> Alcotest.failf "single bit 0 failed: %s" e 345 345 346 346 (** Single bit set: only the last information bit is 1. *) 347 347 let test_pattern_single_bit_last () = ··· 354 354 Alcotest.(check string) 355 355 "single bit last roundtrip" (Bytes.to_string data) 356 356 (Bytes.to_string recovered) 357 - | Error e -> Alcotest.fail (Fmt.str "single bit last failed: %s" e) 357 + | Error e -> Alcotest.failf "single bit last failed: %s" e 358 358 359 359 (** Single bit set in the middle: bit 512. *) 360 360 let test_pattern_single_bit_middle () = ··· 367 367 Alcotest.(check string) 368 368 "single bit middle roundtrip" (Bytes.to_string data) 369 369 (Bytes.to_string recovered) 370 - | Error e -> Alcotest.fail (Fmt.str "single bit middle failed: %s" e) 370 + | Error e -> Alcotest.failf "single bit middle failed: %s" e 371 371 372 372 (* --- Decoder rejects oversized input --- *) 373 373 374 374 (** Decode with oversized input: extra bytes beyond n bits should be ignored and 375 375 decoding should still succeed. *) 376 376 let test_decode_oversized_input () = 377 - let data = make_data 42 in 377 + let data = data 42 in 378 378 let codeword = Ldpc.encode ldpc data in 379 379 (* Append 100 extra bytes *) 380 380 let oversized = Bytes.make (Bytes.length codeword + 100) '\xAB' in ··· 385 385 Alcotest.(check string) 386 386 "oversized input: data recovered" (Bytes.to_string data) 387 387 (Bytes.to_string recovered) 388 - | Error e -> Alcotest.fail (Fmt.str "oversized input decode failed: %s" e) 388 + | Error e -> Alcotest.failf "oversized input decode failed: %s" e 389 389 390 390 (** Encode with oversized input: extra bytes beyond k bits should be ignored. *) 391 391 let test_encode_oversized_input () = 392 - let data = make_data 42 in 392 + let data = data 42 in 393 393 (* Append 50 extra bytes *) 394 394 let oversized = Bytes.make (128 + 50) '\xCD' in 395 395 Bytes.blit data 0 oversized 0 128; ··· 404 404 Alcotest.(check string) 405 405 "oversized encode: roundtrip" (Bytes.to_string data) 406 406 (Bytes.to_string recovered) 407 - | Error e -> Alcotest.fail (Fmt.str "oversized encode roundtrip failed: %s" e) 407 + | Error e -> Alcotest.failf "oversized encode roundtrip failed: %s" e 408 408 409 409 let suite = 410 410 ( "ldpc",