#![allow(dead_code)]
use super::binary_matrix::BinaryMatrix;
use super::tanner_graph::TannerGraphDegree2;
use crate::data_manager::DataManager;
use crate::traits::LDPC;
use crate::types::{CodeParams, InactivationStrategy};
use smallvec::SmallVec;
use std::collections::VecDeque;
type ActiveVarIds = SmallVec<[usize; 8]>;
type VarAdjacentList = SmallVec<[usize; 4]>;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EquationStatus {
Decoded,
Unused,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VariableStatus {
Active,
Decoded { equ_id: usize },
Inactive { seq: usize },
}
#[derive(Clone, Debug)]
pub struct SparseEquation {
data_id: usize,
active_var_ids: ActiveVarIds,
}
impl SparseEquation {
fn degree(&self) -> usize {
self.active_var_ids.len()
}
fn decode_variable(&mut self, var_id: usize) {
if let Some(pos) = self.active_var_ids.iter().position(|&x| x == var_id) {
self.active_var_ids.swap_remove(pos);
} else {
eprintln!(
"Unexpected: variable {} is not active in equation {}",
var_id, self.data_id
);
}
}
fn inactivate_variable(&mut self, var_id: usize) {
if let Some(pos) = self.active_var_ids.iter().position(|&x| x == var_id) {
self.active_var_ids.swap_remove(pos);
} else {
eprintln!(
"Unexpected: variable {} is not active in equation {}",
var_id, self.data_id
);
}
}
}
struct IDPosition {
ids: Vec<usize>,
pos: Vec<usize>,
}
impl IDPosition {
pub fn new(len: usize) -> Self {
let ids: Vec<usize> = (0..len).collect();
let pos: Vec<usize> = (0..len).collect();
Self { ids, pos }
}
pub fn push_next(&mut self) {
let id = self.ids.len();
self.ids.push(id);
self.pos.push(id);
}
pub fn swap(&mut self, i: usize, j: usize) {
let vi = self.ids[i];
let vj = self.ids[j];
self.ids[i] = vj;
self.ids[j] = vi;
self.pos[vi] = j;
self.pos[vj] = i;
}
}
pub struct SparseSystem {
params: CodeParams,
num_decoded: usize,
num_original_inactive: usize,
num_inactive: usize,
max_inactive_num: usize,
inac_strategy: InactivationStrategy,
var_ids: IDPosition,
var_adjacent: Vec<VarAdjacentList>,
inactive_coeff_matrix: BinaryMatrix,
sparse_equations: Vec<SparseEquation>,
equation_ids: IDPosition,
solvable_equations: VecDeque<usize>,
scratch_targets: Vec<usize>,
bs_peers: Option<Vec<VarAdjacentList>>,
}
impl SparseSystem {
pub fn new(
params: &CodeParams,
max_inactive_num: usize,
inac_strategy: InactivationStrategy,
) -> Self {
let num_total = params.num_total();
let num_decoded = 0;
let num_inactive = params.num_inactive();
let var_adjacent = (0..num_total).map(|_| VarAdjacentList::new()).collect();
Self {
params: params.clone(),
num_decoded,
num_original_inactive: num_inactive,
num_inactive,
max_inactive_num,
inac_strategy,
var_ids: IDPosition::new(num_total),
var_adjacent,
inactive_coeff_matrix: BinaryMatrix::new(max_inactive_num),
sparse_equations: Vec::new(),
equation_ids: IDPosition::new(0),
solvable_equations: VecDeque::new(),
scratch_targets: Vec::with_capacity(16),
bs_peers: None,
}
}
fn inactive_matrix_seq(&self, var_id_seq: usize) -> usize {
let n = self.num_inactive;
let m = n - self.num_original_inactive;
if var_id_seq < m {
n - var_id_seq - 1
} else {
var_id_seq - m
}
}
fn inactive_coeff_row_ge(&self, equ_id: usize) -> Vec<u8> {
self.inactive_coeff_matrix
.row_bytes(equ_id, self.num_inactive)
}
fn var_status(&self, var_id: usize) -> VariableStatus {
let pos = self.var_ids.pos[var_id];
let total = self.params.num_total();
if pos < self.num_decoded {
VariableStatus::Decoded {
equ_id: self.equation_ids.ids[pos],
}
} else if pos >= total - self.num_inactive {
VariableStatus::Inactive {
seq: self.inactive_matrix_seq(pos - (total - self.num_inactive)),
}
} else {
VariableStatus::Active
}
}
fn add_ldpc_constraint_by_id(
&mut self,
manager: &mut DataManager,
ldpc: &dyn LDPC,
ldpc_id: usize,
) {
let data_id = manager.coded_data_id(self.params.k + ldpc_id);
let mut active_var_ids = ldpc.active_row(ldpc_id);
let inactive_var_ids = ldpc.inactive_row(ldpc_id);
active_var_ids.push(self.params.a + ldpc_id);
let next_equation_id = self.sparse_equations.len();
self.inactive_coeff_matrix.push_row();
for &idx in &inactive_var_ids {
self.inactive_coeff_matrix.set(next_equation_id, idx);
}
for &var_id in &active_var_ids {
self.var_adjacent[var_id].push(next_equation_id);
}
self.sparse_equations.push(SparseEquation {
data_id,
active_var_ids: active_var_ids.into(),
});
self.equation_ids.push_next();
if self.sparse_equations.last().unwrap().degree() == 1 {
self.solvable_equations.push_back(next_equation_id);
}
}
pub fn add_ldpc_constraints(&mut self, manager: &mut DataManager, ldpc: &dyn LDPC) {
for ldpc_id in 0..self.params.l {
self.add_ldpc_constraint_by_id(manager, ldpc, ldpc_id);
}
}
pub fn add_lt_coded_vector(
&mut self,
manager: &mut DataManager,
data_id: usize,
degree_set: &[usize],
) -> usize {
let next_equation_id = self.sparse_equations.len();
self.inactive_coeff_matrix.push_row();
let mut active_var_ids: ActiveVarIds = SmallVec::new();
let num_inactive = self.num_inactive;
for &var_id in degree_set {
let status = self.var_status(var_id);
match status {
VariableStatus::Decoded { equ_id } => {
let eq = &self.sparse_equations[equ_id];
manager.add_one_to_vector(eq.data_id, data_id);
self.inactive_coeff_matrix
.xor_rows(next_equation_id, equ_id, num_inactive);
}
VariableStatus::Active => {
active_var_ids.push(var_id);
self.var_adjacent[var_id].push(next_equation_id);
}
VariableStatus::Inactive { seq } => {
self.inactive_coeff_matrix.flip(next_equation_id, seq);
}
}
}
self.sparse_equations.push(SparseEquation {
data_id,
active_var_ids,
});
self.equation_ids.push_next();
if self.sparse_equations.last().unwrap().degree() == 1 {
self.solvable_equations.push_back(next_equation_id);
}
next_equation_id
}
fn bp_solve(&mut self, manager: &mut DataManager, equ_id: usize) {
let Some(var_id) = self.sparse_equations[equ_id].active_var_ids.pop() else {
return;
};
let var_pos = self.var_ids.pos[var_id];
self.var_ids.swap(var_pos, self.num_decoded);
let equ_pos = self.equation_ids.pos[equ_id];
self.equation_ids.swap(equ_pos, self.num_decoded);
self.num_decoded += 1;
let var_data_id = manager.data_id_of_variable_vector(var_id);
manager.move_to(self.sparse_equations[equ_id].data_id, var_data_id);
self.sparse_equations[equ_id].data_id = var_data_id;
let num_inactive = self.num_inactive;
self.scratch_targets.clear();
for &equ_id_adj in &self.var_adjacent[var_id] {
if equ_id_adj == equ_id {
continue;
}
self.inactive_coeff_matrix
.xor_rows(equ_id_adj, equ_id, num_inactive);
let eq_adj = &mut self.sparse_equations[equ_id_adj];
self.scratch_targets.push(eq_adj.data_id);
eq_adj.decode_variable(var_id);
if eq_adj.degree() == 1 {
self.solvable_equations.push_back(equ_id_adj);
}
}
self.broadcast_src_to_scratch_targets(manager, var_data_id);
}
fn inactivate(&mut self, var_id: usize) {
let pos = self.var_ids.pos[var_id];
self.var_ids
.swap(pos, self.params.num_total() - self.num_inactive - 1);
self.num_inactive += 1;
let new_seq = self.num_inactive - 1;
for &equ_id in &self.var_adjacent[var_id] {
let eq = &mut self.sparse_equations[equ_id];
eq.inactivate_variable(var_id);
self.inactive_coeff_matrix.flip(equ_id, new_seq);
if eq.degree() == 1 {
self.solvable_equations.push_back(equ_id);
}
}
}
fn next_solvable(&mut self) -> Option<usize> {
self.solvable_equations.pop_front()
}
pub fn run_bp_inactivation(&mut self, manager: &mut DataManager) -> usize {
loop {
if let Some(equ_id) = self.next_solvable() {
self.bp_solve(manager, equ_id);
} else if let Some(var_id) = self.next_inactivation() {
self.inactivate(var_id);
} else {
break;
}
}
self.num_decoded
}
pub fn is_bp_complete(&self) -> bool {
self.num_decoded + self.num_inactive == self.params.num_total()
}
pub fn num_inactive(&self) -> usize {
self.num_inactive
}
}
impl SparseSystem {
pub fn inactive_coeff_row(&self, equ_id: usize) -> Vec<u8> {
self.inactive_coeff_row_ge(equ_id)
}
#[must_use]
pub fn inactive_coeff_matrix(&self) -> &BinaryMatrix {
&self.inactive_coeff_matrix
}
pub fn inactive_system_from_unused_equations_packed(
&self,
add_row: &mut dyn FnMut(&BinaryMatrix, usize, usize),
) {
for &equ_id in &self.equation_ids.ids[self.num_decoded..] {
let data_id = self.sparse_equations[equ_id].data_id;
add_row(&self.inactive_coeff_matrix, equ_id, data_id);
}
}
pub fn inactive_system_from_unused_equations(&self, add_row: &mut dyn FnMut(Vec<u8>, usize)) {
for &equ_id in &self.equation_ids.ids[self.num_decoded..] {
let eq = &self.sparse_equations[equ_id];
let row = self.inactive_coeff_row_ge(equ_id);
add_row(row, eq.data_id);
}
}
pub fn inactive_coefficients_matrix_from_unused_equations(&self) -> (BinaryMatrix, Vec<usize>) {
let mut matrix = BinaryMatrix::new(self.num_inactive);
let mut rhs_ids = Vec::new();
for &equ_id in &self.equation_ids.ids[self.num_decoded..] {
let data_id = self.sparse_equations[equ_id].data_id;
matrix.append_row_from_words(
self.inactive_coeff_matrix.row_words(equ_id),
self.num_inactive,
);
rhs_ids.push(data_id);
}
(matrix, rhs_ids)
}
pub fn tilde_g_row(&self, var_id: usize) -> Vec<u8> {
let status = self.var_status(var_id);
match status {
VariableStatus::Decoded { equ_id } => self.inactive_coeff_row_ge(equ_id),
VariableStatus::Inactive { seq } => {
let mut row = vec![0u8; self.num_inactive];
row[seq] = 1;
row
}
VariableStatus::Active => {
unreachable!()
}
}
}
pub fn tilde_g_rows(&self) -> Vec<Vec<u8>> {
let kl = self.params.num_message_ldpc();
let n = self.num_inactive;
let mut rows = Vec::with_capacity(kl);
for var_id in 0..kl {
match self.var_status(var_id) {
VariableStatus::Decoded { equ_id } => {
rows.push(self.inactive_coeff_row_ge(equ_id));
}
VariableStatus::Inactive { seq } => {
let mut row = vec![0u8; n];
row[seq] = 1;
rows.push(row);
}
VariableStatus::Active => {
unreachable!("tilde_g_rows requires BP complete (no active vars)")
}
}
}
rows
}
pub fn tilde_g_packed(&self) -> BinaryMatrix {
let kl = self.params.num_message_ldpc();
let n = self.num_inactive;
let mut g = BinaryMatrix::new(n);
for var_id in 0..kl {
let row = g.push_row();
match self.var_status(var_id) {
VariableStatus::Decoded { equ_id } => {
g.copy_row_from(row, &self.inactive_coeff_matrix, equ_id, n);
}
VariableStatus::Inactive { seq } => {
g.set(row, seq);
}
VariableStatus::Active => {
unreachable!("tilde_g_packed requires BP complete (no active vars)")
}
}
}
g
}
}
impl SparseSystem {
fn next_inactivation(&mut self) -> Option<usize> {
if self.num_inactive < self.max_inactive_num
&& self.sparse_equations.len() >= self.params.num_message_ldpc()
&& self.num_decoded < self.params.num_total() - self.num_inactive
{
match self.inac_strategy {
InactivationStrategy::ByIndex => self.inact_strategy_by_index(),
InactivationStrategy::FirstActive => self.inact_strategy_first_active(),
InactivationStrategy::LastActive => self.inact_strategy_last_active(),
InactivationStrategy::MinDegree => self.inact_strategy_min_degree(),
InactivationStrategy::TrailRun => self.inact_strategy_trail_run(),
}
} else {
None
}
}
fn inact_strategy_first_active(&mut self) -> Option<usize> {
Some(self.var_ids.ids[self.num_decoded])
}
fn inact_strategy_by_index(&mut self) -> Option<usize> {
let active_end = self.params.num_total() - self.num_inactive;
for i in 0..self.params.num_active() {
let pos = self.var_ids.pos[i];
if pos < self.num_decoded || pos >= active_end {
continue;
}
return Some(self.var_ids.ids[pos]);
}
None
}
fn inact_strategy_last_active(&mut self) -> Option<usize> {
Some(self.var_ids.ids[self.params.num_total() - self.num_inactive - 1])
}
fn inact_strategy_min_degree(&mut self) -> Option<usize> {
let mut min_degree = usize::MAX;
let mut min_equ_id = usize::MAX;
for pos in self.num_decoded..self.sparse_equations.len() {
let equ_id = self.equation_ids.ids[pos];
let equ = &self.sparse_equations[equ_id];
if equ.degree() < min_degree {
min_degree = equ.degree();
min_equ_id = equ_id;
if min_degree <= 2 {
break;
}
}
}
if min_degree >= 2 {
Some(self.sparse_equations[min_equ_id].active_var_ids[0])
} else {
eprintln!("Unexpected: min_degree is {}, less than 2.", min_degree);
None
}
}
fn inact_strategy_trail_run(&mut self) -> Option<usize> {
let mut graph = self.build_tanner_graph_for_trial_bp();
if graph.size() == 0 {
return self.inact_strategy_last_active();
}
graph.best_inactivation_by_trial_run()
}
pub fn build_tanner_graph_for_trial_bp(&self) -> TannerGraphDegree2 {
let start = self.num_decoded;
let end = self.params.num_total() - self.num_inactive;
let active_var_ids: Vec<usize> = self.var_ids.ids[start..end].to_vec();
let equ_deg_2: Vec<Vec<usize>> = self.equation_ids.ids[self.num_decoded..]
.iter()
.filter(|&equ_id| self.sparse_equations[*equ_id].degree() == 2)
.map(|&equ_id| {
self.sparse_equations[equ_id]
.active_var_ids
.iter()
.copied()
.collect()
})
.collect();
TannerGraphDegree2::from_active_and_equations(&active_var_ids, &equ_deg_2)
}
}
impl SparseSystem {
pub fn build_bs_peers(&mut self) {
let n = self.num_decoded;
let mut peers: Vec<VarAdjacentList> = (0..n).map(|_| VarAdjacentList::new()).collect();
for (pos, peer) in peers.iter_mut().enumerate().take(n.saturating_sub(1)) {
let var_id = self.var_ids.ids[pos];
for &equ_id_adj in &self.var_adjacent[var_id] {
let ep = self.equation_ids.pos[equ_id_adj];
if ep > pos && ep < n {
peer.push(equ_id_adj);
}
}
}
self.bs_peers = Some(peers);
}
pub fn clear_bs_peers(&mut self) {
self.bs_peers = None;
}
fn broadcast_src_to_scratch_targets(&mut self, manager: &mut DataManager, src_id: usize) {
match self.scratch_targets.len() {
0 => {}
1 => manager.add_one_to_vector(src_id, self.scratch_targets[0]),
2 => {
manager.add_one_to_vector(src_id, self.scratch_targets[0]);
manager.add_one_to_vector(src_id, self.scratch_targets[1]);
}
3 => {
manager.add_one_to_vector(src_id, self.scratch_targets[0]);
manager.add_one_to_vector(src_id, self.scratch_targets[1]);
manager.add_one_to_vector(src_id, self.scratch_targets[2]);
}
_ => manager.broadcast_add_owned(src_id, self.scratch_targets.split_off(0)),
}
}
pub fn iter_decoded_var(&self) -> impl Iterator<Item = (usize, usize)> {
self.var_ids.ids[..self.num_decoded]
.iter()
.enumerate()
.map(move |(pos, &var_id)| {
let equ_id = self.equation_ids.ids[pos];
(var_id, equ_id)
})
}
pub fn iter_inactive_var(&self) -> impl Iterator<Item = (usize, usize)> {
let inactive_start = self.params.num_total() - self.num_inactive;
self.var_ids.ids[inactive_start..self.params.num_total()]
.iter()
.enumerate()
.map(|(pos, &var_id)| (var_id, self.inactive_matrix_seq(pos)))
}
pub fn reverse_bp(&mut self, manager: &mut DataManager) {
let peers = self
.bs_peers
.take()
.expect("build_bs_peers must be called before reverse_bp");
let num_inactive = self.num_inactive;
for pos in (0..self.num_decoded - 1).rev() {
let var_id = self.var_ids.ids[pos];
let equ_id = self.equation_ids.ids[pos];
self.scratch_targets.clear();
for &equ_id_adj in &peers[pos] {
self.inactive_coeff_matrix
.xor_rows(equ_id_adj, equ_id, num_inactive);
self.scratch_targets
.push(self.sparse_equations[equ_id_adj].data_id);
}
let var_data_id = manager.data_id_of_variable_vector(var_id);
self.broadcast_src_to_scratch_targets(manager, var_data_id);
}
self.bs_peers = Some(peers);
}
pub fn forward_bp(&mut self, manager: &mut DataManager) {
let peers = self
.bs_peers
.take()
.expect("build_bs_peers must be called before forward_bp");
for (pos, peer) in peers.iter().enumerate().take(self.num_decoded - 1) {
let var_id = self.var_ids.ids[pos];
self.scratch_targets.clear();
for &equ_id_adj in peer {
self.scratch_targets
.push(self.sparse_equations[equ_id_adj].data_id);
}
let var_data_id = manager.data_id_of_variable_vector(var_id);
self.broadcast_src_to_scratch_targets(manager, var_data_id);
}
self.bs_peers = Some(peers);
}
}
#[cfg(test)]
mod inactive_seq_tests {
use super::*;
use crate::types::{CodeParams, InactivationStrategy};
#[test]
fn pre_inactive_vars_use_internal_matrix_columns() {
let params = CodeParams::new(5, 2, 0, 0);
let sys = SparseSystem::new(¶ms, 8, InactivationStrategy::ByIndex);
for (var_id, expected_internal) in [(2, 0), (3, 1), (4, 2)] {
match sys.var_status(var_id) {
VariableStatus::Inactive { seq } => assert_eq!(seq, expected_internal),
other => panic!("var {var_id} expected inactive, got {other:?}"),
}
}
}
#[test]
fn dynamic_inactive_internal_seq_matches_inactivate_flip() {
let params = CodeParams::new(5, 2, 0, 0);
let mut sys = SparseSystem::new(¶ms, 8, InactivationStrategy::ByIndex);
sys.inactivate(0);
match sys.var_status(0) {
VariableStatus::Inactive { seq } => assert_eq!(seq, params.num_inactive()),
other => panic!("var 0 expected inactive after first inactivation, got {other:?}"),
}
assert_eq!(sys.num_inactive(), params.num_inactive() + 1);
sys.inactivate(1);
match sys.var_status(1) {
VariableStatus::Inactive { seq } => {
assert_eq!(seq, params.num_inactive() + 1);
}
other => panic!("var 1 expected inactive after second inactivation, got {other:?}"),
}
}
#[test]
fn iter_inactive_var_uses_ge_lu_column_indices() {
let params = CodeParams::new(5, 2, 0, 0);
let mut sys = SparseSystem::new(¶ms, 8, InactivationStrategy::ByIndex);
sys.inactivate(0);
sys.inactivate(1);
let mut ge_cols: Vec<_> = sys.iter_inactive_var().map(|(_, col)| col).collect();
ge_cols.sort_unstable();
assert_eq!(ge_cols, (0..sys.num_inactive()).collect::<Vec<_>>());
}
}