1use alloc::vec;
19use alloc::vec::Vec;
20use core::iter::repeat_n;
21use hekate_core::errors::Error;
22use hekate_core::poly::PolyVariant;
23use hekate_core::trace::{ColumnType, Trace, TraceColumn, TraceCompatibleField};
24use hekate_math::{Bit, Block8, Block16, Block32, Block64, Flat, HardwareField};
25
26#[derive(Clone, Copy, Debug)]
28pub enum ExpansionEntry {
29 ExpandBits {
30 count: usize,
31 storage: ColumnType,
32 },
33 PassThrough {
34 count: usize,
35 storage: ColumnType,
36 },
37 ControlBits {
38 count: usize,
39 },
40 ReusePassThrough {
41 phy_col_start: usize,
42 count: usize,
43 storage: ColumnType,
44 },
45 ReuseExpandBits {
46 phy_col_start: usize,
47 count: usize,
48 storage: ColumnType,
49 },
50}
51
52#[derive(Clone, Copy, Debug)]
54enum EntryKind {
55 ExpandBits { count: usize, storage: ColumnType },
58
59 PassThrough { count: usize, storage: ColumnType },
62
63 ControlBits { count: usize },
66}
67
68impl EntryKind {
69 fn count(&self) -> usize {
70 match self {
71 Self::ExpandBits { count, .. }
72 | Self::PassThrough { count, .. }
73 | Self::ControlBits { count } => *count,
74 }
75 }
76
77 fn storage(&self) -> ColumnType {
78 match self {
79 Self::ExpandBits { storage, .. } | Self::PassThrough { storage, .. } => *storage,
80 Self::ControlBits { .. } => ColumnType::Bit,
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug)]
88struct CompiledEntry {
89 phy_col_start: usize,
92
93 byte_offset: usize,
95 kind: EntryKind,
96
97 reuse: bool,
100}
101
102#[derive(Clone, Debug)]
110pub struct VirtualExpander {
111 entries: Vec<CompiledEntry>,
112 num_virtual: usize,
113 num_physical: usize,
114 physical_row_bytes: usize,
115 virtual_layout: Vec<ColumnType>,
116 error: Option<Error>,
117}
118
119impl VirtualExpander {
120 pub fn new() -> Self {
121 Self {
122 entries: Vec::new(),
123 num_virtual: 0,
124 num_physical: 0,
125 physical_row_bytes: 0,
126 virtual_layout: Vec::new(),
127 error: None,
128 }
129 }
130
131 pub fn build(self) -> Result<Self, Error> {
134 match self.error {
135 Some(e) => Err(e),
136 None => Ok(self),
137 }
138 }
139
140 pub fn expand_bits(mut self, count: usize, storage: ColumnType) -> Self {
143 if self.error.is_some() {
144 return self;
145 }
146
147 let bits_per = match expand_bit_width(storage) {
148 Ok(v) => v,
149 Err(e) => {
150 self.error = Some(e);
151 return self;
152 }
153 };
154
155 let byte_offset = self.physical_row_bytes;
156 let phy_col_start = self.num_physical;
157
158 self.entries.push(CompiledEntry {
159 phy_col_start,
160 byte_offset,
161 kind: EntryKind::ExpandBits { count, storage },
162 reuse: false,
163 });
164
165 let virt_count = count * bits_per;
166 self.virtual_layout
167 .extend(repeat_n(ColumnType::Bit, virt_count));
168
169 self.num_virtual += virt_count;
170 self.num_physical += count;
171 self.physical_row_bytes += count * storage.byte_size();
172
173 self
174 }
175
176 pub fn pass_through(mut self, count: usize, storage: ColumnType) -> Self {
179 let byte_offset = self.physical_row_bytes;
180 let phy_col_start = self.num_physical;
181
182 self.entries.push(CompiledEntry {
183 phy_col_start,
184 byte_offset,
185 kind: EntryKind::PassThrough { count, storage },
186 reuse: false,
187 });
188
189 self.virtual_layout.extend(repeat_n(storage, count));
190
191 self.num_virtual += count;
192 self.num_physical += count;
193 self.physical_row_bytes += count * storage.byte_size();
194
195 self
196 }
197
198 pub fn control_bits(mut self, count: usize) -> Self {
200 let byte_offset = self.physical_row_bytes;
201 let phy_col_start = self.num_physical;
202
203 self.entries.push(CompiledEntry {
204 phy_col_start,
205 byte_offset,
206 kind: EntryKind::ControlBits { count },
207 reuse: false,
208 });
209
210 self.virtual_layout.extend(repeat_n(ColumnType::Bit, count));
211
212 self.num_virtual += count;
213 self.num_physical += count;
214 self.physical_row_bytes += count;
215
216 self
217 }
218
219 pub fn reuse_pass_through(mut self, phy_col_start: usize, count: usize) -> Self {
223 if self.error.is_some() {
224 return self;
225 }
226
227 if phy_col_start + count > self.num_physical {
228 self.error = Some(Error::Protocol {
229 protocol: "virtual_expand",
230 message: "reuse_pass_through: range exceeds declared physical columns",
231 });
232 return self;
233 }
234
235 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
236 Ok(v) => v,
237 Err(e) => {
238 self.error = Some(e);
239 return self;
240 }
241 };
242
243 self.entries.push(CompiledEntry {
244 phy_col_start,
245 byte_offset,
246 kind: EntryKind::PassThrough { count, storage },
247 reuse: true,
248 });
249
250 self.virtual_layout.extend(repeat_n(storage, count));
251
252 self.num_virtual += count;
253
254 self
255 }
256
257 pub fn reuse_expand_bits(mut self, phy_col_start: usize, count: usize) -> Self {
261 if self.error.is_some() {
262 return self;
263 }
264
265 if phy_col_start + count > self.num_physical {
266 self.error = Some(Error::Protocol {
267 protocol: "virtual_expand",
268 message: "reuse_expand_bits: range exceeds declared physical columns",
269 });
270 return self;
271 }
272
273 let (byte_offset, storage) = match self.find_phy_source(phy_col_start, count) {
274 Ok(v) => v,
275 Err(e) => {
276 self.error = Some(e);
277 return self;
278 }
279 };
280
281 let bits_per = match expand_bit_width(storage) {
282 Ok(v) => v,
283 Err(e) => {
284 self.error = Some(e);
285 return self;
286 }
287 };
288
289 self.entries.push(CompiledEntry {
290 phy_col_start,
291 byte_offset,
292 kind: EntryKind::ExpandBits { count, storage },
293 reuse: true,
294 });
295
296 let virt_count = count * bits_per;
297 self.virtual_layout
298 .extend(repeat_n(ColumnType::Bit, virt_count));
299
300 self.num_virtual += virt_count;
301
302 self
303 }
304
305 #[inline]
306 pub fn num_virtual_columns(&self) -> usize {
307 self.num_virtual
308 }
309
310 #[inline]
311 pub fn num_physical_columns(&self) -> usize {
312 self.num_physical
313 }
314
315 #[inline]
316 pub fn physical_row_bytes(&self) -> usize {
317 self.physical_row_bytes
318 }
319
320 #[inline]
321 pub fn virtual_layout(&self) -> &[ColumnType] {
322 &self.virtual_layout
323 }
324
325 pub fn parse_row<F: TraceCompatibleField>(
329 &self,
330 bytes: &[u8],
331 res: &mut Vec<Flat<F>>,
332 ) -> Result<(), Error> {
333 if bytes.len() != self.physical_row_bytes {
334 return Err(Error::Protocol {
335 protocol: "virtual_expand",
336 message: "parse_row: byte slice length mismatch",
337 });
338 }
339
340 res.reserve(self.num_virtual);
341
342 for entry in &self.entries {
343 let off = entry.byte_offset;
344 match entry.kind {
345 EntryKind::ExpandBits { count, storage } => {
346 let bsz = storage.byte_size();
347 let bits = expand_bit_width(storage)?;
348
349 for i in 0..count {
350 let start = off + i * bsz;
351 for bit_idx in 0..bits {
352 let bit = parse_tower_bit(storage, &bytes[start..start + bsz], bit_idx);
353 res.push(Flat::from_raw(F::from(Bit::from(bit))));
354 }
355 }
356 }
357 EntryKind::PassThrough { count, storage } => {
358 let bsz = storage.byte_size();
359 for i in 0..count {
360 let start = off + i * bsz;
361 res.push(storage.parse_from_bytes(&bytes[start..start + bsz]));
362 }
363 }
364 EntryKind::ControlBits { count } => {
365 for i in 0..count {
366 res.push(Flat::from_raw(F::from(Bit::from(bytes[off + i] & 1))));
367 }
368 }
369 }
370 }
371
372 Ok(())
373 }
374
375 pub fn expand_variants<'a, F, T: Trace + ?Sized>(
379 &self,
380 trace: &'a T,
381 phy_start_idx: usize,
382 ) -> Result<Vec<PolyVariant<'a, F>>, Error>
383 where
384 F: TraceCompatibleField + 'static,
385 {
386 let columns = trace.columns();
387
388 let mut variants = Vec::with_capacity(self.num_virtual);
389 for entry in &self.entries {
390 let base = phy_start_idx + entry.phy_col_start;
391 match entry.kind {
392 EntryKind::ExpandBits { count, storage } => {
393 let bits = expand_bit_width(storage)?;
394 for i in 0..count {
395 let col = columns.get(base + i).ok_or(Error::Protocol {
396 protocol: "virtual_expand",
397 message: "missing physical column for ExpandBits",
398 })?;
399
400 for bit_idx in 0..bits {
401 variants.push(expand_packed_bit(col, storage, bit_idx)?);
402 }
403 }
404 }
405 EntryKind::PassThrough { count, storage } => {
406 for i in 0..count {
407 let col = columns.get(base + i).ok_or(Error::Protocol {
408 protocol: "virtual_expand",
409 message: "missing physical column for PassThrough",
410 })?;
411
412 variants.push(expand_pass_through(col, storage)?);
413 }
414 }
415 EntryKind::ControlBits { count } => {
416 for i in 0..count {
417 let col = columns.get(base + i).ok_or(Error::Protocol {
418 protocol: "virtual_expand",
419 message: "missing physical column for ControlBits",
420 })?;
421 let data = col.as_bit_slice().ok_or(Error::Protocol {
422 protocol: "virtual_expand",
423 message: "control column must be Bit",
424 })?;
425
426 variants.push(PolyVariant::BitSlice(data));
427 }
428 }
429 }
430 }
431
432 Ok(variants)
433 }
434
435 pub fn expansion_entries(&self) -> Vec<ExpansionEntry> {
437 self.entries
438 .iter()
439 .map(|e| match (e.kind, e.reuse) {
440 (EntryKind::PassThrough { count, storage }, true) => {
441 ExpansionEntry::ReusePassThrough {
442 phy_col_start: e.phy_col_start,
443 count,
444 storage,
445 }
446 }
447 (EntryKind::ExpandBits { count, storage }, true) => {
448 ExpansionEntry::ReuseExpandBits {
449 phy_col_start: e.phy_col_start,
450 count,
451 storage,
452 }
453 }
454 (EntryKind::ExpandBits { count, storage }, false) => {
455 ExpansionEntry::ExpandBits { count, storage }
456 }
457 (EntryKind::PassThrough { count, storage }, false) => {
458 ExpansionEntry::PassThrough { count, storage }
459 }
460 (EntryKind::ControlBits { count }, _) => ExpansionEntry::ControlBits { count },
461 })
462 .collect()
463 }
464
465 fn find_phy_source(
468 &self,
469 target_start: usize,
470 target_count: usize,
471 ) -> Result<(usize, ColumnType), Error> {
472 let mut running_phy = 0usize;
473 for entry in &self.entries {
474 if entry.phy_col_start != running_phy {
475 continue;
476 }
477
478 let entry_count = entry.kind.count();
479 let entry_end = running_phy + entry_count;
480
481 if target_start >= running_phy && target_start + target_count <= entry_end {
482 let storage = entry.kind.storage();
483 let offset_in_entry = target_start - running_phy;
484
485 return Ok((
486 entry.byte_offset + offset_in_entry * storage.byte_size(),
487 storage,
488 ));
489 }
490
491 running_phy = entry_end;
492 }
493
494 Err(Error::Protocol {
495 protocol: "virtual_expand",
496 message: "reuse: source columns not found in any single fresh entry",
497 })
498 }
499}
500
501impl Default for VirtualExpander {
502 fn default() -> Self {
503 Self::new()
504 }
505}
506
507pub struct RingSwitchPlan {
512 pub num_units: usize,
513 pub units: Vec<(bool, usize)>,
514 pub phys_rs: Vec<ColumnType>,
515 phys_bit: Vec<Vec<usize>>,
516 phys_whole: Vec<Vec<usize>>,
517}
518
519impl RingSwitchPlan {
520 pub fn new(
521 layout: &[ColumnType],
522 entries: Option<&[ExpansionEntry]>,
523 num_blind: usize,
524 ) -> Result<Self, Error> {
525 let num_phys = layout.len();
526 let total = num_phys + num_blind;
527
528 let mut phys_bit = vec![Vec::new(); total];
529 let mut phys_whole = vec![Vec::new(); total];
530 let mut units: Vec<(bool, usize)> = Vec::new();
531
532 let mut phys_rs: Vec<ColumnType> = layout.iter().map(|ct| ct.rs_field()).collect();
533 phys_rs.extend((0..num_blind).map(|_| ColumnType::B128));
534
535 let bounds = |upper: usize| -> Result<(), Error> {
536 if upper > num_phys {
537 return Err(Error::Protocol {
538 protocol: "ring_switch_plan",
539 message: "expansion entry exceeds the physical column layout",
540 });
541 }
542
543 Ok(())
544 };
545
546 match entries {
547 Some(entries) => {
548 let mut running = 0usize;
549 for e in entries {
550 match *e {
551 ExpansionEntry::ExpandBits { count, storage } => {
552 let bits = expand_bit_width(storage)?;
553
554 bounds(running + count)?;
555
556 for j in 0..count {
557 phys_bit[running + j].push(units.len());
558 units.push((true, bits));
559 }
560
561 running += count;
562 }
563 ExpansionEntry::PassThrough { count, .. }
564 | ExpansionEntry::ControlBits { count } => {
565 bounds(running + count)?;
566
567 for j in 0..count {
568 phys_whole[running + j].push(units.len());
569 units.push((false, 1));
570 }
571
572 running += count;
573 }
574 ExpansionEntry::ReusePassThrough {
575 phy_col_start,
576 count,
577 ..
578 } => {
579 bounds(phy_col_start + count)?;
580 for j in 0..count {
581 phys_whole[phy_col_start + j].push(units.len());
582 units.push((false, 1));
583 }
584 }
585 ExpansionEntry::ReuseExpandBits {
586 phy_col_start,
587 count,
588 storage,
589 } => {
590 let bits = expand_bit_width(storage)?;
591 bounds(phy_col_start + count)?;
592 for j in 0..count {
593 phys_bit[phy_col_start + j].push(units.len());
594 units.push((true, bits));
595 }
596 }
597 }
598 }
599 }
600 None => {
601 for pw in phys_whole.iter_mut().take(num_phys) {
602 pw.push(units.len());
603 units.push((false, 1));
604 }
605 }
606 }
607
608 for b in 0..num_blind {
609 phys_whole[num_phys + b].push(units.len());
610 units.push((false, 1));
611 }
612
613 let num_units = units.len();
614
615 Ok(Self {
616 units,
617 phys_bit,
618 phys_whole,
619 phys_rs,
620 num_units,
621 })
622 }
623
624 pub fn has_ring(&self) -> bool {
625 self.units.iter().any(|(is_ring, _)| *is_ring)
626 }
627
628 pub fn total_claims(&self) -> usize {
629 self.units.iter().map(|(_, n)| n).sum()
630 }
631
632 pub fn column_coeffs<F>(&self, eta: Flat<F>) -> (Vec<Flat<F>>, Vec<Flat<F>>, Flat<F>)
636 where
637 F: HardwareField,
638 {
639 let mut eta_pows = Vec::with_capacity(self.num_units + 1);
640 let mut e = Flat::from_raw(F::ONE);
641
642 for _ in 0..=self.num_units {
643 eta_pows.push(e);
644 e *= eta;
645 }
646
647 let total = self.phys_rs.len();
648
649 let mut coeff_bit = vec![Flat::from_raw(F::ZERO); total];
650 let mut coeff_whole = vec![Flat::from_raw(F::ZERO); total];
651
652 for p in 0..total {
653 for &u in &self.phys_bit[p] {
654 coeff_bit[p] += eta_pows[u];
655 }
656
657 for &u in &self.phys_whole[p] {
658 coeff_whole[p] += eta_pows[u];
659 }
660 }
661
662 (coeff_bit, coeff_whole, eta_pows[self.num_units])
663 }
664}
665
666fn expand_bit_width(storage: ColumnType) -> Result<usize, Error> {
667 match storage {
668 ColumnType::B8 => Ok(8),
669 ColumnType::B16 => Ok(16),
670 ColumnType::B32 => Ok(32),
671 ColumnType::B64 => Ok(64),
672 _ => Err(Error::Protocol {
673 protocol: "virtual_expand",
674 message: "ExpandBits requires B8/B16/B32/B64",
675 }),
676 }
677}
678
679fn parse_tower_bit(storage: ColumnType, bytes: &[u8], bit_idx: usize) -> u8 {
681 match storage {
682 ColumnType::B8 => Flat::from_raw(Block8(bytes[0])).tower_bit(bit_idx),
683 ColumnType::B16 => {
684 let mut arr = [0u8; 2];
685 arr.copy_from_slice(bytes);
686
687 Flat::from_raw(Block16(u16::from_le_bytes(arr))).tower_bit(bit_idx)
688 }
689 ColumnType::B32 => {
690 let mut arr = [0u8; 4];
691 arr.copy_from_slice(bytes);
692
693 Flat::from_raw(Block32(u32::from_le_bytes(arr))).tower_bit(bit_idx)
694 }
695 ColumnType::B64 => {
696 let mut arr = [0u8; 8];
697 arr.copy_from_slice(bytes);
698
699 Flat::from_raw(Block64(u64::from_le_bytes(arr))).tower_bit(bit_idx)
700 }
701 _ => unreachable!(),
702 }
703}
704
705fn expand_packed_bit<F: TraceCompatibleField + 'static>(
706 col: &'_ TraceColumn,
707 storage: ColumnType,
708 bit_idx: usize,
709) -> Result<PolyVariant<'_, F>, Error> {
710 match storage {
711 ColumnType::B8 => {
712 let data = col.as_b8_slice().ok_or(Error::Protocol {
713 protocol: "virtual_expand",
714 message: "ExpandBits B8: column type mismatch",
715 })?;
716
717 Ok(PolyVariant::PackedBitB8 { data, bit_idx })
718 }
719 ColumnType::B16 => {
720 let data = col.as_b16_slice().ok_or(Error::Protocol {
721 protocol: "virtual_expand",
722 message: "ExpandBits B16: column type mismatch",
723 })?;
724
725 Ok(PolyVariant::PackedBitB16 { data, bit_idx })
726 }
727 ColumnType::B32 => {
728 let data = col.as_b32_slice().ok_or(Error::Protocol {
729 protocol: "virtual_expand",
730 message: "ExpandBits B32: column type mismatch",
731 })?;
732
733 Ok(PolyVariant::PackedBitB32 { data, bit_idx })
734 }
735 ColumnType::B64 => {
736 let data = col.as_b64_slice().ok_or(Error::Protocol {
737 protocol: "virtual_expand",
738 message: "ExpandBits B64: column type mismatch",
739 })?;
740
741 Ok(PolyVariant::PackedBitB64 { data, bit_idx })
742 }
743 _ => unreachable!(),
744 }
745}
746
747fn expand_pass_through<F: TraceCompatibleField + 'static>(
748 col: &TraceColumn,
749 storage: ColumnType,
750) -> Result<PolyVariant<'_, F>, Error> {
751 match storage {
752 ColumnType::Bit => {
753 let data = col.as_bit_slice().ok_or(Error::Protocol {
754 protocol: "virtual_expand",
755 message: "PassThrough Bit: column type mismatch",
756 })?;
757
758 Ok(PolyVariant::BitSlice(data))
759 }
760 ColumnType::B8 => {
761 let data = col.as_b8_slice().ok_or(Error::Protocol {
762 protocol: "virtual_expand",
763 message: "PassThrough B8: column type mismatch",
764 })?;
765
766 Ok(PolyVariant::B8Slice(data))
767 }
768 ColumnType::B16 => {
769 let data = col.as_b16_slice().ok_or(Error::Protocol {
770 protocol: "virtual_expand",
771 message: "PassThrough B16: column type mismatch",
772 })?;
773
774 Ok(PolyVariant::B16Slice(data))
775 }
776 ColumnType::B32 => {
777 let data = col.as_b32_slice().ok_or(Error::Protocol {
778 protocol: "virtual_expand",
779 message: "PassThrough B32: column type mismatch",
780 })?;
781
782 Ok(PolyVariant::B32Slice(data))
783 }
784 ColumnType::B64 => {
785 let data = col.as_b64_slice().ok_or(Error::Protocol {
786 protocol: "virtual_expand",
787 message: "PassThrough B64: column type mismatch",
788 })?;
789
790 Ok(PolyVariant::B64Slice(data))
791 }
792 ColumnType::B128 => {
793 let data = col.as_b128_slice().ok_or(Error::Protocol {
794 protocol: "virtual_expand",
795 message: "PassThrough B128: column type mismatch",
796 })?;
797
798 Ok(PolyVariant::B128Slice(data))
799 }
800 }
801}
802
803#[cfg(test)]
804mod tests {
805 use super::*;
806 use hekate_core::trace::TraceBuilder;
807 use hekate_math::{Block128, TowerField};
808
809 #[test]
810 fn ram_layout() {
811 let e = VirtualExpander::new()
812 .expand_bits(2, ColumnType::B32)
813 .pass_through(13, ColumnType::B32)
814 .pass_through(1, ColumnType::B128)
815 .control_bits(4)
816 .build()
817 .unwrap();
818
819 assert_eq!(e.num_virtual_columns(), 82);
820 assert_eq!(e.num_physical_columns(), 20);
821 assert_eq!(e.physical_row_bytes(), 80);
822
823 let layout = e.virtual_layout();
824 assert_eq!(layout.len(), 82);
825 assert!(layout[..64].iter().all(|&t| t == ColumnType::Bit));
826 assert!(layout[64..77].iter().all(|&t| t == ColumnType::B32));
827 assert_eq!(layout[77], ColumnType::B128);
828 assert!(layout[78..82].iter().all(|&t| t == ColumnType::Bit));
829 }
830
831 #[test]
832 fn keccak_layout() {
833 let e = VirtualExpander::new()
834 .expand_bits(25, ColumnType::B64)
835 .expand_bits(1, ColumnType::B64)
836 .reuse_pass_through(0, 25)
837 .control_bits(2)
838 .build()
839 .unwrap();
840
841 assert_eq!(e.num_virtual_columns(), 1691);
842 assert_eq!(e.num_physical_columns(), 28);
843 assert_eq!(e.physical_row_bytes(), 210);
844
845 let layout = e.virtual_layout();
846 assert_eq!(layout.len(), 1691);
847 assert!(layout[..1600].iter().all(|&t| t == ColumnType::Bit));
848 assert!(layout[1600..1664].iter().all(|&t| t == ColumnType::Bit));
849 assert!(layout[1664..1689].iter().all(|&t| t == ColumnType::B64));
850 assert!(layout[1689..1691].iter().all(|&t| t == ColumnType::Bit));
851 }
852
853 #[test]
854 fn reuse_partial_range() {
855 let e = VirtualExpander::new()
856 .expand_bits(10, ColumnType::B32)
857 .reuse_pass_through(3, 4)
858 .build()
859 .unwrap();
860
861 assert_eq!(e.num_virtual_columns(), 324);
862 assert_eq!(e.num_physical_columns(), 10);
863 assert_eq!(e.physical_row_bytes(), 40);
864
865 let layout = e.virtual_layout();
866 assert_eq!(layout[320..324].len(), 4);
867 assert!(layout[320..324].iter().all(|&t| t == ColumnType::B32));
868 }
869
870 #[test]
871 fn reuse_exceeds_declared() {
872 let result = VirtualExpander::new()
873 .expand_bits(5, ColumnType::B32)
874 .reuse_pass_through(3, 5)
875 .build();
876 assert!(result.is_err());
877 }
878
879 #[test]
880 fn reuse_expand_bits_from_pass_through() {
881 let e = VirtualExpander::new()
882 .pass_through(4, ColumnType::B64)
883 .reuse_expand_bits(0, 4)
884 .build()
885 .unwrap();
886
887 assert_eq!(e.num_physical_columns(), 4);
888 assert_eq!(e.physical_row_bytes(), 32);
889 assert_eq!(e.num_virtual_columns(), 4 + 256);
890
891 let layout = e.virtual_layout();
892 assert!(layout[0..4].iter().all(|&t| t == ColumnType::B64));
893 assert!(layout[4..260].iter().all(|&t| t == ColumnType::Bit));
894 }
895
896 #[test]
897 fn reuse_expand_bits_exceeds_declared() {
898 let result = VirtualExpander::new()
899 .pass_through(4, ColumnType::B64)
900 .reuse_expand_bits(2, 4)
901 .build();
902 assert!(result.is_err());
903 }
904
905 #[test]
906 fn reuse_expand_bits_rejects_b128_source() {
907 let result = VirtualExpander::new()
908 .pass_through(1, ColumnType::B128)
909 .reuse_expand_bits(0, 1)
910 .build();
911 assert!(result.is_err());
912 }
913
914 #[test]
915 fn expand_rejects_bit() {
916 let result = VirtualExpander::new()
917 .expand_bits(1, ColumnType::Bit)
918 .build();
919 assert!(result.is_err());
920 }
921
922 #[test]
923 fn expand_rejects_b128() {
924 let result = VirtualExpander::new()
925 .expand_bits(1, ColumnType::B128)
926 .build();
927 assert!(result.is_err());
928 }
929
930 #[test]
931 fn empty_expander() {
932 let e = VirtualExpander::new();
933 assert_eq!(e.num_virtual_columns(), 0);
934 assert_eq!(e.num_physical_columns(), 0);
935 assert_eq!(e.physical_row_bytes(), 0);
936 assert!(e.virtual_layout().is_empty());
937 }
938
939 #[test]
940 fn parse_row_b32_roundtrip() {
941 let expander = VirtualExpander::new()
942 .expand_bits(1, ColumnType::B32)
943 .pass_through(1, ColumnType::B32)
944 .control_bits(1)
945 .build()
946 .unwrap();
947
948 let val: u32 = 0xDEAD_BEEF;
949 let pass_val: u32 = 0x1234_5678;
950
951 let mut bytes = Vec::new();
952 bytes.extend_from_slice(&val.to_le_bytes());
953 bytes.extend_from_slice(&pass_val.to_le_bytes());
954 bytes.push(1);
955
956 let mut res: Vec<Flat<Block128>> = Vec::new();
957 expander.parse_row(&bytes, &mut res).unwrap();
958
959 assert_eq!(res.len(), 34);
960
961 for (bit_idx, elem) in res.iter().enumerate().take(32) {
962 let expected = Flat::from_raw(Block32(val)).tower_bit(bit_idx);
963 let got = elem.tower_bit(0);
964 assert_eq!(got, expected, "bit {bit_idx} mismatch");
965 }
966
967 let pass = res[32];
968 assert_eq!(
969 pass,
970 <Block128 as hekate_math::FlatPromote<Block32>>::promote_flat(Flat::from_raw(Block32(
971 pass_val
972 )))
973 );
974
975 let ctrl = res[33].tower_bit(0);
976 assert_eq!(ctrl, 1);
977 }
978
979 #[test]
980 fn expand_variants_b32() {
981 let expander = VirtualExpander::new()
982 .expand_bits(1, ColumnType::B32)
983 .pass_through(1, ColumnType::B32)
984 .control_bits(1)
985 .build()
986 .unwrap();
987
988 let layout = [ColumnType::B32, ColumnType::B32, ColumnType::Bit];
989 let num_vars = 2;
990
991 let mut tb = TraceBuilder::new(&layout, num_vars).unwrap();
992 tb.set_b32(0, 0, Block32(0xAAAA_BBBB)).unwrap();
993 tb.set_b32(1, 0, Block32(0x1111_2222)).unwrap();
994 tb.set_bit(2, 0, Bit::ONE).unwrap();
995
996 let trace = tb.build();
997
998 let variants: Vec<PolyVariant<'_, Block128>> = expander.expand_variants(&trace, 0).unwrap();
999
1000 assert_eq!(variants.len(), 34);
1001
1002 for (i, v) in variants.iter().enumerate().take(32) {
1003 assert!(matches!(v, PolyVariant::PackedBitB32 { bit_idx, .. } if *bit_idx == i));
1004 }
1005
1006 assert!(matches!(variants[32], PolyVariant::B32Slice(_)));
1007 assert!(matches!(variants[33], PolyVariant::BitSlice(_)));
1008 }
1009}