viterbi 0.2.0

Pure-Rust, no-unsafe convolutional encoder + Viterbi (MLSE) decoder — CCSDS inner FEC: hard & soft-decision (LLR), rate 1/2 & 1/3, puncturing (2/3–7/8), generic constraint length K<=9.
Documentation
// Author: BolivarTech
// Version: 1.0.0
// Date: 2026-07-02
//! # `viterbi` — CCSDS convolutional encoder + block Viterbi (MLSE) decoder.
//!
//! This crate is the **inner code** of the classic CCSDS concatenated coding scheme, designed to pair
//! with an outer Reed–Solomon code via an interleaver:
//!
//! ```text
//! TX:  data → [RS(255,223)] → [interleaver] → [conv. K=7] → CHANNEL
//! RX:  CHANNEL → [Viterbi] → [de-interleaver] → [RS decode] → data
//!                └── this crate
//! ```
//!
//! ## Feature set (`v0.2.x`)
//! - **Hard-decision** block encode/decode ([`HardHamming`], [`CcsdsViterbiDecoder::decode_block`]) —
//!   the first-class CCSDS `K = 7`, `R = 1/2` path.
//! - **Soft-decision** decoding over quantized `i8` LLRs ([`SoftLlr`], [`CcsdsSoftDecoder`]) for the
//!   ≈ 2 dB coding gain, validated by a BER-vs-Eb/N0 sweep (see `examples/ber_curve`).
//! - **Rate-1/3** mother code (`d_free = 14`) via [`CodeParams::ccsds_r1_3`] and the `n`-generic trellis.
//! - **Puncturing** to rates 2/3, 3/4, 5/6, 7/8 as a separable wrapping layer over both mother codes
//!   ([`Puncturer`] / [`DePuncturer`], puncture matrix as runtime data).
//! - **Generic constraint length** `K ≤ 9` via the const-generic state count (`K = 7` stays first-class);
//!   preconfigured codes are discoverable through [`CodeProfile`].
//!
//! All new capability is **additive**: the released hard-decision `R = 1/2`, `K = 7` public API and
//! behaviour are unchanged. Streaming, concatenation, `no_std`/MCU, and SOVA remain for later minors.
//!
//! ## Design invariants
//! - Pure Rust, no `unsafe` (`forbid`ed crate-wide via `Cargo.toml` lints and here).
//! - Deterministic: same input ⇒ same output, bit-for-bit, on every platform.
//! - No panic in the decode hot path; `Result` only at configuration boundaries.
//!
//! # Examples
//!
//! Encode a payload, then recover it with the Viterbi decoder (clean channel):
//!
//! ```
//! use viterbi::{CcsdsViterbiDecoder, CodeParams, ViterbiEncoder};
//!
//! // Build the encoder and decoder from the first-class CCSDS profile.
//! let encoder = ViterbiEncoder::new(CodeParams::ccsds_r1_2()).unwrap();
//! let mut decoder = CcsdsViterbiDecoder::new(CodeParams::ccsds_r1_2(), 4096).unwrap();
//!
//! let payload = b"hello, viterbi";
//! let coded = encoder.encode(payload).unwrap();
//! let decoded = decoder.decode_block(&coded).unwrap();
//!
//! assert_eq!(&decoded.bytes, payload);
//! ```
#![forbid(unsafe_code)]

/// Constraint length.
pub const K: u8 = 7;
/// Memory (shift-register length) = K - 1.
pub const M: u8 = 6;
/// Trellis states for the CCSDS profile = 2^M.
pub const STATES: usize = 1 << M;
/// Output bits per input bit (rate 1/2).
pub const N: usize = 2;
/// Hard upper bound on `max_info_bits` (R18).
pub const MAX_SUPPORTED_INFO_BITS: usize = 1_000_000;

pub mod decoder;
pub mod encoder;
pub mod metric;
pub mod packing;
pub mod params;
pub mod puncture;
pub(crate) mod trellis;

// Flat public API: re-export the primary types at the crate root.
pub use decoder::{
    CcsdsSoftDecoder, CcsdsViterbiDecoder, ConfigError, DecodeError, ViterbiDecoder,
};
pub use encoder::{EncodeError, ViterbiEncoder};
pub use metric::{BranchMetric, HardHamming, SoftLlr, HARD_ERASURE, SOFT_MAX_SPREAD};
pub use packing::{CodedBlock, DecodedBlock};
pub use params::{CodeParams, CodeProfile, ParamError};
pub use puncture::{
    DePuncturer, PunctureError, PunctureMatrix, PunctureOrder, PuncturedRate, Puncturer,
};

#[cfg(test)]
mod tests {
    #[test]
    fn crate_constants_are_ccsds() {
        assert_eq!(super::K, 7);
        assert_eq!(super::M, 6);
        assert_eq!(super::STATES, 1 << super::M); // 64
        assert_eq!(super::N, 2);
        assert_eq!(super::MAX_SUPPORTED_INFO_BITS, 1_000_000);
    }
}