2019-03-23 01:25:07 +00:00
|
|
|
use crate::arraymap::UsizeArrayMap;
|
2019-03-28 21:13:19 +00:00
|
|
|
use crate::arraymap::{ArrayMap, BoolArrayMap};
|
2019-03-23 01:25:07 +00:00
|
|
|
use crate::matrix::OctetMatrix;
|
|
|
|
use crate::octet::Octet;
|
|
|
|
use crate::symbol::Symbol;
|
|
|
|
use crate::systematic_constants::num_hdpc_symbols;
|
|
|
|
use crate::systematic_constants::num_intermediate_symbols;
|
|
|
|
use crate::systematic_constants::num_ldpc_symbols;
|
|
|
|
use crate::systematic_constants::num_pi_symbols;
|
|
|
|
use crate::util::get_both_indices;
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
struct FirstPhaseRowSelectionStats {
|
|
|
|
original_degree: UsizeArrayMap,
|
|
|
|
non_zeros_per_row: UsizeArrayMap,
|
|
|
|
ones_per_row: UsizeArrayMap,
|
|
|
|
non_zeros_histogram: UsizeArrayMap,
|
|
|
|
hdpc_rows: Vec<bool>,
|
|
|
|
start_col: usize,
|
|
|
|
end_col: usize,
|
2019-03-28 21:13:19 +00:00
|
|
|
start_row: usize,
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl FirstPhaseRowSelectionStats {
|
|
|
|
#[inline(never)]
|
|
|
|
#[allow(non_snake_case)]
|
2019-04-06 04:18:25 +00:00
|
|
|
pub fn new<T: OctetMatrix>(
|
|
|
|
matrix: &T,
|
2019-03-28 21:13:19 +00:00
|
|
|
end_col: usize,
|
|
|
|
num_source_symbols: u32,
|
|
|
|
) -> FirstPhaseRowSelectionStats {
|
2019-02-19 01:02:54 +00:00
|
|
|
let S = num_ldpc_symbols(num_source_symbols);
|
|
|
|
let H = num_hdpc_symbols(num_source_symbols);
|
|
|
|
|
|
|
|
// See section 5.3.3.4.2, Figure 5.
|
|
|
|
let mut hdpc_rows = vec![false; matrix.height()];
|
|
|
|
for row in S..(S + H) {
|
|
|
|
hdpc_rows[row as usize] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut result = FirstPhaseRowSelectionStats {
|
|
|
|
original_degree: UsizeArrayMap::new(0, 0),
|
|
|
|
non_zeros_per_row: UsizeArrayMap::new(0, matrix.height()),
|
|
|
|
ones_per_row: UsizeArrayMap::new(0, matrix.height()),
|
|
|
|
non_zeros_histogram: UsizeArrayMap::new(0, end_col + 1),
|
|
|
|
hdpc_rows,
|
|
|
|
start_col: 0,
|
|
|
|
end_col,
|
2019-03-28 21:13:19 +00:00
|
|
|
start_row: 0,
|
2019-02-19 01:02:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
for row in 0..matrix.height() {
|
2019-04-06 03:13:00 +00:00
|
|
|
let (ones, non_zero) = matrix.count_ones_and_nonzeros(row, 0, end_col);
|
2019-02-19 01:02:54 +00:00
|
|
|
result.non_zeros_per_row.insert(row, non_zero);
|
|
|
|
result.ones_per_row.insert(row, ones);
|
|
|
|
result.non_zeros_histogram.increment(non_zero);
|
|
|
|
}
|
|
|
|
// Original degree is the degree of each row before processing begins
|
|
|
|
result.original_degree = result.non_zeros_per_row.clone();
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn swap_rows(&mut self, i: usize, j: usize) {
|
|
|
|
self.non_zeros_per_row.swap(i, j);
|
|
|
|
self.ones_per_row.swap(i, j);
|
|
|
|
self.original_degree.swap(i, j);
|
|
|
|
self.hdpc_rows.swap(i, j);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recompute all stored statistics for the given row
|
2019-04-06 04:18:25 +00:00
|
|
|
pub fn recompute_row<T: OctetMatrix>(&mut self, row: usize, matrix: &T) {
|
2019-04-06 03:13:00 +00:00
|
|
|
let (ones, non_zero) = matrix.count_ones_and_nonzeros(row, self.start_col, self.end_col);
|
2019-03-28 21:13:19 +00:00
|
|
|
self.non_zeros_histogram
|
|
|
|
.decrement(self.non_zeros_per_row.get(row));
|
2019-02-19 01:02:54 +00:00
|
|
|
self.non_zeros_histogram.increment(non_zero);
|
|
|
|
self.non_zeros_per_row.insert(row, non_zero);
|
|
|
|
self.ones_per_row.insert(row, ones);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn eliminate_leading_value(&mut self, row: usize, value: &Octet) {
|
|
|
|
debug_assert_ne!(*value, Octet::zero());
|
|
|
|
if *value == Octet::one() {
|
|
|
|
self.ones_per_row.decrement(row);
|
|
|
|
}
|
|
|
|
let non_zeros = self.non_zeros_per_row.get(row);
|
|
|
|
self.non_zeros_histogram.decrement(non_zeros);
|
|
|
|
self.non_zeros_histogram.increment(non_zeros - 1);
|
|
|
|
self.non_zeros_per_row.decrement(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the valid columns, and recalculate statistics
|
2019-04-07 03:51:10 +00:00
|
|
|
// All values in column "start_col - 1" in rows start_row..end_row must be zero
|
2019-02-19 01:02:54 +00:00
|
|
|
#[inline(never)]
|
2019-04-06 04:18:25 +00:00
|
|
|
pub fn resize<T: OctetMatrix>(
|
2019-03-28 21:13:19 +00:00
|
|
|
&mut self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
|
|
|
start_col: usize,
|
|
|
|
end_col: usize,
|
2019-04-06 04:18:25 +00:00
|
|
|
matrix: &T,
|
2019-03-28 21:13:19 +00:00
|
|
|
) {
|
2019-02-19 01:02:54 +00:00
|
|
|
// Only shrinking is supported
|
|
|
|
assert!(end_col <= self.end_col);
|
|
|
|
assert_eq!(self.start_row, start_row - 1);
|
2019-04-07 03:51:10 +00:00
|
|
|
assert_eq!(self.start_col, start_col - 1);
|
2019-02-19 01:02:54 +00:00
|
|
|
|
2019-03-28 21:13:19 +00:00
|
|
|
self.non_zeros_histogram
|
|
|
|
.decrement(self.non_zeros_per_row.get(self.start_row));
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
for row in start_row..end_row {
|
|
|
|
for col in end_col..self.end_col {
|
|
|
|
if matrix.get(row, col) == Octet::one() {
|
|
|
|
self.ones_per_row.decrement(row);
|
|
|
|
}
|
|
|
|
if matrix.get(row, col) != Octet::zero() {
|
|
|
|
let non_zeros = self.non_zeros_per_row.get(row);
|
|
|
|
self.non_zeros_histogram.decrement(non_zeros);
|
|
|
|
self.non_zeros_histogram.increment(non_zeros - 1);
|
|
|
|
self.non_zeros_per_row.decrement(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.start_col = start_col;
|
|
|
|
self.end_col = end_col;
|
|
|
|
self.start_row = start_row;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
2019-04-06 04:18:25 +00:00
|
|
|
fn first_phase_graph_substep_build_adjacency<T: OctetMatrix>(
|
2019-03-28 21:13:19 +00:00
|
|
|
&self,
|
|
|
|
rows_with_two_ones: &Vec<usize>,
|
2019-04-06 04:18:25 +00:00
|
|
|
matrix: &T,
|
2019-03-28 21:13:19 +00:00
|
|
|
) -> ArrayMap<Vec<(usize, usize)>> {
|
2019-03-23 16:50:02 +00:00
|
|
|
let mut adjacent_nodes = ArrayMap::new(self.start_col, self.end_col);
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
for row in rows_with_two_ones.iter() {
|
|
|
|
if self.hdpc_rows[*row] {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let mut ones = [0; 2];
|
|
|
|
let mut found = 0;
|
2019-04-07 06:05:52 +00:00
|
|
|
for (col, value) in matrix.get_row_iter(*row, self.start_col, self.end_col) {
|
2019-02-19 01:02:54 +00:00
|
|
|
// "The following graph defined by the structure of V is used in determining which
|
|
|
|
// row of A is chosen. The columns that intersect V are the nodes in the graph,
|
|
|
|
// and the rows that have exactly 2 nonzero entries in V and are not HDPC rows
|
|
|
|
// are the edges of the graph that connect the two columns (nodes) in the positions
|
|
|
|
// of the two ones."
|
|
|
|
// This part of the matrix is over GF(2), so "nonzero entries" is equivalent to "ones"
|
2019-04-07 06:05:52 +00:00
|
|
|
if value == Octet::one() {
|
2019-02-19 01:02:54 +00:00
|
|
|
ones[found] = col;
|
|
|
|
found += 1;
|
|
|
|
}
|
|
|
|
if found == 2 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-03-22 03:31:54 +00:00
|
|
|
assert_eq!(found, 2);
|
2019-03-23 16:50:02 +00:00
|
|
|
let first = adjacent_nodes.get_mut(ones[0]);
|
|
|
|
if first == None {
|
|
|
|
let mut new_nodes = Vec::with_capacity(10);
|
|
|
|
new_nodes.push((ones[1], *row));
|
|
|
|
adjacent_nodes.insert(ones[0], new_nodes);
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-03-23 16:50:02 +00:00
|
|
|
first.unwrap().push((ones[1], *row));
|
|
|
|
}
|
|
|
|
let second = adjacent_nodes.get_mut(ones[1]);
|
|
|
|
if second == None {
|
|
|
|
let mut new_nodes = Vec::with_capacity(10);
|
|
|
|
new_nodes.push((ones[0], *row));
|
|
|
|
adjacent_nodes.insert(ones[1], new_nodes);
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-03-23 16:50:02 +00:00
|
|
|
second.unwrap().push((ones[0], *row));
|
|
|
|
}
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
2019-03-23 16:50:02 +00:00
|
|
|
return adjacent_nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
2019-04-06 04:18:25 +00:00
|
|
|
fn first_phase_graph_substep<T: OctetMatrix>(
|
2019-03-28 21:13:19 +00:00
|
|
|
&self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
|
|
|
rows_with_two_ones: &Vec<usize>,
|
2019-04-06 04:18:25 +00:00
|
|
|
matrix: &T,
|
2019-03-28 21:13:19 +00:00
|
|
|
) -> usize {
|
|
|
|
let adjacent_nodes =
|
|
|
|
self.first_phase_graph_substep_build_adjacency(rows_with_two_ones, matrix);
|
2019-03-23 16:50:02 +00:00
|
|
|
let mut visited = BoolArrayMap::new(start_row, end_row);
|
|
|
|
|
|
|
|
let mut examplar_largest_component_row = None;
|
|
|
|
let mut largest_component_size = 0;
|
|
|
|
|
|
|
|
let mut node_queue = Vec::with_capacity(10);
|
|
|
|
for key in adjacent_nodes.keys() {
|
|
|
|
let mut component_size = 0;
|
|
|
|
let mut examplar_row = None;
|
|
|
|
// Pick arbitrary node (column) to start
|
|
|
|
node_queue.clear();
|
|
|
|
node_queue.push(key);
|
|
|
|
while !node_queue.is_empty() {
|
|
|
|
let node = node_queue.pop().unwrap();
|
|
|
|
if visited.get(node) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
visited.insert(node, true);
|
|
|
|
let next_nodes = adjacent_nodes.get(node).unwrap();
|
|
|
|
component_size += 1;
|
|
|
|
for &(next_node, row) in next_nodes.iter() {
|
|
|
|
node_queue.push(next_node);
|
2019-03-23 20:10:54 +00:00
|
|
|
examplar_row = Some(row);
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-23 20:10:54 +00:00
|
|
|
if component_size > largest_component_size {
|
2019-03-23 16:50:02 +00:00
|
|
|
examplar_largest_component_row = examplar_row;
|
|
|
|
largest_component_size = component_size;
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-23 16:50:02 +00:00
|
|
|
|
|
|
|
return examplar_largest_component_row.unwrap();
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
2019-03-28 21:13:19 +00:00
|
|
|
fn first_phase_original_degree_substep(
|
|
|
|
&self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
|
|
|
r: usize,
|
|
|
|
) -> usize {
|
2019-02-19 01:02:54 +00:00
|
|
|
let mut chosen_hdpc = None;
|
|
|
|
let mut chosen_hdpc_original_degree = std::usize::MAX;
|
|
|
|
let mut chosen_non_hdpc = None;
|
|
|
|
let mut chosen_non_hdpc_original_degree = std::usize::MAX;
|
|
|
|
for row in start_row..end_row {
|
|
|
|
let non_zero = self.non_zeros_per_row.get(row);
|
|
|
|
let row_original_degree = self.original_degree.get(row);
|
|
|
|
if non_zero == r {
|
|
|
|
if self.hdpc_rows[row] {
|
|
|
|
if row_original_degree < chosen_hdpc_original_degree {
|
|
|
|
chosen_hdpc = Some(row);
|
|
|
|
chosen_hdpc_original_degree = row_original_degree;
|
|
|
|
}
|
2019-03-28 21:13:19 +00:00
|
|
|
} else if row_original_degree < chosen_non_hdpc_original_degree {
|
2019-02-19 01:02:54 +00:00
|
|
|
chosen_non_hdpc = Some(row);
|
|
|
|
chosen_non_hdpc_original_degree = row_original_degree;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if chosen_non_hdpc != None {
|
|
|
|
return chosen_non_hdpc.unwrap();
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
return chosen_hdpc.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify there there are no non-HPDC rows with exactly two non-zero entries, greater than one
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
2019-03-28 21:13:19 +00:00
|
|
|
fn first_phase_graph_substep_verify(
|
|
|
|
&self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
|
|
|
rows_with_two_ones: &Vec<usize>,
|
|
|
|
) {
|
2019-02-19 01:02:54 +00:00
|
|
|
for row in start_row..end_row {
|
|
|
|
if self.non_zeros_per_row.get(row) == 2 {
|
|
|
|
assert!(rows_with_two_ones.contains(&row) || self.hdpc_rows[row]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper method for decoder phase 1
|
|
|
|
// selects from [start_row, end_row) reading [start_col, end_col)
|
|
|
|
// Returns (the chosen row, and "r" number of non-zero values the row has)
|
2019-04-06 04:18:25 +00:00
|
|
|
pub fn first_phase_selection<T: OctetMatrix>(
|
2019-03-28 21:13:19 +00:00
|
|
|
&self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
2019-04-06 04:18:25 +00:00
|
|
|
matrix: &T,
|
2019-03-28 21:13:19 +00:00
|
|
|
) -> (Option<usize>, Option<usize>) {
|
2019-02-19 01:02:54 +00:00
|
|
|
let mut r = None;
|
|
|
|
for i in 1..(self.end_col - self.start_col + 1) {
|
|
|
|
if self.non_zeros_histogram.get(i) > 0 {
|
|
|
|
r = Some(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if r == None {
|
|
|
|
return (None, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.unwrap() == 2 {
|
|
|
|
let mut rows_with_two_ones = vec![];
|
|
|
|
let mut row_with_two_greater_than_one = None;
|
|
|
|
for row in start_row..end_row {
|
|
|
|
let non_zero = self.non_zeros_per_row.get(row);
|
|
|
|
let ones = self.ones_per_row.get(row);
|
|
|
|
if non_zero == 2 && ones != 2 {
|
|
|
|
row_with_two_greater_than_one = Some(row);
|
|
|
|
}
|
|
|
|
if non_zero == 2 && ones == 2 {
|
|
|
|
rows_with_two_ones.push(row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// See paragraph starting "If r = 2 and there is a row with exactly 2 ones in V..."
|
|
|
|
if rows_with_two_ones.len() > 0 {
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.first_phase_graph_substep_verify(start_row, end_row, &rows_with_two_ones);
|
2019-03-28 21:13:19 +00:00
|
|
|
return (
|
|
|
|
Some(self.first_phase_graph_substep(
|
|
|
|
start_row,
|
|
|
|
end_row,
|
|
|
|
&rows_with_two_ones,
|
|
|
|
matrix,
|
|
|
|
)),
|
|
|
|
r,
|
|
|
|
);
|
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
// See paragraph starting "If r = 2 and there is no row with exactly 2 ones in V"
|
|
|
|
return (row_with_two_greater_than_one, r);
|
|
|
|
}
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
Some(self.first_phase_original_degree_substep(start_row, end_row, r.unwrap())),
|
|
|
|
r,
|
|
|
|
);
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// See section 5.4.2.1
|
|
|
|
#[allow(non_snake_case)]
|
2019-04-06 04:18:25 +00:00
|
|
|
pub struct IntermediateSymbolDecoder<T: OctetMatrix> {
|
|
|
|
A: T,
|
|
|
|
X: T,
|
2019-02-19 01:02:54 +00:00
|
|
|
D: Vec<Symbol>,
|
|
|
|
c: Vec<usize>,
|
|
|
|
d: Vec<usize>,
|
|
|
|
i: usize,
|
|
|
|
u: usize,
|
|
|
|
L: usize,
|
|
|
|
num_source_symbols: u32,
|
|
|
|
debug_symbol_mul_ops: u32,
|
|
|
|
debug_symbol_add_ops: u32,
|
|
|
|
debug_symbol_mul_ops_by_phase: Vec<u32>,
|
2019-03-28 21:13:19 +00:00
|
|
|
debug_symbol_add_ops_by_phase: Vec<u32>,
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
2019-04-06 04:18:25 +00:00
|
|
|
impl <T: OctetMatrix> IntermediateSymbolDecoder<T> {
|
2019-03-28 21:13:19 +00:00
|
|
|
pub fn new(
|
2019-04-06 04:18:25 +00:00
|
|
|
matrix: T,
|
2019-03-28 21:13:19 +00:00
|
|
|
symbols: Vec<Symbol>,
|
|
|
|
num_source_symbols: u32,
|
2019-04-06 04:18:25 +00:00
|
|
|
) -> IntermediateSymbolDecoder<T> {
|
2019-02-19 01:02:54 +00:00
|
|
|
assert!(matrix.width() <= symbols.len());
|
|
|
|
assert_eq!(matrix.height(), symbols.len());
|
|
|
|
let mut c = Vec::with_capacity(matrix.width());
|
|
|
|
let mut d = Vec::with_capacity(symbols.len());
|
|
|
|
for i in 0..matrix.width() {
|
|
|
|
c.push(i);
|
|
|
|
}
|
|
|
|
for i in 0..symbols.len() {
|
|
|
|
d.push(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
IntermediateSymbolDecoder {
|
|
|
|
A: matrix.clone(),
|
|
|
|
X: matrix,
|
|
|
|
D: symbols,
|
|
|
|
c,
|
|
|
|
d,
|
|
|
|
i: 0,
|
|
|
|
u: num_pi_symbols(num_source_symbols) as usize,
|
|
|
|
L: num_intermediate_symbols(num_source_symbols) as usize,
|
|
|
|
num_source_symbols,
|
|
|
|
debug_symbol_mul_ops: 0,
|
|
|
|
debug_symbol_add_ops: 0,
|
|
|
|
debug_symbol_mul_ops_by_phase: vec![0; 5],
|
2019-03-28 21:13:19 +00:00
|
|
|
debug_symbol_add_ops_by_phase: vec![0; 5],
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true iff all elements in A between [start_row, end_row)
|
|
|
|
// and [start_column, end_column) are zero
|
|
|
|
#[cfg(debug_assertions)]
|
2019-03-28 21:13:19 +00:00
|
|
|
fn all_zeroes(
|
|
|
|
&self,
|
|
|
|
start_row: usize,
|
|
|
|
end_row: usize,
|
|
|
|
start_column: usize,
|
|
|
|
end_column: usize,
|
|
|
|
) -> bool {
|
2019-02-19 01:02:54 +00:00
|
|
|
for row in start_row..end_row {
|
|
|
|
for column in start_column..end_column {
|
|
|
|
if self.A.get(row, column) != Octet::zero() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performs the column swapping substep of first phase, after the row has been chosen
|
|
|
|
#[inline(never)]
|
|
|
|
fn first_phase_swap_columns_substep(&mut self, r: usize) {
|
|
|
|
let mut swapped_columns = 0;
|
2019-04-07 04:02:30 +00:00
|
|
|
// TODO: optimize for sparse
|
2019-02-19 01:02:54 +00:00
|
|
|
for col in self.i..(self.A.width() - self.u) {
|
|
|
|
if self.A.get(self.i, col) != Octet::zero() {
|
|
|
|
let dest;
|
|
|
|
if swapped_columns == 0 {
|
|
|
|
dest = self.i;
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
dest = self.A.width() - self.u - swapped_columns;
|
|
|
|
}
|
2019-03-23 20:29:00 +00:00
|
|
|
// No need to swap the first i rows, as they are all zero (see submatrix above V)
|
|
|
|
self.swap_columns(dest, col, self.i);
|
2019-02-19 01:02:54 +00:00
|
|
|
// Also apply to X
|
2019-03-23 20:29:00 +00:00
|
|
|
self.X.swap_columns(dest, col, 0);
|
2019-02-19 01:02:54 +00:00
|
|
|
swapped_columns += 1;
|
|
|
|
if swapped_columns == r {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First phase (section 5.4.2.2)
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[inline(never)]
|
|
|
|
fn first_phase(&mut self) -> bool {
|
|
|
|
// First phase (section 5.4.2.2)
|
|
|
|
|
|
|
|
// ----------> i u <--------
|
|
|
|
// | +-----------+-----------------+---------+
|
|
|
|
// | | | | |
|
|
|
|
// | | I | All Zeros | |
|
|
|
|
// v | | | |
|
|
|
|
// i +-----------+-----------------+ U |
|
|
|
|
// | | | |
|
|
|
|
// | | | |
|
|
|
|
// | All Zeros | V | |
|
|
|
|
// | | | |
|
|
|
|
// | | | |
|
|
|
|
// +-----------+-----------------+---------+
|
|
|
|
// Figure 6: Submatrices of A in the First Phase
|
|
|
|
|
2019-03-28 21:13:19 +00:00
|
|
|
let mut selection_helper = FirstPhaseRowSelectionStats::new(
|
|
|
|
&self.A,
|
|
|
|
self.A.width() - self.u,
|
|
|
|
self.num_source_symbols,
|
|
|
|
);
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
while self.i + self.u < self.L {
|
|
|
|
// Calculate r
|
|
|
|
// "Let r be the minimum integer such that at least one row of A has
|
|
|
|
// exactly r nonzeros in V."
|
2019-03-28 21:13:19 +00:00
|
|
|
let (chosen_row, r) =
|
|
|
|
selection_helper.first_phase_selection(self.i, self.A.height(), &self.A);
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
if r == None {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let r = r.unwrap();
|
|
|
|
let chosen_row = chosen_row.unwrap();
|
|
|
|
|
|
|
|
// See paragraph beginning: "After the row is chosen in this step..."
|
|
|
|
// Reorder rows
|
|
|
|
let temp = self.i;
|
|
|
|
self.swap_rows(temp, chosen_row);
|
|
|
|
self.X.swap_rows(temp, chosen_row);
|
|
|
|
selection_helper.swap_rows(temp, chosen_row);
|
|
|
|
// Reorder columns
|
|
|
|
self.first_phase_swap_columns_substep(r);
|
|
|
|
// Zero out leading value in following rows
|
|
|
|
let temp = self.i;
|
|
|
|
for row in (self.i + 1)..self.A.height() {
|
|
|
|
let leading_value = self.A.get(row, temp);
|
|
|
|
if leading_value != Octet::zero() {
|
|
|
|
// Addition is equivalent to subtraction
|
|
|
|
let beta = &leading_value / &self.A.get(temp, temp);
|
|
|
|
self.fma_rows(temp, row, beta);
|
|
|
|
if r == 1 {
|
|
|
|
// Hot path for r == 1, since it's very common due to maximum connected
|
|
|
|
// component selection, and recompute_row() is expensive
|
|
|
|
selection_helper.eliminate_leading_value(row, &leading_value);
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
selection_helper.recompute_row(row, &self.A);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.i += 1;
|
|
|
|
self.u += r - 1;
|
2019-03-28 21:13:19 +00:00
|
|
|
selection_helper.resize(
|
|
|
|
self.i,
|
|
|
|
self.A.height(),
|
|
|
|
self.i,
|
|
|
|
self.A.width() - self.u,
|
|
|
|
&self.A,
|
|
|
|
);
|
2019-02-19 01:02:54 +00:00
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.first_phase_verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.record_symbol_ops(0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// See section 5.4.2.2. Verifies the two all-zeros submatrices and the identity submatrix
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn first_phase_verify(&self) {
|
|
|
|
for row in 0..self.i {
|
|
|
|
for col in 0..self.i {
|
|
|
|
if row == col {
|
|
|
|
assert_eq!(Octet::one(), self.A.get(row, col));
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
assert_eq!(Octet::zero(), self.A.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert!(self.all_zeroes(0, self.i, self.i, self.A.width() - self.u));
|
|
|
|
assert!(self.all_zeroes(self.i, self.A.height(), 0, self.i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second phase (section 5.4.2.3)
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[inline(never)]
|
|
|
|
fn second_phase(&mut self) -> bool {
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.second_phase_verify();
|
|
|
|
|
|
|
|
self.X.resize(self.i, self.i);
|
|
|
|
|
|
|
|
// Convert U_lower to row echelon form
|
|
|
|
let temp = self.i;
|
|
|
|
let size = self.u;
|
|
|
|
if !self.reduce_to_row_echelon(temp, temp, size) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform backwards elimination
|
|
|
|
self.backwards_elimination(temp, temp, size);
|
|
|
|
|
|
|
|
self.A.resize(self.L, self.L);
|
|
|
|
|
|
|
|
self.record_symbol_ops(1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verifies that X is lower triangular. See section 5.4.2.3
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn second_phase_verify(&self) {
|
|
|
|
for row in 0..self.i {
|
|
|
|
for col in (row + 1)..self.i {
|
|
|
|
assert_eq!(Octet::zero(), self.X.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Third phase (section 5.4.2.4)
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[inline(never)]
|
|
|
|
fn third_phase(&mut self) {
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.third_phase_verify();
|
|
|
|
|
|
|
|
// A[0..i][..] = X * A[0..i][..]
|
|
|
|
self.A.mul_assign_submatrix(&self.X, self.i);
|
|
|
|
|
|
|
|
// Now apply the same operations to D.
|
|
|
|
// Note that X is lower triangular, so the row must be processed last to first
|
|
|
|
for row in (0..self.i).rev() {
|
|
|
|
if self.X.get(row, row) != Octet::one() {
|
|
|
|
self.debug_symbol_mul_ops += 1;
|
|
|
|
self.D[self.d[row]].mulassign_scalar(&self.X.get(row, row));
|
|
|
|
}
|
|
|
|
|
2019-04-07 06:05:52 +00:00
|
|
|
for (col, value) in self.X.get_row_iter(row, 0, row) {
|
|
|
|
if value == Octet::zero() {
|
2019-02-19 01:02:54 +00:00
|
|
|
continue;
|
|
|
|
}
|
2019-04-07 06:05:52 +00:00
|
|
|
if value == Octet::one() {
|
2019-02-19 01:02:54 +00:00
|
|
|
self.debug_symbol_add_ops += 1;
|
|
|
|
let (dest, temp) = get_both_indices(&mut self.D, self.d[row], self.d[col]);
|
|
|
|
*dest += temp;
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
self.debug_symbol_mul_ops += 1;
|
|
|
|
self.debug_symbol_add_ops += 1;
|
|
|
|
let (dest, temp) = get_both_indices(&mut self.D, self.d[row], self.d[col]);
|
|
|
|
dest.fused_addassign_mul_scalar(temp, &self.X.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.record_symbol_ops(2);
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.third_phase_verify_end();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn third_phase_verify(&self) {
|
|
|
|
for row in 0..self.A.height() {
|
|
|
|
for col in 0..self.A.width() {
|
|
|
|
if row < self.i && col >= self.A.width() - self.u {
|
|
|
|
// element is in U_upper, which can have arbitrary values at this point
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// The rest of A should be identity matrix
|
|
|
|
if row == col {
|
|
|
|
assert_eq!(Octet::one(), self.A.get(row, col));
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
assert_eq!(Octet::zero(), self.A.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn third_phase_verify_end(&self) {
|
|
|
|
for row in 0..self.i {
|
|
|
|
for col in 0..self.i {
|
|
|
|
assert_eq!(self.X.get(row, col), self.A.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fourth phase (section 5.4.2.5)
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[inline(never)]
|
|
|
|
fn fourth_phase(&mut self) {
|
|
|
|
for i in 0..self.i {
|
2019-04-07 04:02:30 +00:00
|
|
|
// TODO: optimize for sparse
|
2019-02-19 01:02:54 +00:00
|
|
|
for j in 0..self.u {
|
|
|
|
let b = self.A.get(i, j + self.i);
|
|
|
|
if b != Octet::zero() {
|
|
|
|
let temp = self.i;
|
|
|
|
self.fma_rows(temp + j, i, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.record_symbol_ops(3);
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.fourth_phase_verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn fourth_phase_verify(&self) {
|
|
|
|
// ---------> i u <------
|
|
|
|
// | +-----------+--------+
|
|
|
|
// | |\ | |
|
|
|
|
// | | \ Zeros | Zeros |
|
|
|
|
// v | \ | |
|
|
|
|
// i | X \ | |
|
|
|
|
// u +---------- +--------+
|
|
|
|
// ^ | | |
|
|
|
|
// | | All Zeros | I |
|
|
|
|
// | | | |
|
|
|
|
// +-----------+--------+
|
|
|
|
// Same assertion about X being equal to the upper left of A
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.third_phase_verify_end();
|
|
|
|
assert!(self.all_zeroes(0, self.i, self.A.width() - self.u, self.A.width()));
|
|
|
|
assert!(self.all_zeroes(self.A.height() - self.u, self.A.height(), 0, self.i));
|
|
|
|
for row in (self.A.height() - self.u)..self.A.height() {
|
|
|
|
for col in (self.A.width() - self.u)..self.A.width() {
|
|
|
|
if row == col {
|
|
|
|
assert_eq!(Octet::one(), self.A.get(row, col));
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
assert_eq!(Octet::zero(), self.A.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fifth phase (section 5.4.2.6)
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
#[inline(never)]
|
|
|
|
fn fifth_phase(&mut self) {
|
|
|
|
// "For j from 1 to i". Note that A is 1-indexed in the spec, and ranges are inclusive,
|
|
|
|
// this is means [1, i], which is equal to [0, i)
|
|
|
|
for j in 0..self.i as usize {
|
|
|
|
if self.A.get(j, j) != Octet::one() {
|
|
|
|
let temp = self.A.get(j, j);
|
|
|
|
self.mul_row(j, Octet::one() / temp)
|
|
|
|
}
|
|
|
|
// "For l from 1 to j-1". This means the lower triangular columns, not including the
|
|
|
|
// diagonal, which is [0, j)
|
2019-04-07 04:02:30 +00:00
|
|
|
// TODO: optimize for sparse
|
2019-02-19 01:02:54 +00:00
|
|
|
for l in 0..j {
|
|
|
|
let temp = self.A.get(j, l);
|
|
|
|
if temp != Octet::zero() {
|
|
|
|
self.fma_rows(l, j, temp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.record_symbol_ops(4);
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
self.fifth_phase_verify();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
fn fifth_phase_verify(&self) {
|
|
|
|
assert_eq!(self.L, self.A.height());
|
|
|
|
for row in 0..self.A.height() {
|
|
|
|
assert_eq!(self.L, self.A.width());
|
|
|
|
for col in 0..self.A.width() {
|
|
|
|
if row == col {
|
|
|
|
assert_eq!(Octet::one(), self.A.get(row, col));
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
assert_eq!(Octet::zero(), self.A.get(row, col));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn record_symbol_ops(&mut self, phase: usize) {
|
|
|
|
self.debug_symbol_add_ops_by_phase[phase] = self.debug_symbol_add_ops;
|
|
|
|
self.debug_symbol_mul_ops_by_phase[phase] = self.debug_symbol_mul_ops;
|
|
|
|
for i in 0..phase {
|
|
|
|
self.debug_symbol_add_ops_by_phase[phase] -= self.debug_symbol_add_ops_by_phase[i];
|
|
|
|
self.debug_symbol_mul_ops_by_phase[phase] -= self.debug_symbol_mul_ops_by_phase[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reduces the size x size submatrix, starting at row_offset and col_offset as the upper left
|
|
|
|
// corner, to row echelon form
|
|
|
|
#[inline(never)]
|
2019-04-07 04:02:30 +00:00
|
|
|
// TODO: optimize for sparse
|
2019-02-19 01:02:54 +00:00
|
|
|
fn reduce_to_row_echelon(&mut self, row_offset: usize, col_offset: usize, size: usize) -> bool {
|
|
|
|
for i in 0..size {
|
|
|
|
// Swap a row with leading coefficient i into place
|
|
|
|
for j in (row_offset + i)..self.A.height() {
|
|
|
|
if self.A.get(j, col_offset + i) != Octet::zero() {
|
|
|
|
self.swap_rows(row_offset + i, j);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.A.get(row_offset + i, col_offset + i) == Octet::zero() {
|
|
|
|
// If all following rows are zero in this column, then matrix is singular
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scale leading coefficient to 1
|
|
|
|
if self.A.get(row_offset + i, col_offset + i) != Octet::one() {
|
|
|
|
let element_inverse = Octet::one() / self.A.get(row_offset + i, col_offset + i);
|
|
|
|
self.mul_row(row_offset + i, element_inverse);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Zero out all following elements in i'th column
|
|
|
|
for j in (row_offset + i + 1)..self.A.height() {
|
|
|
|
if self.A.get(j, col_offset + i) != Octet::zero() {
|
|
|
|
let scalar = self.A.get(j, col_offset + i);
|
|
|
|
self.fma_rows(row_offset + i, j, scalar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performs backwards elimination in a size x size submatrix, starting at
|
|
|
|
// row_offset and col_offset as the upper left corner of the submatrix
|
|
|
|
#[inline(never)]
|
2019-04-07 04:02:30 +00:00
|
|
|
// TODO: optimize for sparse
|
2019-02-19 01:02:54 +00:00
|
|
|
fn backwards_elimination(&mut self, row_offset: usize, col_offset: usize, size: usize) {
|
|
|
|
// Perform backwards elimination
|
|
|
|
for i in (0..size).rev() {
|
|
|
|
// Zero out all preceding elements in i'th column
|
|
|
|
for j in 0..i {
|
|
|
|
if self.A.get(row_offset + j, col_offset + i) != Octet::zero() {
|
|
|
|
let scalar = self.A.get(row_offset + j, col_offset + i);
|
|
|
|
self.fma_rows(row_offset + i, row_offset + j, scalar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn get_symbol_mul_ops(&self) -> u32 {
|
|
|
|
self.debug_symbol_mul_ops
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn get_symbol_add_ops(&self) -> u32 {
|
|
|
|
self.debug_symbol_add_ops
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn get_symbol_mul_ops_by_phase(&self) -> Vec<u32> {
|
|
|
|
self.debug_symbol_mul_ops_by_phase.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub fn get_symbol_add_ops_by_phase(&self) -> Vec<u32> {
|
|
|
|
self.debug_symbol_add_ops_by_phase.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper operations to apply operations to A, also to D
|
|
|
|
fn mul_row(&mut self, i: usize, beta: Octet) {
|
|
|
|
self.debug_symbol_mul_ops += 1;
|
|
|
|
self.D[self.d[i]].mulassign_scalar(&beta);
|
2019-04-06 03:13:00 +00:00
|
|
|
self.A.mul_assign_row(i, &beta);
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn fma_rows(&mut self, i: usize, iprime: usize, beta: Octet) {
|
|
|
|
if beta == Octet::one() {
|
|
|
|
self.debug_symbol_add_ops += 1;
|
|
|
|
let (dest, temp) = get_both_indices(&mut self.D, self.d[iprime], self.d[i]);
|
|
|
|
*dest += temp;
|
2019-03-28 21:13:19 +00:00
|
|
|
} else {
|
2019-02-19 01:02:54 +00:00
|
|
|
self.debug_symbol_add_ops += 1;
|
|
|
|
self.debug_symbol_mul_ops += 1;
|
|
|
|
let (dest, temp) = get_both_indices(&mut self.D, self.d[iprime], self.d[i]);
|
|
|
|
dest.fused_addassign_mul_scalar(&temp, &beta);
|
|
|
|
}
|
|
|
|
self.A.fma_rows(iprime, i, &beta);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn swap_rows(&mut self, i: usize, iprime: usize) {
|
|
|
|
self.A.swap_rows(i, iprime);
|
|
|
|
self.d.swap(i, iprime);
|
|
|
|
}
|
|
|
|
|
2019-03-23 20:29:00 +00:00
|
|
|
fn swap_columns(&mut self, j: usize, jprime: usize, start_row: usize) {
|
|
|
|
self.A.swap_columns(j, jprime, start_row);
|
2019-02-19 01:02:54 +00:00
|
|
|
self.c.swap(j, jprime);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
pub fn execute(&mut self) -> Option<Vec<Symbol>> {
|
|
|
|
if !self.first_phase() {
|
2019-03-28 21:13:19 +00:00
|
|
|
return None;
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
2019-04-07 02:56:32 +00:00
|
|
|
self.A.freeze_columns();
|
|
|
|
self.X.freeze_columns();
|
|
|
|
|
2019-02-19 01:02:54 +00:00
|
|
|
if !self.second_phase() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.third_phase();
|
|
|
|
self.fourth_phase();
|
|
|
|
self.fifth_phase();
|
|
|
|
|
|
|
|
// See end of section 5.4.2.1
|
2019-03-23 16:50:02 +00:00
|
|
|
let mut index_mapping = UsizeArrayMap::new(0, self.L);
|
2019-02-19 01:02:54 +00:00
|
|
|
for i in 0..self.L {
|
|
|
|
index_mapping.insert(self.c[i], self.d[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut result = Vec::with_capacity(self.L);
|
|
|
|
for i in 0..self.L {
|
|
|
|
result.push(self.D[index_mapping.get(i)].clone());
|
|
|
|
}
|
|
|
|
Some(result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fused implementation for self.inverse().mul_symbols(symbols)
|
|
|
|
// See section 5.4.2.1
|
2019-04-06 04:18:25 +00:00
|
|
|
pub fn fused_inverse_mul_symbols<T: OctetMatrix>(
|
|
|
|
matrix: T,
|
2019-03-28 21:13:19 +00:00
|
|
|
symbols: Vec<Symbol>,
|
|
|
|
num_source_symbols: u32,
|
|
|
|
) -> Option<Vec<Symbol>> {
|
2019-02-19 01:02:54 +00:00
|
|
|
IntermediateSymbolDecoder::new(matrix, symbols, num_source_symbols).execute()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::IntermediateSymbolDecoder;
|
2019-03-23 01:25:07 +00:00
|
|
|
use crate::constraint_matrix::generate_constraint_matrix;
|
|
|
|
use crate::symbol::Symbol;
|
|
|
|
use crate::systematic_constants::extended_source_block_symbols;
|
2019-04-06 04:18:25 +00:00
|
|
|
use crate::matrix::OctetMatrix;
|
|
|
|
use crate::matrix::DenseOctetMatrix;
|
2019-02-19 01:02:54 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn operations_per_symbol() {
|
2019-03-28 21:13:19 +00:00
|
|
|
for &(elements, expected_mul_ops, expected_add_ops) in
|
|
|
|
[(10, 35.0, 50.0), (100, 16.0, 35.0)].iter()
|
|
|
|
{
|
2019-03-23 20:10:54 +00:00
|
|
|
let num_symbols = extended_source_block_symbols(elements);
|
2019-02-19 01:02:54 +00:00
|
|
|
let indices: Vec<u32> = (0..num_symbols).collect();
|
2019-04-06 04:18:25 +00:00
|
|
|
let a = generate_constraint_matrix::<DenseOctetMatrix>(num_symbols, &indices);
|
2019-03-29 08:56:22 +00:00
|
|
|
let symbols = vec![Symbol::zero(1usize); a.width()];
|
2019-02-19 01:02:54 +00:00
|
|
|
let mut decoder = IntermediateSymbolDecoder::new(a, symbols, num_symbols);
|
|
|
|
decoder.execute();
|
2019-03-28 21:13:19 +00:00
|
|
|
assert!(
|
|
|
|
(decoder.get_symbol_mul_ops() as f64 / num_symbols as f64) < expected_mul_ops,
|
|
|
|
"mul ops per symbol = {}",
|
|
|
|
(decoder.get_symbol_mul_ops() as f64 / num_symbols as f64)
|
|
|
|
);
|
|
|
|
assert!(
|
|
|
|
(decoder.get_symbol_add_ops() as f64 / num_symbols as f64) < expected_add_ops,
|
|
|
|
"add ops per symbol = {}",
|
|
|
|
(decoder.get_symbol_add_ops() as f64 / num_symbols as f64)
|
|
|
|
);
|
2019-02-19 01:02:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|