onlinecode/equations.rs
1//! Alternative data structures for online code decoding
2//!
3//! Reimplementation of the BipartiteGraph structure using tables
4//! (vectors), indices, etc. to avoid dynamically allocating explicit
5//! node and edge structures.
6
7
8use std::collections::{HashSet,HashMap};
9use std::collections::VecDeque;
10
11use crate::compat::*;
12
13// There's a degree of duplication in the structures below.
14//
15// An Equation represents a check block, eg:
16//
17// Msg1 + Msg6 + Aux3 = data_block_4
18//
19// Upon receipt, we check whether any of the variables on the left
20// side are known, and if so, we substitute them in. Then:
21//
22// case 1: all variables are known
23//
24// In this case, the equation provides no new information, so it is
25// discarded.
26//
27// case 2: all but one variables are known
28//
29// substitute in the knowns by subtracting them from both sides, eg:
30//
31// Msg6 = data_block_4 - Msg1 - Aux3
32//
33// This solves a new variable. In this case, we would mark Msg6 as
34// solved and queue up that variable for checking to see if makes any
35// other equations solvable.
36//
37// default:
38//
39// Substitute in any knowns and insert equation into the system
40//
41
42type EqID = usize;
43type VarID = usize;
44
45/*
46
47pub struct Equation {
48 // As more variables become solved (case 2 above), they can be
49 // substituted into other equations, so values (variable ids) will
50 // move from the HashSet on the left to the Vec on the right.
51 // Eventually, there will only be a single variable on the left
52 pub lhs : HashSet<VarID>, // unknown variables
53 pub rhs : Vec<VarID>, // expansion (data block [+vars])
54 // Note: we don't store block IDs explicitly, but it is part of
55 // the expansion on the right. The block ID can be inferred by the
56 // index of the equation in the table.
57
58 // count_unsolveds is not strictly necessary since we can simply
59 // count how many items are in the lhs HashSet ...
60 // count_unsolveds : usize,
61}
62
63// We also need to map variables onto the equations in which they
64// appear.
65pub struct Variable {
66 pub equations : HashSet<EqID>,
67}
68 */
69
70// Further note on variable, equation and block IDs...
71//
72// Note that before we can accept check blocks, we have to set up
73// equations for the auxiliary mappings. Since they're explicitly set
74// up at the start, we have to reserve `ablocks` entries in the
75// equations array.
76//
77// Also, the equations in the auxiliary mapping are slightly different
78// from equations derived from check blocks...
79//
80// A check block equation:
81//
82// Msg1 + Msg6 + Aux3 = data_block_4
83//
84// An auxiliary block equation:
85//
86// Msg1 + Msg4 + Msg7 + ... = Aux1
87//
88// We can rewrite this by bringing Aux1 to the left, since at the
89// start of decoding, its value is unknown.
90//
91// Msg1 + Msg4 + Msg7 + ... + Aux1 = 0
92//
93// This zero on the right can be represented as a virtual BlockID 0.
94//
95// If we're not explicitly storing a block ID within an Equation, we
96// have to take care to:
97//
98// * check whether the index of the equation ID is less than
99// `ablocks`, and if it is, treat the data block in the expansion on
100// the right hand side as a block of zeros.
101//
102// * subtract `ablocks` from equation IDs to get the correct data
103// block to use when expanding a check block solution
104//
105// One more thing ...
106//
107// Auxiliary blocks are encoded as the first `ablocks` equations
108// entered into the system, but as the last `ablocks` *variables*.
109
110#[derive(Debug)]
111pub enum EquationType {
112 // dropping AlreadySolved in favour of using Option<T>
113 // AlreadySolved,
114 // Solved: single variable on lhs, expansion on rhs
115 Solved(VarID, Vec<VarID>),
116 // Unsolved: several variables on lhs, expansion on rhs
117 Unsolved(HashSet<VarID>, Vec<VarID>),
118}
119pub enum VariableType {
120 Solved(EqID),
121 Unsolved(HashSet<EqID>)
122}
123
124use std::iter::Iterator;
125
126impl EquationType {
127 fn iter(&self) -> impl Iterator<Item = &'_ VarID> {
128 match self {
129 Self::Unsolved(hash,_) => { hash.iter() },
130 _ => {
131 panic!("Can't insert into already-solved variable");
132 }
133
134 }
135 }
136}
137
138impl VariableType {
139 fn insert(&mut self, var : VarID) {
140 match self {
141 Self::Solved(_) => {
142 panic!("Can't insert into already-solved variable");
143 },
144 Self::Unsolved(hash) => {
145 hash.insert(var);
146 }
147 }
148 }
149 fn iter(&self) -> impl Iterator<Item = &'_ EqID> {
150 match self {
151 Self::Solved(_) => {
152 panic!("Can't insert into already-solved variable");
153 },
154 Self::Unsolved(ref hash) => {
155 hash.iter()
156 }
157 }
158 }
159}
160
161pub struct Decoder {
162 pub mblocks : usize, // message blocks
163 pub ablocks : usize, // auxiliary blocks
164 pub coblocks: usize, // mblocks + ablocks
165
166 // These two arrays effectively implement the bipartite graph
167 // logic. Both types come in solved and unsolved flavours.
168
169 variables : Vec<VariableType>,
170 equations : Vec<EquationType>,
171
172 // maintain a list of variables that need visiting when an
173 // equation becomes solved
174 pub stack : VecDeque<VarID>,
175
176 // count only unsolved mblocks? That makes sense.
177 pub count_unsolveds : usize,
178 pub done : bool,
179}
180
181// to-do
182//
183// at the cost of a little more space, we can use an enum to
184// distinguish between unsolved variables and solved ones.
185//
186// Something like:
187//
188// enum Variable {
189// Solved(EqID),
190// Unsolved(HashSet<EqID>)
191// }
192
193
194impl Decoder {
195
196 pub fn new(mblocks : usize, ablocks : usize) -> Self {
197
198 // variables table is fixed size
199 let coblocks = mblocks + ablocks;
200 let mut variables = Vec::with_capacity(coblocks);
201
202 // allow headroom of ~11% in equations table. If space is
203 // tight, we can reduce this ...
204 let headroom = mblocks / 9;
205 let mut equations = Vec::with_capacity(coblocks + headroom);
206
207 // set up Variable struct for each mblock, ablock
208 for _ in 0..coblocks {
209 variables.push(
210 // Variable { equations : HashSet::<EqID>::new() }
211 VariableType::Unsolved(HashSet::<EqID>::new())
212 );
213 }
214
215 let count_unsolveds = mblocks;
216
217 Self {
218 mblocks, ablocks, coblocks, variables, equations,
219 count_unsolveds,
220 done : false,
221 stack : VecDeque::with_capacity(40)
222 }
223 }
224
225 pub fn add_aux_equations(&mut self, map : &AuxMapping) {
226
227 eprintln!("Decoder adding {} aux blocks", map.aux_to_mblocks.len());
228
229 for (ablock, list) in map.aux_to_mblocks.iter().enumerate() {
230
231 // let mut equation = Equation { }
232 let mut lhs = HashSet::<VarID>::new();
233 let mut rhs = Vec::<VarID>::new();
234
235 // note: the way that auxiliary blocks are generated means
236 // that it is possible (though unlikely) that one is
237 // generated with no links to any message blocks. Strictly
238 // speaking, we should be storing it as "solved" (with
239 // zero value), but since no variables refer to it anyway,
240 // it doesn't affect the algorithm in any way.
241
242 lhs.insert(self.mblocks + ablock);
243 for mblock in list.iter() {
244 lhs.insert(*mblock);
245 self.variables[*mblock].insert(ablock);
246 }
247 // link aux variable to equation
248 self.variables[self.mblocks + ablock].insert(ablock);
249 // store the equation
250 self.equations.push(EquationType::Unsolved(lhs,rhs));
251 }
252 }
253
254
255 /// Add check block to graph.
256 pub fn add_check_equation(&mut self,
257 coblocks : Vec<VarID>, step : bool)
258 -> (bool, usize, Option<Vec<VarID>>) {
259
260 // let mut unknowns = coblocks.len();
261 // let mut equation = Equation {
262 // lhs : HashSet::<VarID>::new(),
263 // rhs : Vec::<VarID>::new(),
264 // };
265
266 // if we're already done, just return
267 if self.done { return (true, self.stack.len(), None) }
268
269 // substitute all previously-solved variables into the
270 // equation
271 let equation = self.new_equation(coblocks);
272
273 if equation.is_none() {
274 // eprintln!("add_check_equation classified as None");
275 return (false, self.stack.len(), None)
276 }
277
278 let equation = equation.unwrap();
279
280 match equation {
281 EquationType::Solved(var,_) => {
282 // insert as newly-solved
283 // eprintln!("add_check_equation classified as Solved");
284 let eq_position = self.equations.len();
285 self.equations.push(equation);
286 self.variables[var].insert(eq_position);
287
288 // search graph starting from newly solved variable
289 self.stack.push_back(var);
290
291 // At this point, we've added a solved *equation*, and
292 // linked the newly-solved variable to it. What
293 // cascade() does is to check if the variable can be
294 // substituted into other equations. It's responsible
295 // for marking the *variable* as solved afterwards.
296 self.cascade(step)
297 },
298 EquationType::Unsolved(ref hash,_) => {
299 // insert as unsolved equation
300 // eprintln!("add_check_equation classified as Unsolved");
301 let eq_position = self.equations.len();
302
303 // link unsolved variables to new equation
304 for var in equation.iter() {
305 self.variables[*var].insert(eq_position);
306 }
307
308 // store the equation
309 self.equations.push(equation);
310 (false, self.stack.len(), None)
311 }
312 }
313
314 }
315
316 // If a variable has been newly solved, it's possible that it now
317 // allows other equations to be solved.
318 //
319 // We follow from the variable to the equations that it appears
320 // in. First, we do substitution, which involves moving the
321 // variable from the lhs to the rhs of the equation. Then we break
322 // the link from the variable to the equation, since it is no
323 // longer an unknown variable.
324 //
325 // If in the process of updating an equation we find that the
326 // number of unknowns becomes 1, then the remaining variable on
327 // the lhs of the equation also becomes solved. We queue up that
328 // variable to have the above step repeated on it.
329 //
330 // This should work equally well for solved message blocks and
331 // auxiliary blocks.
332
333 pub fn cascade(&mut self, stepping : bool)
334 -> (bool, usize, Option<Vec<VarID>>) {
335
336 // return list of newly-solved variables
337 let mut newly_solved = Vec::<VarID>::new();
338
339 while let Some(var) = self.stack.pop_front() {
340
341 if let VariableType::Solved(_) = self.variables[var] {
342 continue;
343 //panic!("variable {} already marked as solved",var)
344 }
345
346 // var is always a solved variable at this point
347 newly_solved.push(var);
348
349 // we know that the variable is solved, but we don't know
350 // which equation solved it. After scanning the list of
351 // equations that the variable appears in, we should find
352 // exactly one equation that matches.
353 let mut found_solved = 0;
354 let mut solved_equation = 0;
355
356 // eprintln!("Propagating solved variable {} into equations", var);
357
358 for eq_id in self.variables[var].iter() {
359 // eprintln!("Propagating into eq {}: {:?}",
360 // *eq_id, self.equations[*eq_id]);
361 match &mut self.equations[*eq_id] {
362
363 EquationType::Solved(v,rhs) => {
364 assert_eq!(*v, var);
365 // eprintln!("var {} solved by equation {}",var,*eq_id);
366 found_solved += 1;
367 solved_equation = *eq_id;
368 },
369 EquationType::Unsolved(hash, rhs) => {
370 // Need to do a little dance here. If the
371 // equation would become solved, we need to
372 // replace it with a ::Solved flavour. The
373 // borrow checker will not be happy, though. I
374 // may need to use an if let form instead of a
375 // match.
376
377 // Anyway, the gist is...
378 //
379 // if there are two entries in hash, delete
380 // var from it, placing it into the rhs, and
381 // use the remaining variable in the hash to
382 // create a Solved EquationType (using
383 // remaining variable and updated rhs). Also
384 // add the remaining variable to the stack,
385 // since it is now newly-solved. Then write
386 // the new Solved flavour back to the table.
387 //
388 // or else ... remove var from hash and append
389 // it to rhs
390
391 match hash.len() {
392 2 => {
393 let mut vars = hash.iter();
394 let mut other = vars.next().unwrap();
395 if *other == var {
396 other = vars.next().unwrap()
397 }
398 // eprintln!("Eq {} had {},{} on lhs",
399 // eq_id, var, other);
400 rhs.push(var);
401 self.stack.push_back(*other);
402 // the contentious bit (I think to_vec() copies)
403 self.equations[*eq_id] =
404 EquationType::Solved(*other,rhs.to_vec());
405 },
406 1 => {
407 // This case shouldn't happen since
408 // unsolved equation with a single
409 // unknown should have been replaced
410 // with a solved variant.
411
412 // Actually, it can happen if this is
413 // an aux block that didn't get linked
414 // to any message blocks, so refine
415 // the test:
416 if (*eq_id >= self.ablocks) {
417 panic!("Internal Error: Unsolved eq {} wrong",
418 eq_id);
419 }
420 },
421 0 => { panic!("Equation had no lhs"); },
422 _ => {
423 hash.remove(&var);
424 rhs.push(var);
425 }
426 };
427 }
428 }
429 // eprintln!("After propagating var {} into eq {}: {:?}",
430 // var, *eq_id, self.equations[*eq_id]);
431
432 } // for eq_id in self.variables.iter()
433
434 // variable also need to be marked as solved ...
435 // debug_assert_eq!(found_solved, 1);
436 match &self.variables[var] {
437 VariableType::Solved(_eq_id) => {
438 // panic!("Internal error: var {} was solved twice", var);
439 },
440 VariableType::Unsolved(_hash) => {
441 self.variables[var] = VariableType::Solved(solved_equation)
442 },
443 }
444
445 // update self's count of unsolved message blocks
446 if var < self.mblocks {
447 self.count_unsolveds -= 1;
448 if self.count_unsolveds == 0 {
449 self.done = true;
450 break;
451 }
452 }
453
454 // I should probably also be returning the number of items
455 // left in the stack if I want single-stepping to be
456 // useful.
457 //
458 // When single-stepping, call add_check_equation() once
459 // with the stepping option = true. Then while the second
460 // return value (the number of variables pending) is >0,
461 // call cascade(). This should solve equations/variables
462 // in the same order as a non-stepping call to
463 // add_check_equation().
464 if stepping { break }
465
466 }
467
468 ( self.done, self.stack.len(), Some(newly_solved) )
469 }
470
471 // There's a back-and-forth between variables and equations:
472 //
473 // we detect that a new or existing equation only has one unknown
474 // on the lhs. That counts as solving the equation (and hence, a
475 // variable).
476 //
477 // A newly-solved variable is substituted into all equations that
478 // it appears in. This may cause one of those equation to become
479 // solved.
480
481 // There's also the case where a new check block equation comes in
482 // and it contains only a single unknown.
483
484 // I want to have a nice, clean division among the three pieces of
485 // code above.
486
487 // I was thinking of making an enum for solved/unsolved variables.
488 // It might also be a good idea to do the same for equations.
489
490 // think of it in terms of forward/back propagation...
491 //
492 // substituting a newly-solved variable into all equations it
493 // appears in is like forward propagation
494 //
495 // if that equation becomes solved, then propagating the new info
496 // deriving from it is kind of like back propagation (from
497 // equations to variables)
498
499 // on enum describing equation ... besides solved and unsolved, we
500 // also have a third class, which represents "no new information".
501 // These correspond to a number of unknowns of 0 (no new info), 1
502 // (solved) and >1 (unsolved) in the code to add a new check block
503 // equation. The case of 0 unknowns (over-specified/idempotent
504 // equations) *could* be passed back to the caller to allow them
505 // to verify that all the received check blocks are consistent
506 // with each other, although it's easiest to just drop them and
507 // assume that the sender is working correctly.
508
509
510 /// Take a list of variables (block IDs) that comprise a check
511 /// block and substitute any already-solved ones in, returning
512 /// some kind of Option<EquationType> depending on the number of
513 /// unknowns:
514 ///
515 /// * 0 unknowns → None (already solved)
516 /// * 1 unknown → Some(Solved(VarID, Vec<VarID))
517 /// * 1+ unknowns → Some(Unsolved(HashSet<VarID>, Vec<VarID>))
518
519
520 // Hmm... might be better to get rid of already-solved enum type.
521 // They will never get stored in the graph, and is only needed to
522 // decide whether to drop a new check block. For that reason, I
523 // think I should return Option<EquationType> here instead
524
525 pub fn new_equation(&self, vars : Vec<VarID>) -> Option<EquationType> {
526
527 // Use two passes. First determine how many unsolved vars
528 // there are. Then, depending on whether it's 0, 1 or more
529 // than 1, return different enum
530
531 let mut count_unsolved = 0;
532 for var in vars.iter() {
533 // check to see if vars are solved
534 match self.variables[*var] {
535 VariableType::Solved(_) => {},
536 VariableType::Unsolved(_) => {
537 count_unsolved += 1;
538 // if count_unsolved > 1 { break }
539 }
540 }
541 }
542
543 match count_unsolved {
544 0 => { return None },
545 1 => {
546 let mut single_unsolved = 0;
547 let mut rhs = Vec::new();
548 for var in vars.iter() {
549 match self.variables[*var] {
550 VariableType::Solved(_) => {
551 rhs.push(*var)
552 },
553 VariableType::Unsolved(_) => {
554 single_unsolved = *var;
555 }
556 }
557 }
558 return Some(EquationType::Solved(single_unsolved, rhs))
559 },
560 _ => {
561 let mut lhs = HashSet::new();
562 let mut rhs = Vec::new();
563 for var in vars.iter() {
564 match self.variables[*var] {
565 VariableType::Solved(_) => {
566 rhs.push(*var)
567 },
568 VariableType::Unsolved(_) => {
569 lhs.insert(*var);
570 }
571 }
572 }
573 return Some(EquationType::Unsolved(lhs,rhs))
574 }
575 }
576
577 }
578
579 pub fn var_solution(&self, var : VarID)
580 -> Option<(Option<usize>, Vec<EqID>)> {
581
582 // actually ... we don't need initial, do we? Isn't the
583 // expansion for the variable completely on the RHS of the
584 // equation? No, it's not...
585
586 // var -> eq, and eq contains rhs (and a copy of var)
587 //
588 // if eq < ablocks, it represents an auxiliary block, so the
589 // initial block should be zero/not-present
590 //
591 // if eq >= ablocks, then it has an implicitly associated
592 // check block, which is eq - ablocks
593
594 // Or should I be checking whether var < mblocks instead?
595 // No, I should test eq < ablocks.
596 //
597 // Variables in general can be solved by auxiliary equations
598 // or check equations, so we look at the equation number, not
599 // the variable number to determine the correct expansion.
600
601 let mut initial = None;
602
603 match self.variables[var] {
604 VariableType::Solved(eq_id) => {
605
606 // if var < self.mblocks {
607 // initial = Some(eq_id);
608 // }
609 match &self.equations[eq_id] {
610 EquationType::Solved(lhs,rhs) => {
611 debug_assert_eq!(*lhs, var); // internal error
612 // let mut vec = Vec::new();
613 if eq_id >= self.ablocks {
614 // variable solved by check block
615 // vec.push(eq_id - self.ablocks);
616 initial = Some(eq_id - self.ablocks);
617 }
618 // vec.extend(rhs.to_vec());
619 Some((initial, rhs.to_vec()))
620 },
621 _ => {
622 // caller error
623 panic!("eq[{}] not marked as solved", eq_id)
624 }
625 }
626 },
627 _ => { None }
628 }
629 }
630}
631
632
633#[cfg(test)]
634mod test_decoder {
635
636 use super::*;
637
638 #[test]
639 fn single_unknown_no_aux() {
640 let mut d = Decoder::new(1,0);
641 // check block with a single unknown
642 let (done,pending,solved)
643 = d.add_check_equation(vec![0usize], false);
644
645 assert_eq!(done, true); // last block was solved
646 assert_eq!(pending, 0); // cascade didn't solve extra
647 assert_eq!(solved.is_none(), false); // got Some(Vec)
648
649 // unwrap the vector of solved vars and ensure 0 is the only
650 // item in it
651 let solved = solved.unwrap();
652 assert_eq!(solved[0], 0);
653 assert_eq!(solved.len(), 1);
654 }
655
656 #[test]
657 fn two_unknowns_no_aux_no_step() {
658 let mut d = Decoder::new(2,0);
659 // check block with two unknowns (blocks 0, 1)
660 let (done,pending,solved)
661 = d.add_check_equation(vec![0usize,1], false);
662
663 assert_eq!(done, false); // nothing solved
664 assert_eq!(pending, 0); // cascade didn't solve extra
665 assert!(solved.is_none()); // None instead of Some(Vec)
666
667 // a check block with either 0 or 1 by itself should
668 // solve both due to cascade.
669 let (done,pending,solved)
670 = d.add_check_equation(vec![0usize], false);
671
672 assert_eq!(done, true); // all solved
673 assert_eq!(pending, 0); // nothing left in pending
674 assert!(!solved.is_none()); // Some(Vec)
675
676 // unwrap the vector of solved vars. It should have [0,1] in
677 // that order
678
679 let solved = solved.unwrap();
680 assert_eq!(solved, [0,1]);
681 // assert_eq!(solved.len(), 2);
682
683 // confirm that solutions are actually correct
684 // var[0] -> eq[1], containing (1, empty vec)
685 // (interpretation: var_0 = check_block_1, no other xors)
686
687 match d.variables[0] {
688 VariableType::Solved(var0_eq) => {
689 assert_eq!(var0_eq, 1);
690 match &d.equations[var0_eq] {
691 EquationType::Solved(lhs,rhs) => {
692 assert_eq!(*lhs, 0); // solves variable 0
693 assert_eq!(*rhs, vec![]);
694 },
695 _ => { panic!("eq[1] not marked as solved") }
696 }
697 },
698 _ => { panic!("var[0] not marked as solved") }
699 }
700
701 // var[1] -> eq[0], containing (0, vec![0])
702 // (interpretation: var_1 = check_block_0 xor var_0)
703
704 match d.variables[1] {
705 VariableType::Solved(var1_eq) => {
706 assert_eq!(var1_eq, 0);
707 match &d.equations[var1_eq] {
708 EquationType::Solved(lhs,rhs) => {
709 assert_eq!(*lhs, 1); // solves variable 1
710 assert_eq!(*rhs, vec![0]);
711 },
712 _ => { panic!("eq[0] not marked as solved") }
713 }
714 },
715 _ => { panic!("var[1] not marked as solved") }
716 }
717 }
718
719
720 // make sure that solving via auxiliary blocks works as expected
721 //
722 // Setup (4 cases):
723 //
724 // let aux_0 = msg_0
725 // let chk_0 = (msg_0 or aux_0) + msg_1
726 // let chk_1 = (msg_0 or aux_0)
727 //
728 // If we receive a new check block solving either aux_0 or msg_0
729 // we should be able to solve msg_1.
730 //
731 // Code these as separate tests
732
733 // Case 1:
734 // chk_0 = msg_0 + msg_1
735 // chk_1 = msg_0
736 //
737 // Doesn't use aux for solution (so it's like two unknowns test as
738 // above), but does check that add_aux_equations() works correctly
739 // and that array indices in solutions are still correct.
740 // #[test]
741 fn solve_via_aux_case_1() {
742
743 // system with two unknown message blocks (0, 1) and one aux
744 let mut d = Decoder::new(2,1);
745
746 // first, add auxiliary mapping (aux -*-> msg)
747 let aux_map = AuxMapping { aux_to_mblocks : vec![vec![0]] };
748 d.add_aux_equations(&aux_map);
749
750 // Test that system is set up as expected. We shouldn't need
751 // to repeat this part of the testing for other cases.
752
753 match &d.variables[0] {
754 VariableType::Unsolved(hash) => {
755 assert_eq!(hash.len(), 1);
756 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
757 match &d.equations[0] {
758 EquationType::Unsolved(hash, vec) => {
759 assert_eq!(hash.len(), 2);
760 assert!(hash.contains(&0)); // msg0
761 assert!(hash.contains(&2)); // aux0
762 assert_eq!(vec.len(), 0); // rhs empty
763 },
764 _ => { panic!("aux0 equation wrongly set as solved")
765 }
766 }
767 },
768 _ => { panic!("msg0 wrongly set as solved") }
769 }
770 match &d.variables[1] {
771 VariableType::Unsolved(hash) => {
772 assert_eq!(hash.len(), 0);
773 },
774 _ => { panic!("msg1 wrongly set as solved") }
775 }
776 match &d.variables[2] {
777 VariableType::Unsolved(hash) => {
778 assert_eq!(hash.len(), 1);
779 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
780 // contents of equation already checked above
781 },
782 _ => { panic!("aux0 wrongly set as solved") }
783 }
784
785 // add chk0 = msg0 + msg1
786 let (done,pending,solved)
787 = d.add_check_equation(vec![0usize,1], false);
788 assert_eq!(done, false); // nothing solved
789 assert_eq!(pending, 0); // cascade didn't solve extra
790 assert!(solved.is_none()); // None instead of Some(Vec)
791
792 // add chk1 = msg0
793 let (done,pending,solved)
794 = d.add_check_equation(vec![0usize], false);
795 assert_eq!(done, true); // all solved
796
797 // Unfortunately, HashSet::iter() returns keys in
798 // nondeterministic order, so testing is not so
799 // straightforward.
800
801 assert_eq!(pending, 1); // aux should be pending?
802 // Actually, it depends on how hash keys are traversed. The
803 // order of keys from iterating HashMap/HashSet seems to be
804 // non-deterministic.
805
806 assert!(!solved.is_none()); // Some(Vec)
807 let solved = solved.unwrap();
808
809 // order of solutions should be msg0, msg1
810 assert_eq!(solved, [0,1]);
811
812 // check contents of solved variables ...
813 //
814 // There's a fair bit of boilerplate code involved in
815 // following variables to equations and then destructuring
816 // things. I'll add a new method to help with that...
817
818 // the method needs to return an optional equation number,
819 // which corresponds to Some(received check block) or None in
820 // the case that
821
822 let rhs = d.var_solution(0).unwrap();
823
824 // Some(1) means check block 1
825 // the empty vector means no solved variables to xor in
826 assert_eq!(rhs, (Some(1),vec![]));
827
828 }
829
830 // Rewrite test cases above to eliminate nondeterminism
831 //
832 // Forget about msg1 completely
833 //
834 // R1: (aux) aux0 = msg0
835 // R2a: chk0 = aux0
836 // R2b: chk0 = msg0
837
838 #[test]
839 fn deterministic_aux_case_1() {
840
841 // system with one unknown message blocks 0 and one aux
842 let mut d = Decoder::new(1,1);
843
844 // first, add auxiliary mapping (aux -*-> msg)
845 let aux_map = AuxMapping { aux_to_mblocks : vec![vec![0]] };
846 d.add_aux_equations(&aux_map);
847
848 // Test that system is set up as expected. We shouldn't need
849 // to repeat this part of the testing for other cases.
850
851 match &d.variables[0] {
852 VariableType::Unsolved(hash) => {
853 assert_eq!(hash.len(), 1);
854 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
855 match &d.equations[0] {
856 EquationType::Unsolved(hash, vec) => {
857 assert_eq!(hash.len(), 2);
858 assert!(hash.contains(&0)); // msg0
859 assert!(hash.contains(&1)); // aux0
860 assert_eq!(vec.len(), 0); // rhs empty
861 },
862 _ => { panic!("aux0 equation wrongly set as solved")
863 }
864 }
865 },
866 _ => { panic!("msg0 wrongly set as solved") }
867 }
868 match &d.variables[1] {
869 VariableType::Unsolved(hash) => {
870 assert_eq!(hash.len(), 1);
871 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
872 // contents of equation already checked above
873 },
874 _ => { panic!("aux0 wrongly set as solved") }
875 }
876
877 // add chk0 = aux0
878 let (done,pending,solved)
879 = d.add_check_equation(vec![1usize], false);
880 assert_eq!(done, true); // everything solved
881 assert_eq!(pending, 0); // cascade didn't solve extra
882 assert!(!solved.is_none()); // Some(Vec)
883
884 let solved = solved.unwrap();
885
886 // order of solutions should be aux0, msg0
887 assert_eq!(solved, [1,0]);
888
889 // test solution of aux block first (values in the returned
890 // array are all check block IDs)
891 let rhs = d.var_solution(1).unwrap();
892
893 assert_eq!(rhs, (Some(0), vec![])); // = chk0
894
895 // using the above, we can set chk1 (ie, aux0) = chk0
896
897 // That then tallies with the solution msg1 = chk1 (=chk0)
898 let rhs = d.var_solution(0).unwrap();
899
900 assert_eq!(rhs, (None, vec![1]));
901
902 }
903
904 #[test]
905 fn deterministic_aux_case_2() {
906
907 // system with one unknown message blocks 0 and one aux
908 let mut d = Decoder::new(1,1);
909
910 // first, add auxiliary mapping (aux -*-> msg)
911 let aux_map = AuxMapping { aux_to_mblocks : vec![vec![0]] };
912 d.add_aux_equations(&aux_map);
913
914 // Test that system is set up as expected. We shouldn't need
915 // to repeat this part of the testing for other cases.
916
917 match &d.variables[0] {
918 VariableType::Unsolved(hash) => {
919 assert_eq!(hash.len(), 1);
920 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
921 match &d.equations[0] {
922 EquationType::Unsolved(hash, vec) => {
923 assert_eq!(hash.len(), 2);
924 assert!(hash.contains(&0)); // msg0
925 assert!(hash.contains(&1)); // aux0
926 assert_eq!(vec.len(), 0); // rhs empty
927 },
928 _ => { panic!("aux0 equation wrongly set as solved")
929 }
930 }
931 },
932 _ => { panic!("msg0 wrongly set as solved") }
933 }
934 match &d.variables[1] {
935 VariableType::Unsolved(hash) => {
936 assert_eq!(hash.len(), 1);
937 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
938 // contents of equation already checked above
939 },
940 _ => { panic!("aux0 wrongly set as solved") }
941 }
942
943 // add chk0 = msg0
944 let (done,pending,solved)
945 = d.add_check_equation(vec![0usize], false);
946 assert_eq!(done, true); // everything solved
947 assert_eq!(pending, 1); // cascade detected done, w/o solving aux
948 assert!(!solved.is_none()); // Some(Vec)
949
950 let solved = solved.unwrap();
951
952 // order of solutions should be msg0
953 assert_eq!(solved, [0]);
954
955 // test solution of aux block first (values in the returned
956 // array are all check block IDs)
957 let rhs = d.var_solution(0).unwrap();
958
959 assert_eq!(rhs,(Some(0), vec![])); // = chk0
960
961 }
962
963 // The first deterministic case above showed an aux block solving
964 // a msg block, but because of how `done` is handled, the second
965 // case didn't fully show what happens then a msg block gets
966 // solved first. Extend that by adding in a second, unrelated
967 // message block.
968
969 #[test]
970 fn deterministic_aux_case_3() {
971
972 // system with one unknown message blocks 0 and one aux
973 let mut d = Decoder::new(2,1);
974
975 // first, add auxiliary mapping (aux -*-> msg)
976 let aux_map = AuxMapping { aux_to_mblocks : vec![vec![0]] };
977 d.add_aux_equations(&aux_map);
978
979 // Test that system is set up as expected. We shouldn't need
980 // to repeat this part of the testing for other cases.
981
982 match &d.variables[0] {
983 VariableType::Unsolved(hash) => {
984 assert_eq!(hash.len(), 1);
985 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
986 match &d.equations[0] {
987 EquationType::Unsolved(hash, vec) => {
988 assert_eq!(hash.len(), 2);
989 assert!(hash.contains(&0)); // msg0
990 assert!(hash.contains(&2)); // aux0
991 assert_eq!(vec.len(), 0); // rhs empty
992 },
993 _ => { panic!("aux0 equation wrongly set as solved")
994 }
995 }
996 },
997 _ => { panic!("msg0 wrongly set as solved") }
998 }
999 // check aux block
1000 match &d.variables[2] {
1001 VariableType::Unsolved(hash) => {
1002 assert_eq!(hash.len(), 1);
1003 assert!(hash.contains(&0)); // eq0 = aux0 + msg0
1004 // contents of equation already checked above
1005 },
1006 _ => { panic!("aux0 wrongly set as solved") }
1007 }
1008
1009 // add chk0 = msg0
1010 let (done,pending,solved)
1011 = d.add_check_equation(vec![0usize], false);
1012 assert_eq!(done, false); // msg1 not solved yet
1013 assert_eq!(pending, 0); // cascade doesn't do done
1014 assert!(!solved.is_none()); // Some(Vec)
1015
1016 let solved = solved.unwrap();
1017
1018 // order of solutions should be msg0,aux0
1019 assert_eq!(solved, [0,2]);
1020
1021 // test solution of aux block first (values in the returned
1022 // array are all check block IDs)
1023 let rhs = d.var_solution(0).unwrap();
1024
1025 assert_eq!(rhs, (Some(0), vec![]) ); // = chk0
1026
1027 // aux0 should be solved by msg0
1028 let rhs = d.var_solution(2).unwrap();
1029
1030 assert_eq!(rhs, (None, vec![0])); // = chk0
1031
1032 }
1033
1034
1035 // Random testing
1036 //
1037 // The Online Code algorithm specifies a particular construction
1038 // of auxiliary blocks and check blocks. However, we can simplify
1039 // that for the purposes of random testing on the decoder. It will
1040 // take longer to fully decode the original message, but it should
1041 // terminate at some point.
1042 //
1043 // There are two things to test in this mode:
1044 //
1045 // * that new solutions are composed only of already-solved
1046 // variables; and
1047 //
1048 // * when we do the XORs that the decoder tells us to do, we
1049 // actually recover the correct values
1050 //
1051 // If this fails, it probably won't be easy to find out why it
1052 // failed, and it won't be possible to reproduce the error. On the
1053 // other hand, if it succeeds, we can have very high confidence
1054 // that the decoder is bug-free.
1055
1056 // Will need some support routines which can be considered toy
1057 // versions of the proper Online Code algorithm
1058
1059 use rand::{Rng,thread_rng};
1060
1061 fn random_message(size : usize) -> Vec<usize> {
1062 let mut rng = thread_rng();
1063 let mut vec = Vec::with_capacity(size);
1064 for _ in 0..size {
1065 vec.push(rng.gen())
1066 }
1067 vec
1068 }
1069
1070 use crate::floyd::*;
1071
1072 fn generate_check_block(max_picks : usize, from : usize)
1073 -> HashSet<usize> {
1074
1075 let mut rng = thread_rng();
1076 let how_many = rng.gen_range(1..max_picks + 1);
1077 assert!(how_many > 0);
1078 assert!(how_many <= max_picks);
1079
1080 floyd_usize(&mut rng, how_many, from)
1081 }
1082
1083 fn hashset_to_vec(set : &HashSet<usize>) -> Vec<usize> {
1084 set.iter().cloned().collect::<Vec<_>>()
1085 }
1086
1087 // ablocks each comprising a random selection of `m_per_a` mblocks
1088 fn toy_aux_mapping(mblocks : usize, ablocks : usize, m_per_a : usize)
1089 -> Vec<Vec<usize>> {
1090
1091 let mut rng = thread_rng();
1092 let mut vec = Vec::with_capacity(ablocks);
1093 for _ in 0..ablocks {
1094 let aux_map = floyd_usize(&mut rng, m_per_a, mblocks);
1095 vec.push(hashset_to_vec(&aux_map))
1096 }
1097 vec
1098 }
1099
1100 #[test]
1101 fn test_toy_codec() {
1102
1103 let mblocks = 100;
1104 let ablocks = 25;
1105 let m_per_a = 5;
1106 let max_picks = 8; // max blocks to put in check block
1107
1108 // coding side
1109 //
1110 let mut message = random_message(mblocks);
1111
1112 // aux blocks stored at the end of message
1113 // let aux_blocks = Vec::with_capacity(ablocks);
1114
1115 // check_blocks can be shared between coder and decoder
1116 let mut check_blocks = Vec::<usize>::with_capacity(200);
1117
1118 let aux_mapping = toy_aux_mapping(mblocks, ablocks, m_per_a);
1119
1120 // calculate aux blocks and append them to message for easier
1121 // check block creation
1122 for aux_list in aux_mapping.iter() {
1123 let mut sum = 0usize;
1124 for mblock in aux_list {
1125 sum ^= message[*mblock];
1126 }
1127 message.push(sum);
1128 }
1129
1130 // decoding side
1131 let mut d = Decoder::new(mblocks, ablocks);
1132 d.add_aux_equations(& AuxMapping { aux_to_mblocks : aux_mapping });
1133
1134 // validate ordering of solutions
1135 let mut solvedp = vec![false ; mblocks + ablocks];
1136
1137 while !d.done {
1138
1139 let check_vec = hashset_to_vec(
1140 &generate_check_block(max_picks, mblocks + ablocks));
1141 let mut check_val = 0;
1142 for index in check_vec.iter() {
1143 check_val ^= message[*index]
1144 }
1145
1146 // eprintln!("Check block {} comprising: {:?}, value {}",
1147 // check_blocks.len(), check_vec, check_val);
1148
1149 // sender and receiver can end up disagreeing on what the
1150 // current check block number is if the receiver drops the
1151 // block because it provides no new info. To fix this,
1152 // I'll restrict changes to just the codec code here...
1153
1154 let mut useless = true;
1155 for maybe_known in check_vec.iter() {
1156 if !solvedp[*maybe_known] {
1157 useless = false;
1158 break;
1159 }
1160 }
1161 if useless {
1162 // eprintln!("Not storing a useless block");
1163 continue
1164 } else {
1165 check_blocks.push(check_val);
1166 }
1167
1168 let (done, pending, solved) =
1169 d.add_check_equation(check_vec, false);
1170
1171 if solved.is_none() {
1172 // see above for proper fix for "useless" check blocks
1173 // check_blocks.pop();
1174 continue
1175 }
1176
1177 let solved = solved.unwrap();
1178
1179 for var in solved.iter() {
1180 // the first, optional part of return is a check block
1181 let (chk, vars) = d.var_solution(*var).unwrap();
1182
1183 // eprintln!("Checking solution for var {}: ({:?},{:?})",
1184 // *var, chk, vars);
1185
1186 let mut sum = match chk {
1187 None => 0,
1188 Some(x) => {
1189 let value = check_blocks[x];
1190 // eprintln!("Check {} value is {}", x, value);
1191 value
1192 }
1193 };
1194
1195 // the remaining values are variables
1196 for v in vars.iter() {
1197 // make sure the variables were already solved
1198 assert_eq!(solvedp[*v], true);
1199 // that being the case, and because we check each
1200 // new var result (below), this is allowed: (in
1201 // practice, decoder will have its own structure)
1202
1203 // eprintln!("adding message {} value {}",
1204 // *v, message[*v]);
1205 sum ^= message[*v];
1206 }
1207
1208 // eprintln!("Solving var {} ({} remain unsolved)",
1209 // *var, d.count_unsolveds);
1210
1211 // mark var as solved
1212 if solvedp[*var] {
1213 panic!("Var {} was already marked as solved", *var);
1214 } else {
1215 solvedp[*var] = true;
1216 }
1217
1218 // compare result to original
1219 assert_eq!(sum, message[*var]);
1220 }
1221
1222 }
1223 }
1224}