1use std::collections::{BTreeMap, BTreeSet};
4use std::sync::{Arc, Mutex};
5
6use glam::IVec3;
7use quizx::fscalar::Zero;
8use quizx::graph::{EType, GraphLike, V, VType};
9use quizx::params::{Parity, Var};
10use quizx::phase::Phase;
11use quizx::tensor::{CompareTensors, TensorF, ToTensor};
12use rand::{Rng, RngExt, SeedableRng, rngs::StdRng};
13use rustc_hash::FxHashMap;
14
15use crate::{
16 Action, ActionNode, BinaryOp, BlockGraph, Expr, FeedbackTarget, NodeKind, Pauli, PauliBasis,
17 PortRole, SelectiveKind, ZXGraph,
18};
19
20use super::correction::{InternalCorrection, ResolvedCorrection, fresh_var};
21use super::{MeasurementKey, VerifyLogicalError, stabilizer_supports_measurement};
22use crate::zx::{CoeffVec, solve_coeff_combination};
23
24pub type QuizxGraph = quizx::vec_graph::Graph;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct BoundaryOrder {
30 inputs: Vec<IVec3>,
31 outputs: Vec<IVec3>,
32}
33
34impl BoundaryOrder {
35 pub fn new(inputs: Vec<IVec3>, outputs: Vec<IVec3>) -> Self {
37 Self { inputs, outputs }
38 }
39
40 pub fn inputs(&self) -> &[IVec3] {
42 &self.inputs
43 }
44
45 pub fn outputs(&self) -> &[IVec3] {
47 &self.outputs
48 }
49
50 fn infer(source: &BlockGraph, zx: &ZXGraph) -> Self {
51 let mut inputs = Vec::new();
52 let mut outputs = Vec::new();
53 for node in zx.nodes().iter().filter(|node| node.kind == NodeKind::Port) {
54 let neighbors = zx.neighbors(node.id).unwrap_or_default();
55 let temporal_input = !neighbors.is_empty()
56 && neighbors
57 .iter()
58 .all(|&neighbor| zx.nodes()[neighbor].pos.z > node.pos.z);
59 let role = source
60 .get_block(node.pos)
61 .and_then(crate::Block::port_role)
62 .unwrap_or(PortRole::Auto);
63 if role.has_input_boundary() || (role == PortRole::Auto && temporal_input) {
64 inputs.push(node.pos);
65 }
66 if role.has_output_boundary() || (role == PortRole::Auto && !temporal_input) {
67 outputs.push(node.pos);
68 }
69 }
70 inputs.sort_by_key(IVec3::to_array);
71 outputs.sort_by_key(IVec3::to_array);
72 let input_keys = inputs
73 .iter()
74 .map(|&position| boundary_tag_key(source, position, true))
75 .collect::<Option<Vec<_>>>();
76 if let Some(mut keys) = input_keys {
77 let mut tagged_inputs = inputs.into_iter().zip(keys.drain(..)).collect::<Vec<_>>();
78 tagged_inputs.sort_by(|(lhs_pos, lhs), (rhs_pos, rhs)| {
79 lhs.cmp(rhs)
80 .then_with(|| lhs_pos.to_array().cmp(&rhs_pos.to_array()))
81 });
82 inputs = tagged_inputs
83 .iter()
84 .map(|(position, _)| *position)
85 .collect();
86 let ranks = tagged_inputs
87 .into_iter()
88 .enumerate()
89 .map(|(rank, (_, key))| (key, rank))
90 .collect::<FxHashMap<_, _>>();
91 outputs.sort_by_key(|&position| {
92 let rank = boundary_tag_key(source, position, false)
93 .and_then(|key| ranks.get(&key).copied())
94 .unwrap_or(usize::MAX);
95 (rank, position.to_array())
96 });
97 }
98 Self { inputs, outputs }
99 }
100
101 fn validate(&self, zx: &ZXGraph) -> Result<(), VerifyLogicalError> {
102 let ports = zx
103 .nodes()
104 .iter()
105 .filter(|node| node.kind == NodeKind::Port)
106 .map(|node| node.pos.to_array())
107 .collect::<BTreeSet<_>>();
108 let collect = |positions: &[IVec3]| {
109 let mut seen = BTreeSet::new();
110 for &position in positions {
111 if !ports.contains(&position.to_array()) {
112 return Err(VerifyLogicalError::InvalidBoundary(position));
113 }
114 if !seen.insert(position.to_array()) {
115 return Err(VerifyLogicalError::DuplicateBoundary(position));
116 }
117 }
118 Ok(seen)
119 };
120 let inputs = collect(&self.inputs)?;
121 let outputs = collect(&self.outputs)?;
122 for position in inputs.intersection(&outputs) {
123 let pos = IVec3::from_array(*position);
124 if zx
125 .node_at(pos)
126 .is_none_or(|node| node.role != PortRole::Multiplex)
127 {
128 return Err(VerifyLogicalError::DuplicateBoundary(pos));
129 }
130 }
131 for node in zx
132 .nodes()
133 .iter()
134 .filter(|node| node.role == PortRole::Multiplex)
135 {
136 if !inputs.contains(&node.pos.to_array()) || !outputs.contains(&node.pos.to_array()) {
137 return Err(VerifyLogicalError::MissingBoundary(node.pos));
138 }
139 }
140 let seen = inputs.union(&outputs).copied().collect::<BTreeSet<_>>();
141 if let Some(position) = ports.difference(&seen).next() {
142 return Err(VerifyLogicalError::MissingBoundary(IVec3::from_array(
143 *position,
144 )));
145 }
146 Ok(())
147 }
148}
149
150fn boundary_tag_key(source: &BlockGraph, position: IVec3, input: bool) -> Option<String> {
151 let tag = source.get_block(position)?.tag()?.to_ascii_lowercase();
152 let prefixes: &[&str] = if input {
153 &["input", "in"]
154 } else {
155 &["output", "out"]
156 };
157 prefixes.iter().find_map(|prefix| {
158 tag.strip_prefix(prefix)
159 .map(|suffix| suffix.trim_start_matches('_').to_string())
160 })
161}
162
163pub type BranchAssignment = BTreeMap<String, bool>;
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum BranchStatus {
169 Instantiated,
171 Rejected,
173 Impossible,
175 Verified,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct LogicalBranch {
182 status: BranchStatus,
183 classical_state: BTreeMap<String, bool>,
184}
185
186impl LogicalBranch {
187 pub const fn status(&self) -> BranchStatus {
189 self.status
190 }
191
192 pub fn classical_state(&self) -> &BTreeMap<String, bool> {
195 &self.classical_state
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct LogicalVerificationReport {
202 pub samples: usize,
204 pub verified: usize,
206 pub rejected: usize,
208 pub impossible: usize,
210}
211
212#[derive(Debug, Clone)]
213struct SelectiveSite {
214 vertex: V,
215 kind: SelectiveKind,
216 is_state: bool,
217}
218
219#[derive(Debug, Clone, Copy)]
220struct WireAnchor {
221 target: V,
222 next: V,
223 edge_type: EType,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227enum InternalMeasurements {
228 Ignore,
229 Correct,
230}
231
232const MAX_CACHED_STRUCTURAL_PROJECTIONS: usize = 64;
233
234#[derive(Debug, Clone)]
235struct StructuralVerifier {
236 actions: Vec<Action>,
237 source: BlockGraph,
238 targets: Vec<IVec3>,
239 boundaries: BoundaryOrder,
240 internal_measurements: InternalMeasurements,
241 arms: Arc<Mutex<BTreeMap<Vec<bool>, Arc<LogicalVerifier>>>>,
242}
243
244impl StructuralVerifier {
245 fn arm(&self, values: &[bool]) -> Result<Arc<LogicalVerifier>, VerifyLogicalError> {
246 if let Some(arm) = self
247 .arms
248 .lock()
249 .expect("projection cache is not poisoned")
250 .get(values)
251 {
252 return Ok(Arc::clone(arm));
253 }
254 let projected = self
255 .source
256 .project_branches(self.targets.iter().copied().zip(values.iter().copied()))?;
257 let arm = Arc::new(LogicalVerifier::with_boundaries_from_zx(
258 projected.to_zx_graph()?,
259 self.boundaries.clone(),
260 self.internal_measurements,
261 )?);
262 let mut cache = self.arms.lock().expect("projection cache is not poisoned");
263 if cache.len() >= MAX_CACHED_STRUCTURAL_PROJECTIONS {
266 cache.clear();
267 }
268 cache.insert(values.to_vec(), Arc::clone(&arm));
269 Ok(arm)
270 }
271
272 fn values_for(
273 &self,
274 assignment: &BranchAssignment,
275 inputs: &BTreeSet<String>,
276 ) -> Result<Vec<bool>, VerifyLogicalError> {
277 let mut state = assigned_inputs(inputs, assignment)?;
278 for action in &self.actions {
279 let Action::Measure { name, .. } = action else {
280 continue;
281 };
282 let value = assignment
283 .get(name)
284 .copied()
285 .ok_or_else(|| VerifyLogicalError::MissingMeasurementValue(name.clone()))?;
286 state.insert(name.clone(), value);
287 }
288
289 let mut values = Vec::new();
290 for action in &self.actions {
291 match action {
292 Action::Let { name, expr } => {
293 state.insert(name.clone(), eval_expr(expr, &state));
294 }
295 Action::Branch { condition, .. } => {
296 values.push(eval_expr(condition, &state));
297 }
298 _ => {}
299 }
300 }
301 Ok(values)
302 }
303}
304
305#[derive(Debug, Clone)]
308pub struct LogicalVerifier {
309 symbolic: QuizxGraph,
310 boundaries: BoundaryOrder,
311 actions: Vec<ActionNode>,
312 inputs: BTreeSet<String>,
313 measurement_vars: BTreeMap<String, Var>,
314 feedback_vars: FxHashMap<usize, Var>,
315 selectives: FxHashMap<IVec3, SelectiveSite>,
316 correction: Option<InternalCorrection>,
317 structural: Option<StructuralVerifier>,
318}
319
320struct ResolvedAssignment {
321 branch: LogicalBranch,
322 variable_values: FxHashMap<Var, bool>,
323 selective_values: FxHashMap<IVec3, PauliBasis>,
324 feedback_values: FxHashMap<usize, bool>,
325 correction: Option<ResolvedCorrection>,
326}
327
328impl LogicalVerifier {
329 pub fn boundaries(&self) -> &BoundaryOrder {
331 &self.boundaries
332 }
333 pub fn new(source: &BlockGraph) -> Result<Self, VerifyLogicalError> {
341 Self::from_source(source, None, InternalMeasurements::Ignore)
342 }
343
344 pub fn with_boundaries(
350 source: &BlockGraph,
351 boundaries: BoundaryOrder,
352 ) -> Result<Self, VerifyLogicalError> {
353 Self::from_source(source, Some(boundaries), InternalMeasurements::Ignore)
354 }
355
356 pub fn with_internal_measurements(source: &BlockGraph) -> Result<Self, VerifyLogicalError> {
364 Self::from_source(source, None, InternalMeasurements::Correct)
365 }
366
367 pub fn with_boundaries_and_internal_measurements(
373 source: &BlockGraph,
374 boundaries: BoundaryOrder,
375 ) -> Result<Self, VerifyLogicalError> {
376 Self::from_source(source, Some(boundaries), InternalMeasurements::Correct)
377 }
378
379 fn from_source(
380 source: &BlockGraph,
381 boundaries: Option<BoundaryOrder>,
382 internal_measurements: InternalMeasurements,
383 ) -> Result<Self, VerifyLogicalError> {
384 source.require_flat_hierarchy("logical verification")?;
385 let regions = source.branch_regions()?;
386 if regions.is_empty() {
387 let zx = source.to_zx_graph()?;
388 let boundaries = boundaries.unwrap_or_else(|| BoundaryOrder::infer(source, &zx));
389 return Self::with_boundaries_from_zx(zx, boundaries, internal_measurements);
390 }
391 let assignments = source
392 .branch_assignments_up_to(1)?
393 .pop()
394 .expect("Boolean branch conditions have a reachable assignment");
395 let projected = source.project_branches(assignments.iter().copied())?;
396 let zx = projected.to_zx_graph()?;
397 let boundaries = boundaries.unwrap_or_else(|| BoundaryOrder::infer(&projected, &zx));
398 let mut verifier =
399 Self::with_boundaries_from_zx(zx, boundaries.clone(), internal_measurements)?;
400 let values = assignments.iter().map(|(_, value)| *value).collect();
401 let arms = Arc::new(Mutex::new(BTreeMap::from([(
402 values,
403 Arc::new(verifier.clone()),
404 )])));
405 verifier.structural = Some(StructuralVerifier {
406 actions: source.actions(),
407 source: source.clone(),
408 targets: assignments.iter().map(|(target, _)| *target).collect(),
409 boundaries,
410 internal_measurements,
411 arms,
412 });
413 Ok(verifier)
414 }
415
416 fn with_boundaries_from_zx(
417 zx: ZXGraph,
418 boundaries: BoundaryOrder,
419 internal_measurements: InternalMeasurements,
420 ) -> Result<Self, VerifyLogicalError> {
421 zx.validate_for_program()?;
422 boundaries.validate(&zx)?;
423 let actions = zx
424 .action_graph()
425 .ordered_nodes()
426 .cloned()
427 .collect::<Vec<_>>();
428 let inputs = zx.action_graph().inputs().map(str::to_owned).collect();
429
430 let mut next_variable = 0;
431 let mut measurement_vars = BTreeMap::new();
432 let mut feedback_vars = FxHashMap::default();
433 for action in &actions {
434 match &action.action {
435 Action::Measure { name, .. } => {
436 measurement_vars.insert(name.clone(), fresh_var(&mut next_variable)?);
437 }
438 Action::Feedback { targets, .. } if !targets.is_empty() => {
439 feedback_vars.insert(action.ordinal, fresh_var(&mut next_variable)?);
440 }
441 _ => {}
442 }
443 }
444
445 let named_phases = named_measurement_phases(&zx, &actions, &measurement_vars)?;
446 let correction = match internal_measurements {
447 InternalMeasurements::Ignore => None,
448 InternalMeasurements::Correct => Some(InternalCorrection::new(
449 &zx,
450 &named_phases,
451 &measurement_vars,
452 &mut next_variable,
453 )?),
454 };
455 let phases = correction
456 .as_ref()
457 .map(InternalCorrection::measurement_vars)
458 .unwrap_or(&named_phases);
459 let (symbolic, selectives) =
460 build_symbolic_graph(&zx, &boundaries, &actions, phases, &feedback_vars)?;
461 Ok(Self {
462 symbolic,
463 boundaries,
464 actions,
465 inputs,
466 measurement_vars,
467 feedback_vars,
468 selectives,
469 correction,
470 structural: None,
471 })
472 }
473
474 pub fn instantiate(
480 &self,
481 assignment: &BranchAssignment,
482 ) -> Result<(LogicalBranch, Option<QuizxGraph>), VerifyLogicalError> {
483 self.instantiate_with(assignment, |_| false)
484 }
485
486 pub fn sample<R: Rng + ?Sized>(
492 &self,
493 rng: &mut R,
494 ) -> Result<(LogicalBranch, Option<QuizxGraph>), VerifyLogicalError> {
495 let assignment = self
496 .inputs
497 .iter()
498 .chain(self.measurement_vars.keys())
499 .map(|name| (name.clone(), rng.random()))
500 .collect::<BranchAssignment>();
501 self.instantiate_with(&assignment, |_| rng.random())
502 }
503
504 pub fn sample_with_seed(
510 &self,
511 seed: u64,
512 ) -> Result<(LogicalBranch, Option<QuizxGraph>), VerifyLogicalError> {
513 self.sample(&mut StdRng::seed_from_u64(seed))
514 }
515
516 pub fn verify_branch(
522 &self,
523 expected: &QuizxGraph,
524 assignment: &BranchAssignment,
525 ) -> Result<LogicalBranch, VerifyLogicalError> {
526 self.check_boundary_arity(expected)?;
527 let expected_tensor = contract(expected);
528 if !tensor_is_nonzero(&expected_tensor) {
529 return Err(VerifyLogicalError::ZeroExpectedMap);
530 }
531 let (branch, diagram) = self.instantiate(assignment)?;
532 compare_branch(branch, diagram, &expected_tensor)
533 }
534
535 pub fn verify(
541 &self,
542 expected: &QuizxGraph,
543 samples: usize,
544 seed: u64,
545 ) -> Result<LogicalVerificationReport, VerifyLogicalError> {
546 self.check_boundary_arity(expected)?;
547 let expected_tensor = contract(expected);
548 if !tensor_is_nonzero(&expected_tensor) {
549 return Err(VerifyLogicalError::ZeroExpectedMap);
550 }
551 let mut rng = StdRng::seed_from_u64(seed);
552 let mut report = LogicalVerificationReport {
553 samples,
554 verified: 0,
555 rejected: 0,
556 impossible: 0,
557 };
558 for _ in 0..samples {
559 let (branch, diagram) = self.sample(&mut rng)?;
560 let branch = compare_branch(branch, diagram, &expected_tensor)?;
561 match branch.status {
562 BranchStatus::Instantiated => {
563 unreachable!("map comparison always resolves an instantiated branch")
564 }
565 BranchStatus::Rejected => report.rejected += 1,
566 BranchStatus::Impossible => report.impossible += 1,
567 BranchStatus::Verified => report.verified += 1,
568 }
569 }
570 if report.verified == 0 {
571 return Err(VerifyLogicalError::NoVerifiedBranch { samples });
572 }
573 Ok(report)
574 }
575
576 fn check_boundary_arity(&self, expected: &QuizxGraph) -> Result<(), VerifyLogicalError> {
577 let actual_inputs = self.boundaries.inputs.len();
578 let actual_outputs = self.boundaries.outputs.len();
579 let expected_inputs = expected.inputs().len();
580 let expected_outputs = expected.outputs().len();
581 if (actual_inputs, actual_outputs) != (expected_inputs, expected_outputs) {
582 return Err(VerifyLogicalError::BoundaryArityMismatch {
583 actual_inputs,
584 actual_outputs,
585 expected_inputs,
586 expected_outputs,
587 });
588 }
589 Ok(())
590 }
591
592 fn instantiate_with(
593 &self,
594 assignment: &BranchAssignment,
595 internal_value: impl FnMut(Var) -> bool,
596 ) -> Result<(LogicalBranch, Option<QuizxGraph>), VerifyLogicalError> {
597 if let Some(structural) = &self.structural {
598 let values = structural.values_for(assignment, &self.inputs)?;
599 let arm = structural.arm(&values)?;
600 return arm.instantiate_with(assignment, internal_value);
601 }
602
603 let resolved = self.resolve_assignment(assignment)?;
604 self.instantiate_resolved(&resolved, internal_value)
605 }
606
607 pub(super) fn branch_family(
609 &self,
610 assignment: &BranchAssignment,
611 limit: usize,
612 ) -> Result<(LogicalBranch, Vec<QuizxGraph>), super::feedback::FeedbackInferenceError> {
613 if let Some(structural) = &self.structural {
614 let values = structural.values_for(assignment, &self.inputs)?;
615 return structural.arm(&values)?.branch_family(assignment, limit);
616 }
617 let resolved = self.resolve_assignment(assignment)?;
618 if resolved.branch.status == BranchStatus::Rejected {
619 return Ok((resolved.branch, Vec::new()));
620 }
621 let representatives = match &self.correction {
622 Some(correction) => correction.representatives(
623 &self.boundaries.outputs,
624 &self.actions,
625 &resolved.variable_values,
626 &resolved.feedback_values,
627 resolved
628 .correction
629 .as_ref()
630 .expect("resolved internal branch"),
631 limit,
632 )?,
633 None => vec![FxHashMap::default()],
634 };
635 let diagrams = representatives
636 .into_iter()
637 .map(|values| {
638 let (_, diagram) =
639 self.instantiate_resolved(&resolved, |variable| values[&variable])?;
640 Ok(diagram.expect("resolved branch is not rejected"))
641 })
642 .collect::<Result<Vec<_>, VerifyLogicalError>>()?;
643 Ok((resolved.branch, diagrams))
644 }
645
646 fn resolve_assignment(
647 &self,
648 assignment: &BranchAssignment,
649 ) -> Result<ResolvedAssignment, VerifyLogicalError> {
650 let mut classical_state = assigned_inputs(&self.inputs, assignment)?;
651 let mut variable_values = FxHashMap::<Var, bool>::default();
652 let mut selective_values = FxHashMap::<IVec3, PauliBasis>::default();
653
654 for action in &self.actions {
656 let Action::Measure { name, .. } = &action.action else {
657 continue;
658 };
659 let value = assignment
660 .get(name)
661 .copied()
662 .ok_or_else(|| VerifyLogicalError::MissingMeasurementValue(name.clone()))?;
663 classical_state.insert(name.clone(), value);
664 variable_values.insert(self.measurement_vars[name], value);
665 }
666
667 for action in &self.actions {
668 match &action.action {
669 Action::Let { name, expr } => {
670 classical_state.insert(name.clone(), eval_expr(expr, &classical_state));
671 }
672 Action::DiscardIf(expr) if eval_expr(expr, &classical_state) => {
673 return Ok(ResolvedAssignment {
674 branch: LogicalBranch {
675 status: BranchStatus::Rejected,
676 classical_state,
677 },
678 variable_values,
679 selective_values,
680 feedback_values: FxHashMap::default(),
681 correction: None,
682 });
683 }
684 Action::Resolve { target, condition } => {
685 let site = self
686 .selectives
687 .get(target)
688 .expect("validated resolve target has a selective site");
689 let chosen = if eval_expr(condition, &classical_state) {
690 site.kind.pauli_if_true()
691 } else {
692 site.kind.pauli_if_false()
693 };
694 selective_values.insert(*target, chosen);
695 }
696 Action::Feedback { condition, .. } => {
697 if let Some(variable) = self.feedback_vars.get(&action.ordinal) {
698 let value = condition
699 .as_ref()
700 .is_none_or(|expr| eval_expr(expr, &classical_state));
701 variable_values.insert(*variable, value);
702 }
703 }
704 Action::Branch { .. } | Action::Measure { .. } | Action::DiscardIf(_) => {}
708 }
709 }
710
711 let feedback_values = self
712 .feedback_vars
713 .iter()
714 .map(|(&ordinal, &variable)| (ordinal, variable_values[&variable]))
715 .collect();
716 let correction = self
717 .correction
718 .as_ref()
719 .map(|correction| correction.resolve(&self.actions, &selective_values))
720 .transpose()?;
721 Ok(ResolvedAssignment {
722 branch: LogicalBranch {
723 status: BranchStatus::Instantiated,
724 classical_state,
725 },
726 variable_values,
727 selective_values,
728 feedback_values,
729 correction,
730 })
731 }
732
733 fn instantiate_resolved(
734 &self,
735 resolved: &ResolvedAssignment,
736 mut internal_value: impl FnMut(Var) -> bool,
737 ) -> Result<(LogicalBranch, Option<QuizxGraph>), VerifyLogicalError> {
738 let mut variable_values = resolved.variable_values.clone();
739 if let Some(correction) = &self.correction {
740 for variable in correction.sampled_vars() {
741 variable_values.insert(variable, internal_value(variable));
742 }
743 }
744 if resolved.branch.status == BranchStatus::Rejected {
745 return Ok((resolved.branch.clone(), None));
746 }
747 let selective_values = &resolved.selective_values;
748
749 let (mut diagram, sites) = if let Some(correction) = &self.correction {
750 build_symbolic_graph(
751 correction.zx_graph(),
752 &self.boundaries,
753 &self.actions,
754 &resolved
755 .correction
756 .as_ref()
757 .expect("resolved internal branch")
758 .phases,
759 &self.feedback_vars,
760 )?
761 } else {
762 (self.symbolic.clone(), self.selectives.clone())
763 };
764 for (&position, site) in &sites {
765 let chosen = selective_values[&position];
766 let (vertex_type, base_phase) = measurement_spider(chosen, site.is_state);
767 diagram.set_vertex_type(site.vertex, vertex_type);
768 diagram.set_phase(site.vertex, base_phase);
769 }
770 for vertex in diagram.vertex_vec() {
771 let odd = super::parity::phase_value(&diagram.vars(vertex), |variable| {
772 variable_values.get(&variable).copied().unwrap_or(false)
773 });
774 if odd {
775 diagram.add_to_phase(vertex, Phase::from(1_i64));
776 }
777 diagram.set_vars(vertex, Parity::default());
778 }
779
780 if let Some(correction) = &self.correction {
781 correction.apply(
782 &mut diagram,
783 &self.boundaries.outputs,
784 &variable_values,
785 resolved
786 .correction
787 .as_ref()
788 .expect("resolved internal branch"),
789 &self.actions,
790 &resolved.feedback_values,
791 )?;
792 }
793
794 Ok((resolved.branch.clone(), Some(diagram)))
795 }
796}
797
798fn assigned_inputs(
799 inputs: &BTreeSet<String>,
800 assignment: &BranchAssignment,
801) -> Result<BTreeMap<String, bool>, VerifyLogicalError> {
802 inputs
803 .iter()
804 .map(|name| {
805 assignment
806 .get(name)
807 .copied()
808 .map(|value| (name.clone(), value))
809 .ok_or_else(|| VerifyLogicalError::MissingInputValue(name.clone()))
810 })
811 .collect()
812}
813
814pub fn verify_logical(
820 graph: &BlockGraph,
821 expected: &QuizxGraph,
822 samples: usize,
823 seed: u64,
824) -> Result<LogicalVerificationReport, VerifyLogicalError> {
825 LogicalVerifier::new(graph)?.verify(expected, samples, seed)
826}
827
828fn compare_branch(
829 mut branch: LogicalBranch,
830 diagram: Option<QuizxGraph>,
831 expected: &TensorF,
832) -> Result<LogicalBranch, VerifyLogicalError> {
833 let Some(diagram) = diagram else {
834 branch.status = BranchStatus::Rejected;
835 return Ok(branch);
836 };
837 let actual = contract(&diagram);
838 if !tensor_is_nonzero(&actual) {
839 branch.status = BranchStatus::Impossible;
840 return Ok(branch);
841 }
842 if !<TensorF as CompareTensors>::scalar_eq(&actual, expected) {
843 return Err(VerifyLogicalError::MapMismatch {
844 classical_state: branch.classical_state,
845 });
846 }
847 branch.status = BranchStatus::Verified;
848 Ok(branch)
849}
850
851fn tensor_is_nonzero(tensor: &TensorF) -> bool {
852 tensor.iter().any(|value| !value.is_zero())
853}
854
855fn contract(diagram: &QuizxGraph) -> TensorF {
856 let mut reduced = diagram.clone();
857 quizx::simplify::full_simp(&mut reduced);
858 reduced.to_tensorf()
859}
860
861pub(super) fn eval_expr(expr: &Expr, values: &BTreeMap<String, bool>) -> bool {
862 match expr {
863 Expr::Var(name) => values[name],
864 Expr::Not(inner) => !eval_expr(inner, values),
865 Expr::Binary(op, lhs, rhs) => {
866 let lhs = eval_expr(lhs, values);
867 let rhs = eval_expr(rhs, values);
868 match op {
869 BinaryOp::Xor => lhs ^ rhs,
870 BinaryOp::And => lhs & rhs,
871 BinaryOp::Or => lhs | rhs,
872 }
873 }
874 }
875}
876
877fn named_measurement_phases(
878 zx: &ZXGraph,
879 actions: &[ActionNode],
880 variables: &BTreeMap<String, Var>,
881) -> Result<BTreeMap<MeasurementKey, Parity>, VerifyLogicalError> {
882 let mut phases = BTreeMap::new();
883 let mut crossings = Vec::new();
884 for action in actions {
885 let Action::Measure { name, .. } = &action.action else {
886 continue;
887 };
888 let key = MeasurementKey::for_action(zx, action).expect("validated measurement has a key");
889 if let MeasurementKey::Node { pos, pauli } = key {
890 let node = zx.node_at(pos).expect("validated measurement node");
891 let native = match node.kind {
892 NodeKind::X => Pauli::Z,
893 NodeKind::Z => Pauli::X,
894 _ => pauli,
895 };
896 if native != pauli {
897 crossings.push((name.as_str(), node.id, pauli));
898 }
899 phases.insert(
900 MeasurementKey::Node { pos, pauli: native },
901 Parity::single(variables[name]),
902 );
903 } else {
904 phases.insert(key, Parity::single(variables[name]));
905 }
906 }
907 if crossings.is_empty() {
908 return Ok(phases);
909 }
910
911 let stabilizers = zx.stabilizers().map_err(|source| {
912 VerifyLogicalError::RuntimeBasisInitializationFailed {
913 source: source.into(),
914 }
915 })?;
916 let rows = actions
917 .iter()
918 .filter_map(|action| {
919 let Action::Measure { name, .. } = &action.action else {
920 return None;
921 };
922 let row = stabilizers
923 .generators
924 .iter()
925 .find(|generator| generator.measurement_name() == Some(name))
926 .expect("canonical table includes each named measurement");
927 Some((name.as_str(), &row.stabilizer))
928 })
929 .collect::<Vec<_>>();
930 for (name, node_id, pauli) in crossings {
931 let row = rows
932 .iter()
933 .find(|(measured, _)| *measured == name)
934 .expect("named row exists")
935 .1;
936 for &neighbor in zx.neighbors(node_id).expect("measurement node exists") {
937 let edge = zx
938 .edge_between(node_id, neighbor)
939 .expect("incident edge exists");
940 let (first, second) = sorted_pair(node_id, neighbor);
941 let key = MeasurementKey::Edge {
942 src: zx.nodes()[first].pos,
943 dst: zx.nodes()[second].pos,
944 pauli: if node_id == second && edge.hadamard {
945 pauli.flip()
946 } else {
947 pauli
948 },
949 };
950 if stabilizer_supports_measurement(row, &key) {
951 phases.entry(key).or_default();
952 }
953 }
954 }
955 let columns = phases
958 .keys()
959 .map(|key| {
960 let mut column = CoeffVec::zeros(rows.len());
961 for (index, (_, row)) in rows.iter().enumerate() {
962 column.set_bit(index, stabilizer_supports_measurement(row, key));
963 }
964 column
965 })
966 .collect::<Vec<_>>();
967 for parity in phases.values_mut() {
968 *parity = Parity::default();
969 }
970 for (index, &(name, _)) in rows.iter().enumerate() {
971 let solution =
972 solve_coeff_combination(&columns, &CoeffVec::singleton(index, rows.len()))
973 .ok_or_else(|| VerifyLogicalError::MeasurementPhaseUnavailable(name.to_owned()))?;
974 for (candidate, parity) in phases.values_mut().enumerate() {
975 if solution.bit(candidate) {
976 *parity = &*parity + &Parity::single(variables[name]);
977 }
978 }
979 }
980 phases.retain(|_, parity| !parity.is_empty());
981 Ok(phases)
982}
983
984fn build_symbolic_graph(
985 zx: &ZXGraph,
986 boundaries: &BoundaryOrder,
987 actions: &[ActionNode],
988 measurement_key_vars: &BTreeMap<MeasurementKey, Parity>,
989 feedback_vars: &FxHashMap<usize, Var>,
990) -> Result<(QuizxGraph, FxHashMap<IVec3, SelectiveSite>), VerifyLogicalError> {
991 let mut graph = QuizxGraph::new();
992 let mut node_vertices = Vec::with_capacity(zx.nodes().len());
993 let mut input_boundaries = FxHashMap::default();
994 let mut output_boundaries = FxHashMap::default();
995 let mut selectives = FxHashMap::default();
996 for node in zx.nodes() {
997 if node.kind == NodeKind::Port && node.role == PortRole::Multiplex {
998 let spider = graph.add_vertex_with_phase(VType::Z, Phase::from(0_i64));
999 let input = graph.add_vertex_with_phase(VType::B, Phase::from(0_i64));
1000 let output = graph.add_vertex_with_phase(VType::B, Phase::from(0_i64));
1001 graph.set_coord(spider, (f64::from(node.pos.z), f64::from(node.pos.x)));
1002 graph.set_coord(input, (f64::from(node.pos.z) - 0.25, f64::from(node.pos.x)));
1003 graph.set_coord(
1004 output,
1005 (f64::from(node.pos.z) + 0.25, f64::from(node.pos.x)),
1006 );
1007 graph.add_edge_with_type(input, spider, EType::N);
1008 graph.add_edge_with_type(spider, output, EType::N);
1009 input_boundaries.insert(node.pos, input);
1010 output_boundaries.insert(node.pos, output);
1011 node_vertices.push(spider);
1012 continue;
1013 }
1014 let is_state = is_state_boundary(zx, node.id);
1015 let (vertex_type, phase) = match node.kind {
1016 NodeKind::X => (VType::X, Phase::from(0_i64)),
1017 NodeKind::Z => (VType::Z, Phase::from(0_i64)),
1018 NodeKind::Y => measurement_spider(PauliBasis::Y, is_state),
1019 NodeKind::Port => (VType::B, Phase::from(0_i64)),
1020 NodeKind::T => (VType::Z, Phase::from((1_i64, 4_i64))),
1021 NodeKind::Selective(kind) => {
1022 let vertex = graph.add_vertex_with_phase(VType::Z, Phase::from(0_i64));
1023 graph.set_coord(vertex, (f64::from(node.pos.z), f64::from(node.pos.x)));
1024 node_vertices.push(vertex);
1025 selectives.insert(
1026 node.pos,
1027 SelectiveSite {
1028 vertex,
1029 kind,
1030 is_state,
1031 },
1032 );
1033 continue;
1034 }
1035 };
1036 let vertex = graph.add_vertex_with_phase(vertex_type, phase);
1037 graph.set_coord(vertex, (f64::from(node.pos.z), f64::from(node.pos.x)));
1038 if node.kind == NodeKind::Port {
1039 input_boundaries.insert(node.pos, vertex);
1040 output_boundaries.insert(node.pos, vertex);
1041 }
1042 node_vertices.push(vertex);
1043 }
1044
1045 let mut edge_measurements = FxHashMap::<(usize, usize), Vec<(PauliBasis, &Parity)>>::default();
1046 for (key, parity) in measurement_key_vars {
1047 match key {
1048 MeasurementKey::Node { pos, .. } => {
1049 let node = zx.node_at(*pos).expect("scheduled measurement node exists");
1050 graph.add_to_vars(node_vertices[node.id], parity);
1051 }
1052 MeasurementKey::Edge { src, dst, pauli } => {
1053 let lhs = zx.node_at(*src).expect("scheduled measurement source").id;
1054 let rhs = zx
1055 .node_at(*dst)
1056 .expect("scheduled measurement destination")
1057 .id;
1058 let basis = match pauli {
1059 Pauli::X => PauliBasis::X,
1060 Pauli::Z => PauliBasis::Z,
1061 Pauli::I | Pauli::Y => unreachable!("wire phases use X or Z components"),
1062 };
1063 edge_measurements
1064 .entry(sorted_pair(lhs, rhs))
1065 .or_default()
1066 .push((basis, parity));
1067 }
1068 }
1069 }
1070 let mut anchors = FxHashMap::<(usize, usize), WireAnchor>::default();
1071 for edge in zx.edges().iter().filter(|edge| edge.n1 < edge.n2) {
1072 let lhs = node_vertices[edge.n1];
1073 let rhs = node_vertices[edge.n2];
1074 let edge_type = if edge.hadamard { EType::H } else { EType::N };
1075 let mut first = None;
1076 let mut last = lhs;
1077 for (observable, parity) in edge_measurements
1078 .get(&(edge.n1, edge.n2))
1079 .into_iter()
1080 .flatten()
1081 {
1082 let (vertex_type, _) = measurement_spider(*observable, false);
1083 let measurement = graph.add_vertex_with_phase(vertex_type, Phase::from(0_i64));
1084 graph.set_vars(measurement, (*parity).clone());
1085 graph.add_edge_with_type(last, measurement, EType::N);
1086 first.get_or_insert(measurement);
1087 last = measurement;
1088 }
1089 graph.add_edge_with_type(last, rhs, edge_type);
1090 anchors.insert(
1091 (edge.n1, edge.n2),
1092 WireAnchor {
1093 target: lhs,
1094 next: first.unwrap_or(rhs),
1095 edge_type: if first.is_some() { EType::N } else { edge_type },
1096 },
1097 );
1098 anchors.insert(
1099 (edge.n2, edge.n1),
1100 WireAnchor {
1101 target: rhs,
1102 next: last,
1103 edge_type,
1104 },
1105 );
1106 }
1107
1108 for action in actions {
1109 match &action.action {
1110 Action::Feedback { targets, .. } if !targets.is_empty() => {
1111 let variable = feedback_vars[&action.ordinal];
1112 for target in targets {
1113 insert_feedback(
1114 &mut graph,
1115 zx,
1116 &node_vertices,
1117 &mut anchors,
1118 target,
1119 variable,
1120 )?;
1121 }
1122 }
1123 _ => {}
1124 }
1125 }
1126
1127 graph.set_inputs(
1128 boundaries
1129 .inputs
1130 .iter()
1131 .map(|position| input_boundaries[position])
1132 .collect(),
1133 );
1134 graph.set_outputs(
1135 boundaries
1136 .outputs
1137 .iter()
1138 .map(|position| output_boundaries[position])
1139 .collect(),
1140 );
1141 Ok((graph, selectives))
1142}
1143
1144fn insert_feedback(
1145 graph: &mut QuizxGraph,
1146 zx: &ZXGraph,
1147 node_vertices: &[V],
1148 anchors: &mut FxHashMap<(usize, usize), WireAnchor>,
1149 target: &FeedbackTarget,
1150 variable: Var,
1151) -> Result<(), VerifyLogicalError> {
1152 let node = zx
1153 .node_at(target.target)
1154 .expect("validated feedback target exists");
1155 let neighbor = zx
1156 .feedback_edge(target)
1157 .ok_or(VerifyLogicalError::FeedbackTargetWithoutWire {
1158 target: target.target,
1159 pauli: target.pauli,
1160 })?
1161 .n2;
1162 let vertex_types = match target.pauli {
1163 PauliBasis::X => [Some(VType::X), None],
1164 PauliBasis::Y => [Some(VType::Z), Some(VType::X)],
1165 PauliBasis::Z => [Some(VType::Z), None],
1166 };
1167 for vertex_type in vertex_types.into_iter().flatten() {
1168 let anchor = *anchors
1169 .get(&(node.id, neighbor))
1170 .expect("every original edge has anchors in both directions");
1171 graph.remove_edge(anchor.target, anchor.next);
1172 let correction = graph.add_vertex_with_phase(vertex_type, Phase::from(0_i64));
1173 graph.set_vars(correction, Parity::single(variable));
1174 graph.add_edge_with_type(anchor.target, correction, EType::N);
1175 graph.add_edge_with_type(correction, anchor.next, anchor.edge_type);
1176 anchors.insert(
1177 (node.id, neighbor),
1178 WireAnchor {
1179 target: anchor.target,
1180 next: correction,
1181 edge_type: EType::N,
1182 },
1183 );
1184
1185 let reverse = anchors
1190 .get_mut(&(neighbor, node.id))
1191 .expect("every original edge has a reverse anchor");
1192 if reverse.next == anchor.target {
1193 reverse.next = correction;
1194 reverse.edge_type = anchor.edge_type;
1195 }
1196 }
1197 debug_assert_eq!(node_vertices[node.id], anchors[&(node.id, neighbor)].target);
1198 Ok(())
1199}
1200
1201fn is_state_boundary(zx: &ZXGraph, node: usize) -> bool {
1202 let position = zx.nodes()[node].pos;
1203 let neighbors = zx.neighbors(node).unwrap_or_default();
1204 !neighbors.is_empty()
1205 && neighbors
1206 .iter()
1207 .all(|&neighbor| zx.nodes()[neighbor].pos.z > position.z)
1208}
1209
1210fn measurement_spider(pauli: PauliBasis, is_state: bool) -> (VType, Phase) {
1211 match pauli {
1212 PauliBasis::X => (VType::Z, Phase::from(0_i64)),
1213 PauliBasis::Z => (VType::X, Phase::from(0_i64)),
1214 PauliBasis::Y if is_state => (VType::Z, Phase::from((1_i64, 2_i64))),
1215 PauliBasis::Y => (VType::Z, Phase::from((-1_i64, 2_i64))),
1216 }
1217}
1218
1219fn sorted_pair(lhs: usize, rhs: usize) -> (usize, usize) {
1220 if lhs <= rhs { (lhs, rhs) } else { (rhs, lhs) }
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use quizx::circuit::Circuit;
1226
1227 use super::*;
1228 use crate::{Basis, Block, BlockKind, BranchArm, CubeKind, Direction, MeasureTarget, Pipe};
1229
1230 fn circuit_graph(qubits: usize, gates: &[(&str, Vec<usize>)]) -> QuizxGraph {
1231 let mut circuit = Circuit::new(qubits);
1232 for (gate, operands) in gates {
1233 circuit.add_gate(gate, operands.clone());
1234 }
1235 circuit.to_graph()
1236 }
1237
1238 fn identity_source() -> BlockGraph {
1239 let mut source = BlockGraph::new();
1240 source.add_block(Block::new(IVec3::new(0, 0, 0), BlockKind::Port));
1241 source.add_block(Block::new(
1242 IVec3::new(0, 0, 1),
1243 BlockKind::Cube(CubeKind::ZXZ),
1244 ));
1245 source.add_block(Block::new(IVec3::new(0, 0, 2), BlockKind::Port));
1246 source.add_pipe(Pipe::new(IVec3::new(0, 0, 0), Direction::ZPLUS));
1247 source.add_pipe(Pipe::new(IVec3::new(0, 0, 1), Direction::ZPLUS));
1248 source
1249 }
1250
1251 fn add_test_branch(source: &mut BlockGraph, name: impl Into<String>, past: IVec3) -> IVec3 {
1252 let target = past + IVec3::Z;
1253 source.add_block(Block::new(past, BlockKind::Cube(CubeKind::ZXZ)));
1254 let arm = |kind| {
1255 BranchArm::new(
1256 vec![Block::new(target, kind)],
1257 vec![Pipe::new(past, Direction::ZPLUS)],
1258 )
1259 };
1260 source
1261 .try_add_branch_region(
1262 name,
1263 arm(BlockKind::Measurement(Basis::X)),
1264 arm(BlockKind::Cube(CubeKind::ZXZ)),
1265 )
1266 .expect("matching terminal arms")
1267 }
1268
1269 fn conditional_scalar_source() -> BlockGraph {
1270 let past = IVec3::new(0, 0, 0);
1271 let target = IVec3::new(0, 0, 1);
1272 let controller = IVec3::new(2, 0, 0);
1273 let mut source = BlockGraph::new();
1274 source.add_block(Block::new(controller, BlockKind::Cube(CubeKind::ZXZ)));
1275 assert_eq!(add_test_branch(&mut source, "b0", past), target);
1276 source
1277 .set_actions(vec![
1278 Action::Measure {
1279 target: MeasureTarget::Node(controller),
1280 name: "m".into(),
1281 },
1282 Action::Branch {
1283 target,
1284 condition: Expr::Var("m".into()),
1285 },
1286 ])
1287 .expect("conditional scalar family validates");
1288 source
1289 }
1290
1291 #[test]
1292 fn cancelled_named_measurement_normalization_keeps_its_typed_cause() {
1293 let source = crate::GalleryItem::ThreeBitAdder.build().flatten().unwrap();
1294 let token = crate::CancellationToken::new();
1295 let error = token.scope(|| {
1296 token.cancel();
1297 LogicalVerifier::new(&source).unwrap_err()
1298 });
1299 let mut cause: &(dyn std::error::Error + 'static) = &error;
1300 while !cause.is::<crate::ComputationCancelled>() {
1301 cause = cause
1302 .source()
1303 .expect("verifier preserves cancellation cause");
1304 }
1305 LogicalVerifier::new(&identity_source()).unwrap();
1306 }
1307
1308 #[test]
1309 fn identity_wire_matches_quizx_identity() {
1310 let source = identity_source();
1311 let verifier = LogicalVerifier::new(&source).unwrap();
1312 let (branch, diagram) = verifier.instantiate(&BranchAssignment::new()).unwrap();
1313 assert_eq!(branch.status(), BranchStatus::Instantiated);
1314 assert!(diagram.is_some());
1315 let expected = circuit_graph(1, &[]);
1316 let report = verifier.verify(&expected, 1, 0).unwrap();
1317
1318 assert_eq!(report.verified, 1);
1319 }
1320
1321 #[test]
1322 fn multiplex_port_is_a_three_leg_z_spider() {
1323 let mut source = BlockGraph::new();
1324 source.add_block(
1325 Block::new(IVec3::ZERO, BlockKind::Port)
1326 .with_port_role(PortRole::Multiplex)
1327 .unwrap(),
1328 );
1329 source.add_block(Block::new(IVec3::X, BlockKind::Cube(CubeKind::ZXZ)));
1330 source.add_pipe(Pipe::new(IVec3::ZERO, Direction::XPLUS));
1331
1332 let verifier = LogicalVerifier::new(&source).unwrap();
1333 assert_eq!(verifier.boundaries.inputs, [IVec3::ZERO]);
1334 assert_eq!(verifier.boundaries.outputs, [IVec3::ZERO]);
1335 let input = verifier.symbolic.inputs()[0];
1336 let output = verifier.symbolic.outputs()[0];
1337 assert_ne!(input, output);
1338 let spider = verifier.symbolic.neighbors(input).next().unwrap();
1339 assert_eq!(verifier.symbolic.vertex_type(spider), VType::Z);
1340 assert_eq!(verifier.symbolic.degree(spider), 3);
1341 assert!(verifier.symbolic.neighbors(spider).any(|v| v == output));
1342 }
1343
1344 #[test]
1345 fn conditional_source_selects_a_projected_static_verifier() {
1346 let verifier = LogicalVerifier::new(&conditional_scalar_source()).unwrap();
1347 let structural = verifier
1348 .structural
1349 .as_ref()
1350 .expect("structural source caches projected verifiers");
1351 assert_eq!(structural.arms.lock().unwrap().len(), 1);
1352
1353 let mut vertex_type_counts = Vec::new();
1354 for value in [false, true] {
1355 let assignment = BTreeMap::from([("m".to_string(), value)]);
1356 let (branch, diagram) = verifier.instantiate(&assignment).unwrap();
1357 assert_eq!(branch.status(), BranchStatus::Instantiated);
1358 assert_eq!(branch.classical_state().get("m"), Some(&value));
1359 let diagram = diagram.expect("selected static arm has a diagram");
1360 vertex_type_counts.push((
1361 diagram
1362 .vertex_vec()
1363 .iter()
1364 .filter(|&&vertex| diagram.vertex_type(vertex) == VType::X)
1365 .count(),
1366 diagram
1367 .vertex_vec()
1368 .iter()
1369 .filter(|&&vertex| diagram.vertex_type(vertex) == VType::Z)
1370 .count(),
1371 ));
1372 }
1373 assert_ne!(
1374 vertex_type_counts[0], vertex_type_counts[1],
1375 "authored false and true arms select different static diagrams"
1376 );
1377 assert_eq!(structural.arms.lock().unwrap().len(), 2);
1378 }
1379
1380 #[test]
1381 fn verifier_limit_counts_reachable_tuples_not_branch_sites() {
1382 let controller = IVec3::new(20, 0, 0);
1383 let mut source = BlockGraph::new();
1384 source.add_block(Block::new(controller, BlockKind::Cube(CubeKind::ZXZ)));
1385 let mut actions = vec![Action::Measure {
1386 target: MeasureTarget::Node(controller),
1387 name: "m".into(),
1388 }];
1389 for x in 0..9 {
1390 let past = IVec3::new(2 * x, 0, 0);
1391 let target = add_test_branch(&mut source, format!("b{x}"), past);
1392 actions.push(Action::Branch {
1393 target,
1394 condition: Expr::Var("m".into()),
1395 });
1396 }
1397 source.set_actions(actions).unwrap();
1398
1399 let verifier = LogicalVerifier::new(&source).unwrap();
1400 for value in [false, true] {
1401 verifier
1402 .instantiate(&BTreeMap::from([("m".to_owned(), value)]))
1403 .unwrap();
1404 }
1405 assert_eq!(verifier.structural.unwrap().arms.lock().unwrap().len(), 2);
1406 }
1407
1408 #[test]
1409 fn verifier_projection_cache_is_bounded_without_limiting_branch_count() {
1410 let mut source = BlockGraph::new();
1411 let mut actions = Vec::new();
1412 for x in 0..9 {
1413 let x = 3 * x;
1414 let past = IVec3::new(x, 0, 0);
1415 let controller = IVec3::new(x, 2, 0);
1416 source.add_block(Block::new(controller, BlockKind::Cube(CubeKind::ZXZ)));
1417 let target = add_test_branch(&mut source, format!("b{x}"), past);
1418 let name = format!("m{x}");
1419 actions.push(Action::Measure {
1420 target: MeasureTarget::Node(controller),
1421 name: name.clone(),
1422 });
1423 actions.push(Action::Branch {
1424 target,
1425 condition: Expr::Var(name),
1426 });
1427 }
1428 source.set_actions_lenient(actions).unwrap();
1429
1430 let verifier = LogicalVerifier::new(&source).unwrap();
1431 assert_eq!(
1432 verifier
1433 .structural
1434 .as_ref()
1435 .unwrap()
1436 .arms
1437 .lock()
1438 .unwrap()
1439 .len(),
1440 1
1441 );
1442 for mask in 0..70 {
1443 let assignment = (0..9)
1444 .map(|bit| (format!("m{}", 3 * bit), mask & (1 << bit) != 0))
1445 .collect();
1446 verifier.instantiate(&assignment).unwrap();
1447 }
1448 assert!(
1449 verifier
1450 .structural
1451 .as_ref()
1452 .unwrap()
1453 .arms
1454 .lock()
1455 .unwrap()
1456 .len()
1457 <= MAX_CACHED_STRUCTURAL_PROJECTIONS
1458 );
1459 }
1460
1461 #[test]
1462 fn named_selective_phases_survive_both_fills() {
1463 let source = crate::GalleryItem::TComparison
1464 .build()
1465 .materialize_root_graph()
1466 .expect("gallery flat projection");
1467 let verifier = LogicalVerifier::with_internal_measurements(&source).unwrap();
1468 let correction = verifier.correction.as_ref().unwrap();
1469 let site = IVec3::new(1, 0, 2);
1470 let sampled = correction.sampled_vars().collect::<BTreeSet<_>>();
1471 assert!(
1472 correction.measurement_vars().iter().any(|(key, parity)| {
1473 matches!(key, MeasurementKey::Node { pos, .. } if *pos == site)
1474 && parity.iter().any(|variable| sampled.contains(&variable))
1475 }),
1476 "named selective must carry a nontrivial unnamed compensation"
1477 );
1478 for chosen in [PauliBasis::X, PauliBasis::Y] {
1479 let resolved = correction
1480 .resolve(&verifier.actions, &FxHashMap::from_iter([(site, chosen)]))
1481 .unwrap();
1482 let basis = resolved.basis;
1483 let phases = resolved.phases;
1484 for name in ["cmp", "parity"] {
1485 let row = basis
1486 .derive_measurement_surface(name, i64::MAX)
1487 .unwrap()
1488 .stabilizer;
1489 let actual = phases
1490 .iter()
1491 .filter(|(key, _)| stabilizer_supports_measurement(&row, key))
1492 .fold(Parity::default(), |parity, (_, term)| &parity + term);
1493 assert_eq!(
1494 actual,
1495 Parity::single(verifier.measurement_vars[name]),
1496 "{chosen:?} {name}"
1497 );
1498 }
1499 }
1500 }
1501
1502 #[test]
1503 fn unnamed_phases_preserve_the_supplied_named_measurement_parities() {
1504 let source = crate::GalleryItem::CCZInjectedAnd
1505 .build()
1506 .materialize_root_graph()
1507 .expect("gallery flat projection");
1508 let zx = source.to_zx_graph().unwrap();
1509 let stabilizers = zx.stabilizers().unwrap();
1510 let verifier = LogicalVerifier::with_internal_measurements(&source).unwrap();
1511 let correction = verifier.correction.as_ref().unwrap();
1512 assert!(correction.sampled_vars().next().is_some());
1513 for generator in &stabilizers.generators {
1514 let Some(name) = generator.measurement_name() else {
1515 continue;
1516 };
1517 let actual = correction
1518 .measurement_vars()
1519 .iter()
1520 .filter(|(key, _)| stabilizer_supports_measurement(&generator.stabilizer, key))
1521 .fold(Parity::default(), |parity, (_, contribution)| {
1522 &parity + contribution
1523 });
1524 assert_eq!(
1525 actual,
1526 Parity::single(verifier.measurement_vars[name]),
1527 "every unnamed phase assignment must preserve action input {name}"
1528 );
1529 }
1530 }
1531
1532 #[test]
1533 fn anonymous_class_weights_match_all_native_outcomes_with_an_odd_closed_relation() {
1534 use quizx::fscalar::FScalar;
1535 let mut source = BlockGraph::new();
1536 for x in [0, 1, 3] {
1537 source.add_block(Block::new(IVec3::new(x, 0, 0), BlockKind::Port));
1538 for z in [1, 2] {
1539 source.add_block(Block::new(
1540 IVec3::new(x, 0, z),
1541 BlockKind::Cube(CubeKind::XZX),
1542 ));
1543 }
1544 source.add_block(Block::new(
1545 IVec3::new(x, 0, 3),
1546 if x == 3 {
1547 BlockKind::Port
1548 } else {
1549 BlockKind::Measurement(Basis::X)
1550 },
1551 ));
1552 for z in 0..3 {
1553 source.add_pipe(Pipe::new(IVec3::new(x, 0, z), Direction::ZPLUS));
1554 }
1555 }
1556 for z in [1, 2] {
1557 source.add_pipe(Pipe::new(IVec3::new(0, 0, z), Direction::XPLUS));
1558 }
1559 source
1560 .set_actions(vec![
1561 Action::Measure {
1562 target: MeasureTarget::Edge {
1563 src: IVec3::Z,
1564 dir: Direction::XPLUS,
1565 },
1566 name: "m".into(),
1567 },
1568 Action::Feedback {
1569 targets: vec![FeedbackTarget {
1570 pauli: PauliBasis::X,
1571 target: IVec3::Z,
1572 direction: None,
1573 }],
1574 condition: None,
1575 },
1576 ])
1577 .unwrap();
1578 let qasm = "OPENQASM 2.0; qreg q[5]; creg m[1]; creg again[1]; creg erased[2];
1581 reset q[3]; reset q[4]; cx q[0],q[3]; cx q[1],q[3]; measure q[3] -> m;
1582 x q[0]; cx q[0],q[4]; cx q[1],q[4]; measure q[4] -> again;
1583 h q[0]; h q[1]; measure q[0] -> erased[0]; measure q[1] -> erased[1];
1584 if(m==1) x q[2];";
1585 let options = crate::FeedbackOptions::default()
1586 .with_search_limit(0)
1587 .with_discarded_measurement("again[0]")
1588 .with_discarded_measurement("erased[0]")
1589 .with_discarded_measurement("erased[1]");
1590 let actions = crate::infer_feedback_with(qasm, &source, &options).unwrap();
1591 assert!(
1592 matches!(actions.as_slice(), [Action::Feedback { targets, condition: Some(Expr::Var(name)) }]
1593 if name == "m" && targets.len() == 1 && targets[0].pauli == PauliBasis::X && targets[0].target == IVec3::new(3,0,3))
1594 );
1595
1596 fn choi(maps: &[TensorF]) -> Vec<FScalar> {
1597 let width = maps[0].len();
1598 let mut result = vec![FScalar::zero(); width * width];
1599 for map in maps {
1600 for (i, a) in map.iter().enumerate() {
1601 for (j, b) in map.iter().enumerate() {
1602 result[i * width + j] += *a * b.conj();
1603 }
1604 }
1605 }
1606 result
1607 }
1608 let verifier = LogicalVerifier::with_internal_measurements(&source).unwrap();
1609 let variables = verifier
1610 .correction
1611 .as_ref()
1612 .unwrap()
1613 .sampled_vars()
1614 .collect::<Vec<_>>();
1615 assert!(
1616 variables.len() <= 6,
1617 "keep exhaustive native enumeration small"
1618 );
1619 let mut resource = Circuit::new(3);
1620 for qubit in 0..3 {
1621 resource.add_gate("h", vec![qubit]);
1622 }
1623 resource.add_gate("ccz", vec![0, 1, 2]);
1624 let mut resource: QuizxGraph = resource.to_graph();
1625 resource.plug_inputs(&[quizx::graph::BasisElem::Z0; 3]);
1626 let mut saw_vanishing_class = false;
1627 for value in [false, true] {
1628 let assignment = BranchAssignment::from([("m".into(), value)]);
1629 let native = (0..1 << variables.len())
1630 .map(|mask| {
1631 verifier
1632 .instantiate_with(&assignment, |variable| {
1633 mask & (1
1634 << variables
1635 .iter()
1636 .position(|candidate| *candidate == variable)
1637 .unwrap())
1638 != 0
1639 })
1640 .unwrap()
1641 .1
1642 .unwrap()
1643 })
1644 .collect::<Vec<_>>();
1645 let (_, representatives) = verifier.branch_family(&assignment, 64).unwrap();
1646 let open = native.iter().map(contract).collect::<Vec<_>>();
1647 let quotient = representatives.iter().map(contract).collect::<Vec<_>>();
1648 let nonzero = open.iter().filter(|map| tensor_is_nonzero(map)).count();
1649 assert!(
1650 nonzero > 0 && nonzero < native.len(),
1651 "odd closure must rule out native coordinates"
1652 );
1653 assert!(quotient.iter().all(tensor_is_nonzero));
1654 assert_eq!(nonzero % quotient.len(), 0);
1655 let multiplicity = FScalar::real((nonzero / quotient.len()) as f64);
1656 assert_eq!(
1657 choi(&open),
1658 choi("ient)
1659 .into_iter()
1660 .map(|value| value * multiplicity)
1661 .collect::<Vec<_>>()
1662 );
1663 let prepared = |diagram: &QuizxGraph| {
1664 let mut prepared = resource.clone();
1665 prepared.plug(diagram);
1666 contract(&prepared)
1667 };
1668 let open = native.iter().map(prepared).collect::<Vec<_>>();
1669 let quotient = representatives.iter().map(prepared).collect::<Vec<_>>();
1670 saw_vanishing_class |= quotient.iter().any(|map| !tensor_is_nonzero(map));
1671 assert_eq!(
1674 choi(&open),
1675 choi("ient)
1676 .into_iter()
1677 .map(|value| value * multiplicity)
1678 .collect::<Vec<_>>()
1679 );
1680 }
1681 assert!(saw_vanishing_class);
1682 }
1683
1684 #[test]
1685 fn spatial_node_records_drive_joint_native_phase_parities() {
1686 let mut source = crate::GalleryItem::CZSpatialH
1687 .build()
1688 .materialize_root_graph()
1689 .expect("gallery flat projection");
1690 source
1691 .set_actions(vec![
1692 Action::Measure {
1693 target: MeasureTarget::Node(IVec3::Z),
1694 name: "a".into(),
1695 },
1696 Action::Measure {
1697 target: MeasureTarget::Node(IVec3::X + IVec3::Z),
1698 name: "b".into(),
1699 },
1700 ])
1701 .unwrap();
1702 assert_eq!(
1703 source
1704 .action_graph()
1705 .node_by_ordinal(1)
1706 .unwrap()
1707 .measurement,
1708 Some(crate::MeasurementObservable::Concrete(PauliBasis::Z))
1709 );
1710 let mut reversed = BlockGraph::new();
1711 for block in source
1712 .blocks()
1713 .cloned()
1714 .collect::<Vec<_>>()
1715 .into_iter()
1716 .rev()
1717 {
1718 reversed.add_block(block);
1719 }
1720 for pipe in source.pipes() {
1721 reversed.add_pipe(if pipe.is_hadamard() {
1722 Pipe::new(pipe.dst(), pipe.dir().negate()).with_hadamard()
1723 } else {
1724 pipe.clone()
1725 });
1726 }
1727 reversed.set_actions(source.actions()).unwrap();
1728 for mut source in [source, reversed] {
1729 let mut with_unnamed = source.clone();
1732 for z in [1, 2] {
1733 with_unnamed.add_block(Block::new(
1734 IVec3::new(-1, 0, z),
1735 BlockKind::Cube(CubeKind::XZX),
1736 ));
1737 }
1738 with_unnamed.add_pipe(Pipe::new(IVec3::new(-1, 0, 1), Direction::XPLUS));
1739 with_unnamed.add_pipe(Pipe::new(IVec3::new(-1, 0, 1), Direction::ZPLUS));
1740 for verifier in [
1741 LogicalVerifier::new(&source).unwrap(),
1742 LogicalVerifier::with_internal_measurements(&with_unnamed).unwrap(),
1743 ] {
1744 let internal_count = verifier
1745 .correction
1746 .as_ref()
1747 .map_or(0, |correction| correction.sampled_vars().count());
1748 if verifier.correction.is_some() {
1749 assert!(
1750 (1..=8).contains(&internal_count),
1751 "spur must supply bounded unnamed samples"
1752 );
1753 }
1754 for a in [false, true] {
1755 for b in [false, true] {
1756 let mut gates = vec![("cz", vec![0, 1])];
1757 if b {
1758 gates.push(("z", vec![0]));
1759 }
1760 if a ^ b {
1761 gates.push(("x", vec![1]));
1762 }
1763 let expected = circuit_graph(2, &gates);
1764 let assignment = BTreeMap::from([("a".into(), a), ("b".into(), b)]);
1765 let expected = contract(&expected);
1766 let mut verified = false;
1767 let mut verified_nonzero = false;
1768 for mask in 0..(1usize << internal_count) {
1769 let mut sample = 0;
1770 let (branch, diagram) = verifier
1771 .instantiate_with(&assignment, |_| {
1772 let value = mask & (1 << sample) != 0;
1773 sample += 1;
1774 value
1775 })
1776 .unwrap();
1777 assert_eq!(sample, internal_count);
1778 match compare_branch(branch, diagram, &expected).unwrap().status() {
1779 BranchStatus::Verified => {
1780 verified = true;
1781 verified_nonzero |= mask != 0;
1782 }
1783 BranchStatus::Impossible => {}
1784 status => panic!("unexpected branch status {status:?}"),
1785 }
1786 }
1787 assert!(verified, "a={a}, b={b} has a nonzero branch");
1788 if internal_count != 0 {
1789 assert!(
1790 verified_nonzero,
1791 "a={a}, b={b} must verify with nonzero unnamed outcomes"
1792 );
1793 }
1794 }
1795 }
1796 }
1797 source.add_block(Block::new(
1798 IVec3::new(5, 0, 0),
1799 BlockKind::Cube(CubeKind::ZXZ),
1800 ));
1801 source
1802 .add_action(Action::Measure {
1803 target: MeasureTarget::Node(IVec3::new(5, 0, 0)),
1804 name: "closed".into(),
1805 })
1806 .unwrap();
1807 let branch = LogicalVerifier::new(&source)
1808 .unwrap()
1809 .verify_branch(
1810 &circuit_graph(2, &[("cz", vec![0, 1])]),
1811 &BTreeMap::from([
1812 ("a".into(), false),
1813 ("b".into(), false),
1814 ("closed".into(), true),
1815 ]),
1816 )
1817 .unwrap();
1818 assert_eq!(branch.status(), BranchStatus::Impossible);
1819 }
1820 }
1821
1822 #[test]
1823 fn spatial_and_edge_records_compose_two_phases_on_one_wire() {
1824 let mut source = crate::GalleryItem::CZSpatialH
1825 .build()
1826 .materialize_root_graph()
1827 .expect("gallery flat projection");
1828 source
1829 .set_actions(vec![
1830 Action::Measure {
1831 target: MeasureTarget::Node(IVec3::X + IVec3::Z),
1832 name: "b".into(),
1833 },
1834 Action::Measure {
1835 target: MeasureTarget::Edge {
1836 src: IVec3::Z,
1837 dir: Direction::XPLUS,
1838 },
1839 name: "m".into(),
1840 },
1841 ])
1842 .unwrap();
1843 let verifier = LogicalVerifier::new(&source).unwrap();
1844 for b in [false, true] {
1845 for m in [false, true] {
1846 let mut gates = vec![("cz", vec![0, 1])];
1847 if b {
1848 gates.push(("z", vec![0]));
1849 }
1850 if m {
1851 gates.push(("z", vec![1]));
1852 }
1853 let branch = verifier
1854 .verify_branch(
1855 &circuit_graph(2, &gates),
1856 &BTreeMap::from([("b".into(), b), ("m".into(), m)]),
1857 )
1858 .unwrap();
1859 assert_eq!(branch.status(), BranchStatus::Verified);
1860 }
1861 }
1862 }
1863
1864 #[test]
1865 fn hadamard_edge_measurement_is_independent_of_storage_and_target_direction() {
1866 let original = crate::GalleryItem::CZSpatialH
1867 .build()
1868 .materialize_root_graph()
1869 .expect("gallery flat projection");
1870 let edge = original.pipes().find(|pipe| pipe.is_hadamard()).unwrap();
1871 for reverse_blocks in [false, true] {
1872 for reverse_pipe in [false, true] {
1873 for reverse_target in [false, true] {
1874 let mut source = BlockGraph::new();
1875 let mut blocks = original.blocks().cloned().collect::<Vec<_>>();
1876 if reverse_blocks {
1877 blocks.reverse();
1878 }
1879 for block in blocks {
1880 source.add_block(block);
1881 }
1882 for pipe in original.pipes() {
1883 source.add_pipe(if pipe.is_hadamard() && reverse_pipe {
1884 crate::Pipe::new(pipe.dst(), pipe.dir().negate()).with_hadamard()
1885 } else {
1886 pipe.clone()
1887 });
1888 }
1889 let (src, dir) = if reverse_target {
1890 (edge.dst(), edge.dir().negate())
1891 } else {
1892 (edge.src(), edge.dir())
1893 };
1894 source
1895 .set_actions(vec![Action::Measure {
1896 target: MeasureTarget::Edge { src, dir },
1897 name: "m".into(),
1898 }])
1899 .unwrap();
1900 source.validate().unwrap();
1901 for value in [false, true] {
1902 let expected = if value {
1903 circuit_graph(2, &[("cz", vec![0, 1]), ("z", vec![1])])
1904 } else {
1905 circuit_graph(2, &[("cz", vec![0, 1])])
1906 };
1907 let branch = LogicalVerifier::new(&source)
1908 .unwrap()
1909 .verify_branch(&expected, &BTreeMap::from([("m".into(), value)]))
1910 .unwrap();
1911 assert_eq!(branch.status(), BranchStatus::Verified);
1912 }
1913 }
1914 }
1915 }
1916 }
1917
1918 #[test]
1919 fn basis_flipped_selective_matches_each_ordered_cap_map() {
1920 for spelling in ["XY", "YX", "XZ", "ZX", "YZ", "ZY"] {
1921 let source = crate::parse_inline_graph(&format!(
1922 "BLOG 1.0\nmodule main {{\nin control\nin q: data = 0\n0: Port [0,0,0]\n1: {spelling} [0,0,1]\n0 -> +Z\nresolve 1 if control\n}}\n"
1923 )).unwrap().materialize_flat_graph().unwrap();
1924 let flipped = source.flip_xz_basis().unwrap();
1925 assert_eq!(
1926 flipped.flip_xz_basis().unwrap().to_blog_text(),
1927 source.to_blog_text()
1928 );
1929 let verifier = LogicalVerifier::new(&flipped).unwrap();
1930 let choices = spelling.chars().collect::<Vec<_>>();
1931 for control in [false, true] {
1932 let before = choices[usize::from(!control)];
1933 let expected_source = BlockGraph::from_blog_text(&format!(
1934 "BLOG 1.0\n0: Port [0,0,0]\n1: {before} [0,0,1]\n0 -> +Z\n"
1935 ))
1936 .unwrap();
1937 let (_, expected) = LogicalVerifier::new(&expected_source)
1938 .unwrap()
1939 .instantiate(&BTreeMap::new())
1940 .unwrap();
1941 let mut dual = circuit_graph(1, &[("h", vec![0])]);
1942 dual.plug(&expected.unwrap());
1943 verifier
1944 .verify_branch(&dual, &BTreeMap::from([("control".into(), control)]))
1945 .unwrap();
1946 }
1947 let BlockKind::Selective(kind) = source.get_block(IVec3::Z).unwrap().kind() else {
1948 unreachable!()
1949 };
1950 let axis = match kind {
1951 crate::SelectiveKind::XY => Some(PauliBasis::X),
1952 crate::SelectiveKind::YZ => Some(PauliBasis::Z),
1953 crate::SelectiveKind::XZ => None,
1954 };
1955 if let Some(pauli) = axis {
1956 let mut corrected = source.clone();
1957 corrected
1958 .add_action(Action::Feedback {
1959 targets: vec![FeedbackTarget {
1960 target: IVec3::Z,
1961 pauli,
1962 direction: None,
1963 }],
1964 condition: None,
1965 })
1966 .unwrap();
1967 let once = corrected.flip_xz_basis().unwrap();
1968 assert!(
1969 !once
1970 .actions()
1971 .iter()
1972 .any(|action| matches!(action, Action::Feedback { .. })),
1973 "adjacent equal corrections cancel as a whole, without empty feedback"
1974 );
1975 assert_eq!(
1976 once.flip_xz_basis().unwrap().to_blog_text(),
1977 corrected.to_blog_text()
1978 );
1979 }
1980 }
1981 }
1982
1983 #[test]
1984 fn basis_flip_dualizes_fixed_resources_through_their_pipes() {
1985 for (kind, gate) in [(BlockKind::T, "t"), (BlockKind::Y, "s")] {
1986 for hadamard in [false, true] {
1987 for reversed in [false, true] {
1988 let mut source = identity_source();
1989 source.set_block_kind(IVec3::ZERO, kind).unwrap();
1990 source.remove_pipe(IVec3::ZERO, IVec3::Z);
1991 let mut pipe = Pipe::new(
1992 if reversed { IVec3::Z } else { IVec3::ZERO },
1993 if reversed {
1994 Direction::ZMINUS
1995 } else {
1996 Direction::ZPLUS
1997 },
1998 )
1999 .with_tag("resource")
2000 .unwrap();
2001 if hadamard {
2002 pipe = pipe.with_hadamard();
2003 }
2004 source.add_pipe(pipe.clone());
2005 let flipped = source.flip_xz_basis().unwrap();
2006 let actual = flipped.get_pipe(IVec3::ZERO, IVec3::Z).unwrap();
2007 assert_eq!(
2008 (actual.src(), actual.dir(), actual.tag()),
2009 (pipe.src(), pipe.dir(), pipe.tag())
2010 );
2011 assert_eq!(actual.is_hadamard(), !hadamard);
2012 let mut gates = vec![("h", vec![0]), (gate, vec![0])];
2013 if !hadamard {
2014 gates.push(("h", vec![0]));
2015 }
2016 let mut expected = circuit_graph(1, &gates);
2017 expected.plug_inputs(&[quizx::graph::BasisElem::Z0]);
2018 LogicalVerifier::new(&flipped)
2019 .unwrap()
2020 .verify_branch(&expected, &BTreeMap::new())
2021 .unwrap();
2022 assert_eq!(
2023 flipped.flip_xz_basis().unwrap().to_blog_text(),
2024 source.to_blog_text()
2025 );
2026 }
2027 }
2028 }
2029 }
2030
2031 #[test]
2032 fn basis_flip_preserves_native_t_comparison_postselection() {
2033 let source = crate::GalleryItem::TComparison
2034 .build()
2035 .materialize_root_graph()
2036 .expect("gallery flat projection");
2037 let flipped = source.flip_xz_basis().unwrap();
2038 assert!(
2039 flipped
2040 .actions()
2041 .iter()
2042 .any(|action| action == source.actions().last().unwrap())
2043 );
2044 for (index, mut graph) in [source, flipped].into_iter().enumerate() {
2045 let actions = graph
2046 .actions()
2047 .into_iter()
2048 .filter(|action| !matches!(action, Action::DiscardIf(_)))
2049 .collect();
2050 graph.set_actions(actions).unwrap();
2051 let verifier = LogicalVerifier::new(&graph).unwrap();
2052 for cmp in [false, true] {
2053 for parity in [false, true] {
2054 let (_, branch) = verifier
2055 .instantiate(&BTreeMap::from([
2056 ("cmp".into(), cmp),
2057 ("parity".into(), parity),
2058 ]))
2059 .unwrap();
2060 assert_eq!(
2061 tensor_is_nonzero(&contract(&branch.unwrap())),
2062 !parity,
2063 "variant={index} cmp={cmp} parity={parity}"
2064 );
2065 }
2066 }
2067 }
2068 }
2069
2070 #[test]
2071 fn isolated_feedback_returns_missing_wire_error() {
2072 let mut source = BlockGraph::new();
2073 source.add_block(Block::new(IVec3::ZERO, BlockKind::Cube(CubeKind::ZXZ)));
2074 source.set_actions_unchecked(vec![Action::Feedback {
2075 targets: vec![FeedbackTarget {
2076 pauli: PauliBasis::X,
2077 target: IVec3::ZERO,
2078 direction: None,
2079 }],
2080 condition: None,
2081 }]);
2082 assert!(matches!(
2083 source.validate(),
2084 Err(crate::BlockGraphError::Stabilizer(
2085 crate::StabilizerError::FeedbackTargetWithoutWire { .. }
2086 ))
2087 ));
2088 assert!(matches!(LogicalVerifier::new(&source),
2089 Err(VerifyLogicalError::FeedbackTargetWithoutWire { target, pauli: PauliBasis::X })
2090 if target == IVec3::ZERO));
2091 }
2092
2093 #[test]
2094 fn unconditional_feedback_is_a_literal_pauli_on_the_wire() {
2095 let target = IVec3::new(0, 0, 1);
2096 let mut source = identity_source();
2097 source
2098 .set_actions(vec![Action::Feedback {
2099 targets: vec![FeedbackTarget {
2100 pauli: PauliBasis::X,
2101 target,
2102 direction: None,
2103 }],
2104 condition: None,
2105 }])
2106 .unwrap();
2107
2108 let verifier = LogicalVerifier::new(&source).unwrap();
2109 verifier
2110 .verify(&circuit_graph(1, &[("x", vec![0])]), 1, 0)
2111 .unwrap();
2112 }
2113
2114 #[test]
2115 fn external_inputs_control_feedback_and_are_sampled() {
2116 let mut source = identity_source();
2117 source
2118 .set_actions_with_inputs(
2119 vec![Action::Feedback {
2120 targets: vec![FeedbackTarget {
2121 pauli: PauliBasis::X,
2122 target: IVec3::Z,
2123 direction: None,
2124 }],
2125 condition: Some(Expr::Var("enabled".into())),
2126 }],
2127 ["enabled".into()],
2128 )
2129 .unwrap();
2130 let verifier = LogicalVerifier::new(&source).unwrap();
2131 for enabled in [false, true] {
2132 let expected = if enabled {
2133 circuit_graph(1, &[("x", vec![0])])
2134 } else {
2135 circuit_graph(1, &[])
2136 };
2137 let branch = verifier
2138 .verify_branch(&expected, &BTreeMap::from([("enabled".into(), enabled)]))
2139 .unwrap();
2140 assert_eq!(branch.status(), BranchStatus::Verified);
2141 assert_eq!(branch.classical_state()["enabled"], enabled);
2142 }
2143 assert!(matches!(
2144 verifier.instantiate(&BTreeMap::new()),
2145 Err(VerifyLogicalError::MissingInputValue(name)) if name == "enabled"
2146 ));
2147 let sampled = (0..16)
2148 .map(|seed| verifier.sample_with_seed(seed).unwrap().0.classical_state()["enabled"])
2149 .collect::<BTreeSet<_>>();
2150 assert_eq!(sampled, BTreeSet::from([false, true]));
2151 }
2152
2153 #[test]
2154 fn external_inputs_select_structural_arms() {
2155 let mut source = BlockGraph::new();
2156 let target = add_test_branch(&mut source, "b0", IVec3::ZERO);
2157 source
2158 .set_actions_with_inputs(
2159 vec![Action::Branch {
2160 target,
2161 condition: Expr::Var("enabled".into()),
2162 }],
2163 ["enabled".into()],
2164 )
2165 .unwrap();
2166 let verifier = LogicalVerifier::new(&source).unwrap();
2167 for enabled in [false, true] {
2168 let (branch, diagram) = verifier
2169 .instantiate(&BTreeMap::from([("enabled".into(), enabled)]))
2170 .unwrap();
2171 assert_eq!(branch.classical_state()["enabled"], enabled);
2172 assert!(diagram.is_some());
2173 }
2174 }
2175
2176 #[test]
2177 fn repeated_t_parities_keep_the_named_branch_frame() {
2178 let expected = circuit_graph(1, &[("t", vec![0])]);
2179 let expected_tensor = contract(&expected);
2180 for layers in 1..=3 {
2181 let position = |x, z| IVec3::new(x, 0, z);
2182 let mut source = BlockGraph::new();
2183 source.add_block(Block::new(position(0, -1), BlockKind::T));
2184 source.add_block(Block::new(position(1, -1), BlockKind::Port));
2185 source.add_block(Block::new(
2186 position(0, layers),
2187 BlockKind::Selective(SelectiveKind::XY),
2188 ));
2189 source.add_block(Block::new(position(1, layers), BlockKind::Port));
2190 for layer in 0..layers {
2191 for x in 0..=1 {
2192 source.add_block(Block::new(
2193 position(x, layer),
2194 BlockKind::Cube(CubeKind::ZXZ),
2195 ));
2196 }
2197 source.add_pipe(Pipe::new(position(0, layer), crate::Direction::XPLUS));
2198 }
2199 for x in 0..=1 {
2200 for layer in -1..layers {
2201 let pipe = Pipe::new(position(x, layer), Direction::ZPLUS);
2202 source.add_pipe(if layer == -1 || layer == layers - 1 {
2203 pipe.with_hadamard()
2204 } else {
2205 pipe
2206 });
2207 }
2208 }
2209 source
2210 .set_actions(vec![
2211 Action::Measure {
2212 target: MeasureTarget::Edge {
2213 src: IVec3::ZERO,
2214 dir: Direction::XPLUS,
2215 },
2216 name: "m".into(),
2217 },
2218 Action::Resolve {
2219 target: position(0, layers),
2220 condition: Expr::Not(Box::new(Expr::Var("m".into()))),
2221 },
2222 ])
2223 .unwrap();
2224
2225 let mut raw = source.clone();
2228 let mut actions = raw.actions();
2229 for layer in 1..layers {
2230 actions.push(Action::Measure {
2231 target: MeasureTarget::Edge {
2232 src: position(0, layer),
2233 dir: Direction::XPLUS,
2234 },
2235 name: format!("raw{layer}"),
2236 });
2237 }
2238 raw.set_actions_deferred(actions).unwrap();
2239 let raw_verifier = LogicalVerifier::new(&raw).unwrap();
2240 for value in [false, true] {
2241 let mut assignment = BranchAssignment::from([("m".into(), value)]);
2242 assignment.extend((1..layers).map(|layer| (format!("raw{layer}"), value)));
2243 assert_eq!(
2244 raw_verifier
2245 .verify_branch(&expected, &assignment)
2246 .unwrap()
2247 .status(),
2248 BranchStatus::Verified
2249 );
2250 }
2251
2252 let corrected = LogicalVerifier::with_internal_measurements(&source).unwrap();
2253 let mut verified = BTreeSet::new();
2254 for seed in 0..128 {
2255 let (branch, diagram) = corrected.sample_with_seed(seed).unwrap();
2256 let branch = compare_branch(branch, diagram, &expected_tensor).unwrap();
2257 if branch.status() == BranchStatus::Verified {
2258 verified.insert(branch.classical_state()["m"]);
2259 }
2260 }
2261 assert_eq!(verified, BTreeSet::from([false, true]));
2262 }
2263 }
2264
2265 #[test]
2266 fn rejected_assignment_stops_without_contracting() {
2267 let source = crate::GalleryItem::TComparison
2268 .build()
2269 .materialize_root_graph()
2270 .expect("gallery flat projection");
2271 let verifier = LogicalVerifier::new(&source).unwrap();
2272 let assignment = BTreeMap::from([("cmp".to_string(), false), ("parity".to_string(), true)]);
2273
2274 let (branch, diagram) = verifier.instantiate(&assignment).unwrap();
2275
2276 assert_eq!(branch.status(), BranchStatus::Rejected);
2277 assert_eq!(branch.classical_state().get("cmp"), Some(&false));
2278 assert_eq!(branch.classical_state().get("parity"), Some(&true));
2279 assert!(diagram.is_none());
2280 }
2281
2282 #[test]
2283 fn resolve_can_read_a_presampled_future_measurement() {
2284 let selective = IVec3::new(0, 0, 0);
2285 let measured = IVec3::new(0, 0, 2);
2286 let mut source = BlockGraph::new();
2287 source.add_block(Block::new(
2288 selective,
2289 BlockKind::Selective(SelectiveKind::XZ),
2290 ));
2291 source.add_block(Block::new(IVec3::new(0, 0, -1), BlockKind::Port));
2293 source.add_block(Block::new(measured, BlockKind::Cube(CubeKind::ZXZ)));
2294 source.add_pipe(Pipe::new(IVec3::new(0, 0, -1), Direction::ZPLUS));
2295 source
2296 .set_actions(vec![
2297 Action::Measure {
2298 target: MeasureTarget::Node(measured),
2299 name: "future".to_string(),
2300 },
2301 Action::Resolve {
2302 target: selective,
2303 condition: Expr::Var("future".to_string()),
2304 },
2305 ])
2306 .unwrap();
2307 let verifier = LogicalVerifier::new(&source).unwrap();
2308
2309 let (branch, diagram) = verifier
2310 .instantiate(&BTreeMap::from([("future".to_string(), true)]))
2311 .unwrap();
2312
2313 assert_eq!(branch.status(), BranchStatus::Instantiated);
2314 assert_eq!(branch.classical_state().get("future"), Some(&true));
2315 assert!(diagram.is_some());
2316 }
2317}