1use crate::error::{Par2Error, Result};
10use crate::gf;
11use crate::gf_pmul;
12use crate::gf_simd;
13use rayon::prelude::*;
14
15const SIMD_ELIMINATION_ROWS: usize = 16;
16const PARALLEL_ELIMINATION_ROWS: usize = 128;
17const PARALLEL_ELIMINATION_THRESHOLD: usize = 256;
18
19const TILED_ELIMINATION_THRESHOLD: usize = PARALLEL_ELIMINATION_THRESHOLD;
26
27fn tiled_env_enabled() -> bool {
31 std::env::var_os("WEAVER_MATRIX_TILED").is_none_or(|v| v != "0")
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub(crate) struct DecodeMatrixError {
36 pub bad_row: Option<usize>,
37 pub reason: String,
38}
39
40impl DecodeMatrixError {
41 fn new(reason: String) -> Self {
42 Self {
43 bad_row: None,
44 reason,
45 }
46 }
47
48 fn singular(bad_row: usize) -> Self {
49 Self {
50 bad_row: Some(bad_row),
51 reason: "matrix is singular (no pivot found)".to_string(),
52 }
53 }
54
55 fn into_par2_error(self) -> Par2Error {
56 Par2Error::ReedSolomonError {
57 reason: self.reason,
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Matrix {
65 pub rows: usize,
66 pub cols: usize,
67 pub data: Vec<u16>,
68}
69
70impl Matrix {
71 pub fn zeros(rows: usize, cols: usize) -> Self {
73 Self {
74 rows,
75 cols,
76 data: vec![0u16; rows.saturating_mul(cols)],
77 }
78 }
79
80 pub fn identity(n: usize) -> Self {
82 let mut m = Self::zeros(n, n);
83 for i in 0..n {
84 m.set(i, i, 1);
85 }
86 m
87 }
88
89 pub fn vandermonde_row(constants: &[u16], exponent: u32) -> Vec<u16> {
94 constants.iter().map(|&c| gf::pow(c, exponent)).collect()
95 }
96
97 #[inline]
98 fn offset(&self, row: usize, col: usize) -> usize {
99 row * self.cols + col
100 }
101
102 #[inline]
103 pub(crate) fn get(&self, row: usize, col: usize) -> u16 {
104 self.data[self.offset(row, col)]
105 }
106
107 #[inline]
108 fn set(&mut self, row: usize, col: usize, value: u16) {
109 let offset = self.offset(row, col);
110 self.data[offset] = value;
111 }
112
113 #[inline]
114 pub(crate) fn row(&self, row: usize) -> &[u16] {
115 let start = row * self.cols;
116 &self.data[start..start + self.cols]
117 }
118
119 #[inline]
120 fn row_mut(&mut self, row: usize) -> &mut [u16] {
121 let start = row * self.cols;
122 &mut self.data[start..start + self.cols]
123 }
124
125 fn extract_columns(&self, start: usize, len: usize) -> Matrix {
126 let mut extracted = Matrix::zeros(self.rows, len);
127 for row_idx in 0..self.rows {
128 let src = &self.row(row_idx)[start..start + len];
129 extracted.row_mut(row_idx).copy_from_slice(src);
130 }
131 extracted
132 }
133
134 #[cfg(test)]
135 fn copy_row_from_slice(&mut self, row: usize, values: &[u16]) {
136 assert_eq!(
137 values.len(),
138 self.cols,
139 "row width must match matrix columns"
140 );
141 self.row_mut(row).copy_from_slice(values);
142 }
143
144 fn swap_rows(data: &mut [u16], cols: usize, a: usize, b: usize) {
145 if a == b {
146 return;
147 }
148
149 let a_start = a * cols;
150 let b_start = b * cols;
151 if a_start < b_start {
152 let (head, tail) = data.split_at_mut(b_start);
153 head[a_start..a_start + cols].swap_with_slice(&mut tail[..cols]);
154 } else {
155 let (head, tail) = data.split_at_mut(a_start);
156 tail[..cols].swap_with_slice(&mut head[b_start..b_start + cols]);
157 }
158 }
159
160 fn split_two_rows(
161 data: &mut [u16],
162 cols: usize,
163 a: usize,
164 b: usize,
165 ) -> (&mut [u16], &mut [u16]) {
166 assert_ne!(a, b, "row split requires distinct rows");
167
168 let a_start = a * cols;
169 let b_start = b * cols;
170 if a_start < b_start {
171 let (head, tail) = data.split_at_mut(b_start);
172 (&mut head[a_start..a_start + cols], &mut tail[..cols])
173 } else {
174 let (head, tail) = data.split_at_mut(a_start);
175 (&mut tail[..cols], &mut head[b_start..b_start + cols])
176 }
177 }
178
179 pub fn gaussian_eliminate(&mut self, rhs: &mut Matrix) -> Result<()> {
187 let mut row_origins = (0..self.rows).collect::<Vec<_>>();
188 self.gaussian_eliminate_tracked(rhs, &mut row_origins)
189 .map_err(DecodeMatrixError::into_par2_error)
190 }
191
192 fn gaussian_eliminate_tracked(
193 &mut self,
194 rhs: &mut Matrix,
195 row_origins: &mut [usize],
196 ) -> std::result::Result<(), DecodeMatrixError> {
197 let n = self.rows;
198 if self.cols != n {
199 return Err(DecodeMatrixError::new(format!(
200 "matrix is not square: {}x{}",
201 self.rows, self.cols
202 )));
203 }
204 if rhs.rows != n {
205 return Err(DecodeMatrixError::new(format!(
206 "RHS row count {} does not match matrix rows {}",
207 rhs.rows, n
208 )));
209 }
210 if row_origins.len() != n {
211 return Err(DecodeMatrixError::new(format!(
212 "row origin count {} does not match matrix rows {}",
213 row_origins.len(),
214 n
215 )));
216 }
217
218 for col in 0..n {
219 let pivot_row = (col..n).find(|&r| self.get(r, col) != 0);
221 let pivot_row = match pivot_row {
222 Some(r) => r,
223 None => {
224 return Err(DecodeMatrixError::singular(row_origins[col]));
225 }
226 };
227
228 if pivot_row != col {
230 Self::swap_rows(&mut self.data, self.cols, col, pivot_row);
231 Self::swap_rows(&mut rhs.data, rhs.cols, col, pivot_row);
232 row_origins.swap(col, pivot_row);
233 }
234
235 let pivot_val = self.get(col, col);
237 if pivot_val != 1 {
238 let pivot_inv = gf::inv(pivot_val);
239 for value in &mut self.row_mut(col)[col..] {
240 *value = gf::mul(*value, pivot_inv);
241 }
242 for value in rhs.row_mut(col).iter_mut() {
243 *value = gf::mul(*value, pivot_inv);
244 }
245 }
246
247 for row in 0..n {
249 if row == col {
250 continue;
251 }
252 let factor = self.get(row, col);
253 if factor == 0 {
254 continue;
255 }
256
257 let (target_row, pivot_row) =
258 Self::split_two_rows(&mut self.data, self.cols, row, col);
259 if factor == 1 {
260 for (target, pivot) in target_row[col..].iter_mut().zip(&pivot_row[col..]) {
261 *target ^= *pivot;
262 }
263 } else {
264 for (target, pivot) in target_row[col..].iter_mut().zip(&pivot_row[col..]) {
265 *target ^= gf::mul(factor, *pivot);
266 }
267 }
268
269 let (target_rhs, pivot_rhs) =
270 Self::split_two_rows(&mut rhs.data, rhs.cols, row, col);
271 if factor == 1 {
272 for (target, pivot) in target_rhs.iter_mut().zip(pivot_rhs.iter()) {
273 *target ^= *pivot;
274 }
275 } else {
276 for (target, pivot) in target_rhs.iter_mut().zip(pivot_rhs.iter()) {
277 *target ^= gf::mul(factor, *pivot);
278 }
279 }
280 }
281 }
282
283 Ok(())
284 }
285
286 pub fn invert(&self) -> Result<Matrix> {
288 let n = self.rows;
289 let mut m = self.clone();
290 let mut inv = Matrix::identity(n);
291 m.gaussian_eliminate(&mut inv)?;
292 Ok(inv)
293 }
294
295 fn gaussian_eliminate_vandermonde(
296 &mut self,
297 rhs: &mut Matrix,
298 ) -> std::result::Result<(), DecodeMatrixError> {
299 let use_tiled = self.rows >= TILED_ELIMINATION_THRESHOLD && tiled_env_enabled();
300 self.gaussian_eliminate_vandermonde_inner(rhs, use_tiled)
301 }
302
303 fn gaussian_eliminate_vandermonde_inner(
310 &mut self,
311 rhs: &mut Matrix,
312 use_tiled: bool,
313 ) -> std::result::Result<(), DecodeMatrixError> {
314 let n = self.rows;
315 if self.cols != n {
316 return Err(DecodeMatrixError::new(format!(
317 "matrix is not square: {}x{}",
318 self.rows, self.cols
319 )));
320 }
321 if rhs.rows != n {
322 return Err(DecodeMatrixError::new(format!(
323 "RHS row count {} does not match matrix rows {}",
324 rhs.rows, n
325 )));
326 }
327
328 if use_tiled {
329 return crate::matrix_tiled::invert_augmented_tiled(
330 &mut self.data,
331 &mut rhs.data,
332 n,
333 rhs.cols,
334 )
335 .map_err(DecodeMatrixError::singular);
336 }
337
338 for col in 0..n {
339 let pivot_val = self.get(col, col);
340 if pivot_val == 0 {
341 return Err(DecodeMatrixError::singular(col));
342 }
343
344 if pivot_val != 1 {
345 let pivot_inv = gf::inv(pivot_val);
346 for value in &mut self.row_mut(col)[col..] {
347 *value = gf::mul(*value, pivot_inv);
348 }
349 for value in rhs.row_mut(col).iter_mut() {
350 *value = gf::mul(*value, pivot_inv);
351 }
352 }
353
354 let pivot_matrix_tail = self.row(col)[col..].to_vec();
355 let pivot_rhs_row = rhs.row(col).to_vec();
356 let pivot_matrix_bytes = words_as_bytes(&pivot_matrix_tail);
357 let pivot_rhs_bytes = words_as_bytes(&pivot_rhs_row);
358 let matrix_ptr = self.data.as_mut_ptr() as usize;
359 let rhs_ptr = rhs.data.as_mut_ptr() as usize;
360 let matrix_cols = self.cols;
361 let rhs_cols = rhs.cols;
362 let row_group = if reedsolomon_rs::threading::parallel_enabled()
369 && n >= PARALLEL_ELIMINATION_THRESHOLD
370 && rayon::current_num_threads() > 1
371 {
372 PARALLEL_ELIMINATION_ROWS
373 } else {
374 SIMD_ELIMINATION_ROWS
375 };
376 let eliminate_batch = |batch_start: usize, batch_end: usize| unsafe {
377 let mut matrix_pairs = Vec::with_capacity(batch_end - batch_start);
378 let mut rhs_pairs = Vec::with_capacity(batch_end - batch_start);
379
380 for row in batch_start..batch_end {
381 if row == col {
382 continue;
383 }
384
385 let factor = *((matrix_ptr as *const u16).add(row * matrix_cols + col));
386 if factor == 0 {
387 continue;
388 }
389
390 let row_start = row * matrix_cols + col;
391 let row_len = matrix_cols - col;
392 let rhs_start = row * rhs_cols;
393
394 let matrix_row = std::slice::from_raw_parts_mut(
395 (matrix_ptr as *mut u16).add(row_start),
396 row_len,
397 );
398 matrix_pairs.push(gf_simd::FactorDst {
399 factor,
400 dst: words_as_bytes_mut(matrix_row),
401 });
402
403 let rhs_row = std::slice::from_raw_parts_mut(
404 (rhs_ptr as *mut u16).add(rhs_start),
405 rhs_cols,
406 );
407 rhs_pairs.push(gf_simd::FactorDst {
408 factor,
409 dst: words_as_bytes_mut(rhs_row),
410 });
411 }
412
413 if !matrix_pairs.is_empty() {
414 gf_simd::mul_acc_multi_region(&mut matrix_pairs, pivot_matrix_bytes);
415 gf_simd::mul_acc_multi_region(&mut rhs_pairs, pivot_rhs_bytes);
416 }
417 };
418
419 if row_group == SIMD_ELIMINATION_ROWS {
420 for batch_start in (0..n).step_by(row_group) {
421 eliminate_batch(batch_start, (batch_start + row_group).min(n));
422 }
423 } else {
424 let batch_starts: Vec<_> = (0..n).step_by(row_group).collect();
425 batch_starts.into_par_iter().for_each(|batch_start| {
426 eliminate_batch(batch_start, (batch_start + row_group).min(n));
427 });
428 }
429 }
430
431 Ok(())
432 }
433}
434
435fn fill_vandermonde_rows(
442 submatrix: &mut Matrix,
443 repair_matrix: &mut Matrix,
444 available_indices: &[usize],
445 missing_indices: &[usize],
446 recovery_exponents: &[u32],
447 constants: &[u16],
448) {
449 let avail_len = available_indices.len();
450 let sub_base: Vec<u16> = missing_indices.iter().map(|&idx| constants[idx]).collect();
451 let rep_base: Vec<u16> = available_indices
452 .iter()
453 .map(|&idx| constants[idx])
454 .collect();
455
456 for (i, &exp) in recovery_exponents.iter().enumerate() {
457 let sequential = i > 0 && recovery_exponents[i - 1].checked_add(1) == Some(exp);
459 if sequential {
460 let (cur, prev) = Matrix::split_two_rows(&mut submatrix.data, submatrix.cols, i, i - 1);
461 gf_pmul::pmul_region(
462 words_as_bytes_mut(cur),
463 words_as_bytes(prev),
464 words_as_bytes(&sub_base),
465 );
466
467 let (cur, prev) =
468 Matrix::split_two_rows(&mut repair_matrix.data, repair_matrix.cols, i, i - 1);
469 gf_pmul::pmul_region(
470 words_as_bytes_mut(&mut cur[..avail_len]),
471 words_as_bytes(&prev[..avail_len]),
472 words_as_bytes(&rep_base),
473 );
474 cur[avail_len + i] = 1;
475 } else {
476 let row = submatrix.row_mut(i);
477 for (slot, &idx) in row.iter_mut().zip(missing_indices.iter()) {
478 *slot = gf::pow(constants[idx], exp);
479 }
480
481 let row = repair_matrix.row_mut(i);
482 for (slot, &idx) in row.iter_mut().take(avail_len).zip(available_indices.iter()) {
483 *slot = gf::pow(constants[idx], exp);
484 }
485 row[avail_len + i] = 1;
486 }
487 }
488}
489
490#[inline]
491fn words_as_bytes(words: &[u16]) -> &[u8] {
492 unsafe { std::slice::from_raw_parts(words.as_ptr().cast::<u8>(), words.len() * 2) }
493}
494
495#[inline]
496fn words_as_bytes_mut(words: &mut [u16]) -> &mut [u8] {
497 unsafe { std::slice::from_raw_parts_mut(words.as_mut_ptr().cast::<u8>(), words.len() * 2) }
498}
499
500pub fn build_decode_matrix(
512 missing_indices: &[usize],
513 recovery_exponents: &[u32],
514 constants: &[u16],
515) -> Result<Matrix> {
516 build_decode_matrix_with_bad_row(missing_indices, recovery_exponents, constants)
517 .map_err(DecodeMatrixError::into_par2_error)
518}
519
520pub(crate) fn build_decode_matrix_with_bad_row(
521 missing_indices: &[usize],
522 recovery_exponents: &[u32],
523 constants: &[u16],
524) -> std::result::Result<Matrix, DecodeMatrixError> {
525 build_repair_matrix_with_bad_row(&[], missing_indices, recovery_exponents, constants)
526 .map(|(_, decode)| decode)
527}
528
529pub(crate) fn build_repair_matrix_with_bad_row(
530 available_indices: &[usize],
531 missing_indices: &[usize],
532 recovery_exponents: &[u32],
533 constants: &[u16],
534) -> std::result::Result<(Matrix, Matrix), DecodeMatrixError> {
535 build_repair_matrix_core(
537 available_indices,
538 missing_indices,
539 recovery_exponents,
540 constants,
541 None,
542 )
543}
544
545pub(crate) fn build_repair_matrix_with_bad_row_using(
549 available_indices: &[usize],
550 missing_indices: &[usize],
551 recovery_exponents: &[u32],
552 constants: &[u16],
553 use_tiled: bool,
554) -> std::result::Result<(Matrix, Matrix), DecodeMatrixError> {
555 build_repair_matrix_core(
556 available_indices,
557 missing_indices,
558 recovery_exponents,
559 constants,
560 Some(use_tiled),
561 )
562}
563
564#[doc(hidden)]
568pub fn build_repair_matrix_ab(
569 available_indices: &[usize],
570 missing_indices: &[usize],
571 recovery_exponents: &[u32],
572 constants: &[u16],
573 use_tiled: bool,
574) -> Result<(Matrix, Matrix)> {
575 build_repair_matrix_with_bad_row_using(
576 available_indices,
577 missing_indices,
578 recovery_exponents,
579 constants,
580 use_tiled,
581 )
582 .map_err(DecodeMatrixError::into_par2_error)
583}
584
585fn build_repair_matrix_core(
586 available_indices: &[usize],
587 missing_indices: &[usize],
588 recovery_exponents: &[u32],
589 constants: &[u16],
590 force_tiled: Option<bool>,
591) -> std::result::Result<(Matrix, Matrix), DecodeMatrixError> {
592 let n = missing_indices.len();
593 if recovery_exponents.len() != n {
594 return Err(DecodeMatrixError::new(format!(
595 "recovery exponent count ({}) does not match missing slice count ({n})",
596 recovery_exponents.len()
597 )));
598 }
599 if n == 0 {
600 return Ok((
601 Matrix::zeros(0, available_indices.len()),
602 Matrix::zeros(0, 0),
603 ));
604 }
605
606 let mut submatrix = Matrix::zeros(n, n);
607 let mut repair_matrix = Matrix::zeros(n, available_indices.len() + n);
608 fill_vandermonde_rows(
609 &mut submatrix,
610 &mut repair_matrix,
611 available_indices,
612 missing_indices,
613 recovery_exponents,
614 constants,
615 );
616 match force_tiled {
617 Some(use_tiled) => {
618 submatrix.gaussian_eliminate_vandermonde_inner(&mut repair_matrix, use_tiled)?
619 }
620 None => submatrix.gaussian_eliminate_vandermonde(&mut repair_matrix)?,
621 }
622 let decode = repair_matrix.extract_columns(available_indices.len(), n);
623 Ok((repair_matrix, decode))
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn identity_inversion() {
632 let id = Matrix::identity(4);
633 let inv = id.invert().unwrap();
634 assert_eq!(inv, Matrix::identity(4));
635 }
636
637 #[test]
638 fn vandermonde_row_basic() {
639 let constants = vec![2u16, 4, 16];
640 let row = Matrix::vandermonde_row(&constants, 0);
641 assert_eq!(row, vec![1, 1, 1]);
643
644 let row1 = Matrix::vandermonde_row(&constants, 1);
645 assert_eq!(row1, vec![2, 4, 16]);
647 }
648
649 #[test]
650 fn small_matrix_inversion() {
651 let constants = crate::gf::input_slice_constants(2);
653 let mut m = Matrix::zeros(2, 2);
654 for (i, exp) in [0u32, 1].iter().enumerate() {
655 let row = Matrix::vandermonde_row(&constants, *exp);
656 m.copy_row_from_slice(i, &row);
657 }
658
659 let inv = m.invert().unwrap();
660
661 let n = 2;
663 for i in 0..n {
664 for j in 0..n {
665 let mut sum = 0u16;
666 for k in 0..n {
667 sum = gf::add(sum, gf::mul(m.get(i, k), inv.get(k, j)));
668 }
669 let expected = if i == j { 1 } else { 0 };
670 assert_eq!(sum, expected, "M*M^-1 [{i}][{j}] should be {expected}");
671 }
672 }
673 }
674
675 #[test]
676 fn larger_matrix_inversion() {
677 let constants = crate::gf::input_slice_constants(5);
679 let exponents = [0u32, 1, 2, 4, 7]; let mut m = Matrix::zeros(5, 5);
681 for (i, &exp) in exponents.iter().enumerate() {
682 let row = Matrix::vandermonde_row(&constants, exp);
683 m.copy_row_from_slice(i, &row);
684 }
685
686 let orig = m.clone();
687 let inv = m.invert().unwrap();
688
689 let n = 5;
691 for i in 0..n {
692 for j in 0..n {
693 let mut sum = 0u16;
694 for k in 0..n {
695 sum = gf::add(sum, gf::mul(orig.get(i, k), inv.get(k, j)));
696 }
697 let expected = if i == j { 1 } else { 0 };
698 assert_eq!(
699 sum, expected,
700 "M*M^-1 [{i}][{j}] should be {expected}, got {sum}"
701 );
702 }
703 }
704 }
705
706 #[test]
707 fn singular_matrix_fails() {
708 let mut m = Matrix::zeros(2, 2);
710 m.copy_row_from_slice(0, &[1, 2]);
711 m.copy_row_from_slice(1, &[1, 2]);
712 let err = m.invert().unwrap_err();
713 assert!(matches!(err, Par2Error::ReedSolomonError { .. }));
714 }
715
716 #[test]
717 fn singular_decode_matrix_reports_bad_recovery_row() {
718 let constants = crate::gf::input_slice_constants(2);
719 let missing = vec![0usize, 1];
720 let err = build_decode_matrix_with_bad_row(&missing, &[0, 0], &constants).unwrap_err();
721 assert_eq!(err.bad_row, Some(1));
722 }
723
724 #[test]
725 fn build_decode_matrix_basic() {
726 let constants = crate::gf::input_slice_constants(4);
727 let missing = vec![1usize, 3];
728 let exponents = vec![0u32, 1];
729
730 let decode = build_decode_matrix(&missing, &exponents, &constants).unwrap();
731 assert_eq!(decode.rows, 2);
732 assert_eq!(decode.cols, 2);
733
734 let mut sub = Matrix::zeros(2, 2);
736 for (i, &exp) in exponents.iter().enumerate() {
737 for (j, &idx) in missing.iter().enumerate() {
738 sub.set(i, j, gf::pow(constants[idx], exp));
739 }
740 }
741
742 for i in 0..2 {
743 for j in 0..2 {
744 let mut sum = 0u16;
745 for k in 0..2 {
746 sum = gf::add(sum, gf::mul(sub.get(i, k), decode.get(k, j)));
747 }
748 let expected = if i == j { 1 } else { 0 };
749 assert_eq!(sum, expected);
750 }
751 }
752 }
753
754 #[test]
755 fn build_decode_matrix_mismatched_counts() {
756 let constants = crate::gf::input_slice_constants(4);
757 let err = build_decode_matrix(&[0, 1], &[0u32], &constants).unwrap_err();
758 assert!(matches!(err, Par2Error::ReedSolomonError { .. }));
759 }
760
761 #[test]
762 fn build_decode_matrix_empty() {
763 let constants = crate::gf::input_slice_constants(4);
764 let decode = build_decode_matrix(&[], &[], &constants).unwrap();
765 assert_eq!(decode.rows, 0);
766 assert_eq!(decode.cols, 0);
767 }
768
769 #[test]
773 fn tiled_equals_serial() {
774 for n in [100usize, 300, 512, 1000] {
775 let total = 2 * n;
776 let constants = crate::gf::input_slice_constants(total);
777 let missing: Vec<usize> = (0..n).collect();
778 let available: Vec<usize> = (n..total).collect();
779 let exponents: Vec<u32> = (0..n as u32).collect();
780
781 let (rank1_repair, rank1_decode) = build_repair_matrix_with_bad_row_using(
782 &available, &missing, &exponents, &constants, false,
783 )
784 .expect("rank-1 solve");
785 let (tiled_repair, tiled_decode) = build_repair_matrix_with_bad_row_using(
786 &available, &missing, &exponents, &constants, true,
787 )
788 .expect("tiled solve");
789
790 assert_eq!(
791 rank1_repair.data, tiled_repair.data,
792 "n={n}: repair matrix must be byte-identical"
793 );
794 assert_eq!(
795 rank1_decode.data, tiled_decode.data,
796 "n={n}: decode matrix must be byte-identical"
797 );
798 }
799 }
800
801 fn fill_vandermonde_rows_pow_only(
804 submatrix: &mut Matrix,
805 repair_matrix: &mut Matrix,
806 available_indices: &[usize],
807 missing_indices: &[usize],
808 recovery_exponents: &[u32],
809 constants: &[u16],
810 ) {
811 for (i, &exp) in recovery_exponents.iter().enumerate() {
812 let row = submatrix.row_mut(i);
813 for (slot, &idx) in row.iter_mut().zip(missing_indices.iter()) {
814 *slot = gf::pow(constants[idx], exp);
815 }
816 let row = repair_matrix.row_mut(i);
817 for (slot, &idx) in row
818 .iter_mut()
819 .take(available_indices.len())
820 .zip(available_indices.iter())
821 {
822 *slot = gf::pow(constants[idx], exp);
823 }
824 row[available_indices.len() + i] = 1;
825 }
826 }
827
828 #[test]
832 fn fast_fill_matches_pow_fill() {
833 let exponent_patterns: Vec<Vec<u32>> = vec![
834 (0..24).collect(), (1..25).collect(), vec![0, 1, 2, 5, 6, 7, 100, 101, 102, 4, 9, 10], vec![7, 3, 9, 2, 60000, 60001], vec![65534, 65535, 65536, 65537], vec![4, 4, 5, 6], vec![0], ];
842 for exponents in &exponent_patterns {
843 let n = exponents.len();
844 let total = n + 48;
845 let constants = crate::gf::input_slice_constants(total);
846 let missing: Vec<usize> = (0..n).collect();
847 let available: Vec<usize> = (n..total).collect();
848
849 let mut sub_fast = Matrix::zeros(n, n);
850 let mut rep_fast = Matrix::zeros(n, available.len() + n);
851 fill_vandermonde_rows(
852 &mut sub_fast,
853 &mut rep_fast,
854 &available,
855 &missing,
856 exponents,
857 &constants,
858 );
859
860 let mut sub_ref = Matrix::zeros(n, n);
861 let mut rep_ref = Matrix::zeros(n, available.len() + n);
862 fill_vandermonde_rows_pow_only(
863 &mut sub_ref,
864 &mut rep_ref,
865 &available,
866 &missing,
867 exponents,
868 &constants,
869 );
870
871 assert_eq!(sub_fast.data, sub_ref.data, "submatrix {exponents:?}");
872 assert_eq!(rep_fast.data, rep_ref.data, "repair {exponents:?}");
873 }
874 }
875
876 #[test]
879 fn tiled_singular_reports_same_bad_row() {
880 let n = 300usize;
881 let bad = 150usize;
882 let total = 2 * n;
883 let constants = crate::gf::input_slice_constants(total);
884 let missing: Vec<usize> = (0..n).collect();
885 let available: Vec<usize> = (n..total).collect();
886 let mut exponents: Vec<u32> = (0..n as u32).collect();
887 exponents[bad] = exponents[bad - 1]; let rank1_err = build_repair_matrix_with_bad_row_using(
890 &available, &missing, &exponents, &constants, false,
891 )
892 .unwrap_err();
893 let tiled_err = build_repair_matrix_with_bad_row_using(
894 &available, &missing, &exponents, &constants, true,
895 )
896 .unwrap_err();
897
898 assert_eq!(rank1_err.bad_row, Some(bad));
899 assert_eq!(rank1_err.bad_row, tiled_err.bad_row);
900 }
901}