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}