Skip to main content

qec_code/
distance_bound.rs

1use crate::code::StabilizerCode;
2use crate::css::CssCode;
3use crate::distance::LogicalClass;
4use crate::error::{QecError, Result};
5use crate::gf2;
6use crate::Pauli;
7use serde::{Deserialize, Serialize};
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum DistanceBoundMethod {
13    RandomizedUpperBound,
14    RandomWindowUpperBound,
15    Exact,
16}
17
18impl DistanceBoundMethod {
19    pub fn label(&self) -> &'static str {
20        match self {
21            Self::RandomizedUpperBound => "randomized-upper-bound",
22            Self::RandomWindowUpperBound => "random-window-upper-bound",
23            Self::Exact => "exact",
24        }
25    }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum BoundType {
31    Upper,
32    Exact,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "lowercase")]
37pub enum DistanceBoundStatus {
38    Completed,
39}
40
41pub trait DistanceBoundOptions {
42    fn validate(&self) -> Result<()>;
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct RandomizedUpperBoundOptions {
47    pub iterations: usize,
48    pub restarts: usize,
49    pub seed: u64,
50    pub target_weight: Option<usize>,
51}
52
53impl RandomizedUpperBoundOptions {
54    pub fn validate(&self) -> Result<()> {
55        validate_upper_bound_options(self.iterations, self.restarts, self.target_weight)
56    }
57}
58
59impl DistanceBoundOptions for RandomizedUpperBoundOptions {
60    fn validate(&self) -> Result<()> {
61        RandomizedUpperBoundOptions::validate(self)
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct RandomWindowUpperBoundOptions {
67    pub iterations: usize,
68    pub restarts: usize,
69    pub seed: u64,
70    pub target_weight: Option<usize>,
71}
72
73impl RandomWindowUpperBoundOptions {
74    pub fn validate(&self) -> Result<()> {
75        validate_upper_bound_options(self.iterations, self.restarts, self.target_weight)
76    }
77}
78
79impl DistanceBoundOptions for RandomWindowUpperBoundOptions {
80    fn validate(&self) -> Result<()> {
81        RandomWindowUpperBoundOptions::validate(self)
82    }
83}
84
85fn validate_upper_bound_options(
86    iterations: usize,
87    restarts: usize,
88    target_weight: Option<usize>,
89) -> Result<()> {
90    if iterations == 0 {
91        return Err(QecError::InvalidDistanceBoundOption {
92            option: "iterations",
93            reason: "must be greater than zero".to_owned(),
94        });
95    }
96    if restarts == 0 {
97        return Err(QecError::InvalidDistanceBoundOption {
98            option: "restarts",
99            reason: "must be greater than zero".to_owned(),
100        });
101    }
102    if target_weight == Some(0) {
103        return Err(QecError::InvalidDistanceBoundOption {
104            option: "target_weight",
105            reason: "must be greater than zero when provided".to_owned(),
106        });
107    }
108    Ok(())
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct DistanceBoundWitness {
113    pub x: Vec<u8>,
114    pub z: Vec<u8>,
115    pub weight: usize,
116}
117
118impl DistanceBoundWitness {
119    pub fn from_pauli(pauli: &Pauli) -> Self {
120        Self {
121            x: pauli.x_bits().to_vec(),
122            z: pauli.z_bits().to_vec(),
123            weight: pauli.weight(),
124        }
125    }
126
127    pub fn to_pauli(&self) -> Result<Pauli> {
128        Pauli::from_xz_bits(self.x.clone(), self.z.clone())
129    }
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
133pub struct DistanceBoundProvenance {
134    pub tool: String,
135    pub tool_version: String,
136    pub method_revision: u32,
137}
138
139impl DistanceBoundProvenance {
140    pub fn current() -> Self {
141        Self {
142            tool: "qec-code".to_owned(),
143            tool_version: env!("CARGO_PKG_VERSION").to_owned(),
144            method_revision: 1,
145        }
146    }
147}
148
149#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
150pub struct RandomWindowSearchStats {
151    pub permutations_sampled: usize,
152    pub kernel_basis_generations: usize,
153    pub component_candidates_generated: usize,
154    pub zero_candidates_rejected: usize,
155    pub weight_pruned_candidates: usize,
156    pub stabilizer_span_candidates_rejected: usize,
157    pub witness_validation_candidates_rejected: usize,
158    pub valid_witnesses_found: usize,
159    pub best_witness_updates: usize,
160    pub target_reached: bool,
161    pub permutation_time_ns: u64,
162    pub kernel_basis_time_ns: u64,
163    pub span_filter_time_ns: u64,
164    pub witness_validation_time_ns: u64,
165    pub best_update_time_ns: u64,
166    pub total_search_time_ns: u64,
167}
168
169fn duration_ns(duration: Duration) -> u64 {
170    duration.as_nanos().try_into().unwrap_or(u64::MAX)
171}
172
173fn add_elapsed_ns(total: &mut u64, started: Instant) {
174    *total = total.saturating_add(duration_ns(started.elapsed()));
175}
176
177fn finish_search_timing(search_stats: &mut RandomWindowSearchStats, started: Instant) {
178    search_stats.total_search_time_ns = duration_ns(started.elapsed()).max(1);
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182pub struct DistanceBoundResult<Options = RandomizedUpperBoundOptions> {
183    pub status: DistanceBoundStatus,
184    pub method: DistanceBoundMethod,
185    pub bound_type: BoundType,
186    pub upper_bound: usize,
187    pub logical_class: LogicalClass,
188    pub witness: DistanceBoundWitness,
189    pub options: Options,
190    pub provenance: DistanceBoundProvenance,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub search_stats: Option<RandomWindowSearchStats>,
193}
194
195impl<Options> DistanceBoundResult<Options> {
196    fn completed_with_method(
197        method: DistanceBoundMethod,
198        upper_bound: usize,
199        logical_class: LogicalClass,
200        witness: DistanceBoundWitness,
201        options: Options,
202    ) -> Self {
203        Self {
204            status: DistanceBoundStatus::Completed,
205            method,
206            bound_type: BoundType::Upper,
207            upper_bound,
208            logical_class,
209            witness,
210            options,
211            provenance: DistanceBoundProvenance::current(),
212            search_stats: None,
213        }
214    }
215}
216
217impl DistanceBoundResult<RandomizedUpperBoundOptions> {
218    pub fn completed(
219        upper_bound: usize,
220        logical_class: LogicalClass,
221        witness: DistanceBoundWitness,
222        options: RandomizedUpperBoundOptions,
223    ) -> Self {
224        Self::completed_with_method(
225            DistanceBoundMethod::RandomizedUpperBound,
226            upper_bound,
227            logical_class,
228            witness,
229            options,
230        )
231    }
232}
233
234impl DistanceBoundResult<RandomWindowUpperBoundOptions> {
235    pub fn completed_random_window_upper_bound(
236        upper_bound: usize,
237        logical_class: LogicalClass,
238        witness: DistanceBoundWitness,
239        options: RandomWindowUpperBoundOptions,
240    ) -> Self {
241        let mut result = Self::completed_with_method(
242            DistanceBoundMethod::RandomWindowUpperBound,
243            upper_bound,
244            logical_class,
245            witness,
246            options,
247        );
248        result.search_stats = Some(RandomWindowSearchStats::default());
249        result
250    }
251
252    fn completed_random_window_upper_bound_with_stats(
253        upper_bound: usize,
254        logical_class: LogicalClass,
255        witness: DistanceBoundWitness,
256        options: RandomWindowUpperBoundOptions,
257        stats: RandomWindowSearchStats,
258    ) -> Self {
259        let mut result = Self::completed_with_method(
260            DistanceBoundMethod::RandomWindowUpperBound,
261            upper_bound,
262            logical_class,
263            witness,
264            options,
265        );
266        result.search_stats = Some(stats);
267        result
268    }
269}
270
271pub fn randomized_css_upper_bound(
272    css: &CssCode,
273    options: RandomizedUpperBoundOptions,
274) -> Result<DistanceBoundResult> {
275    options.validate()?;
276
277    let code = css.code();
278    if code.num_logical_qubits() == 0 {
279        return Err(QecError::DistanceWitnessNotFound);
280    }
281
282    let basis = code.canonical_logical_basis()?;
283    let logical_rows = basis
284        .logical_x
285        .iter()
286        .chain(&basis.logical_z)
287        .map(Pauli::to_symplectic_row)
288        .collect::<Vec<_>>();
289    let stabilizer_rows = code.stabilizer_rows();
290    let mut rng = SplitMix64::new(options.seed);
291    let mut best_witness: Option<Pauli> = None;
292
293    for _restart in 0..options.restarts {
294        for _iteration in 0..options.iterations {
295            let candidate_row =
296                sampled_logical_plus_stabilizer_row(&logical_rows, &stabilizer_rows, &mut rng);
297            let candidate = Pauli::from_symplectic_row(candidate_row)?;
298
299            if validate_witness_against_code(code, &candidate).is_err() {
300                continue;
301            }
302
303            let replace = match &best_witness {
304                Some(current) => candidate.weight() < current.weight(),
305                None => true,
306            };
307            if replace {
308                best_witness = Some(candidate);
309            }
310
311            if best_witness.as_ref().is_some_and(|witness| {
312                options
313                    .target_weight
314                    .is_some_and(|target| witness.weight() <= target)
315            }) {
316                return completed_randomized_upper_bound_result(
317                    code,
318                    best_witness.unwrap(),
319                    options,
320                );
321            }
322        }
323    }
324
325    let witness = best_witness.ok_or(QecError::RandomizedUpperBoundWitnessNotFound)?;
326    completed_randomized_upper_bound_result(code, witness, options)
327}
328
329pub fn random_window_css_upper_bound(
330    css: &CssCode,
331    options: RandomWindowUpperBoundOptions,
332) -> Result<DistanceBoundResult<RandomWindowUpperBoundOptions>> {
333    options.validate()?;
334
335    let code = css.code();
336    if code.num_logical_qubits() == 0 {
337        return Err(QecError::DistanceWitnessNotFound);
338    }
339
340    let width = code.n();
341    let hx_span = gf2::try_rref_with_width(css.hx(), width)?;
342    let hz_span = gf2::try_rref_with_width(css.hz(), width)?;
343    let x_component_filter = PackedCssComponentFilter::try_new(css.hz(), &hx_span)?;
344    let z_component_filter = PackedCssComponentFilter::try_new(css.hx(), &hz_span)?;
345    let mut rng = SplitMix64::new(options.seed);
346    let mut best_witness: Option<Pauli> = None;
347    let mut search_stats = RandomWindowSearchStats::default();
348    let mut kernel_workspace = gf2::RandomWindowKernelWorkspace::new();
349    let search_started = Instant::now();
350
351    for _restart in 0..options.restarts {
352        for _iteration in 0..options.iterations {
353            let permutation_started = Instant::now();
354            let permutation = shuffled_columns(width, &mut rng);
355            add_elapsed_ns(&mut search_stats.permutation_time_ns, permutation_started);
356            search_stats.permutations_sampled += 1;
357            consider_component_candidates(
358                css.hz(),
359                &x_component_filter,
360                ComponentKind::XLike,
361                width,
362                &permutation,
363                &mut kernel_workspace,
364                &mut best_witness,
365                &mut search_stats,
366            )?;
367            if target_reached(&best_witness, options.target_weight) {
368                search_stats.target_reached = true;
369                finish_search_timing(&mut search_stats, search_started);
370                return completed_random_window_upper_bound_result(
371                    code,
372                    best_witness.unwrap(),
373                    options,
374                    search_stats,
375                );
376            }
377
378            consider_component_candidates(
379                css.hx(),
380                &z_component_filter,
381                ComponentKind::ZLike,
382                width,
383                &permutation,
384                &mut kernel_workspace,
385                &mut best_witness,
386                &mut search_stats,
387            )?;
388            if target_reached(&best_witness, options.target_weight) {
389                search_stats.target_reached = true;
390                finish_search_timing(&mut search_stats, search_started);
391                return completed_random_window_upper_bound_result(
392                    code,
393                    best_witness.unwrap(),
394                    options,
395                    search_stats,
396                );
397            }
398        }
399    }
400
401    let witness = best_witness.ok_or(QecError::RandomizedUpperBoundWitnessNotFound)?;
402    finish_search_timing(&mut search_stats, search_started);
403    completed_random_window_upper_bound_result(code, witness, options, search_stats)
404}
405
406fn completed_randomized_upper_bound_result(
407    code: &StabilizerCode,
408    witness: Pauli,
409    options: RandomizedUpperBoundOptions,
410) -> Result<DistanceBoundResult> {
411    let result = DistanceBoundResult::completed(
412        witness.weight(),
413        classify_witness_support(&witness),
414        DistanceBoundWitness::from_pauli(&witness),
415        options,
416    );
417    validate_randomized_upper_bound_result(
418        &result,
419        BoundValidationContext {
420            code,
421            known_exact_distance: None,
422        },
423    )?;
424    Ok(result)
425}
426
427#[derive(Debug, Clone, Copy)]
428enum ComponentKind {
429    XLike,
430    ZLike,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
434enum CssComponentCandidateVerdict {
435    Accepted,
436    Zero,
437    NonKernel,
438    StabilizerSpan,
439}
440
441#[allow(dead_code)]
442fn css_component_candidate_verdict(
443    opposite_checks: &[Vec<u8>],
444    stabilizer_component_span: &gf2::ReducedRows,
445    candidate: &[u8],
446) -> Result<CssComponentCandidateVerdict> {
447    let width = stabilizer_component_span.width;
448    gf2::validate_rows_with_width(opposite_checks, width)?;
449    gf2::validate_target(candidate)?;
450    if candidate.len() != width {
451        return Err(QecError::RowWidthMismatch {
452            expected: width,
453            actual: candidate.len(),
454        });
455    }
456    if !candidate.iter().any(|bit| *bit == 1) {
457        return Ok(CssComponentCandidateVerdict::Zero);
458    }
459
460    for check in opposite_checks {
461        let parity = check
462            .iter()
463            .zip(candidate)
464            .fold(0, |acc, (&check_bit, &candidate_bit)| {
465                acc ^ (check_bit & candidate_bit)
466            });
467        if parity != 0 {
468            return Ok(CssComponentCandidateVerdict::NonKernel);
469        }
470    }
471
472    if gf2::try_in_reduced_row_span(stabilizer_component_span, candidate)? {
473        return Ok(CssComponentCandidateVerdict::StabilizerSpan);
474    }
475
476    Ok(CssComponentCandidateVerdict::Accepted)
477}
478
479struct PackedCssComponentFilter {
480    opposite_checks: Vec<gf2::BitPackedRow>,
481    stabilizer_component_span: gf2::PackedReducedRows,
482}
483
484impl PackedCssComponentFilter {
485    fn try_new(
486        opposite_checks: &[Vec<u8>],
487        stabilizer_component_span: &gf2::ReducedRows,
488    ) -> Result<Self> {
489        let width = stabilizer_component_span.width;
490        gf2::validate_rows_with_width(opposite_checks, width)?;
491        let opposite_checks = opposite_checks
492            .iter()
493            .map(|row| gf2::BitPackedRow::try_from_dense(row, width))
494            .collect::<Result<Vec<_>>>()?;
495        Ok(Self {
496            opposite_checks,
497            stabilizer_component_span: gf2::PackedReducedRows::try_from_reduced_rows(
498                stabilizer_component_span,
499            )?,
500        })
501    }
502
503    fn width(&self) -> usize {
504        self.stabilizer_component_span.width()
505    }
506}
507
508fn bitpacked_css_component_candidate_verdict(
509    filter: &PackedCssComponentFilter,
510    candidate: &gf2::BitPackedRow,
511) -> Result<CssComponentCandidateVerdict> {
512    if candidate.width() != filter.width() {
513        return Err(QecError::RowWidthMismatch {
514            expected: filter.width(),
515            actual: candidate.width(),
516        });
517    }
518    if candidate.is_zero() {
519        return Ok(CssComponentCandidateVerdict::Zero);
520    }
521    for check in &filter.opposite_checks {
522        if check.dot_parity(candidate)? != 0 {
523            return Ok(CssComponentCandidateVerdict::NonKernel);
524        }
525    }
526    if gf2::try_in_packed_reduced_row_span(&filter.stabilizer_component_span, candidate)? {
527        return Ok(CssComponentCandidateVerdict::StabilizerSpan);
528    }
529    Ok(CssComponentCandidateVerdict::Accepted)
530}
531
532fn consider_component_candidates(
533    kernel_checks: &[Vec<u8>],
534    component_filter: &PackedCssComponentFilter,
535    component: ComponentKind,
536    width: usize,
537    permutation: &[usize],
538    kernel_workspace: &mut gf2::RandomWindowKernelWorkspace,
539    best_witness: &mut Option<Pauli>,
540    search_stats: &mut RandomWindowSearchStats,
541) -> Result<()> {
542    search_stats.kernel_basis_generations += 1;
543    let kernel_started = Instant::now();
544    let candidates =
545        kernel_workspace.try_kernel_basis_with_width(kernel_checks, width, permutation);
546    add_elapsed_ns(&mut search_stats.kernel_basis_time_ns, kernel_started);
547    let candidates = candidates?;
548
549    consider_component_candidate_rows(
550        candidates,
551        component_filter,
552        component,
553        best_witness,
554        search_stats,
555    )
556}
557
558fn consider_component_candidate_rows(
559    candidates: &[Vec<u8>],
560    component_filter: &PackedCssComponentFilter,
561    component: ComponentKind,
562    best_witness: &mut Option<Pauli>,
563    search_stats: &mut RandomWindowSearchStats,
564) -> Result<()> {
565    search_stats.component_candidates_generated += candidates.len();
566
567    for candidate in candidates {
568        let span_started = Instant::now();
569        let packed_candidate =
570            gf2::BitPackedRow::try_from_dense(candidate, component_filter.width())?;
571        let candidate_weight = packed_candidate.weight();
572        if candidate_weight == 0 {
573            add_elapsed_ns(&mut search_stats.span_filter_time_ns, span_started);
574            search_stats.zero_candidates_rejected += 1;
575            continue;
576        }
577        if best_witness
578            .as_ref()
579            .is_some_and(|current| candidate_weight >= current.weight())
580        {
581            add_elapsed_ns(&mut search_stats.span_filter_time_ns, span_started);
582            search_stats.weight_pruned_candidates += 1;
583            continue;
584        }
585        let component_verdict =
586            bitpacked_css_component_candidate_verdict(component_filter, &packed_candidate)?;
587        add_elapsed_ns(&mut search_stats.span_filter_time_ns, span_started);
588        match component_verdict {
589            CssComponentCandidateVerdict::Accepted => {}
590            CssComponentCandidateVerdict::Zero => {
591                search_stats.zero_candidates_rejected += 1;
592                continue;
593            }
594            CssComponentCandidateVerdict::NonKernel => {
595                search_stats.witness_validation_candidates_rejected += 1;
596                continue;
597            }
598            CssComponentCandidateVerdict::StabilizerSpan => {
599                search_stats.stabilizer_span_candidates_rejected += 1;
600                continue;
601            }
602        }
603
604        let validation_started = Instant::now();
605        let witness = component_candidate_to_pauli(component, candidate)?;
606        add_elapsed_ns(
607            &mut search_stats.witness_validation_time_ns,
608            validation_started,
609        );
610        search_stats.valid_witnesses_found += 1;
611        let best_update_started = Instant::now();
612        let should_update = best_witness
613            .as_ref()
614            .is_none_or(|current| witness.weight() < current.weight());
615        if should_update {
616            search_stats.best_witness_updates += 1;
617            *best_witness = Some(witness);
618        }
619        add_elapsed_ns(&mut search_stats.best_update_time_ns, best_update_started);
620    }
621
622    Ok(())
623}
624
625fn component_candidate_to_pauli(component: ComponentKind, candidate: &[u8]) -> Result<Pauli> {
626    let width = candidate.len();
627    match component {
628        ComponentKind::XLike => Pauli::from_xz_bits(candidate.to_vec(), vec![0; width]),
629        ComponentKind::ZLike => Pauli::from_xz_bits(vec![0; width], candidate.to_vec()),
630    }
631}
632
633fn shuffled_columns(width: usize, rng: &mut SplitMix64) -> Vec<usize> {
634    let mut permutation = (0..width).collect::<Vec<_>>();
635    for i in (1..width).rev() {
636        let j = rng.next_usize(i + 1);
637        permutation.swap(i, j);
638    }
639    permutation
640}
641
642fn target_reached(best_witness: &Option<Pauli>, target_weight: Option<usize>) -> bool {
643    best_witness
644        .as_ref()
645        .is_some_and(|witness| target_weight.is_some_and(|target| witness.weight() <= target))
646}
647
648fn completed_random_window_upper_bound_result(
649    code: &StabilizerCode,
650    witness: Pauli,
651    options: RandomWindowUpperBoundOptions,
652    search_stats: RandomWindowSearchStats,
653) -> Result<DistanceBoundResult<RandomWindowUpperBoundOptions>> {
654    let result = DistanceBoundResult::completed_random_window_upper_bound_with_stats(
655        witness.weight(),
656        classify_witness_support(&witness),
657        DistanceBoundWitness::from_pauli(&witness),
658        options,
659        search_stats,
660    );
661    validate_random_window_upper_bound_result(
662        &result,
663        BoundValidationContext {
664            code,
665            known_exact_distance: None,
666        },
667    )?;
668    Ok(result)
669}
670
671fn sampled_logical_plus_stabilizer_row(
672    logical_rows: &[Vec<u8>],
673    stabilizer_rows: &[Vec<u8>],
674    rng: &mut SplitMix64,
675) -> Vec<u8> {
676    let width = logical_rows
677        .first()
678        .or_else(|| stabilizer_rows.first())
679        .map(Vec::len)
680        .unwrap_or(0);
681    let mut row = vec![0; width];
682    let mut selected_logical = false;
683
684    for logical in logical_rows {
685        if rng.next_bool() {
686            xor_assign(&mut row, logical);
687            selected_logical = true;
688        }
689    }
690    if !selected_logical {
691        let index = rng.next_usize(logical_rows.len());
692        xor_assign(&mut row, &logical_rows[index]);
693    }
694
695    for stabilizer in stabilizer_rows {
696        if rng.next_bool() {
697            xor_assign(&mut row, stabilizer);
698        }
699    }
700
701    row
702}
703
704fn xor_assign(target: &mut [u8], source: &[u8]) {
705    for (target_bit, source_bit) in target.iter_mut().zip(source) {
706        *target_bit ^= *source_bit;
707    }
708}
709
710struct SplitMix64 {
711    state: u64,
712}
713
714impl SplitMix64 {
715    fn new(seed: u64) -> Self {
716        Self { state: seed }
717    }
718
719    fn next_u64(&mut self) -> u64 {
720        self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
721        let mut value = self.state;
722        value = (value ^ (value >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
723        value = (value ^ (value >> 27)).wrapping_mul(0x94D049BB133111EB);
724        value ^ (value >> 31)
725    }
726
727    fn next_bool(&mut self) -> bool {
728        self.next_u64() & 1 == 1
729    }
730
731    fn next_usize(&mut self, upper_bound: usize) -> usize {
732        debug_assert!(upper_bound > 0);
733        (self.next_u64() as usize) % upper_bound
734    }
735}
736
737#[derive(Debug, Clone, Copy)]
738pub struct BoundValidationContext<'a> {
739    pub code: &'a StabilizerCode,
740    pub known_exact_distance: Option<usize>,
741}
742
743#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
744pub struct Issue225LadderCase {
745    pub case_id: String,
746    pub source_issue: u64,
747    pub code_id: String,
748    pub expected_upper_bound: usize,
749    pub target_weight: usize,
750    pub tier: String,
751    pub run_mode: String,
752}
753
754#[derive(Debug, Clone, Copy)]
755pub struct MethodAwareBoundValidationContext<'a> {
756    pub code: &'a StabilizerCode,
757    pub expected_method: DistanceBoundMethod,
758    pub known_exact_distance: Option<usize>,
759}
760
761pub fn validate_randomized_upper_bound_result(
762    result: &DistanceBoundResult,
763    context: BoundValidationContext<'_>,
764) -> Result<()> {
765    validate_distance_bound_result(
766        result,
767        MethodAwareBoundValidationContext {
768            code: context.code,
769            expected_method: DistanceBoundMethod::RandomizedUpperBound,
770            known_exact_distance: context.known_exact_distance,
771        },
772    )
773}
774
775pub fn validate_random_window_upper_bound_result(
776    result: &DistanceBoundResult<RandomWindowUpperBoundOptions>,
777    context: BoundValidationContext<'_>,
778) -> Result<()> {
779    validate_distance_bound_result(
780        result,
781        MethodAwareBoundValidationContext {
782            code: context.code,
783            expected_method: DistanceBoundMethod::RandomWindowUpperBound,
784            known_exact_distance: context.known_exact_distance,
785        },
786    )
787}
788
789pub fn validate_distance_bound_result<Options: DistanceBoundOptions>(
790    result: &DistanceBoundResult<Options>,
791    context: MethodAwareBoundValidationContext<'_>,
792) -> Result<()> {
793    result.options.validate()?;
794
795    if result.method != context.expected_method {
796        return Err(QecError::DistanceBoundValidationFailed(format!(
797            "expected method {}, got {}",
798            context.expected_method.label(),
799            result.method.label()
800        )));
801    }
802    if result.bound_type != BoundType::Upper {
803        return Err(QecError::DistanceBoundValidationFailed(
804            "distance bound results must use bound_type upper".to_owned(),
805        ));
806    }
807    if result.upper_bound == 0 {
808        return Err(QecError::DistanceBoundValidationFailed(
809            "completed upper_bound must be positive".to_owned(),
810        ));
811    }
812    if result.upper_bound != result.witness.weight {
813        return Err(QecError::DistanceBoundValidationFailed(
814            "upper_bound must equal witness weight".to_owned(),
815        ));
816    }
817
818    let witness = result.witness.to_pauli()?;
819    if witness.n() != context.code.n() {
820        return Err(QecError::DistanceBoundValidationFailed(
821            "witness width must match code length".to_owned(),
822        ));
823    }
824    if witness.weight() == 0 {
825        return Err(QecError::DistanceBoundValidationFailed(
826            "witness must be non-identity".to_owned(),
827        ));
828    }
829    if result.witness.weight != witness.weight() {
830        return Err(QecError::DistanceBoundValidationFailed(
831            "witness weight field must equal Pauli weight".to_owned(),
832        ));
833    }
834    if result.logical_class != classify_witness_support(&witness) {
835        return Err(QecError::DistanceBoundValidationFailed(
836            "logical_class must match witness support".to_owned(),
837        ));
838    }
839    validate_witness_against_code(context.code, &witness)?;
840
841    if let Some(known_exact_distance) = context.known_exact_distance {
842        if result.upper_bound < known_exact_distance {
843            return Err(QecError::DistanceBoundValidationFailed(format!(
844                "upper_bound {} is below known exact distance {}",
845                result.upper_bound, known_exact_distance
846            )));
847        }
848    }
849
850    Ok(())
851}
852
853pub fn verify_issue_225_ladder_case<Options: DistanceBoundOptions>(
854    case: &Issue225LadderCase,
855    result: &DistanceBoundResult<Options>,
856    css: &CssCode,
857    expected_method: DistanceBoundMethod,
858) -> Result<()> {
859    if result.method != expected_method {
860        return Err(QecError::DistanceBoundValidationFailed(format!(
861            "{} expected method {}, got {}",
862            case.case_id,
863            expected_method.label(),
864            result.method.label()
865        )));
866    }
867
868    validate_distance_bound_result(
869        result,
870        MethodAwareBoundValidationContext {
871            code: css.code(),
872            expected_method,
873            known_exact_distance: None,
874        },
875    )
876    .map_err(|error| prefix_ladder_case_error(&case.case_id, error))?;
877
878    if result.upper_bound > case.expected_upper_bound {
879        return Err(QecError::DistanceBoundValidationFailed(format!(
880            "{} expected upper_bound <= {}, got {}",
881            case.case_id, case.expected_upper_bound, result.upper_bound
882        )));
883    }
884
885    Ok(())
886}
887
888fn prefix_ladder_case_error(case_id: &str, error: QecError) -> QecError {
889    match error {
890        QecError::DistanceBoundValidationFailed(message) => {
891            QecError::DistanceBoundValidationFailed(format!("{case_id} {message}"))
892        }
893        other => QecError::DistanceBoundValidationFailed(format!("{case_id} {other}")),
894    }
895}
896
897fn classify_witness_support(witness: &Pauli) -> LogicalClass {
898    let has_x = witness.x_bits().contains(&1);
899    let has_z = witness.z_bits().contains(&1);
900
901    match (has_x, has_z) {
902        (true, false) => LogicalClass::XLike,
903        (false, true) => LogicalClass::ZLike,
904        (true, true) => LogicalClass::Mixed,
905        (false, false) => unreachable!("witness support classification requires non-identity"),
906    }
907}
908
909fn validate_witness_against_code(code: &StabilizerCode, witness: &Pauli) -> Result<()> {
910    let stabilizer_rows = code.stabilizer_rows();
911    let stabilizer_span = gf2::try_rref_with_width(&stabilizer_rows, code.n() * 2)?;
912    validate_witness_against_code_with_span(code, &stabilizer_span, witness)
913}
914
915fn validate_witness_against_code_with_span(
916    code: &StabilizerCode,
917    stabilizer_span: &gf2::ReducedRows,
918    witness: &Pauli,
919) -> Result<()> {
920    if witness.weight() == 0 {
921        return Err(QecError::DistanceBoundValidationFailed(
922            "witness must be non-identity".to_owned(),
923        ));
924    }
925    for stabilizer in code.stabilizers() {
926        if !witness.try_commutes_with(stabilizer)? {
927            return Err(QecError::DistanceBoundValidationFailed(
928                "witness does not commute with stabilizers".to_owned(),
929            ));
930        }
931    }
932    if gf2::try_in_reduced_row_span(stabilizer_span, &witness.to_symplectic_row())? {
933        return Err(QecError::DistanceBoundValidationFailed(
934            "witness lies in stabilizer span".to_owned(),
935        ));
936    }
937    Ok(())
938}
939
940#[cfg(test)]
941mod tests {
942    use super::*;
943    use crate::codes::built_in_css::built_in_css_checks;
944    use crate::css::{CssCode, SparseRowsMatrix};
945
946    fn empty_reduced_rows(width: usize) -> gf2::ReducedRows {
947        gf2::try_rref_with_width(&[], width).unwrap()
948    }
949
950    fn css_from_sparse_rows(num_cols: usize, hx: Vec<Vec<usize>>, hz: Vec<Vec<usize>>) -> CssCode {
951        let hx = SparseRowsMatrix::new(num_cols, hx).unwrap().to_dense_rows();
952        let hz = SparseRowsMatrix::new(num_cols, hz).unwrap().to_dense_rows();
953        CssCode::from_hx_hz(hx, hz).unwrap()
954    }
955
956    fn css_from_built_in_code_id(code_id: &str) -> CssCode {
957        let checks = built_in_css_checks(code_id).unwrap();
958        css_from_sparse_rows(checks.num_cols, checks.hx, checks.hz)
959    }
960
961    fn first_non_kernel_candidate(checks: &[Vec<u8>], width: usize) -> Vec<u8> {
962        let column = (0..width)
963            .find(|&column| checks.iter().any(|row| row[column] == 1))
964            .expect("expected at least one nonzero check column");
965        let mut candidate = vec![0; width];
966        candidate[column] = 1;
967        candidate
968    }
969
970    fn component_filter_reference_candidates(
971        kernel_checks: &[Vec<u8>],
972        component_span_rows: &[Vec<u8>],
973        width: usize,
974        permutation: &[usize],
975    ) -> Vec<Vec<u8>> {
976        let mut candidates = Vec::new();
977        candidates.push(vec![0; width]);
978        candidates.push(first_non_kernel_candidate(kernel_checks, width));
979        if let Some(span_row) = component_span_rows.first() {
980            candidates.push(span_row.clone());
981        }
982        candidates.extend(
983            gf2::try_random_window_kernel_basis_with_width(kernel_checks, width, permutation)
984                .unwrap(),
985        );
986        candidates
987    }
988
989    fn full_validator_component_verdict(
990        code: &StabilizerCode,
991        stabilizer_span: &gf2::ReducedRows,
992        component: ComponentKind,
993        candidate: &[u8],
994    ) -> Result<CssComponentCandidateVerdict> {
995        let witness = component_candidate_to_pauli(component, candidate)?;
996        match validate_witness_against_code_with_span(code, stabilizer_span, &witness) {
997            Ok(()) => Ok(CssComponentCandidateVerdict::Accepted),
998            Err(QecError::DistanceBoundValidationFailed(message))
999                if message == "witness must be non-identity" =>
1000            {
1001                Ok(CssComponentCandidateVerdict::Zero)
1002            }
1003            Err(QecError::DistanceBoundValidationFailed(message))
1004                if message == "witness does not commute with stabilizers" =>
1005            {
1006                Ok(CssComponentCandidateVerdict::NonKernel)
1007            }
1008            Err(QecError::DistanceBoundValidationFailed(message))
1009                if message == "witness lies in stabilizer span" =>
1010            {
1011                Ok(CssComponentCandidateVerdict::StabilizerSpan)
1012            }
1013            Err(error) => Err(error),
1014        }
1015    }
1016
1017    fn x_pauli(width: usize, support: &[usize]) -> Pauli {
1018        let mut x = vec![0; width];
1019        for &index in support {
1020            x[index] = 1;
1021        }
1022        Pauli::from_xz_bits(x, vec![0; width]).unwrap()
1023    }
1024
1025    #[test]
1026    fn random_window_prunes_candidates_that_cannot_improve_best() {
1027        let width = 3;
1028        let component_span = empty_reduced_rows(width);
1029        let component_filter = PackedCssComponentFilter::try_new(&[], &component_span).unwrap();
1030        let mut best_witness = Some(x_pauli(width, &[0, 1]));
1031        let mut search_stats = RandomWindowSearchStats::default();
1032
1033        consider_component_candidate_rows(
1034            &[vec![0, 0, 0], vec![1, 1, 0], vec![1, 1, 1], vec![0, 0, 1]],
1035            &component_filter,
1036            ComponentKind::XLike,
1037            &mut best_witness,
1038            &mut search_stats,
1039        )
1040        .unwrap();
1041
1042        let best = best_witness.expect("strictly lighter candidate should replace current best");
1043        assert_eq!(best.weight(), 1);
1044        assert_eq!(search_stats.component_candidates_generated, 4);
1045        assert_eq!(search_stats.zero_candidates_rejected, 1);
1046        assert_eq!(search_stats.weight_pruned_candidates, 2);
1047        assert_eq!(search_stats.valid_witnesses_found, 1);
1048        assert_eq!(search_stats.best_witness_updates, 1);
1049        assert_eq!(search_stats.stabilizer_span_candidates_rejected, 0);
1050        assert_eq!(search_stats.witness_validation_candidates_rejected, 0);
1051
1052        let stats_json = serde_json::to_value(search_stats).unwrap();
1053        assert_eq!(stats_json["weight_pruned_candidates"], 2);
1054    }
1055
1056    #[test]
1057    fn random_window_pruning_does_not_skip_strictly_better_candidate() {
1058        let width = 5;
1059        let component_span = empty_reduced_rows(width);
1060        let component_filter = PackedCssComponentFilter::try_new(&[], &component_span).unwrap();
1061        let mut best_witness = Some(x_pauli(width, &[0, 1, 2, 3, 4]));
1062        let mut search_stats = RandomWindowSearchStats::default();
1063
1064        consider_component_candidate_rows(
1065            &[vec![1, 1, 1, 0, 0]],
1066            &component_filter,
1067            ComponentKind::XLike,
1068            &mut best_witness,
1069            &mut search_stats,
1070        )
1071        .unwrap();
1072
1073        let best = best_witness.expect("weight-3 candidate should replace weight-5 best");
1074        assert_eq!(best.weight(), 3);
1075        assert_eq!(search_stats.component_candidates_generated, 1);
1076        assert_eq!(search_stats.weight_pruned_candidates, 0);
1077        assert_eq!(search_stats.valid_witnesses_found, 1);
1078        assert_eq!(search_stats.best_witness_updates, 1);
1079    }
1080
1081    #[test]
1082    fn random_window_bitpacked_component_filter_matches_dense_filter() {
1083        for code_id in ["surface_rotated:d=3", "bb72"] {
1084            let css = css_from_built_in_code_id(code_id);
1085            let width = css.code().n();
1086            let identity_permutation = (0..width).collect::<Vec<_>>();
1087
1088            for (component, kernel_checks, component_span_rows) in [
1089                (ComponentKind::XLike, css.hz(), css.hx()),
1090                (ComponentKind::ZLike, css.hx(), css.hz()),
1091            ] {
1092                let component_span = gf2::try_rref_with_width(component_span_rows, width).unwrap();
1093                let packed_filter =
1094                    PackedCssComponentFilter::try_new(kernel_checks, &component_span).unwrap();
1095                let mut candidates = component_filter_reference_candidates(
1096                    kernel_checks,
1097                    component_span_rows,
1098                    width,
1099                    &identity_permutation,
1100                );
1101
1102                for candidate in candidates.drain(..) {
1103                    let dense_verdict =
1104                        css_component_candidate_verdict(kernel_checks, &component_span, &candidate)
1105                            .unwrap();
1106                    let packed_candidate =
1107                        gf2::BitPackedRow::try_from_dense(&candidate, width).unwrap();
1108                    let packed_verdict = bitpacked_css_component_candidate_verdict(
1109                        &packed_filter,
1110                        &packed_candidate,
1111                    )
1112                    .unwrap();
1113
1114                    assert_eq!(
1115                        packed_verdict, dense_verdict,
1116                        "{code_id} {component:?} candidate {candidate:?}"
1117                    );
1118                }
1119            }
1120        }
1121    }
1122
1123    #[test]
1124    fn random_window_bitpacked_component_filter_rejects_tail_bit_and_span_false_positive_cases() {
1125        let span = gf2::try_rref_with_width(
1126            &[{
1127                let mut row = vec![0; 65];
1128                row[0] = 1;
1129                row
1130            }],
1131            65,
1132        )
1133        .unwrap();
1134        let packed_filter = PackedCssComponentFilter::try_new(&[], &span).unwrap();
1135
1136        let mut dirty_zero = gf2::BitPackedRow::zeros(65);
1137        dirty_zero.set_storage_padding_for_test();
1138        assert_eq!(
1139            bitpacked_css_component_candidate_verdict(&packed_filter, &dirty_zero).unwrap(),
1140            CssComponentCandidateVerdict::Zero
1141        );
1142
1143        let nonmember_same_word = gf2::BitPackedRow::try_from_dense(
1144            &{
1145                let mut row = vec![0; 65];
1146                row[1] = 1;
1147                row
1148            },
1149            65,
1150        )
1151        .unwrap();
1152        assert_eq!(
1153            bitpacked_css_component_candidate_verdict(&packed_filter, &nonmember_same_word)
1154                .unwrap(),
1155            CssComponentCandidateVerdict::Accepted
1156        );
1157
1158        let nonkernel_filter = PackedCssComponentFilter::try_new(
1159            &[{
1160                let mut row = vec![0; 65];
1161                row[1] = 1;
1162                row
1163            }],
1164            &gf2::try_rref_with_width(&[], 65).unwrap(),
1165        )
1166        .unwrap();
1167        assert_eq!(
1168            bitpacked_css_component_candidate_verdict(&nonkernel_filter, &nonmember_same_word)
1169                .unwrap(),
1170            CssComponentCandidateVerdict::NonKernel
1171        );
1172    }
1173
1174    #[test]
1175    fn random_window_candidate_rows_accepts_workspace_output_without_stale_rows() {
1176        let width = 3;
1177        let component_span = empty_reduced_rows(width);
1178        let component_filter = PackedCssComponentFilter::try_new(&[], &component_span).unwrap();
1179        let mut workspace = gf2::RandomWindowKernelWorkspace::new();
1180        let permutation = vec![2, 0, 1];
1181        let candidates = workspace
1182            .try_kernel_basis_with_width(&[], width, &permutation)
1183            .unwrap();
1184        assert_eq!(candidates, &[vec![0, 0, 1], vec![1, 0, 0], vec![0, 1, 0],]);
1185
1186        let mut best_witness = Some(x_pauli(width, &[0, 1]));
1187        let mut search_stats = RandomWindowSearchStats::default();
1188        consider_component_candidate_rows(
1189            candidates,
1190            &component_filter,
1191            ComponentKind::XLike,
1192            &mut best_witness,
1193            &mut search_stats,
1194        )
1195        .unwrap();
1196
1197        let best = best_witness.expect("workspace candidate should update the best witness");
1198        assert_eq!(best.weight(), 1);
1199        assert_eq!(search_stats.component_candidates_generated, 3);
1200        assert_eq!(search_stats.weight_pruned_candidates, 2);
1201        assert_eq!(search_stats.valid_witnesses_found, 1);
1202        assert_eq!(search_stats.best_witness_updates, 1);
1203    }
1204
1205    #[test]
1206    fn random_window_component_filter_matches_full_witness_validation() {
1207        for code_id in ["surface_rotated:d=3", "bb72"] {
1208            let css = css_from_built_in_code_id(code_id);
1209            let width = css.code().n();
1210            let stabilizer_span =
1211                gf2::try_rref_with_width(&css.code().stabilizer_rows(), width * 2).unwrap();
1212            let identity_permutation = (0..width).collect::<Vec<_>>();
1213
1214            for (component, kernel_checks, component_span_rows) in [
1215                (ComponentKind::XLike, css.hz(), css.hx()),
1216                (ComponentKind::ZLike, css.hx(), css.hz()),
1217            ] {
1218                let component_span = gf2::try_rref_with_width(component_span_rows, width).unwrap();
1219                let packed_filter =
1220                    PackedCssComponentFilter::try_new(kernel_checks, &component_span).unwrap();
1221                let candidates = component_filter_reference_candidates(
1222                    kernel_checks,
1223                    component_span_rows,
1224                    width,
1225                    &identity_permutation,
1226                );
1227
1228                let mut accepted = 0;
1229                let mut non_kernel_rejected = 0;
1230                let mut stabilizer_span_rejected = 0;
1231                for candidate in candidates {
1232                    let packed_candidate =
1233                        gf2::BitPackedRow::try_from_dense(&candidate, width).unwrap();
1234                    let component_verdict = bitpacked_css_component_candidate_verdict(
1235                        &packed_filter,
1236                        &packed_candidate,
1237                    )
1238                    .unwrap();
1239                    let full_verdict = full_validator_component_verdict(
1240                        css.code(),
1241                        &stabilizer_span,
1242                        component,
1243                        &candidate,
1244                    )
1245                    .unwrap();
1246
1247                    assert_eq!(
1248                        component_verdict, full_verdict,
1249                        "{code_id} {component:?} candidate {candidate:?}"
1250                    );
1251                    match component_verdict {
1252                        CssComponentCandidateVerdict::Accepted => accepted += 1,
1253                        CssComponentCandidateVerdict::NonKernel => non_kernel_rejected += 1,
1254                        CssComponentCandidateVerdict::StabilizerSpan => {
1255                            stabilizer_span_rejected += 1
1256                        }
1257                        CssComponentCandidateVerdict::Zero => {}
1258                    }
1259                }
1260
1261                assert!(
1262                    accepted > 0,
1263                    "{code_id} {component:?} should have accepted rows"
1264                );
1265                assert!(
1266                    non_kernel_rejected > 0,
1267                    "{code_id} {component:?} should exercise non-kernel rejection"
1268                );
1269                assert!(
1270                    stabilizer_span_rejected > 0,
1271                    "{code_id} {component:?} should exercise stabilizer-span rejection"
1272                );
1273            }
1274        }
1275    }
1276
1277    #[test]
1278    fn random_window_component_filter_rejects_non_kernel_and_stabilizer_span_candidates() {
1279        let css = css_from_sparse_rows(3, vec![vec![0, 1]], vec![vec![2]]);
1280        let width = css.code().n();
1281
1282        let hx_span = gf2::try_rref_with_width(css.hx(), width).unwrap();
1283        let x_component_filter = PackedCssComponentFilter::try_new(css.hz(), &hx_span).unwrap();
1284        let mut x_best = None;
1285        let mut x_stats = RandomWindowSearchStats::default();
1286        consider_component_candidate_rows(
1287            &[vec![0, 0, 1], vec![1, 1, 0]],
1288            &x_component_filter,
1289            ComponentKind::XLike,
1290            &mut x_best,
1291            &mut x_stats,
1292        )
1293        .unwrap();
1294        assert!(x_best.is_none());
1295        assert_eq!(x_stats.component_candidates_generated, 2);
1296        assert_eq!(x_stats.witness_validation_candidates_rejected, 1);
1297        assert_eq!(x_stats.stabilizer_span_candidates_rejected, 1);
1298        assert_eq!(x_stats.valid_witnesses_found, 0);
1299        assert_eq!(x_stats.best_witness_updates, 0);
1300
1301        let hz_span = gf2::try_rref_with_width(css.hz(), width).unwrap();
1302        let z_component_filter = PackedCssComponentFilter::try_new(css.hx(), &hz_span).unwrap();
1303        let mut z_best = None;
1304        let mut z_stats = RandomWindowSearchStats::default();
1305        consider_component_candidate_rows(
1306            &[vec![1, 0, 0], vec![0, 0, 1]],
1307            &z_component_filter,
1308            ComponentKind::ZLike,
1309            &mut z_best,
1310            &mut z_stats,
1311        )
1312        .unwrap();
1313        assert!(z_best.is_none());
1314        assert_eq!(z_stats.component_candidates_generated, 2);
1315        assert_eq!(z_stats.witness_validation_candidates_rejected, 1);
1316        assert_eq!(z_stats.stabilizer_span_candidates_rejected, 1);
1317        assert_eq!(z_stats.valid_witnesses_found, 0);
1318        assert_eq!(z_stats.best_witness_updates, 0);
1319    }
1320
1321    #[test]
1322    fn random_window_component_filter_reports_validation_errors() {
1323        let span = empty_reduced_rows(3);
1324        let packed_filter = PackedCssComponentFilter::try_new(&[], &span).unwrap();
1325
1326        assert_eq!(
1327            css_component_candidate_verdict(&[], &span, &[1, 0]).unwrap_err(),
1328            QecError::RowWidthMismatch {
1329                expected: 3,
1330                actual: 2,
1331            }
1332        );
1333        assert_eq!(
1334            css_component_candidate_verdict(&[], &span, &[1, 2, 0]).unwrap_err(),
1335            QecError::InvalidBinaryEntry {
1336                row: 0,
1337                col: 1,
1338                value: 2,
1339            }
1340        );
1341        assert_eq!(
1342            css_component_candidate_verdict(&[vec![1, 0]], &span, &[1, 0, 0]).unwrap_err(),
1343            QecError::RowWidthMismatch {
1344                expected: 3,
1345                actual: 2,
1346            }
1347        );
1348        assert_eq!(
1349            css_component_candidate_verdict(&[vec![1, 2, 0]], &span, &[1, 0, 0]).unwrap_err(),
1350            QecError::InvalidBinaryEntry {
1351                row: 0,
1352                col: 1,
1353                value: 2,
1354            }
1355        );
1356        assert_eq!(
1357            bitpacked_css_component_candidate_verdict(&packed_filter, &gf2::BitPackedRow::zeros(2))
1358                .unwrap_err(),
1359            QecError::RowWidthMismatch {
1360                expected: 3,
1361                actual: 2,
1362            }
1363        );
1364        let invalid_span = gf2::ReducedRows {
1365            rows: vec![vec![1, 2, 0]],
1366            pivot_cols: vec![0],
1367            width: 3,
1368        };
1369        assert_eq!(
1370            PackedCssComponentFilter::try_new(&[], &invalid_span).err(),
1371            Some(QecError::InvalidBinaryEntry {
1372                row: 0,
1373                col: 1,
1374                value: 2,
1375            })
1376        );
1377    }
1378
1379    #[test]
1380    fn full_validator_component_verdict_propagates_unexpected_errors() {
1381        let code = StabilizerCode::from_stabilizers(2, vec![]).unwrap();
1382        let stabilizer_span = empty_reduced_rows(4);
1383
1384        assert_eq!(
1385            full_validator_component_verdict(
1386                &code,
1387                &stabilizer_span,
1388                ComponentKind::XLike,
1389                &[1, 0, 0],
1390            )
1391            .unwrap_err(),
1392            QecError::RowWidthMismatch {
1393                expected: 4,
1394                actual: 6,
1395            }
1396        );
1397    }
1398
1399    #[test]
1400    fn witness_validation_rejects_identity_witness() {
1401        let code = StabilizerCode::from_stabilizers(1, vec![]).unwrap();
1402        let witness = Pauli::from_xz_bits(vec![0], vec![0]).unwrap();
1403
1404        assert_eq!(
1405            validate_witness_against_code(&code, &witness),
1406            Err(QecError::DistanceBoundValidationFailed(
1407                "witness must be non-identity".to_owned(),
1408            ))
1409        );
1410    }
1411}